feat(simulation): 支持冲洗阀门状态与设置值
Generic Container CI/CD / test-build-publish (push) Successful in 2m39s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m40s

This commit is contained in:
2026-08-17 18:28:57 +08:00
parent c250e97b87
commit 2581631b51
7 changed files with 397 additions and 29 deletions
+140
View File
@@ -383,6 +383,7 @@ def test_flushing_endpoint_passes_required_scheme_name(monkeypatch):
"modify_pattern_start_time": "2025-01-02T03:04:05+08:00",
"modify_total_duration": 900,
"modify_valve_opening": {"V1": 0.5},
"valve_control": None,
"drainage_node_ID": "N1",
"flushing_flow": 100.0,
"scheme_name": "flush_case_01",
@@ -390,6 +391,145 @@ def test_flushing_endpoint_passes_required_scheme_name(monkeypatch):
}
def test_flushing_endpoint_allows_omitting_valves(monkeypatch):
module = _load_simulation_module(monkeypatch)
captured = {}
def fake_flushing_analysis(**kwargs):
captured.update(kwargs)
return "ok"
monkeypatch.setattr(module, "flushing_analysis", fake_flushing_analysis)
client = _build_authenticated_client(module)
response = client.post(
"/api/v1/flushing-analyses",
params={
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"drainage_node_ID": "N1",
"scheme_name": "flush_without_valves",
},
)
assert response.status_code == 200
assert response.text == "ok"
assert captured["modify_valve_opening"] is None
assert captured["valve_control"] is None
assert captured["drainage_node_ID"] == "N1"
def test_flushing_endpoint_passes_explicit_valve_control(monkeypatch):
module = _load_simulation_module(monkeypatch)
captured = {}
def fake_flushing_analysis(**kwargs):
captured.update(kwargs)
return "ok"
monkeypatch.setattr(module, "flushing_analysis", fake_flushing_analysis)
client = _build_authenticated_client(module)
response = client.post(
"/api/v1/flushing-analyses",
params=[
("network", "demo"),
("start_time", "2025-01-02T03:04:05+08:00"),
("valves", "V1"),
("valves", "V2"),
("valve_statuses", "ACTIVE"),
("valve_statuses", "CLOSED"),
("valve_settings", "2.5"),
("valve_settings", ""),
("drainage_node_ID", "N1"),
("scheme_name", "flush_with_valve_control"),
],
)
assert response.status_code == 200
assert captured["modify_valve_opening"] is None
assert captured["valve_control"] == {
"V1": {"status": "ACTIVE", "setting": "2.5"},
"V2": {"status": "CLOSED"},
}
def test_flushing_endpoint_requires_setting_for_active_valve(monkeypatch):
module = _load_simulation_module(monkeypatch)
client = _build_authenticated_client(module)
response = client.post(
"/api/v1/flushing-analyses",
params={
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"valves": "V1",
"valve_statuses": "ACTIVE",
"drainage_node_ID": "N1",
"scheme_name": "flush_without_active_setting",
},
)
assert response.status_code == 422
def test_flushing_endpoint_rejects_mixed_valve_control_modes(monkeypatch):
module = _load_simulation_module(monkeypatch)
client = _build_authenticated_client(module)
response = client.post(
"/api/v1/flushing-analyses",
params={
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"valves": "V1",
"valves_k": 0.5,
"valve_statuses": "ACTIVE",
"valve_settings": "2.5",
"drainage_node_ID": "N1",
"scheme_name": "flush_with_mixed_controls",
},
)
assert response.status_code == 422
def test_flushing_endpoint_rejects_settings_without_statuses(monkeypatch):
module = _load_simulation_module(monkeypatch)
client = _build_authenticated_client(module)
response = client.post(
"/api/v1/flushing-analyses",
params={
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"valves": "V1",
"valves_k": 0.5,
"valve_settings": "2.5",
"drainage_node_ID": "N1",
"scheme_name": "flush_with_orphan_settings",
},
)
assert response.status_code == 422
def test_flushing_endpoint_requires_drainage_node(monkeypatch):
module = _load_simulation_module(monkeypatch)
client = _build_authenticated_client(module)
response = client.post(
"/api/v1/flushing-analyses",
params={
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"scheme_name": "flush_without_drainage_node",
},
)
assert response.status_code == 422
def test_contaminant_endpoint_passes_current_username(monkeypatch):
module = _load_simulation_module(monkeypatch)
captured = {}
@@ -1,3 +1,4 @@
import inspect
import json
from datetime import timedelta
@@ -7,6 +8,70 @@ from app.infra.db.timescaledb.repositories.scheme import SchemeRepository
from app.services.time_api import parse_utc_time
def test_run_simulation_exposes_explicit_valve_control():
from app.services import simulation
parameters = inspect.signature(simulation.run_simulation).parameters
assert "valve_control" in parameters
def test_apply_valve_control_matches_run_simulation_ex_semantics(monkeypatch):
from app.services import simulation
updates: dict[str, dict] = {}
monkeypatch.setattr(
simulation,
"get_status",
lambda project_name, valve_name: {
"link": valve_name,
"status": "OPEN",
"setting": 1.0,
},
)
monkeypatch.setattr(
simulation,
"set_status",
lambda project_name, changeset: updates.update(
{
changeset.operations[0]["link"]: changeset.operations[0].copy()
}
),
)
simulation._apply_valve_control(
"demo",
{
"V-status": {"status": "ACTIVE"},
"V-setting": {"setting": 2.5},
"V-closed": {"status": "ACTIVE", "setting": 9.0, "k": 0},
"V-k": {"status": "ACTIVE", "setting": 9.0, "k": 0.5},
},
)
assert updates["V-status"] == {
"link": "V-status",
"status": "ACTIVE",
"setting": 1.0,
}
assert updates["V-setting"] == {
"link": "V-setting",
"status": "OPEN",
"setting": 2.5,
}
assert updates["V-closed"] == {
"link": "V-closed",
"status": "CLOSED",
"setting": 9.0,
}
assert updates["V-k"] == {
"link": "V-k",
"status": "ACTIVE",
"setting": 0.1036 * pow(0.5, -3.105),
}
def _node_result(periods: int) -> list[dict]:
return [
{