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
+10 -1
View File
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from app.services.operational_tasks import OperationalTaskService
RECOVERY_WORKFLOWS = {"burst_report", "scada_anomaly"}
RECOVERY_WORKFLOWS = {"burst_report", "scada_anomaly", "water_pollution"}
def track_work_order(task: dict, action: dict) -> bool:
@@ -60,6 +60,8 @@ def accept_completion(task: dict, feedback: dict, *, recorded_at: datetime | Non
# edits. A revised field receipt can explicitly correct the verification start.
if feedback["status"] == "completed":
order["returned_at"] = feedback["occurred_at"]
if task.get("workflow") == "water_pollution":
order["feedback_evidence_id"] = (feedback.get("evidence") or {}).get("evidence_id")
else:
order.pop("returned_at", None)
if order.get("superseded_by"):
@@ -67,6 +69,13 @@ def accept_completion(task: dict, feedback: dict, *, recorded_at: datetime | Non
orders = [item for item in context["work_orders"].values() if not item.get("superseded_by")]
if orders and all(item["status"] == "completed" for item in orders):
returned_at = max((item["returned_at"] for item in orders), key=datetime.fromisoformat)
if task.get("workflow") == "water_pollution":
# A completion receipt resumes review, never the pressure-based
# automatic recovery calculation. Source revisions may carry new
# sampling or retest facts even when the field time is unchanged.
context.update(phase="field_review", title="水污染处置反馈复核", returned_at=returned_at)
context.pop("verification_evidence_id", None)
return True
previous = context.get("returned_at")
if (previous is None or datetime.fromisoformat(previous) != datetime.fromisoformat(returned_at)
or context.get("phase") == "waiting_work_order"):