Files
next-tjwater-frontend/src/features/workbench/utils/investigation-presentation.ts
T
jiang 4e3ed5098e
Generic Container CI/CD / test-build-publish (push) Successful in 1m13s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m13s
fix(workbench): retry approved task actions after dispatch failure
2026-11-07 22:20:04 +08:00

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);
}