aebf152cd4
Co-authored-by: multica-agent <github@multica.ai>
211 lines
7.8 KiB
Python
211 lines
7.8 KiB
Python
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
from sqlalchemy import create_engine, select
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from app.core.database import Base
|
|
from app.models.atp_model import AtpModel, AtpModelVersion, AtpSimulationRun
|
|
from app.models.fl_analysis import FlAnalysisJob, FlAnalysisRun, FlAnalysisTowerResult, FlAnalysisTowerSnapshot
|
|
from app.models.line import Line
|
|
from app.models.line_tower import LineTower
|
|
from app.models.tower_profile import TowerProfile
|
|
from app.schemas.fl_analysis import FlAnalysisJobCreateRequest
|
|
from app.services import fl_analysis_external, fl_analysis_service
|
|
|
|
|
|
def _build_sessionmaker():
|
|
engine = create_engine("sqlite+pysqlite:///:memory:")
|
|
Base.metadata.create_all(
|
|
bind=engine,
|
|
tables=[
|
|
Line.__table__,
|
|
LineTower.__table__,
|
|
TowerProfile.__table__,
|
|
FlAnalysisJob.__table__,
|
|
FlAnalysisRun.__table__,
|
|
FlAnalysisTowerSnapshot.__table__,
|
|
FlAnalysisTowerResult.__table__,
|
|
AtpModel.__table__,
|
|
AtpModelVersion.__table__,
|
|
AtpSimulationRun.__table__,
|
|
],
|
|
)
|
|
return sessionmaker(bind=engine, autocommit=False, autoflush=False, expire_on_commit=False)
|
|
|
|
|
|
def test_render_atp_template_supports_binding_formats_and_defaults() -> None:
|
|
rendered = fl_analysis_external.render_atp_template(
|
|
"R={{GROUND_RES}}\nHEAD={{profile.current_head_time_us|F1|6|2.6}}\nNAME={{snapshot.tower_no}}",
|
|
context={
|
|
"snapshot": {"tower_no": "N-01"},
|
|
"base_tower": {"ground_resistance_ohm": 12.34},
|
|
"profile": {},
|
|
},
|
|
parameter_bindings={
|
|
"GROUND_RES": {
|
|
"path": "base_tower.ground_resistance_ohm",
|
|
"format": "F1",
|
|
"width": 6,
|
|
}
|
|
},
|
|
)
|
|
|
|
assert "R= 12.3" in rendered
|
|
assert "HEAD= 2.6" in rendered
|
|
assert "NAME=N-01" in rendered
|
|
|
|
|
|
def test_execute_job_runs_external_adapter_and_backfills_results(monkeypatch, tmp_path) -> None:
|
|
testing_session = _build_sessionmaker()
|
|
session: Session = testing_session()
|
|
try:
|
|
monkeypatch.setattr(fl_analysis_service, "SessionLocal", testing_session)
|
|
monkeypatch.setattr(fl_analysis_service, "_publish_change", lambda *args, **kwargs: None)
|
|
monkeypatch.setattr(fl_analysis_external, "_resolve_engine_workdir", lambda: tmp_path)
|
|
monkeypatch.setattr(fl_analysis_external, "_resolve_native_engine_executable", lambda: ("/bin/sh", None))
|
|
|
|
line = Line(
|
|
code="L-001",
|
|
name="示例线路",
|
|
voltage_kv=220,
|
|
lightning_param_json={"雷电流幅值a": 31.0, "雷电流幅值b": 2.6},
|
|
)
|
|
session.add(line)
|
|
session.flush()
|
|
|
|
tower = LineTower(
|
|
line_id=line.id,
|
|
seq_no=1,
|
|
tower_no="N1",
|
|
tower_model="220-TEST-ZX",
|
|
tower_type="直线",
|
|
altitude_m=1680.0,
|
|
ground_resistance_ohm=12.0,
|
|
lightning_density=3.2,
|
|
span_large_m=260.0,
|
|
slope_1=3.0,
|
|
slope_2=1.5,
|
|
circuit_geometry_json={
|
|
"I": {
|
|
"phase_spacing_m": {"upper": 9.0, "middle": 4.5, "lower": 8.5},
|
|
"phase_height_m": {"upper": 29.0, "middle": 31.0, "lower": 25.0},
|
|
},
|
|
"lightning_wire": {
|
|
"left_mid_distance_m": 9.0,
|
|
"right_mid_distance_m": 9.0,
|
|
"height_m": 41.0,
|
|
},
|
|
"insulator_length_mm": 4200.0,
|
|
},
|
|
lightning_result_json={},
|
|
)
|
|
session.add(tower)
|
|
session.flush()
|
|
|
|
session.add(
|
|
TowerProfile(
|
|
tower_id=tower.id,
|
|
structure_kind="直线",
|
|
arrester_a="是",
|
|
arrester_b="否",
|
|
arrester_c="是",
|
|
shield_wire_height_m=41.0,
|
|
insulator_length_m=4200.0,
|
|
current_a=31.0,
|
|
current_b=2.6,
|
|
current_type="Heidler",
|
|
current_head_time_us=2.6,
|
|
current_tail_time_us=50.0,
|
|
)
|
|
)
|
|
|
|
model = AtpModel(code="fl-normal-model", name="普通计算ATP模型", source_type="atp", status="enabled")
|
|
session.add(model)
|
|
session.flush()
|
|
|
|
session.add(
|
|
AtpModelVersion(
|
|
model_id=model.id,
|
|
version_no=1,
|
|
status="released",
|
|
entry_file="runner.sh",
|
|
artifact_manifest_json={
|
|
"fl_analysis": {
|
|
"result_file": "result.json",
|
|
"parameter_bindings": {
|
|
"GROUND_RES": {
|
|
"path": "base_tower.ground_resistance_ohm",
|
|
"format": "F1",
|
|
}
|
|
},
|
|
}
|
|
},
|
|
atp_text="""#!/bin/sh
|
|
cat > result.json <<'JSON'
|
|
{"counterstrike_withstand_ka": {{GROUND_RES}}, "risk_grade": 3, "score": 91, "summary_text": "ATP执行完成"}
|
|
JSON
|
|
""",
|
|
content_hash="normal-v1",
|
|
)
|
|
)
|
|
session.commit()
|
|
|
|
created = fl_analysis_service.create_job(
|
|
session,
|
|
FlAnalysisJobCreateRequest(
|
|
line_id=line.id,
|
|
job_name="普通计算-ATP",
|
|
job_type="normal",
|
|
external_adapter="atp",
|
|
adapter_config_json={"model_id": model.id},
|
|
execution_options_json={
|
|
"current_waveform": "double_slope",
|
|
"flashover_method": "intersection",
|
|
"altitude_correction": "formula1",
|
|
"induced_voltage_formula": "formula2",
|
|
"head_time_min_us": 2.4,
|
|
"head_time_max_us": 2.6,
|
|
"head_time_step_us": 0.2,
|
|
"tail_time_min_us": 45.0,
|
|
"tail_time_max_us": 50.0,
|
|
"tail_time_step_us": 5.0,
|
|
},
|
|
),
|
|
actor=SimpleNamespace(id="tester"),
|
|
)
|
|
session.close()
|
|
|
|
fl_analysis_service.execute_job(created.job.id)
|
|
|
|
verify_session: Session = testing_session()
|
|
try:
|
|
saved_job = fl_analysis_service.get_job_by_id(verify_session, created.job.id)
|
|
assert saved_job is not None
|
|
assert saved_job.status == "success"
|
|
assert saved_job.result_summary_json["adapter_status"] == "executed"
|
|
assert saved_job.result_summary_json["external_model_code"] == "fl-normal-model"
|
|
assert saved_job.result_summary_json["external_version_no"] == 1
|
|
|
|
result_row = verify_session.execute(
|
|
select(FlAnalysisTowerResult).where(FlAnalysisTowerResult.job_id == created.job.id)
|
|
).scalar_one()
|
|
assert result_row.risk_level == "high"
|
|
assert result_row.summary_text == "ATP执行完成"
|
|
assert result_row.result_json["counterstrike_withstand_ka"] == 12.0
|
|
assert result_row.result_json["external_execution"]["adapter"] == "atp"
|
|
assert result_row.result_json["external_execution"]["model_code"] == "fl-normal-model"
|
|
|
|
saved_run = verify_session.execute(
|
|
select(FlAnalysisRun).where(FlAnalysisRun.job_id == created.job.id)
|
|
).scalar_one()
|
|
assert saved_run.status == "success"
|
|
assert saved_run.runner_kind == "atp"
|
|
assert saved_run.engine_command is not None
|
|
assert saved_run.working_dir is not None
|
|
finally:
|
|
verify_session.close()
|
|
finally:
|
|
session.close()
|