Files
fquiz/api/tests/test_fl_analysis_grading.py
T

371 lines
12 KiB
Python
Raw Normal View History

from __future__ import annotations
import pytest
from app.services.fl_analysis_rules import grade_mitigation_snapshot_payload, grade_snapshot_payload
def _build_circuit_geometry(
*,
shield_left_m: float,
shield_right_m: float,
shield_height_m: float,
insulator_length_mm: float,
circuit_i_upper_m: float,
circuit_i_middle_m: float,
circuit_i_lower_m: float,
circuit_i_upper_h_m: float,
circuit_i_middle_h_m: float,
circuit_i_lower_h_m: float,
circuit_ii_upper_m: float | None = None,
circuit_ii_middle_m: float | None = None,
circuit_ii_lower_m: float | None = None,
circuit_ii_upper_h_m: float | None = None,
circuit_ii_middle_h_m: float | None = None,
circuit_ii_lower_h_m: float | None = None,
) -> dict[str, object]:
return {
"I": {
"phase_spacing_m": {
"upper": circuit_i_upper_m,
"middle": circuit_i_middle_m,
"lower": circuit_i_lower_m,
},
"phase_height_m": {
"upper": circuit_i_upper_h_m,
"middle": circuit_i_middle_h_m,
"lower": circuit_i_lower_h_m,
},
},
"II": {
"phase_spacing_m": {
"upper": circuit_ii_upper_m,
"middle": circuit_ii_middle_m,
"lower": circuit_ii_lower_m,
},
"phase_height_m": {
"upper": circuit_ii_upper_h_m,
"middle": circuit_ii_middle_h_m,
"lower": circuit_ii_lower_h_m,
},
},
"lightning_wire": {
"left_mid_distance_m": shield_left_m,
"right_mid_distance_m": shield_right_m,
"height_m": shield_height_m,
},
"insulator_length_mm": insulator_length_mm,
}
def test_grade_snapshot_payload_marks_high_risk_and_actions() -> None:
payload = {
"base_tower_json": {
"tower_no": "T001",
"tower_type": "耐张",
"ground_resistance_ohm": 35.0,
"lightning_density": 6.5,
"span_large_m": 620.0,
"line_voltage_kv": 110,
"slope_1": 18.0,
"slope_2": 6.0,
"circuit_geometry_json": {
"I": {
"phase_height_m": {"upper": 28.0, "middle": 24.0, "lower": 20.0},
},
"lightning_wire": {
"left_mid_distance_m": 9.0,
"right_mid_distance_m": 9.0,
"height_m": 32.0,
},
},
},
"profile_json": {
"structure_kind": "耐张",
"stroke_mode": "反击",
"arrester_a": "否",
"arrester_b": "否",
"arrester_c": "否",
"insulator_length_m": 1200.0,
},
}
result = grade_snapshot_payload(payload)
assert result["risk_level"] == "high"
assert result["score"] >= 80
assert "接地电阻偏高" in result["cause_analysis"]
assert "避雷器" in result["mitigation_recommendation"]
assert any(item["code"] == "insulator_length" for item in result["reason_details"])
def test_grade_snapshot_payload_marks_low_risk_when_inputs_are_good() -> None:
payload = {
"base_tower_json": {
"tower_no": "T002",
"tower_type": "直线",
"ground_resistance_ohm": 4.0,
"lightning_density": 1.5,
"span_large_m": 180.0,
"line_voltage_kv": 110,
},
"profile_json": {
"structure_kind": "直线",
"stroke_mode": "绕击",
"arrester_a": "是",
"arrester_b": "是",
"arrester_c": "是",
"insulator_length_m": 1800.0,
},
}
result = grade_snapshot_payload(payload)
assert result["risk_level"] == "low"
assert result["score"] < 40
assert result["cause_analysis"]
assert result["mitigation_recommendation"]
def test_grade_mitigation_snapshot_payload_builds_actions_and_reduces_expected_risk() -> None:
payload = {
"base_tower_json": {
"tower_no": "T003",
"tower_type": "耐张",
"ground_resistance_ohm": 28.0,
"lightning_density": 5.0,
"span_large_m": 420.0,
"line_voltage_kv": 220,
"slope_1": 12.0,
"slope_2": 8.0,
"circuit_geometry_json": {
"I": {
"phase_height_m": {"upper": 36.0, "middle": 32.0, "lower": 28.0},
},
"lightning_wire": {
"left_mid_distance_m": 8.5,
"right_mid_distance_m": 8.5,
"height_m": 40.0,
},
},
},
"profile_json": {
"structure_kind": "耐张",
"stroke_mode": "反击",
"arrester_a": "否",
"arrester_b": "否",
"arrester_c": "否",
"insulator_length_m": 2000.0,
},
}
result = grade_mitigation_snapshot_payload(payload, non_construction=False)
assert result["recommendation_result"] in {"需要安装避雷器", "不需要安装避雷器"}
assert result["mitigation_actions"]
assert result["expected_score"] < result["current_score"]
assert any(action["code"] == "grounding_upgrade" for action in result["mitigation_actions"])
def test_grade_mitigation_snapshot_payload_includes_non_construction_action() -> None:
payload = {
"base_tower_json": {
"tower_no": "T004",
"tower_model": "s220guxing",
"tower_type": "直线",
"ground_resistance_ohm": 16.0,
"lightning_density": 3.5,
"span_large_m": 260.0,
"line_voltage_kv": 220,
"circuit_geometry_json": {
"I": {
"phase_height_m": {"upper": 30.0, "middle": 26.0, "lower": 22.0},
},
"lightning_wire": {
"left_mid_distance_m": 10.0,
"right_mid_distance_m": 10.0,
"height_m": 33.0,
},
},
},
"profile_json": {
"structure_kind": "直线",
"stroke_mode": "绕击",
"arrester_a": "是",
"arrester_b": "是",
"arrester_c": "是",
"insulator_length_m": 2500.0,
},
}
result = grade_mitigation_snapshot_payload(payload, non_construction=True)
assert result["non_construction"] is True
assert any(action["code"] == "shielding_geometry" for action in result["mitigation_actions"])
def test_grade_mitigation_snapshot_payload_prefers_source_risk_result() -> None:
payload = {
"base_tower_json": {
"tower_no": "T005",
"tower_type": "直线",
"ground_resistance_ohm": 4.0,
"lightning_density": 1.5,
"span_large_m": 180.0,
"line_voltage_kv": 110,
},
"profile_json": {
"structure_kind": "直线",
"stroke_mode": "绕击",
"arrester_a": "是",
"arrester_b": "是",
"arrester_c": "是",
"insulator_length_m": 1800.0,
},
"source_result_json": {
"risk_level": "high",
"score": 92,
"cause_analysis": "沿用前驱风险结果",
"reason_details": [{"code": "source_reason", "label": "前驱原因"}],
"inputs": {"ground_resistance_ohm": 35.0},
},
}
result = grade_mitigation_snapshot_payload(payload, non_construction=False)
assert result["current_risk_level"] == "high"
assert result["current_score"] == 92
assert result["cause_analysis"] == "沿用前驱风险结果"
assert result["reason_details"][0]["code"] == "source_reason"
def test_grade_snapshot_payload_uses_source_formula_and_converts_mm_to_m() -> None:
geometry = _build_circuit_geometry(
shield_left_m=12.4,
shield_right_m=15.9,
shield_height_m=73.5,
insulator_length_mm=5945.0,
circuit_i_upper_m=14.5,
circuit_i_middle_m=10.5,
circuit_i_lower_m=12.0,
circuit_i_upper_h_m=53.0,
circuit_i_middle_h_m=65.5,
circuit_i_lower_h_m=42.0,
circuit_ii_upper_m=14.5,
circuit_ii_middle_m=10.5,
circuit_ii_lower_m=12.0,
circuit_ii_upper_h_m=53.0,
circuit_ii_middle_h_m=65.5,
circuit_ii_lower_h_m=42.0,
)
payload = {
"base_tower_json": {
"tower_no": "P1",
"tower_type": "耐张",
"tower_model": "500-MC31S-DJC2-42",
"line_name": "交流500kV雁船线(双回段)",
"line_voltage_kv": 500,
"ground_resistance_ohm": 20.0,
"lightning_density": 5.42528304182587,
"slope_1": -5.70339674677262,
"slope_2": -1.42923539492821,
"circuit_geometry_json": geometry,
"lightning_result_json": {},
},
"profile_json": {
"structure_kind": "耐张",
"arrester_a": "否",
"arrester_b": "否",
"arrester_c": "否",
"shield_wire_height_m": 73.5,
"insulator_length_m": 5945.0,
"current_a": 25.109,
"current_b": 3.62,
},
}
result = grade_snapshot_payload(payload)
assert result["risk_level"] == "high"
assert result["risk_grade"] == 3
assert result["inputs"]["insulator_length_m"] == pytest.approx(5.945, abs=1e-6)
assert result["counterstrike_withstand_ka"] == pytest.approx(75.8070, rel=1e-4)
assert result["counterstrike_trip_rate"] == pytest.approx(0.529876, rel=1e-4)
assert result["shielding_withstand_ka"] == pytest.approx(31.2860, rel=1e-4)
assert result["shielding_trip_rate"] == pytest.approx(0.000003, abs=1e-6)
assert "接地电阻" in result["cause_analysis"]
assert "规程法等级 3" in result["summary_text"]
def test_grade_snapshot_payload_marks_low_risk_for_good_inputs() -> None:
geometry = _build_circuit_geometry(
shield_left_m=8.0,
shield_right_m=8.0,
shield_height_m=40.0,
insulator_length_mm=3500.0,
circuit_i_upper_m=10.0,
circuit_i_middle_m=3.0,
circuit_i_lower_m=8.0,
circuit_i_upper_h_m=28.0,
circuit_i_middle_h_m=31.0,
circuit_i_lower_h_m=24.0,
)
payload = {
"base_tower_json": {
"tower_no": "L1",
"tower_type": "直线",
"tower_model": "220-TEST-ZX",
"line_name": "交流220kV示例线",
"line_voltage_kv": 220,
"ground_resistance_ohm": 5.0,
"lightning_density": 1.2,
"slope_1": 0.0,
"slope_2": 0.0,
"circuit_geometry_json": geometry,
"lightning_result_json": {},
},
"profile_json": {
"structure_kind": "直线",
"arrester_a": "是",
"arrester_b": "是",
"arrester_c": "是",
"shield_wire_height_m": 40.0,
"insulator_length_m": 3500.0,
"current_a": 31.0,
"current_b": 2.6,
},
}
result = grade_snapshot_payload(payload)
assert result["risk_level"] == "low"
assert result["risk_grade"] == 1
assert result["score"] < 30
assert result["counterstrike_trip_rate"] == pytest.approx(0.109404, rel=1e-4)
assert result["shielding_trip_rate"] == pytest.approx(0.006737, rel=1e-4)
assert "高风险" not in result["summary_text"]
def test_grade_snapshot_payload_falls_back_to_legacy_result_when_geometry_is_missing() -> None:
payload = {
"base_tower_json": {
"tower_no": "F1",
"lightning_result_json": {
"counterstroke_withstand_ka": 52.3,
"counterstroke_trip_rate": 0.12,
"shielding_withstand_ka": 18.6,
"shielding_trip_rate": 0.03,
"risk_level": 2,
},
},
"profile_json": {},
}
result = grade_snapshot_payload(payload)
assert result["used_legacy_fallback"] is True
assert result["risk_level"] == "medium"
assert result["counterstrike_withstand_ka"] == pytest.approx(52.3)
assert result["shielding_trip_rate"] == pytest.approx(0.03)
assert "历史结果" in result["cause_analysis"]