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:
@@ -80,6 +80,78 @@ async def test_failed_attempt_cannot_be_relabelled_as_a_success():
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_existing_waiting_node_accepts_a_status_only_update_without_extending_the_graph():
|
||||
task, repo, service = setup_service()
|
||||
first = await publish(service, repo, task["task_id"], nodes=[
|
||||
{"node_id": "read", "label": "读取监测", "status": "recorded"},
|
||||
{"node_id": "review", "label": "核对补充资料", "status": "needs_input", "detail": "等待现场记录"},
|
||||
], edges=[{"source": "read", "target": "review"}])
|
||||
original = first.task.latest_progress.progress.diagram
|
||||
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand.model_validate({
|
||||
"request_id": str(uuid4()), "source_session_id": repo.run["agent_session_id"],
|
||||
"run_id": repo.run["run_id"], "report": {
|
||||
"title": "阶段报告", "current_conclusion": "仍需核对现场资料", "conclusion_boundary": "现有资料范围",
|
||||
"stage": "interim", "outcome": "concluded", "data_gaps": [],
|
||||
},
|
||||
}))
|
||||
result = await publish(service, repo, task["task_id"], nodes=[
|
||||
{"node_id": "review", "status": "recorded", "detail": "已核对收到的现场记录"},
|
||||
])
|
||||
updated = result.task.latest_progress.progress.diagram
|
||||
assert [node.node_id for node in updated.node_refs] == ["read", "review"]
|
||||
assert updated.node_refs[1].label == "核对补充资料"
|
||||
assert updated.node_refs[1].status == "recorded"
|
||||
assert updated.node_refs[1].detail == "已核对收到的现场记录"
|
||||
assert updated.edges == original.edges
|
||||
assert updated.mermaid == original.mermaid
|
||||
assert original.node_refs[1].status == "needs_input"
|
||||
archived = (await service.report(task["task_id"], 1)).report.diagram
|
||||
assert archived.node_refs[1].status == "needs_input"
|
||||
assert archived.node_refs[1].detail == "等待现场记录"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("label_patch", [{}, {"label": None}])
|
||||
def test_partial_updates_keep_labels_and_accumulated_references_including_legacy_nodes(label_patch):
|
||||
evidence_id = "saved-reading"
|
||||
action_id = uuid4()
|
||||
original = InvestigationDiagram.model_validate({
|
||||
"title": "旧调查图", "mermaid": "flowchart LR\nold{核对资料} --> next[继续调查]",
|
||||
"node_refs": [{"node_id": "old", "label": "核对资料", "status": "waiting_human",
|
||||
"detail": "等待资料", "evidence_refs": [evidence_id], "action_refs": [action_id],
|
||||
"map_targets": [{"source_id": "scada", "feature_id": "P1"}]}],
|
||||
})
|
||||
updated = update_investigation_graph(original, InvestigationGraphUpdate.model_validate({
|
||||
"nodes": [{"node_id": "old", "status": "recorded", **label_patch}],
|
||||
}))
|
||||
assert updated.mermaid == updated.source_mermaid == original.mermaid
|
||||
assert updated.node_refs[0] == original.node_refs[0].model_copy(update={"status": "recorded"})
|
||||
assert original.node_refs[0].status == "needs_input"
|
||||
|
||||
|
||||
def test_status_only_updates_cannot_create_an_unlabelled_node_or_rewrite_a_failed_attempt():
|
||||
with pytest.raises(DomainError) as error:
|
||||
update_investigation_graph(None, InvestigationGraphUpdate.model_validate({
|
||||
"nodes": [{"node_id": "missing", "status": "recorded"}],
|
||||
}))
|
||||
assert error.value.code == "GRAPH_NODE_LABEL_REQUIRED"
|
||||
failed = update_investigation_graph(None, InvestigationGraphUpdate.model_validate({
|
||||
"nodes": [{"node_id": "attempt", "label": "失败尝试", "status": "failed", "detail": "接口超时"}],
|
||||
}))
|
||||
enriched = update_investigation_graph(failed, InvestigationGraphUpdate.model_validate({
|
||||
"nodes": [{"node_id": "attempt", "evidence_refs": ["timeout-record"]}],
|
||||
}))
|
||||
assert enriched.node_refs[0].label == "失败尝试"
|
||||
assert enriched.node_refs[0].detail == "接口超时"
|
||||
assert enriched.node_refs[0].status == "failed"
|
||||
assert enriched.node_refs[0].evidence_refs == ["timeout-record"]
|
||||
with pytest.raises(DomainError) as error:
|
||||
update_investigation_graph(enriched, InvestigationGraphUpdate.model_validate({
|
||||
"nodes": [{"node_id": "attempt", "status": "recorded"}],
|
||||
}))
|
||||
assert error.value.code == "GRAPH_FAILED_ATTEMPT_IMMUTABLE"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_new_execution_round_continues_the_same_task_graph():
|
||||
task, repo, service = setup_service()
|
||||
|
||||
Reference in New Issue
Block a user