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
+23 -3
View File
@@ -19,6 +19,7 @@ from app.mcp.work_order_skills import read_work_order_skill
("water_quality_complaint", "water-quality-complaint"),
("general", "appointment-follow-up"),
("scada_anomaly", "scada-anomaly"),
("water_pollution", "water-pollution-event"),
])
def test_skill_descriptor_and_body_have_same_version_and_digest(workflow, name):
descriptor = read_work_order_skill(workflow)
@@ -59,9 +60,20 @@ def test_scada_instructions_advance_candidate_analysis_independently_of_source_l
assert not any(word in text for word in ("演示", "mock", "location_demo", "只发布回放成果"))
def test_task_skill_uses_persisted_workflow_and_preserves_task_scope(monkeypatch):
def test_pollution_instructions_offer_work_order_before_simulation_and_reuse_the_investigation():
skill = read_work_order_skill("water_pollution", include_content=True)
text = skill["instructions"]
assert text.index("propose_task_action") < text.index("run_contaminant_simulation")
assert "include_impact_coverage=true" in text and "不等模拟完成才发起工单" in text
assert "同一 task_id" in text and "不覆盖已审核工单或绕过版本校验" in text
assert "模型曲线不填观测专用 chart.scada" in text
assert "不能把浊度、余氯下降或感官描述直接换算成污染源浓度" in text
@pytest.mark.parametrize("workflow", ["burst_report", "water_quality_complaint", "water_pollution"])
def test_task_skill_uses_persisted_workflow_and_preserves_task_scope(monkeypatch, workflow):
task_id, project_id = uuid4(), uuid4()
reader = AsyncMock(return_value={"task": {"workflow": "burst_report"}})
reader = AsyncMock(return_value={"task": {"workflow": workflow}})
monkeypatch.setattr(query_service.reads, "task_context_summary", reader)
monkeypatch.setattr(query_service, "permissions_for_context", lambda project: {"operational_task.view"})
scope = SimpleNamespace(connection=object(), project=object())
@@ -80,7 +92,15 @@ def test_task_skill_uses_persisted_workflow_and_preserves_task_scope(monkeypatch
body = query("skill")
assert summary.data["handling_skill"]["content_sha256"] == body.data["skill"]["content_sha256"]
assert summary.data["display_time_zone"] == "Asia/Shanghai"
assert body.data["skill"]["workflow"] == "burst_report"
assert body.data["skill"]["workflow"] == workflow
for result in (summary, body):
assert ("source_trace_capability" in result.data) == (workflow == "water_pollution")
if workflow == "water_pollution":
assert result.data["source_trace_capability"]["tool"] == "run_contaminant_source_trace"
capability = result.data["source_trace_capability"]
assert capability["work_order_wait_blocks_analysis"] is False
assert capability["simulation_parameters"] == {"include_impact_coverage": True}
assert capability["next_steps"].index("propose_task_action") < capability["next_steps"].index("run_contaminant_simulation")
assert body.data["skill"]["instructions"] and not body.truncated
assert body.evidence_id is None and body.semantics == "task"
reader.assert_awaited_with(scope.connection, project_id=project_id, task_id=task_id, report_revision=None)