fix(simulation): export ACTIVE valves correctly for EPANET 2
Generic Container CI/CD / test-build-publish (push) Successful in 2m12s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m12s

The legacy online simulation path still exported internal ACTIVE states as STATUS keywords, which EPANET 2 rejects. Preserve numeric settings and EPANET 3 output while passing the format through both exporters. Surface the original engine error inside the model transaction so failed runs roll back before result storage.

Regression guard: six failures reproduced on the deployed baseline, including native input validation. All 34 focused export, service and endpoint checks pass after the fix; the real project also returns native node and link results.
This commit is contained in:
2026-11-03 13:48:50 +08:00
parent 38a51ed65c
commit 9ab7b10e3c
5 changed files with 141 additions and 7 deletions
+48
View File
@@ -277,3 +277,51 @@ def test_extended_simulation_marks_run_failed_when_result_storage_fails(monkeypa
)
assert [call[1]["status"] for call in lifecycle_calls] == ["failed"]
@pytest.mark.parametrize("has_output", [False, True])
def test_engine_failure_preserves_report_and_rolls_back_model_changes(monkeypatch, has_output):
from app.services import simulation
transaction_outcome = []
@contextmanager
def project_transaction(_name):
try:
yield
except Exception:
transaction_outcome.append("rollback")
raise
else:
transaction_outcome.append("commit")
monkeypatch.setattr(simulation, "project_transaction", project_transaction)
monkeypatch.setattr(simulation, "get_time", lambda _: {
"HYDRAULIC TIMESTEP": "0:15:00",
"DURATION": "0:00",
"PATTERN START": "0:00",
})
monkeypatch.setattr(simulation, "set_time", lambda *_: None)
result = {
"simulation_result": "failed",
"report": "EPANET report\n Error 202: illegal numeric value ACTIVE in [STATUS] section:\n V1 ACTIVE\n",
}
if has_output:
result["output"] = {"node_results": [], "link_results": [], "times": {"num_periods": 1}}
monkeypatch.setattr(simulation, "run_project", lambda _: json.dumps(result))
refresh = Mock()
store = Mock()
monkeypatch.setattr(simulation, "refresh_materialized_views_after_commit", refresh)
monkeypatch.setattr(simulation.TimescaleInternalStorage, "store_realtime_simulation", store)
with pytest.raises(RuntimeError, match="EPANET.*Error 202"):
simulation.run_simulation(
name="demo",
simulation_type="realtime",
modify_pattern_start_time="2026-07-30T04:30:00Z",
scada_mappings=_empty_scada_mappings(simulation),
)
assert transaction_outcome == ["rollback"]
refresh.assert_not_called()
store.assert_not_called()