Files
fquiz/api/tests/test_fl_analysis_export.py
T
chengkai3 0746e3ce28 [migrate]:[FL-31][add fl-analysis result export]
Co-authored-by: multica-agent <github@multica.ai>
2026-06-07 23:28:54 +08:00

188 lines
6.5 KiB
Python

from __future__ import annotations
import csv
import io
from pathlib import Path
from types import SimpleNamespace
from app.services.fl_analysis_service import export_fl_analysis_results_to_csv
API_FILE = Path(__file__).resolve().parents[1] / "app" / "api" / "v1" / "fl_analysis.py"
def _decode_csv(content: bytes) -> list[list[str]]:
return list(csv.reader(io.StringIO(content.decode("utf-8-sig"))))
def _build_job(job_type: str) -> SimpleNamespace:
return SimpleNamespace(
job_type=job_type,
job_name=f"{job_type}-job",
line=SimpleNamespace(code="XL-001"),
)
def _build_row(*, result_json: dict[str, object], risk_level: str | None = None) -> SimpleNamespace:
return SimpleNamespace(
id="result-1",
risk_level=risk_level,
summary_text=result_json.get("summary_text"),
result_json=result_json,
snapshot=SimpleNamespace(
seq_no=1,
tower_no="001",
tower_model="220-TEST-ZX",
tower_type="直线",
base_tower_json={
"tower_no": "001",
"tower_type": "直线",
"slope_1": 3.2,
"slope_2": 1.8,
"altitude_m": 1680.0,
"terrain": "山地",
"ground_resistance_ohm": 22.0,
"lightning_density": 4.8,
},
profile_json={
"stroke_mode": "反击",
"current_a": 72.5,
"current_b": 1.35,
"current_head_time_us": 2.6,
"current_tail_time_us": 50.0,
},
),
)
def test_export_normal_results_csv_contains_waveform_columns() -> None:
result_json = {
"risk_level": "high",
"risk_grade": 3,
"score": 88,
"summary_text": "001普通计算结果,高风险。",
"cause_analysis": "接地电阻偏高",
"mitigation_recommendation": "优先降低接地电阻",
"counterstrike_withstand_ka": 71.2,
"counterstrike_trip_rate": 0.0123,
"shielding_withstand_ka": 64.8,
"shielding_trip_rate": 0.0081,
"reason_details": [{"code": "ground_resistance", "label": "接地电阻", "grade": 1, "triggered": True}],
"inputs": {
"current_a": 72.5,
"current_b": 1.35,
"ground_resistance_ohm": 22.0,
"lightning_density": 4.8,
"insulator_length_mm": 4200.0,
"protection_angle_deg": 24.5,
},
"workflow": {
"current_waveform": "double_slope",
"flashover_method": "intersection",
"altitude_correction": "formula1",
"induced_voltage_formula": "formula2",
"scan_point_count": 4,
},
"selected_case": {
"head_time_us": 2.4,
"tail_time_us": 45.0,
},
}
_filename, content = export_fl_analysis_results_to_csv(_build_job("normal"), [_build_row(result_json=result_json)])
rows = _decode_csv(content)
assert len(rows) == 2
header = rows[0]
values = rows[1]
assert "最不利波头时间(μs)" in header
assert "雷电流波形" in header
assert "当前风险等级" not in header
assert values[header.index("风险等级")] == "高风险"
assert values[header.index("雷电流波形")] == "double_slope"
assert values[header.index("最不利波头时间(μs)")] == "2.4"
def test_export_mitigation_results_csv_contains_recommendation_columns() -> None:
result_json = {
"risk_level": "medium",
"current_risk_level": "high",
"current_score": 92,
"expected_risk_level": "medium",
"expected_score": 63,
"summary_text": "001当前高风险,建议后预期降为中风险。",
"cause_analysis": "接地电阻偏高;保护角暴露偏大",
"mitigation_recommendation": "降低接地电阻;优化保护角;补装避雷器",
"recommendation_result": "需要安装避雷器",
"reason_details": [{"code": "ground_resistance", "label": "接地电阻", "grade": 1, "triggered": True}],
"inputs": {
"current_a": 72.5,
"ground_resistance_ohm": 35.0,
"insulator_length_mm": 4200.0,
"protection_angle_deg": 27.0,
},
"mitigation_actions": [
{
"code": "grounding_upgrade",
"label": "降低接地电阻",
"summary": "将接地电阻优化至 5.0 Ω 以内",
"current_value": 35.0,
"target_value": 5.0,
"unit": "ohm",
},
{
"code": "insulator_upgrade",
"label": "提高绝缘子串长度",
"summary": "将绝缘子串长度提高至约 5200.0 mm",
"current_value": 4200.0,
"target_value": 5200.0,
"unit": "mm",
},
{
"code": "shielding_geometry",
"label": "优化保护角",
"summary": "按非建线口径将保护角收紧至约 18.5°",
"target_value": 18.5,
"unit": "deg",
},
{
"code": "arrester_install",
"label": "补装避雷器",
"summary": "建议在 A,C 相补装或复核避雷器",
"phases": ["A", "C"],
},
],
}
_filename, content = export_fl_analysis_results_to_csv(
_build_job("mitigation"),
[_build_row(result_json=result_json, risk_level="medium")],
)
rows = _decode_csv(content)
assert len(rows) == 2
header = rows[0]
values = rows[1]
assert "绝缘子串长推荐值(mm)" in header
assert "避雷器推荐相别" in header
assert "最不利波头时间(μs)" not in header
assert values[header.index("当前风险等级")] == "高风险"
assert values[header.index("避雷器推荐相别")] == "A,C"
assert values[header.index("绝缘子串长推荐值(mm)")] == "5200"
def test_export_risk_results_csv_keeps_header_when_rows_are_empty() -> None:
_filename, content = export_fl_analysis_results_to_csv(_build_job("risk"), [])
rows = _decode_csv(content)
assert len(rows) == 1
assert "风险等级" in rows[0]
assert "综合结论" in rows[0]
def test_fl_analysis_api_exposes_results_download_route() -> None:
source = API_FILE.read_text(encoding="utf-8")
assert '@router.get("/jobs/{job_id}/results/download")' in source
assert "download_result_csv" in source