109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
import { z } from "zod";
|
|
import type { AgentTaskDiscussionContext, AnswerUiSelection } from "@/features/agent";
|
|
import { reportUiBlocksSchema } from "@/features/agent";
|
|
|
|
export type InvestigationDiscussion = {
|
|
prompt?: string;
|
|
context?: AgentTaskDiscussionContext;
|
|
// Display metadata from the selected report, never the task's current round.
|
|
reportGeneration?: number | null;
|
|
reference?: AnswerUiSelection;
|
|
};
|
|
|
|
const text = z.string().trim().min(1);
|
|
export const investigationMapTargetSchema = z.object({
|
|
source_id: z.enum(["junctions", "pipes", "valves", "pumps", "tanks", "reservoirs", "scada"]),
|
|
feature_id: text.max(128)
|
|
}).strict();
|
|
export const investigationDiagramSchema = z
|
|
.object({
|
|
title: text,
|
|
mermaid: text
|
|
.max(20000)
|
|
.refine(
|
|
(source) =>
|
|
/^\s*(?:flowchart|graph)\s+(?:TD|TB|BT|LR|RL)\b/.test(source) &&
|
|
!source.includes("```") &&
|
|
!source.includes("%%{") &&
|
|
!/(?:^|[;\n])\s*click\b/.test(source),
|
|
"调查图只接受不含指令或回调的 Mermaid 流程描述"
|
|
),
|
|
node_refs: z
|
|
.array(
|
|
z
|
|
.object({
|
|
node_id: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
|
|
label: text,
|
|
status: z.enum(["pending", "running", "completed", "waiting_human", "failed", "skipped", "cancelled"]).nullish(),
|
|
map_targets: z.array(investigationMapTargetSchema).max(100).optional(),
|
|
evidence_refs: z.array(text).default([]),
|
|
action_refs: z.array(z.string().uuid()).default([])
|
|
})
|
|
.strict()
|
|
)
|
|
.max(100)
|
|
.default([])
|
|
})
|
|
.strict()
|
|
.superRefine((diagram, ctx) => {
|
|
if (new Set(diagram.node_refs.map((node) => node.node_id)).size !== diagram.node_refs.length) {
|
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "调查图节点引用重复" });
|
|
}
|
|
});
|
|
const dataGapSchema = z
|
|
.object({
|
|
gap_id: text,
|
|
node_id: text.nullish(),
|
|
missing_data: text,
|
|
scope: text,
|
|
impact: text,
|
|
required_data: text,
|
|
evidence_refs: z.array(text).min(1),
|
|
blocking: z.boolean()
|
|
})
|
|
.strict();
|
|
|
|
/** Historical omissions remain unknown. Never infer outcomes from report prose. */
|
|
export const investigationReportSchema = z
|
|
.object({
|
|
title: text,
|
|
current_conclusion: text,
|
|
conclusion_boundary: text,
|
|
evidence_refs: z.array(text),
|
|
recommendations: z.array(text),
|
|
pending_items: z.array(text),
|
|
stage: z.enum(["interim", "final"]).nullish(),
|
|
outcome: z.enum(["concluded", "insufficient_data"]).nullish(),
|
|
stop_reason: text.nullish(),
|
|
data_gaps: z.array(dataGapSchema).nullish(),
|
|
diagram: investigationDiagramSchema.nullish(),
|
|
ui_blocks: reportUiBlocksSchema.optional()
|
|
})
|
|
.strict()
|
|
.superRefine((report, ctx) => {
|
|
const invalid = (message: string) => ctx.addIssue({ code: z.ZodIssueCode.custom, message });
|
|
if (report.outcome == null) {
|
|
if (report.stage != null || report.stop_reason != null || report.data_gaps != null)
|
|
invalid("报告结构化字段必须一起提供");
|
|
return;
|
|
}
|
|
if (report.stage == null || report.data_gaps == null) {
|
|
invalid("报告缺少阶段或数据缺口清单");
|
|
return;
|
|
}
|
|
if (new Set(report.data_gaps.map((gap) => gap.gap_id)).size !== report.data_gaps.length)
|
|
invalid("数据缺口编号重复");
|
|
const blocking = report.data_gaps.some((gap) => gap.blocking);
|
|
if (report.outcome === "insufficient_data") {
|
|
if (report.stage !== "final" || !report.stop_reason || !blocking)
|
|
invalid("证据不足必须提供最终报告、结束原因和关键缺口");
|
|
if (report.recommendations.length || report.pending_items.length)
|
|
invalid("证据不足退出后不再生成后续处置建议或待办");
|
|
} else if (report.stop_reason != null || blocking)
|
|
invalid("已形成结论不能包含结束原因或阻塞缺口");
|
|
});
|
|
|
|
export type InvestigationReport = z.infer<typeof investigationReportSchema>;
|
|
export type InvestigationDiagram = z.infer<typeof investigationDiagramSchema>;
|
|
export type InvestigationMapTarget = z.infer<typeof investigationMapTargetSchema>;
|