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:
@@ -56,6 +56,7 @@ from app.domain.schemas.operational_task import (
|
||||
OperationalTaskCreate,
|
||||
OperationalTaskDetail,
|
||||
OperationalTaskNotification,
|
||||
WorkOrderNotificationReference,
|
||||
OperationalTaskPermissions,
|
||||
OperationalTaskSchedule,
|
||||
OperationalTaskScheduleCreate,
|
||||
@@ -874,7 +875,7 @@ class OperationalTaskService:
|
||||
diagram = update_investigation_graph(saved_diagram, command.progress.graph_update)
|
||||
elif saved_diagram is not None:
|
||||
require(diagram is None or saved_diagram.edges is None, "GRAPH_UPDATE_REQUIRED",
|
||||
"Use graph_update to extend the saved investigation graph", status=422)
|
||||
"Use graph_update to patch saved nodes or extend the investigation graph", status=422)
|
||||
if diagram is None:
|
||||
diagram = adopt_legacy_investigation_graph(saved_diagram)
|
||||
if diagram:
|
||||
@@ -1258,6 +1259,7 @@ class OperationalTaskService:
|
||||
task_id, "action_approval_pending", task["title"], command.reason, now,
|
||||
required_permission=approval_permission,
|
||||
subject_type="action", subject_id=action.action_id,
|
||||
work_order_action=action if action.kind == "create_work_order" else None,
|
||||
)
|
||||
await self._save_request(
|
||||
scope, command.request_id, body, {"action_id": str(action.action_id)}
|
||||
@@ -1743,6 +1745,29 @@ class OperationalTaskService:
|
||||
task_id, "task_failed",
|
||||
task["title"], command.detail, now,
|
||||
)
|
||||
if command.source_revision > 1 and task["task_type"] in {"external_work_order", "event_investigation"}:
|
||||
# The feedback, its notification and outbox event commit together.
|
||||
# Duplicate request/source versions return above, before publishing.
|
||||
work_order_action = next((item for item in await self.repo.list_actions(str(task_id))
|
||||
if item["kind"] == "create_work_order" and item.get("receipt")
|
||||
and command.source_event_id == f"{item['receipt']['adapter']}:{item['receipt']['receipt_id']}"), None)
|
||||
is_work_order_feedback = (task["task_type"] == "external_work_order"
|
||||
or work_order_action is not None
|
||||
or (task["task_type"] == "event_investigation" and matching_source))
|
||||
if is_work_order_feedback:
|
||||
await self._notify(
|
||||
task_id, "work_order_feedback", "收到工单反馈",
|
||||
command.evidence.summary if command.evidence else command.detail, now,
|
||||
subject_type="action" if work_order_action else "task",
|
||||
subject_id=UUID(str(work_order_action["action_id"])) if work_order_action else task_id,
|
||||
work_order_action=HumanApprovalAction.model_validate(work_order_action) if work_order_action else None,
|
||||
work_order_reference=WorkOrderNotificationReference(
|
||||
source_event_id=command.source_event_id,
|
||||
source_revision=command.source_revision,
|
||||
evidence_id=command.evidence.evidence_id if command.evidence else None,
|
||||
status=command.status,
|
||||
),
|
||||
)
|
||||
response = {
|
||||
"source_event_id": command.source_event_id,
|
||||
"source_revision": command.source_revision,
|
||||
@@ -2161,12 +2186,24 @@ class OperationalTaskService:
|
||||
lifecycle = self._investigation_status(actions, task)
|
||||
recovery = task.get("handling_context") or {}
|
||||
if lifecycle == "completed" and recovery:
|
||||
require(recovery.get("verification_evidence_id") in report.report.evidence_refs,
|
||||
"RECOVERY_EVIDENCE_REQUIRED", "最终复核报告必须引用恢复监测证据", status=409)
|
||||
if task.get("workflow") == "water_pollution":
|
||||
orders = [item for item in recovery.get("work_orders", {}).values()
|
||||
if not item.get("superseded_by")]
|
||||
feedback_refs = [item.get("feedback_evidence_id") for item in orders]
|
||||
require(recovery.get("phase") in {"field_review", "completed"} and bool(orders)
|
||||
and all(item["status"] == "completed" for item in orders)
|
||||
and all(ref and ref in report.report.evidence_refs for ref in feedback_refs),
|
||||
"POLLUTION_FEEDBACK_REQUIRED", "水污染最终报告必须复核并引用全部最新现场反馈证据", status=409)
|
||||
verification_id = feedback_refs[-1]
|
||||
else:
|
||||
verification_id = recovery.get("verification_evidence_id")
|
||||
require(verification_id in report.report.evidence_refs,
|
||||
"RECOVERY_EVIDENCE_REQUIRED", "最终复核报告必须引用恢复监测证据", status=409)
|
||||
require(report.report.outcome == "concluded", "RECOVERY_EVIDENCE_REQUIRED",
|
||||
"恢复复核通过后才能结束事件", status=409)
|
||||
recovery.update(phase="completed", title="恢复供水复核成功", completed_at=now.isoformat())
|
||||
await self._resolve_investigation_alerts(str(task_id), recovery["verification_evidence_id"])
|
||||
recovery.update(phase="completed", title=("水污染处置反馈已复核" if task.get("workflow") == "water_pollution"
|
||||
else "恢复供水复核成功"), completed_at=now.isoformat())
|
||||
await self._resolve_investigation_alerts(str(task_id), verification_id)
|
||||
elif report.report.outcome == "insufficient_data":
|
||||
terminal = all(node.status in {"completed", "skipped", "cancelled"}
|
||||
for node in plan.plan.nodes)
|
||||
@@ -2519,6 +2556,8 @@ class OperationalTaskService:
|
||||
*, required_permission: str | None = None,
|
||||
subject_type: str = "task", subject_id: UUID | None = None,
|
||||
report_reference: InvestigationReportReference | None = None,
|
||||
work_order_reference: WorkOrderNotificationReference | None = None,
|
||||
work_order_action: HumanApprovalAction | None = None,
|
||||
) -> None:
|
||||
notification = OperationalTaskNotification(
|
||||
notification_id=uuid4(), task_id=task_id, kind=kind,
|
||||
@@ -2527,6 +2566,8 @@ class OperationalTaskService:
|
||||
lifecycle_status="active", subject_type=subject_type,
|
||||
subject_id=subject_id or task_id,
|
||||
report_reference=report_reference,
|
||||
work_order_reference=work_order_reference,
|
||||
work_order_action=work_order_action,
|
||||
)
|
||||
await self.repo.insert_notification(_json(notification))
|
||||
await self._append_event(
|
||||
|
||||
Reference in New Issue
Block a user