277 lines
9.6 KiB
TypeScript
277 lines
9.6 KiB
TypeScript
import type { AnswerUiBlock } from "@/features/agent";
|
||
import type { InvestigationPresentationCommand } from "../api/investigation-presentation";
|
||
import { INVESTIGATION_DEV_NETWORK } from "./investigation-dev-network";
|
||
import { complexInvestigationDevCases, createComplexInvestigationDevCase } from "./investigation-dev-complex-cases";
|
||
import {
|
||
createInvestigationDevSample,
|
||
createPresentationRecord,
|
||
createUiRecord,
|
||
serializeInvestigationDevJsonl
|
||
} from "./investigation-dev-preview";
|
||
|
||
export const investigationDevCases = [
|
||
{
|
||
id: "combined",
|
||
label: "组合联动",
|
||
expected: "合成空间点、压力曲线与候选表格一起展示;要素定位和高亮请使用地图调试页签。"
|
||
},
|
||
...complexInvestigationDevCases,
|
||
{
|
||
id: "pressure_line",
|
||
label: "压力趋势对比",
|
||
expected: "打开两条合成压力曲线,核对时间轴、单位、图例和数据表。"
|
||
},
|
||
{
|
||
id: "impact_bar",
|
||
label: "影响用户统计",
|
||
expected: "打开合成用户数柱状图;统计仅供展示测试,不是实际停水户数。"
|
||
},
|
||
{
|
||
id: "impact_area",
|
||
label: "合成影响范围",
|
||
expected: "在当前地图中心附近显示一个合成面;不代表真实隔离边界。"
|
||
},
|
||
{
|
||
id: "complaint_heatmap",
|
||
label: "投诉热力分布",
|
||
expected: "显示三个带非负权重的合成投诉点,按热力模式渲染。"
|
||
},
|
||
{
|
||
id: "candidates_table",
|
||
label: "候选管段对比",
|
||
expected: "展示两条合成候选数据和对比表格,不查询或高亮虚拟管段。"
|
||
},
|
||
{
|
||
id: "valves_table",
|
||
label: "隔离阀门清单",
|
||
expected: "展示两条合成阀门数据和核查表格,不执行关阀或发布处置方案。"
|
||
},
|
||
{
|
||
id: "data_only",
|
||
label: "纯 JSONL 数据",
|
||
expected: "仅展示候选与影响数据;数据行中出现 action 字段也不会触发地图动作。"
|
||
},
|
||
{
|
||
id: "invalid_json",
|
||
label: "错误:JSON 语法",
|
||
expected: "第 2 行报语法错误,整批不执行;可点击执行验证拦截结果。"
|
||
},
|
||
{
|
||
id: "invalid_command",
|
||
label: "错误:不支持的动作",
|
||
expected: "第 1 行展示指令被拒绝,不执行任意命令。"
|
||
},
|
||
{
|
||
id: "invalid_chart",
|
||
label: "错误:图表数据不齐",
|
||
expected: "第 1 行图表校验失败,时间轴和序列长度不一致时不能执行。"
|
||
},
|
||
{
|
||
id: "invalid_comparison",
|
||
label: "错误:同期时刻错位",
|
||
expected: "第 1 行拒绝历史采样时间与横轴槽位不一致的复合图,整批不执行。"
|
||
}
|
||
] as const;
|
||
|
||
export type InvestigationDevCaseId = (typeof investigationDevCases)[number]["id"];
|
||
|
||
const jsonl = serializeInvestigationDevJsonl;
|
||
const presentation = createPresentationRecord;
|
||
// Invalid cases deliberately bypass the validated writer to exercise rejection.
|
||
const invalidJsonl = (records: unknown[]) =>
|
||
records.map((record) => JSON.stringify(record)).join("\n");
|
||
const data = (record_type: string, fields: Record<string, unknown>) => ({
|
||
record_type,
|
||
origin: "synthetic",
|
||
source_evidence_refs: [],
|
||
...fields
|
||
});
|
||
|
||
function table(id: string, title: string, columns: string[], rows: string[][]): unknown {
|
||
const block: AnswerUiBlock = {
|
||
version: "answer-ui@1",
|
||
block_id: `dev-case-${id}`,
|
||
fallback_text: `${title},仅供本地展示测试。`,
|
||
spec: {
|
||
root: "table",
|
||
elements: {
|
||
table: { type: "Table", props: { title, columns, rows }, children: [] }
|
||
}
|
||
}
|
||
};
|
||
return createUiRecord(block);
|
||
}
|
||
|
||
/** Synthetic fixtures use current map coordinates and never query invented asset IDs. */
|
||
export function createInvestigationDevCase(
|
||
caseId: InvestigationDevCaseId
|
||
): string {
|
||
const [longitude, latitude] = INVESTIGATION_DEV_NETWORK.center;
|
||
if (caseId === "combined") return createInvestigationDevSample();
|
||
const point = (dx = 0, dy = 0): [number, number] => [
|
||
Math.max(-180, Math.min(180, longitude + dx)),
|
||
latitude + dy
|
||
];
|
||
const overlay = (
|
||
mode: "geometry" | "heatmap",
|
||
title: string,
|
||
features: Extract<
|
||
InvestigationPresentationCommand,
|
||
{ action: "set_overlay" }
|
||
>["data"]["features"]
|
||
) =>
|
||
presentation({
|
||
action: "set_overlay",
|
||
overlay_id: `dev-case-${caseId}`,
|
||
title,
|
||
mode,
|
||
evidence_id: "dev:synthetic",
|
||
data: { type: "FeatureCollection", features }
|
||
});
|
||
|
||
switch (caseId) {
|
||
case "complex_investigation":
|
||
case "complex_tables":
|
||
case "complex_charts":
|
||
return createComplexInvestigationDevCase(caseId, [longitude, latitude]);
|
||
case "pressure_line":
|
||
return jsonl([
|
||
presentation({
|
||
action: "open_chart",
|
||
chart_id: "dev-case-pressure-line",
|
||
evidence_refs: ["dev:synthetic"],
|
||
chart: {
|
||
title: "本地合成压力对比",
|
||
chart_type: "line",
|
||
x_data: ["08:00", "08:15", "08:30", "08:45"],
|
||
series: [
|
||
{ name: "异常日压力水头(m)", data: [32, 18, 22, 30] },
|
||
{ name: "基线压力水头(m)", data: [33, 32, 32, 33] }
|
||
]
|
||
}
|
||
})
|
||
]);
|
||
case "impact_bar":
|
||
return jsonl([
|
||
presentation({
|
||
action: "open_chart",
|
||
chart_id: "dev-case-impact-bar",
|
||
evidence_refs: ["dev:synthetic"],
|
||
chart: {
|
||
title: "本地合成影响用户统计",
|
||
chart_type: "bar",
|
||
x_data: ["合成片区 A", "合成片区 B", "合成片区 C"],
|
||
series: [{ name: "合成用户数(户)", data: [120, 80, 45] }]
|
||
}
|
||
})
|
||
]);
|
||
case "impact_area":
|
||
return jsonl([
|
||
overlay("geometry", "本地合成影响范围", [
|
||
{
|
||
type: "Feature",
|
||
properties: { project_code: INVESTIGATION_DEV_NETWORK.projectCode, origin: "synthetic", purpose: "display_test_only" },
|
||
geometry: {
|
||
type: "Polygon",
|
||
coordinates: [
|
||
[
|
||
point(-0.002, -0.0015),
|
||
point(0.002, -0.0015),
|
||
point(0.002, 0.0015),
|
||
point(-0.002, 0.0015),
|
||
point(-0.002, -0.0015)
|
||
]
|
||
]
|
||
}
|
||
}
|
||
])
|
||
]);
|
||
case "complaint_heatmap":
|
||
return jsonl([
|
||
overlay(
|
||
"heatmap",
|
||
"本地合成投诉热力",
|
||
[-0.0015, 0, 0.0015].map((offset, index) => ({
|
||
type: "Feature",
|
||
properties: { project_code: INVESTIGATION_DEV_NETWORK.projectCode, origin: "synthetic", weight: [1, 3, 2][index] },
|
||
geometry: { type: "Point", coordinates: point(offset, offset / 2) }
|
||
}))
|
||
)
|
||
]);
|
||
case "candidates_table":
|
||
return jsonl([
|
||
table(
|
||
"candidates",
|
||
"合成候选管段对比",
|
||
["候选", "优先级", "核查依据"],
|
||
[
|
||
[INVESTIGATION_DEV_NETWORK.pipeIds[0], "1", "合成压力降幅较大,待核查"],
|
||
[INVESTIGATION_DEV_NETWORK.pipeIds[1], "2", "合成相关性较弱,待排除"]
|
||
]
|
||
),
|
||
data("candidate", { element_id: INVESTIGATION_DEV_NETWORK.pipeIds[0], rank: 1, status: "unverified" }),
|
||
data("candidate", { element_id: INVESTIGATION_DEV_NETWORK.pipeIds[1], rank: 2, status: "unverified" })
|
||
]);
|
||
case "valves_table":
|
||
return jsonl([
|
||
table(
|
||
"valves",
|
||
"合成隔离阀门清单",
|
||
["阀门", "操作建议", "核查要求"],
|
||
[
|
||
[INVESTIGATION_DEV_NETWORK.valveIds[0], "待评估", "位置与可操作性未核实"],
|
||
[INVESTIGATION_DEV_NETWORK.valveIds[1], "待评估", "连通性与影响范围未核实"]
|
||
]
|
||
),
|
||
data("valve", { element_id: INVESTIGATION_DEV_NETWORK.valveIds[0], status: "unverified", executed: false }),
|
||
data("valve", { element_id: INVESTIGATION_DEV_NETWORK.valveIds[1], status: "unverified", executed: false })
|
||
]);
|
||
case "data_only":
|
||
return jsonl([
|
||
data("candidate", {
|
||
element_id: INVESTIGATION_DEV_NETWORK.pipeIds[2],
|
||
action: "clear_highlights",
|
||
status: "data_only"
|
||
}),
|
||
data("impact", {
|
||
item: "合成片区 A",
|
||
estimated_users: 120,
|
||
unit: "户",
|
||
status: "example_only"
|
||
})
|
||
]);
|
||
case "invalid_json":
|
||
return `${jsonl([presentation({ action: "clear_highlights" })])}\n{"record_type":"candidate",`;
|
||
case "invalid_command":
|
||
return invalidJsonl([
|
||
{ record_type: "presentation", command: { action: "unsupported_test_action" } }
|
||
]);
|
||
case "invalid_chart":
|
||
return invalidJsonl([
|
||
{
|
||
record_type: "presentation",
|
||
command: {
|
||
action: "open_chart",
|
||
chart_id: "dev-invalid-chart",
|
||
evidence_refs: ["dev:synthetic"],
|
||
chart: {
|
||
title: "错误案例:数据长度不齐",
|
||
chart_type: "line",
|
||
x_data: ["08:00", "08:15"],
|
||
series: [{ name: "合成压力水头(m)", data: [32] }]
|
||
}
|
||
}
|
||
}
|
||
]);
|
||
case "invalid_comparison":
|
||
return invalidJsonl([{ record_type: "presentation", command: {
|
||
action: "open_chart", chart_id: "dev-invalid-comparison", evidence_refs: ["dev:synthetic"],
|
||
chart: { title: "错误案例:历史点时刻错位", chart_type: "time_comparison", x_data: ["08:00", "08:15"],
|
||
series: [
|
||
{ name: "当前", type: "line", data: [32, 30], sample_times: ["2026-07-16T08:00:00+08:00", "2026-07-16T08:15:00+08:00"] },
|
||
{ name: "历史", type: "scatter", data: [33, null], sample_times: ["2026-07-15T08:15:00+08:00", null] }
|
||
] }
|
||
} }]);
|
||
}
|
||
}
|