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, "#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); }