117 lines
4.8 KiB
TypeScript
117 lines
4.8 KiB
TypeScript
import type { InvestigationDiagram } from "../api/investigation-report";
|
|
import type { OperationalTask, OperationalTaskReport } from "../api/operational-task-client";
|
|
|
|
const diagramLabel = (value: string) =>
|
|
value
|
|
.replace(/[\r\n]/g, " ")
|
|
.replace(/"/g, "#quot;")
|
|
.replace(/</g, "#lt;")
|
|
.replace(/>/g, "#gt;")
|
|
.replace(/`/g, "#96;");
|
|
|
|
/** Preserve recorded relationships, without turning plan statuses into execution facts. */
|
|
export function investigationDiagram(
|
|
task: OperationalTask,
|
|
snapshot: OperationalTaskReport | null
|
|
): InvestigationDiagram | null {
|
|
if (snapshot?.report.diagram) return snapshot.report.diagram;
|
|
const plan = task.latest_plan;
|
|
if (
|
|
!plan ||
|
|
(snapshot
|
|
? snapshot.revision !== task.latest_report?.revision ||
|
|
plan.execution_generation !== snapshot.execution_generation
|
|
: plan.execution_generation !== task.execution_generation)
|
|
)
|
|
return null;
|
|
const ids = new Map(plan.plan.nodes.map((node, index) => [node.id, `n${index}`]));
|
|
const nodeDefinition = (node: (typeof plan.plan.nodes)[number]) => {
|
|
const label = `\`${diagramLabel(node.title)}\``;
|
|
return node.kind === "decision" || node.kind === "gate"
|
|
? `${ids.get(node.id)}{"${label}"}:::tjDecision`
|
|
: `${ids.get(node.id)}["${label}"]:::${node.owner === "human" ? "tjHuman" : "tjAgent"}`;
|
|
};
|
|
const agentNodes = plan.plan.nodes.filter((node) => node.owner === "agent");
|
|
const humanNodes = plan.plan.nodes.filter((node) => node.owner === "human");
|
|
const nodes =
|
|
agentNodes.length && humanNodes.length
|
|
? [
|
|
'subgraph tj_agent_group["Agent · 资料核查"]',
|
|
...agentNodes.map(nodeDefinition),
|
|
"end",
|
|
'subgraph tj_human_group["人工 · 现场协作"]',
|
|
...humanNodes.map(nodeDefinition),
|
|
"end"
|
|
]
|
|
: plan.plan.nodes.map(nodeDefinition);
|
|
const edges = plan.plan.edges.map((edge) =>
|
|
edge.condition
|
|
? `${ids.get(edge.from)} -->|"${diagramLabel(edge.condition)}"| ${ids.get(edge.to)}`
|
|
: `${ids.get(edge.from)} --> ${ids.get(edge.to)}`
|
|
);
|
|
return {
|
|
title: "调查脉络",
|
|
mermaid: ["flowchart TD", ...nodes, ...edges].join("\n"),
|
|
node_refs: plan.plan.nodes
|
|
.filter((node) => node.evidence_refs?.length || node.action_refs?.length)
|
|
.map((node) => ({
|
|
node_id: ids.get(node.id)!,
|
|
label: node.title,
|
|
evidence_refs: node.evidence_refs ?? [],
|
|
action_refs: node.action_refs ?? []
|
|
}))
|
|
};
|
|
}
|
|
|
|
export function investigationStatus(task: OperationalTask): string {
|
|
if (task.active_run?.status === "queued") return "等待调查";
|
|
if (task.active_run?.status === "claimed") return "调查启动中";
|
|
if (task.active_run?.status === "running") return "调查中";
|
|
if (task.lifecycle_status === "failed") return "本轮执行失败";
|
|
if (task.lifecycle_status === "cancelled") return "本轮已取消";
|
|
if (task.questions?.some((question) => question.status === "pending" && question.execution_generation === task.execution_generation)) return "等待回答";
|
|
if (task.actions.some((action) => action.approval_status === "pending")) return "待人工审核";
|
|
if (task.actions.some(isRetryableInvestigationAction)) return "待重试动作";
|
|
if (
|
|
task.actions.some(
|
|
(action) =>
|
|
action.approval_status === "approved" && action.execution_status === "not_submitted"
|
|
)
|
|
)
|
|
return "待执行动作";
|
|
if (
|
|
task.actions.some(
|
|
(action) =>
|
|
action.approval_status === "approved" &&
|
|
["submitted", "unknown"].includes(action.execution_status)
|
|
)
|
|
)
|
|
return "待核对回执";
|
|
if (
|
|
task.latest_report?.execution_generation === task.execution_generation &&
|
|
task.latest_report.report.outcome === "insufficient_data" &&
|
|
task.lifecycle_status === "completed"
|
|
)
|
|
return "已结束 · 证据不足";
|
|
const recoveryPhase = task.handling_context?.phase;
|
|
if (recoveryPhase === "waiting_work_order") return "等待现场工单返回";
|
|
if (recoveryPhase === "waiting_scada") return "恢复供水和监测复核";
|
|
if (recoveryPhase === "verification_ready") return "等待恢复复核报告";
|
|
if (recoveryPhase === "completed" && task.lifecycle_status === "completed") return "复核成功 · 已完成";
|
|
if (task.lifecycle_status === "completed") return "本轮调查完成";
|
|
return "调查中";
|
|
}
|
|
|
|
export function isPendingInvestigationAction(action: OperationalTask["actions"][number]) {
|
|
return (
|
|
action.approval_status === "pending" ||
|
|
(action.approval_status === "approved" &&
|
|
["not_submitted", "submitted", "unknown"].includes(action.execution_status)) ||
|
|
isRetryableInvestigationAction(action)
|
|
);
|
|
}
|
|
|
|
export function isRetryableInvestigationAction(action: OperationalTask["actions"][number]) {
|
|
return action.approval_status === "approved" && ["failed", "unsupported"].includes(action.execution_status);
|
|
}
|