feat(investigations): add event impact and work-order feedback flows

Add isolated burst and pollution impact evidence, parallel work-order workflows, persisted notifications and graph updates.

Checkpoint current code. Known follow-ups remain for large pollution result previews and graceful geometry failure.
This commit is contained in:
2026-11-14 15:14:02 +08:00
parent 471ad92a47
commit 2e4f6cb93b
49 changed files with 1826 additions and 93 deletions
+24
View File
@@ -373,6 +373,30 @@ def test_replay_binding_denies_future_and_allows_device_metadata(monkeypatch):
asyncio.run(query_service.authorize_query(SimpleNamespace(project=object()), bound, TOOLS["list_scada_devices"], SimpleNamespace()))
def test_replay_burst_scenario_requires_coverage_permission_and_known_start(monkeypatch):
bound = binding(task=True)
bound.replay_context = {"knowledge_cutoff": "2026-10-08T01:00:00Z"}
permissions = PERMISSIONS | {"simulation.run", "scada.view", "simulation.view"}
bound.permission_cap = sorted(permissions)
monkeypatch.setattr(query_service, "permissions_for_context", lambda _: permissions)
scope = SimpleNamespace(project=SimpleNamespace(project_role="member"))
args = TOOLS["run_burst_simulation"].model.model_validate({
"context_handle": bound.context_handle, "start_time": "2026-10-08T01:00:00Z",
"output_node_ids": ["J1"], "burst_pipe_ids": ["P1"], "orifice_areas_cm2": [2],
"include_impact_coverage": True,
})
asyncio.run(query_service.authorize_query(scope, bound, TOOLS["run_burst_simulation"], args))
for changed, status in (({"include_impact_coverage": False}, 422),
({"start_time": datetime(2026, 10, 8, 2, tzinfo=timezone.utc)}, 422)):
with pytest.raises(HTTPException) as error:
asyncio.run(query_service.authorize_query(scope, bound, TOOLS["run_burst_simulation"], args.model_copy(update=changed)))
assert error.value.status_code == status
bound.permission_cap = sorted(permissions - {"simulation.view"})
with pytest.raises(HTTPException) as error:
asyncio.run(query_service.authorize_query(scope, bound, TOOLS["run_burst_simulation"], args))
assert error.value.status_code == 403
def test_model_response_displays_utc8_without_changing_query_instants_or_source(runtime):
app, client, _, _ = runtime
at = datetime(2026, 12, 31, 16, tzinfo=timezone.utc)