fix: align drainage frontend configuration
This commit is contained in:
@@ -17,7 +17,7 @@ export function AgentCollapsedRail({
|
||||
statusLabel = "Agent 已就绪",
|
||||
onExpand
|
||||
}: AgentCollapsedRailProps) {
|
||||
const expandLabel = `展开排水助手面板,当前状态:${statusLabel}`;
|
||||
const expandLabel = `展开 Agent 助手面板,当前状态:${statusLabel}`;
|
||||
|
||||
return (
|
||||
<aside
|
||||
@@ -29,7 +29,7 @@ export function AgentCollapsedRail({
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="展开排水助手面板"
|
||||
aria-label={expandLabel}
|
||||
title={expandLabel}
|
||||
onClick={onExpand}
|
||||
className="agent-rail-item surface-control group relative flex h-14 w-full items-center justify-center rounded-xl border transition-[background-color,transform] hover:bg-white active:scale-95"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
||||
import { env } from "@/shared/config/env";
|
||||
import {
|
||||
splitSpeechTextIntoChunks,
|
||||
type AgentSpeakOptions,
|
||||
@@ -123,7 +122,7 @@ export function useAgentSpeechSynthesis() {
|
||||
|
||||
const controller = new AbortController();
|
||||
fetchControllersRef.current.add(controller);
|
||||
const promise = fetch(env.DRAINAGE_TTS_API_URL, {
|
||||
const promise = fetch("/api/tts/edge", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ text }),
|
||||
|
||||
@@ -20,6 +20,13 @@ describe("toTrustedMapAction", () => {
|
||||
})
|
||||
).toBeNull();
|
||||
expect(toTrustedMapAction("apply_layer_style", {})).toBeNull();
|
||||
expect(
|
||||
toTrustedMapAction("apply_layer_style", {
|
||||
layer_group_id: "conduits",
|
||||
layer_id: "scada",
|
||||
visible: true
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects out-of-range coordinates and zoom", () => {
|
||||
@@ -63,11 +70,11 @@ describe("toTrustedMapAction", () => {
|
||||
expect(action?.type === "zoom_to_map" ? action.center[1] : undefined).toBeCloseTo(39.1, 3);
|
||||
});
|
||||
|
||||
it("normalizes legacy locate tool actions", () => {
|
||||
expect(toTrustedMapAction("locate_pipes", { pipe_ids: "P1,P2" })).toEqual({
|
||||
it("normalizes drainage locate tool actions", () => {
|
||||
expect(toTrustedMapAction("locate_conduits", { conduit_ids: "C1,C2" })).toEqual({
|
||||
type: "locate_features",
|
||||
featureIds: ["P1", "P2"],
|
||||
layer: "geo_pipes_mat",
|
||||
featureIds: ["C1", "C2"],
|
||||
layer: "geo_conduits_mat",
|
||||
fallbackText: undefined
|
||||
});
|
||||
});
|
||||
@@ -79,4 +86,34 @@ describe("toTrustedMapAction", () => {
|
||||
layer: "geo_junctions_mat"
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["locate_orifices", "orifice_id", "geo_orifices_mat"],
|
||||
["locate_outfalls", "outfall_id", "geo_outfalls_mat"],
|
||||
["locate_pumps", "pump_id", "geo_pumps_mat"],
|
||||
["locate_scada", "scada_id", "geo_scadas_mat"]
|
||||
])("accepts %s for a drainage source", (action, key, layer) => {
|
||||
expect(toTrustedMapAction(action, { [key]: "asset-1" })).toMatchObject({
|
||||
type: "locate_features",
|
||||
featureIds: ["asset-1"],
|
||||
layer
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects stale supply layers and accepts drainage visibility controls", () => {
|
||||
expect(toTrustedMapAction("locate_pipes", { pipe_ids: ["P1"] })).toBeNull();
|
||||
expect(toTrustedMapAction("locate_features", { ids: ["P1"], layer: "geo_pipes_mat" })).toBeNull();
|
||||
expect(toTrustedMapAction("apply_layer_style", { layer_id: "conduits", visible: true })).toMatchObject({
|
||||
type: "apply_layer_style",
|
||||
layerGroupId: "conduits",
|
||||
layerId: "conduits",
|
||||
visible: true
|
||||
});
|
||||
expect(toTrustedMapAction("apply_layer_style", { layer_group_id: "simulation", visible: false })).toMatchObject({
|
||||
type: "apply_layer_style",
|
||||
layerGroupId: "simulation",
|
||||
visible: false
|
||||
});
|
||||
expect(toTrustedMapAction("apply_layer_style", { layer_id: "pipes", visible: true })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export type TrustedMapAction =
|
||||
}
|
||||
| {
|
||||
type: "apply_layer_style";
|
||||
layerGroupId?: string;
|
||||
layerGroupId: string;
|
||||
layerId?: string;
|
||||
visible?: boolean;
|
||||
fallbackText?: string;
|
||||
@@ -33,12 +33,14 @@ export function toTrustedMapAction(action: string, params: unknown, fallbackText
|
||||
if (action === "locate_features" || action in LEGACY_LOCATE_ACTION_LAYERS) {
|
||||
const featureIds = readLocateIds(params);
|
||||
if (featureIds.length === 0 || featureIds.length > 100 || featureIds.some((id) => id.length > 128)) return null;
|
||||
const layer =
|
||||
stringValue(params.layer ?? params.target_layer ?? params.targetLayer) ??
|
||||
LEGACY_LOCATE_ACTION_LAYERS[action];
|
||||
if (layer && !ALLOWED_LOCATE_LAYERS.has(layer)) return null;
|
||||
return {
|
||||
type: "locate_features",
|
||||
featureIds,
|
||||
layer:
|
||||
stringValue(params.layer ?? params.target_layer ?? params.targetLayer) ??
|
||||
LEGACY_LOCATE_ACTION_LAYERS[action],
|
||||
layer,
|
||||
fallbackText
|
||||
};
|
||||
}
|
||||
@@ -66,11 +68,18 @@ export function toTrustedMapAction(action: string, params: unknown, fallbackText
|
||||
if (action === "apply_layer_style") {
|
||||
const layerGroupId = stringValue(params.layer_group_id ?? params.layerGroupId);
|
||||
const layerId = stringValue(params.layer_id ?? params.layerId);
|
||||
const resolvedLayerGroupId = layerGroupId ?? layerId;
|
||||
const visible = booleanValue(params.visible);
|
||||
if ((!layerGroupId && !layerId) || visible === undefined || (layerId && !ALLOWED_LAYER_IDS.has(layerId))) return null;
|
||||
if (
|
||||
!resolvedLayerGroupId ||
|
||||
visible === undefined ||
|
||||
(layerGroupId && !ALLOWED_LAYER_GROUP_IDS.has(layerGroupId)) ||
|
||||
(layerId && !ALLOWED_LAYER_IDS.has(layerId)) ||
|
||||
(layerGroupId && layerId && layerGroupId !== layerId)
|
||||
) return null;
|
||||
return {
|
||||
type: "apply_layer_style",
|
||||
layerGroupId,
|
||||
layerGroupId: resolvedLayerGroupId,
|
||||
layerId,
|
||||
visible,
|
||||
fallbackText
|
||||
@@ -155,16 +164,27 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
}
|
||||
|
||||
const LEGACY_LOCATE_ACTION_LAYERS: Record<string, string> = {
|
||||
locate_conduits: "geo_conduits_mat",
|
||||
locate_junctions: "geo_junctions_mat",
|
||||
locate_pipes: "geo_pipes_mat",
|
||||
locate_valves: "geo_valves",
|
||||
locate_reservoirs: "geo_reservoirs",
|
||||
locate_pumps: "geo_pumps",
|
||||
locate_tanks: "geo_tanks"
|
||||
locate_orifices: "geo_orifices_mat",
|
||||
locate_outfalls: "geo_outfalls_mat",
|
||||
locate_pumps: "geo_pumps_mat",
|
||||
locate_scada: "geo_scadas_mat"
|
||||
};
|
||||
|
||||
const RESULT_REF_PATTERN = /^res-[A-Za-z0-9_-]{8,128}$/;
|
||||
const ALLOWED_LAYER_IDS = new Set(["junctions", "pipes"]);
|
||||
const DRAINAGE_SOURCE_IDS = ["conduits", "junctions", "orifices", "outfalls", "pumps", "scada"];
|
||||
const ALLOWED_LAYER_IDS = new Set(DRAINAGE_SOURCE_IDS);
|
||||
const ALLOWED_LAYER_GROUP_IDS = new Set([...DRAINAGE_SOURCE_IDS, "simulation"]);
|
||||
const ALLOWED_LOCATE_LAYERS = new Set([
|
||||
...DRAINAGE_SOURCE_IDS,
|
||||
"geo_conduits_mat",
|
||||
"geo_junctions_mat",
|
||||
"geo_orifices_mat",
|
||||
"geo_outfalls_mat",
|
||||
"geo_pumps_mat",
|
||||
"geo_scadas_mat"
|
||||
]);
|
||||
const WEB_MERCATOR_RADIUS = 6378137;
|
||||
|
||||
const LOCATE_ID_PARAM_KEYS = [
|
||||
@@ -178,16 +198,16 @@ const LOCATE_ID_PARAM_KEYS = [
|
||||
"node_id",
|
||||
"junction_ids",
|
||||
"junction_id",
|
||||
"pipe_ids",
|
||||
"pipe_id",
|
||||
"valve_ids",
|
||||
"valve_id",
|
||||
"reservoir_ids",
|
||||
"reservoir_id",
|
||||
"conduit_ids",
|
||||
"conduit_id",
|
||||
"orifice_ids",
|
||||
"orifice_id",
|
||||
"outfall_ids",
|
||||
"outfall_id",
|
||||
"pump_ids",
|
||||
"pump_id",
|
||||
"tank_ids",
|
||||
"tank_id"
|
||||
"scada_ids",
|
||||
"scada_id"
|
||||
] as const;
|
||||
|
||||
function readLocateIds(params: Record<string, unknown>) {
|
||||
|
||||
@@ -483,7 +483,7 @@ function IncidentWorkOrderPanel({
|
||||
const submitted = Boolean(submittedWorkOrder);
|
||||
const steps: ExecutionStep[] = [
|
||||
{ id: "analysis", title: "事件分析", status: "done", description: "汇总异常证据、影响分区和风险等级。" },
|
||||
{ id: "solution", title: "形成方案", status: "done", description: "生成阀门、泵站和现场复核操作清单。" },
|
||||
{ id: "solution", title: "形成方案", status: "done", description: "生成管渠、泵站和现场复核操作清单。" },
|
||||
{ id: "confirm", title: "人工确认", status: submitted ? "done" : "confirmation", description: "调度员确认影响范围和操作边界。" },
|
||||
{ id: "submit", title: "提交工单", status: submitted ? "done" : "pending", description: "写入系统工单并通知执行岗位。" }
|
||||
];
|
||||
@@ -497,7 +497,7 @@ function IncidentWorkOrderPanel({
|
||||
summary={
|
||||
submitted
|
||||
? `${submittedWorkOrder?.code} 已进入系统工单流转,并通知相关班组执行管网要素操作。`
|
||||
: "异常分析形成处置方案后,由调度员确认并提交系统工单,通知现场或调度岗位操作阀门、泵站和相关测点。"
|
||||
: "异常分析形成处置方案后,由调度员确认并提交系统工单,通知现场或调度岗位操作管渠、泵站和相关测点。"
|
||||
}
|
||||
meta={
|
||||
submittedWorkOrder
|
||||
@@ -1202,22 +1202,22 @@ function createSubmittedIncidentWorkOrder(condition: ScheduledConditionItem): Su
|
||||
|
||||
function createIncidentOperations(condition: ScheduledConditionItem): IncidentOperation[] {
|
||||
const baseVerification =
|
||||
condition.status === "error" ? "执行后 5 分钟内复核压力、流量和影响用户反馈。" : "下一轮工况继续跟踪偏差是否回落。";
|
||||
condition.status === "error" ? "执行后 5 分钟内复核液位、流量和受影响道路与汇水区反馈。" : "下一轮工况继续跟踪偏差是否回落。";
|
||||
|
||||
if (condition.title.includes("智能调度")) {
|
||||
return [
|
||||
{
|
||||
target: "华山路沿线阀门组 VG-11",
|
||||
elementType: "阀门",
|
||||
action: "阀门开度调整",
|
||||
command: "按方案将边界阀门预置到 45% 开度,执行前确认上下游压力。",
|
||||
verification: "确认高区末端压力不低于 0.22MPa。"
|
||||
target: "高水位片区管渠组 VG-11",
|
||||
elementType: "管渠",
|
||||
action: "管渠过流状态调整",
|
||||
command: "按方案将关键管渠预置到 45% 过流能力,执行前确认上下游液位。",
|
||||
verification: "确认高水位风险区检查井液位回落至 2.50m 以下。"
|
||||
},
|
||||
{
|
||||
target: "北辰二级泵站 P-02",
|
||||
target: "2# 排水泵站 P-02",
|
||||
elementType: "泵站",
|
||||
action: "泵站出水复核",
|
||||
command: "保持当前泵组组合,复核出水压力和频率波动。",
|
||||
command: "保持当前泵组组合,复核集水池液位和频率波动。",
|
||||
verification: baseVerification
|
||||
}
|
||||
];
|
||||
@@ -1233,10 +1233,10 @@ function createIncidentOperations(condition: ScheduledConditionItem): IncidentOp
|
||||
verification: "数据维护岗确认通信链路恢复后再解除隔离。"
|
||||
},
|
||||
{
|
||||
target: "异常 DMA 现场巡检点",
|
||||
target: "异常汇水分区现场巡检点",
|
||||
elementType: "巡检点",
|
||||
action: "现场复核",
|
||||
command: "通知巡检班组核对压力表、流量计和阀井状态。",
|
||||
command: "通知巡检班组核对液位计、流量计和检查井状态。",
|
||||
verification: baseVerification
|
||||
}
|
||||
];
|
||||
@@ -1244,17 +1244,17 @@ function createIncidentOperations(condition: ScheduledConditionItem): IncidentOp
|
||||
|
||||
return [
|
||||
{
|
||||
target: "疑似影响区上下游阀门",
|
||||
elementType: "阀门",
|
||||
action: "阀门边界确认",
|
||||
command: "核对阀门当前开度,暂不执行大范围关断,保留应急隔离方案。",
|
||||
verification: "确认影响区压力恢复趋势和用户侧反馈。"
|
||||
target: "疑似影响区上下游检查井",
|
||||
elementType: "管渠",
|
||||
action: "管渠疏通范围确认",
|
||||
command: "核对管渠当前过流状态,暂不执行大范围关断,保留应急隔离方案。",
|
||||
verification: "确认影响区液位恢复趋势和现场积水反馈。"
|
||||
},
|
||||
{
|
||||
target: "关联泵站与分区边界",
|
||||
elementType: "泵站/边界",
|
||||
action: "调度边界复核",
|
||||
command: "使用最新泵站出水、阀门开度和 SCADA 流量刷新模拟边界。",
|
||||
command: "使用最新泵站排水量、管渠过流状态和 SCADA 流量刷新模拟边界。",
|
||||
verification: baseVerification
|
||||
}
|
||||
];
|
||||
@@ -1270,7 +1270,7 @@ function getConditionAnalysisInsight(condition: ScheduledConditionItem): Schedul
|
||||
summary: "Agent 判断这条工况未完整闭环,需要先确认异常来源,再决定是否进入调度处置。",
|
||||
notes: [
|
||||
"优先核对遥测源新鲜度和关键测点完整率。",
|
||||
"结合历史基线复核压力/流量偏差是否持续。",
|
||||
"结合历史基线复核液位/流量偏差是否持续。",
|
||||
"处置前应先确认影响范围,避免误触发联动动作。"
|
||||
],
|
||||
solutionOptions: [
|
||||
@@ -1284,15 +1284,15 @@ function getConditionAnalysisInsight(condition: ScheduledConditionItem): Schedul
|
||||
{
|
||||
id: "fallback-model",
|
||||
title: "模型比对",
|
||||
scenario: "压力或流量偏差集中在局部分区。",
|
||||
scenario: "液位或流量偏差集中在局部分区。",
|
||||
action: "对比基线工况并圈定疑似影响范围。",
|
||||
tradeoff: "响应更快,但依赖模型边界条件质量。"
|
||||
},
|
||||
{
|
||||
id: "fallback-manual",
|
||||
title: "人工复核",
|
||||
scenario: "异常持续或伴随用户侧反馈。",
|
||||
action: "交由调度员确认阀门、泵站和巡检动作。",
|
||||
scenario: "异常持续或伴随现场积水反馈。",
|
||||
action: "交由调度员确认管渠、泵站和巡检动作。",
|
||||
tradeoff: "闭环完整,但会占用人工资源。"
|
||||
}
|
||||
]
|
||||
@@ -1309,7 +1309,7 @@ function getConditionAnalysisInsight(condition: ScheduledConditionItem): Schedul
|
||||
escalationCriteria: [
|
||||
"连续两轮工况仍高于关注阈值。",
|
||||
"关键测点完整率继续下降。",
|
||||
"同一分区出现用户投诉、低压或水质联动线索。"
|
||||
"同一汇水分区出现现场积水、倒灌或溢流联动线索。"
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ export function ScenarioMenu({
|
||||
<MenuSeparator />
|
||||
<MenuSectionLabel>场景命令</MenuSectionLabel>
|
||||
<MenuAction icon={PlayCircle} label="预览影响范围" description="打开模拟图层与影响区" tone="primary" onSelect={onPreviewScenario} />
|
||||
<MenuAction icon={SlidersHorizontal} label="比较候选方案" description="影响、阀门与保供风险差异" onSelect={onCompareScenario} />
|
||||
<MenuAction icon={SlidersHorizontal} label="比较候选方案" description="影响、管渠与输排风险差异" onSelect={onCompareScenario} />
|
||||
<MenuAction icon={FileText} label="导出场景报告" description="生成调度复核材料" onSelect={onExportScenarioReport} />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const runningWorkflowDefinitions: Record<ScheduledConditionTaskId, Runnin
|
||||
{
|
||||
id: "ingest-telemetry",
|
||||
title: "读取 SCADA 遥测",
|
||||
detail: "拉取压力、流量和关键阀门测点,校验时间戳与回传频率。"
|
||||
detail: "拉取液位、流量和关键排放口测点,校验时间戳与回传频率。"
|
||||
},
|
||||
{
|
||||
id: "freshness-check",
|
||||
@@ -36,7 +36,7 @@ export const runningWorkflowDefinitions: Record<ScheduledConditionTaskId, Runnin
|
||||
{
|
||||
id: "consistency-check",
|
||||
title: "压流一致性诊断",
|
||||
detail: "比对压力变化、流量变化和相邻测点趋势是否符合水力逻辑。"
|
||||
detail: "比对液位变化、流量变化和相邻测点趋势是否符合水力逻辑。"
|
||||
},
|
||||
{
|
||||
id: "isolate-sources",
|
||||
@@ -57,17 +57,17 @@ export const runningWorkflowDefinitions: Record<ScheduledConditionTaskId, Runnin
|
||||
{
|
||||
id: "read-condition",
|
||||
title: "读取实时工况",
|
||||
detail: "汇总目标分区压力、流量、泵站出水和阀门开度。"
|
||||
detail: "汇总目标汇水分区液位、流量、泵站排水量和管渠过流状态。"
|
||||
},
|
||||
{
|
||||
id: "candidate-actions",
|
||||
title: "生成候选动作",
|
||||
detail: "枚举泵站目标压力、阀门边界和高频复核测点的可执行动作。"
|
||||
detail: "枚举泵站启停组合、管渠疏通范围和高频复核测点的可执行动作。"
|
||||
},
|
||||
{
|
||||
id: "simulate-impact",
|
||||
title: "模拟影响范围",
|
||||
detail: "预估指令执行后的压力改善、分区压差和关键用户影响。"
|
||||
detail: "预估指令执行后的液位回落、溢流风险和周边道路影响。"
|
||||
},
|
||||
{
|
||||
id: "rank-actions",
|
||||
@@ -88,17 +88,17 @@ export const runningWorkflowDefinitions: Record<ScheduledConditionTaskId, Runnin
|
||||
{
|
||||
id: "sync-boundary",
|
||||
title: "同步模型边界",
|
||||
detail: "读取最新泵站启停、阀门开度、需水量和 SCADA 边界条件。"
|
||||
detail: "读取最新泵站启停、来水量、管渠过流能力和 SCADA 边界条件。"
|
||||
},
|
||||
{
|
||||
id: "run-hydraulic-model",
|
||||
title: "运行水力模型",
|
||||
detail: "滚动计算全网节点压力、管段流量和分区边界平衡。"
|
||||
detail: "滚动计算全网检查井液位、管渠流量和排放口边界平衡。"
|
||||
},
|
||||
{
|
||||
id: "compare-telemetry",
|
||||
title: "比对实测偏差",
|
||||
detail: "将模拟压力、流量与在线测点回传值进行偏差校验。"
|
||||
detail: "将模拟液位、流量与在线测点回传值进行偏差校验。"
|
||||
},
|
||||
{
|
||||
id: "locate-deviation",
|
||||
@@ -119,7 +119,7 @@ export const runningWorkflowDefinitions: Record<ScheduledConditionTaskId, Runnin
|
||||
{
|
||||
id: "read-pump-load",
|
||||
title: "读取泵组负载",
|
||||
detail: "采集泵站频率、电流、出水压力、出水量和启停记录。"
|
||||
detail: "采集泵站频率、电流、集水池液位、出水量和启停记录。"
|
||||
},
|
||||
{
|
||||
id: "calculate-energy",
|
||||
@@ -134,12 +134,12 @@ export const runningWorkflowDefinitions: Record<ScheduledConditionTaskId, Runnin
|
||||
{
|
||||
id: "pressure-guard",
|
||||
title: "校验供压约束",
|
||||
detail: "复核节能建议不会引发末端低压或分区边界异常。"
|
||||
detail: "复核节能建议不会引发检查井高水位或分区边界异常。"
|
||||
},
|
||||
{
|
||||
id: "energy-advice",
|
||||
title: "形成节能建议",
|
||||
detail: "输出启停优化、压力设定和继续观察条件。"
|
||||
detail: "输出启停优化、液位设定和继续观察条件。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -71,28 +71,28 @@ const CONDITION_TASKS: ConditionTaskDefinition[] = [
|
||||
{
|
||||
id: "scada-diagnosis",
|
||||
title: "SCADA 数据诊断",
|
||||
summary: "对压力、流量 SCADA 数据进行新鲜度、完整率、突变和一致性诊断,覆盖分区压力巡检范围。",
|
||||
summary: "对液位、流量 SCADA 数据进行新鲜度、完整率、突变和一致性诊断,覆盖汇水分区监测范围。",
|
||||
intervalMinutes: 5,
|
||||
durationMinutes: 3
|
||||
},
|
||||
{
|
||||
id: "smart-dispatch",
|
||||
title: "管网智能调度",
|
||||
summary: "结合实时工况生成管网要素调整指令,输出泵站、阀门或分区边界的建议动作。",
|
||||
summary: "结合实时工况生成管网要素调整指令,输出泵站、管渠或排放口的建议动作。",
|
||||
intervalMinutes: 15,
|
||||
durationMinutes: 4
|
||||
},
|
||||
{
|
||||
id: "network-simulation",
|
||||
title: "管网定时模拟",
|
||||
summary: "基于最新遥测、泵站启停和阀门开度滚动复核全网水力状态。",
|
||||
summary: "基于最新遥测、泵站启停和管渠过流能力滚动复核全网水力状态。",
|
||||
intervalMinutes: 15,
|
||||
durationMinutes: 6
|
||||
},
|
||||
{
|
||||
id: "pump-energy",
|
||||
title: "泵站能耗检查",
|
||||
summary: "检查泵组负载、单位排水电耗、启停频率和供压稳定性。",
|
||||
summary: "检查泵组负载、单位排水电耗、启停频率和集水池液位稳定性。",
|
||||
intervalMinutes: 60,
|
||||
durationMinutes: 10
|
||||
}
|
||||
@@ -100,13 +100,13 @@ const CONDITION_TASKS: ConditionTaskDefinition[] = [
|
||||
|
||||
const MOCK_WORK_ORDER_TEMPLATES: MockWorkOrderTemplate[] = [
|
||||
{
|
||||
id: "mock-incident-valve-dispatch",
|
||||
title: "系统工单:华山路阀门边界确认",
|
||||
summary: "异常事件分析后生成阀门边界确认任务,派发前需调度员复核影响范围。",
|
||||
id: "mock-high-water-dispatch",
|
||||
title: "系统工单:高水位片区管渠疏通范围确认",
|
||||
summary: "异常事件分析后生成管渠疏通范围确认任务,派发前需调度员复核影响范围。",
|
||||
detail: "该工单由异常事件分析流程生成,用于演示人工确认后提交系统工单并通知管网要素操作。",
|
||||
recommendation: "确认影响用户、上下游压力和应急隔离边界后,再通知执行岗位到场。",
|
||||
recommendation: "确认受影响道路与汇水区、上下游液位和应急隔离边界后,再通知执行岗位到场。",
|
||||
source: "异常事件分析",
|
||||
location: "华山路沿线阀门组 VG-11",
|
||||
location: "高水位风险区主干管渠 C-11",
|
||||
dispatcher: "调度中心值班长",
|
||||
assignee: "调度执行岗",
|
||||
priority: "urgent",
|
||||
@@ -115,34 +115,34 @@ const MOCK_WORK_ORDER_TEMPLATES: MockWorkOrderTemplate[] = [
|
||||
replyWindowMinutes: 20,
|
||||
riskLevel: "attention",
|
||||
stage: "dispatch",
|
||||
replyRequirements: ["确认阀门当前开度", "记录影响分区和用户范围", "回传派发确认截图"]
|
||||
replyRequirements: ["确认管渠当前过流状态", "记录受影响道路与汇水区", "回传派发确认截图"]
|
||||
},
|
||||
{
|
||||
id: "mock-valve-operation-execution",
|
||||
title: "系统工单:北辰分区阀门开度调整",
|
||||
summary: "根据管网智能调度方案,现场调整北辰分区边界阀门开度并复核压力。",
|
||||
detail: "该工单模拟系统派发后的执行阶段,班组正在按方案对阀门要素进行操作。",
|
||||
recommendation: "执行过程中保持调度语音联络,调整后立即复核末端压力和流量变化。",
|
||||
id: "mock-conduit-cleaning-execution",
|
||||
title: "系统工单:重点汇水分区管渠疏通",
|
||||
summary: "根据管网智能调度方案,现场疏通重点汇水分区主干管渠并复核液位。",
|
||||
detail: "该工单模拟系统派发后的执行阶段,班组正在按方案开展管渠清淤疏通。",
|
||||
recommendation: "执行过程中保持调度语音联络,调整后立即复核检查井液位和流量变化。",
|
||||
source: "调度方案确认",
|
||||
location: "北辰分区边界阀门 BV-07",
|
||||
location: "重点汇水分区主干管渠 C-07",
|
||||
dispatcher: "管网调度岗",
|
||||
assignee: "北辰抢修二组",
|
||||
assignee: "排水抢修二组",
|
||||
priority: "urgent",
|
||||
durationMinutes: 55,
|
||||
dispatchLeadMinutes: 25,
|
||||
replyWindowMinutes: 20,
|
||||
riskLevel: "attention",
|
||||
stage: "execution",
|
||||
replyRequirements: ["上传阀门操作前后照片", "记录最终开度", "复核北辰末端压力不低于 0.22MPa"]
|
||||
replyRequirements: ["上传管渠处置前后照片", "记录清淤疏通结果", "复核检查井液位回落至 2.50m 以下"]
|
||||
},
|
||||
{
|
||||
id: "mock-pump-boundary-reply",
|
||||
title: "系统工单:泵站边界复核复令",
|
||||
summary: "泵站出水边界复核已完成,等待班组复令和调度确认闭环。",
|
||||
detail: "该工单模拟执行完成后的复令阶段,用于检查处置结果、照片和指标回传。",
|
||||
recommendation: "核对泵站出水压力、频率曲线和分区边界模拟结果后完成复令。",
|
||||
recommendation: "核对泵站集水池液位、频率曲线和分区边界模拟结果后完成复令。",
|
||||
source: "模型复核建议",
|
||||
location: "北辰二级泵站 P-02",
|
||||
location: "2# 排水泵站 P-02",
|
||||
dispatcher: "模型复核岗",
|
||||
assignee: "泵站运行班组",
|
||||
priority: "normal",
|
||||
@@ -151,64 +151,64 @@ const MOCK_WORK_ORDER_TEMPLATES: MockWorkOrderTemplate[] = [
|
||||
replyWindowMinutes: 35,
|
||||
riskLevel: "normal",
|
||||
stage: "reply",
|
||||
replyRequirements: ["上传泵站运行截图", "填写出水压力复核结果", "确认模型边界已同步更新"]
|
||||
replyRequirements: ["上传泵站运行截图", "填写集水池液位复核结果", "确认模型边界已同步更新"]
|
||||
}
|
||||
];
|
||||
|
||||
const MANUAL_WORK_ORDERS: ManualWorkOrderDefinition[] = [
|
||||
{
|
||||
id: "huashan-valve-well-service",
|
||||
title: "人工工单:华山路阀井现场处置",
|
||||
summary: "现场班组复核华山路阀井积水与井盖松动问题,完成照片和处置记录回传。",
|
||||
id: "high-water-junction-service",
|
||||
title: "人工工单:高水位检查井现场处置",
|
||||
summary: "现场班组复核高水位检查井积水与井盖松动问题,完成照片和处置记录回传。",
|
||||
detail: "该工单由调度员人工创建,来源为现场巡检反馈,不属于周期工况任务。",
|
||||
recommendation: "到点后确认现场安全措施、处置照片和回填记录,必要时关联资产缺陷单。",
|
||||
source: "现场巡检反馈",
|
||||
location: "华山路阀井",
|
||||
location: "高水位风险区检查井 J-11",
|
||||
dispatcher: "调度中心值班长",
|
||||
assignee: "北片区抢修一组",
|
||||
assignee: "排水抢修一组",
|
||||
priority: "urgent",
|
||||
startMinute: 9 * 60 + 40,
|
||||
durationMinutes: 35,
|
||||
dispatchLeadMinutes: 20,
|
||||
replyWindowMinutes: 15,
|
||||
riskLevel: "attention",
|
||||
replyRequirements: ["上传处置前后照片", "记录阀井积水处理结果", "复核井盖密封和周边安全状态"]
|
||||
replyRequirements: ["上传处置前后照片", "记录检查井积水处理结果", "复核井盖密封和周边安全状态"]
|
||||
},
|
||||
{
|
||||
id: "renmin-road-meter-change",
|
||||
title: "人工工单:人民路流量计更换",
|
||||
summary: "更换人民路 DN300 支线流量计通信模块,并复测回传质量。",
|
||||
detail: "该工单由计量维护计划人工创建,执行前需确认旁通和作业窗口。",
|
||||
id: "trunk-flow-meter-service",
|
||||
title: "人工工单:主干管渠流量计维护",
|
||||
summary: "更换主干管渠流量计通信模块,并复测回传质量。",
|
||||
detail: "该工单由监测设备维护计划人工创建,执行前需确认作业安全窗口。",
|
||||
recommendation: "作业完成后核对 15 分钟回传稳定性,并记录新模块编号。",
|
||||
source: "计量维护计划",
|
||||
location: "人民路 DN300 支线",
|
||||
dispatcher: "计量调度岗",
|
||||
assignee: "计量维护二组",
|
||||
location: "主干管渠流量监测点 F-088",
|
||||
dispatcher: "监测调度岗",
|
||||
assignee: "监测维护二组",
|
||||
priority: "normal",
|
||||
startMinute: 10 * 60 + 30,
|
||||
durationMinutes: 50,
|
||||
dispatchLeadMinutes: 30,
|
||||
replyWindowMinutes: 20,
|
||||
riskLevel: "normal",
|
||||
replyRequirements: ["登记新通信模块编号", "回传 15 分钟稳定性截图", "确认旁通恢复和计量数据连续"]
|
||||
replyRequirements: ["登记新通信模块编号", "回传 15 分钟稳定性截图", "确认流量数据连续"]
|
||||
},
|
||||
{
|
||||
id: "beichen-fire-hydrant-repair",
|
||||
title: "人工工单:北辰消防栓维修",
|
||||
summary: "处理北辰片区消防栓渗漏缺陷,现场确认关停影响和恢复时间。",
|
||||
detail: "该工单来自客服缺陷转派,由调度员人工排入当日维修窗口。",
|
||||
recommendation: "维修前通知影响点位,完成后复核周边压力和栓体密封状态。",
|
||||
source: "客服缺陷转派",
|
||||
location: "北辰片区消防栓",
|
||||
dispatcher: "客服联动调度",
|
||||
assignee: "北辰维修班组",
|
||||
id: "outfall-backflow-inspection",
|
||||
title: "人工工单:排放口倒灌巡检",
|
||||
summary: "复核排放口倒灌和淤积风险,现场确认潮位、出流状态和恢复时间。",
|
||||
detail: "该工单来自异常监测转派,由调度员人工排入当日巡检窗口。",
|
||||
recommendation: "巡检前确认现场安全条件,完成后复核排放口水位和出流状态。",
|
||||
source: "异常监测转派",
|
||||
location: "重点排放口 O-03",
|
||||
dispatcher: "排水联动调度",
|
||||
assignee: "排放口巡检班组",
|
||||
priority: "urgent",
|
||||
startMinute: 14 * 60,
|
||||
durationMinutes: 60,
|
||||
dispatchLeadMinutes: 45,
|
||||
replyWindowMinutes: 20,
|
||||
riskLevel: "attention",
|
||||
replyRequirements: ["回填关停影响范围", "上传维修完成照片", "复测周边压力和消防栓密封状态"]
|
||||
replyRequirements: ["记录潮位和倒灌范围", "上传巡检完成照片", "复测排放口水位和出流状态"]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -580,12 +580,12 @@ function createTaskKpis(task: ConditionTaskDefinition, signal: TaskSignal): Sche
|
||||
return [
|
||||
{
|
||||
id: "pressure-scada-freshness",
|
||||
label: "压力 SCADA 新鲜度",
|
||||
label: "液位 SCADA 新鲜度",
|
||||
value: pressureFreshness.toString(),
|
||||
unit: "%",
|
||||
threshold: "关注 < 94%,异常 < 90%",
|
||||
status: getInverseMetricRisk(pressureFreshness, 94, 90),
|
||||
description: "压力监测点最近 5 分钟内有效回传占比,覆盖 DMA 分区压力巡检范围。"
|
||||
description: "液位监测点最近 5 分钟内有效回传占比,覆盖重点汇水分区监测范围。"
|
||||
},
|
||||
{
|
||||
id: "flow-scada-freshness",
|
||||
@@ -603,16 +603,16 @@ function createTaskKpis(task: ConditionTaskDefinition, signal: TaskSignal): Sche
|
||||
unit: "个",
|
||||
threshold: "关注 >= 5,异常 >= 8",
|
||||
status: getMetricRisk(abnormalSamples, 5, 8),
|
||||
description: "压力、流量样本中出现突变、越界或跨源不一致的数量。"
|
||||
description: "液位、流量样本中出现突变、越界或跨源不一致的数量。"
|
||||
},
|
||||
{
|
||||
id: "pressure-flow-consistency",
|
||||
label: "压流一致性评分",
|
||||
label: "液流一致性评分",
|
||||
value: consistencyScore.toString(),
|
||||
unit: "分",
|
||||
threshold: "关注 < 86,异常 < 80",
|
||||
status: getInverseMetricRisk(consistencyScore, 86, 80),
|
||||
description: "压力变化与流量变化是否符合水力逻辑的综合评分。"
|
||||
description: "液位变化与流量变化是否符合水力逻辑的综合评分。"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -642,13 +642,13 @@ function createTaskKpis(task: ConditionTaskDefinition, signal: TaskSignal): Sche
|
||||
description: "基于实时工况、SCADA 数据质量和模拟结果的调度指令可信度。"
|
||||
},
|
||||
{
|
||||
id: "expected-pressure-gain",
|
||||
label: "预计压力改善",
|
||||
id: "expected-level-drop",
|
||||
label: "预计液位改善",
|
||||
value: expectedPressureGain.toString(),
|
||||
unit: "m",
|
||||
threshold: "常规 0.5-3.5m",
|
||||
status: "normal",
|
||||
description: "执行调整指令后,目标片区末端压力的预计改善幅度。"
|
||||
description: "执行调整指令后,目标片区检查井液位的预计回落幅度。"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -662,13 +662,13 @@ function createTaskKpis(task: ConditionTaskDefinition, signal: TaskSignal): Sche
|
||||
return [
|
||||
{
|
||||
id: "pressure-deviation",
|
||||
label: "模拟-实测压力偏差",
|
||||
label: "模拟-实测液位偏差",
|
||||
value: pressureDeviation.toString(),
|
||||
unit: "%",
|
||||
baseline: "近 7 日同窗模型偏差",
|
||||
threshold: "关注 > 8%,异常 > 12%",
|
||||
status: getMetricRisk(pressureDeviation, 8, 12),
|
||||
description: "对比在线水力模拟结果与 SCADA 压力监测值的平均相对偏差。"
|
||||
description: "对比在线水力模拟结果与 SCADA 液位监测值的平均相对偏差。"
|
||||
},
|
||||
{
|
||||
id: "flow-deviation",
|
||||
@@ -682,12 +682,12 @@ function createTaskKpis(task: ConditionTaskDefinition, signal: TaskSignal): Sche
|
||||
},
|
||||
{
|
||||
id: "max-pressure-delta",
|
||||
label: "最大节点压差",
|
||||
label: "最大液位差",
|
||||
value: maxPressureDelta.toString(),
|
||||
unit: "m",
|
||||
threshold: "关注 > 5m,异常 > 8m",
|
||||
status: getMetricRisk(maxPressureDelta, 5, 8),
|
||||
description: "模拟节点压力与实测折算压力之间的最大差值。"
|
||||
description: "模拟检查井液位与实测折算液位之间的最大差值。"
|
||||
},
|
||||
{
|
||||
id: "hydraulic-balance-score",
|
||||
@@ -696,7 +696,7 @@ function createTaskKpis(task: ConditionTaskDefinition, signal: TaskSignal): Sche
|
||||
unit: "分",
|
||||
threshold: "关注 < 84,异常 < 78",
|
||||
status: getInverseMetricRisk(balanceScore, 84, 78),
|
||||
description: "综合压力、流量、边界条件一致性后的模型可信评分。"
|
||||
description: "综合液位、流量、边界条件一致性后的模型可信评分。"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -757,22 +757,22 @@ function createDispatchInstructions(signal: TaskSignal): DispatchInstruction[] {
|
||||
const templates: DispatchInstruction[][] = [
|
||||
[
|
||||
{
|
||||
action: "泵站目标压力微调",
|
||||
target: "2# 泵站出水压力",
|
||||
command: "将目标压力从 0.42MPa 调整至 0.44MPa,保持变频泵优先运行",
|
||||
verification: "15 分钟后复核 DMA-03 末端压力是否提升 1.5m 以上"
|
||||
action: "泵站启停液位优化",
|
||||
target: "2# 泵站集水池液位",
|
||||
command: "将高液位启动值从 2.40m 调整至 2.20m,保持变频泵优先运行",
|
||||
verification: "15 分钟后复核汇水分区 C-03 检查井液位是否回落 0.15m 以上"
|
||||
},
|
||||
{
|
||||
action: "边界阀开度校正",
|
||||
target: "DMA-03 / DMA-05 边界阀 BV-031",
|
||||
command: "开度由 38% 调整至 42%,限制单次调整幅度不超过 5%",
|
||||
verification: "观察相邻分区压差是否回落至 3m 内"
|
||||
action: "主干管渠疏通复核",
|
||||
target: "汇水分区 C-03 / C-05 联络管渠",
|
||||
command: "安排现场核查淤积和异物阻塞,确认后执行分段清淤",
|
||||
verification: "观察上下游检查井液位差是否回落至 0.30m 内"
|
||||
},
|
||||
{
|
||||
action: "末端压力观察",
|
||||
target: "华山路末端压力点 P-117",
|
||||
action: "检查井液位观察",
|
||||
target: "高水位片区检查井液位点 P-117",
|
||||
command: "将该点加入 15 分钟高频复核队列",
|
||||
verification: "若压力低于 0.18MPa 持续两轮,转人工调度确认"
|
||||
verification: "若液位高于 2.80m 持续两轮,转人工调度确认"
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -783,50 +783,50 @@ function createDispatchInstructions(signal: TaskSignal): DispatchInstruction[] {
|
||||
verification: "复核单位排水电耗是否低于 0.34kWh/m3"
|
||||
},
|
||||
{
|
||||
action: "高区排水边界收敛",
|
||||
target: "高区联络阀 HV-204",
|
||||
command: "开度下调 3%,减少高区向中区回流",
|
||||
verification: "确认高区末端压力不低于 0.22MPa"
|
||||
action: "排放口倒灌复核",
|
||||
target: "重点排放口 O-03",
|
||||
command: "核对外河潮位与排放口出流状态,必要时启动关联泵组",
|
||||
verification: "确认上游检查井液位回落至 2.50m 以下"
|
||||
},
|
||||
{
|
||||
action: "流量计复核",
|
||||
target: "人民路 DN300 流量计 F-088",
|
||||
target: "主干管渠流量计 F-088",
|
||||
command: "将该流量计设为本轮调度结果校验点",
|
||||
verification: "15 分钟内流量波动需低于 6%"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
action: "DMA 边界切换准备",
|
||||
target: "DMA-07 北侧边界阀 BV-072",
|
||||
command: "保持现状,预置 45% 开度为备选边界方案",
|
||||
verification: "若下一轮 SCADA 诊断正常,可进入调度员确认"
|
||||
action: "疑似淤堵管渠巡检",
|
||||
target: "汇水分区 C-07 北侧主干管渠",
|
||||
command: "保持泵站现状,预派巡检班组核查管渠过流断面",
|
||||
verification: "若下一轮 SCADA 诊断仍异常,转入清淤工单确认"
|
||||
},
|
||||
{
|
||||
action: "低压片区补压",
|
||||
target: "北辰片区支线 PRV-12",
|
||||
command: "出口压力设定值上调 0.01MPa,禁止联动超过 0.03MPa",
|
||||
verification: "复核北辰消防栓维修工单影响范围内压力恢复"
|
||||
action: "高水位片区排涝",
|
||||
target: "汇水分区 C-07 关联泵站 P-02",
|
||||
command: "提前启用备用泵组,单次调整后观察 10 分钟",
|
||||
verification: "复核高水位风险区检查井液位持续回落"
|
||||
},
|
||||
{
|
||||
action: "模型边界刷新",
|
||||
target: "全网模型边界条件",
|
||||
command: "使用最新泵站出水、阀门开度和 SCADA 流量刷新模拟边界",
|
||||
verification: "模拟-实测压力偏差需回落至 8% 内"
|
||||
command: "使用最新泵站排水量、管渠过流状态和 SCADA 流量刷新模拟边界",
|
||||
verification: "模拟-实测液位偏差需回落至 8% 内"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
action: "阀门开度回退",
|
||||
target: "华山路沿线阀门组 VG-11",
|
||||
command: "将昨日临时开度回退 2%,恢复常规排水边界",
|
||||
verification: "观察华山路和人民路支线压差不超过 2.5m"
|
||||
action: "管渠疏通效果复核",
|
||||
target: "高水位风险区主干管渠 C-11",
|
||||
command: "复核昨日清淤断面和残余淤积,恢复常规巡检频率",
|
||||
verification: "观察上下游检查井液位差不超过 0.25m"
|
||||
},
|
||||
{
|
||||
action: "泵站压力保持",
|
||||
target: "3# 泵站出口压力",
|
||||
command: "维持 0.40MPa 不变,禁止自动升压策略介入",
|
||||
verification: "若末端压力连续两轮低于 0.19MPa 再触发升压建议"
|
||||
action: "泵站液位保持",
|
||||
target: "3# 泵站集水池液位",
|
||||
command: "维持 2.10m 高液位启动值不变,禁止频繁启停",
|
||||
verification: "若检查井液位连续两轮高于 2.80m,再触发增开泵组建议"
|
||||
},
|
||||
{
|
||||
action: "异常测点隔离",
|
||||
@@ -887,8 +887,8 @@ function createGuidanceItems(
|
||||
) {
|
||||
if (task.id === "scada-diagnosis") {
|
||||
return [
|
||||
"优先处理新鲜度或一致性异常的压力、流量测点,避免后续模拟和调度引用失真数据。",
|
||||
"异常样本集中在同一 DMA 时,可直接替代分区压力巡检形成关注对象。",
|
||||
"优先处理新鲜度或一致性异常的液位、流量测点,避免后续模拟和调度引用失真数据。",
|
||||
"异常样本集中在同一汇水分区时,可直接形成重点巡检对象。",
|
||||
"若连续两轮 SCADA 诊断异常,应通知数据维护或现场巡检确认通信链路。"
|
||||
];
|
||||
}
|
||||
@@ -903,9 +903,9 @@ function createGuidanceItems(
|
||||
|
||||
if (task.id === "network-simulation") {
|
||||
return [
|
||||
"优先核对压力和流量偏差最大的 SCADA 测点,确认模型边界是否滞后。",
|
||||
"若连续两轮模拟-实测偏差扩大,应重新校准泵站出水边界和关键阀门开度。",
|
||||
"偏差回落前,不建议直接依据模型结果执行大范围阀门或泵站动作。"
|
||||
"优先核对液位和流量偏差最大的 SCADA 测点,确认模型边界是否滞后。",
|
||||
"若连续两轮模拟-实测偏差扩大,应重新校准泵站出水边界和关键管渠过流状态。",
|
||||
"偏差回落前,不建议直接依据模型结果执行大范围管渠或泵站动作。"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -913,7 +913,7 @@ function createGuidanceItems(
|
||||
return [
|
||||
"关注单位排水电耗和启停频率,判断是否存在泵组组合不经济。",
|
||||
"若电耗偏差持续升高,建议比较相邻泵组效率曲线并优化启停策略。",
|
||||
"调整前需确认供压稳定性,避免节能动作引发末端低压。"
|
||||
"调整前需确认集水池液位稳定性,避免节能动作引发检查井高水位。"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1003,15 +1003,15 @@ function createAnalysisInsight(
|
||||
{
|
||||
id: "hydraulic-compare",
|
||||
title: "水力模型比对",
|
||||
scenario: "压力/流量同时偏离基线,疑似局部水力异常。",
|
||||
scenario: "液位/流量同时偏离基线,疑似局部水力异常。",
|
||||
action: "调用最近基线工况对比异常分区,输出可能影响范围。",
|
||||
tradeoff: "可快速缩小范围,但依赖模型参数和边界条件质量。"
|
||||
},
|
||||
{
|
||||
id: "manual-dispatch",
|
||||
title: "人工调度复核",
|
||||
scenario: "异常持续超过一个调度窗口或伴随用户侧反馈。",
|
||||
action: "转入人工复核,准备阀门、泵站和现场巡检联动清单。",
|
||||
scenario: "异常持续超过一个调度窗口或伴随现场积水反馈。",
|
||||
action: "转入人工复核,准备管渠、泵站和现场巡检联动清单。",
|
||||
tradeoff: "处置闭环更完整,但会占用调度员和巡检资源。"
|
||||
}
|
||||
]
|
||||
@@ -1023,13 +1023,13 @@ function createAnalysisInsight(
|
||||
summary: `Agent 将本轮${task.title}标记为关注项,当前偏差不足以触发自动处置。`,
|
||||
notes: [
|
||||
`${signal.target} 偏离运行基线 ${(signal.deviation * 100).toFixed(0)}%,建议保留本轮证据。`,
|
||||
"重点观察压力/流量偏差是否持续扩大,避免单点噪声误判。",
|
||||
"重点观察液位/流量偏差是否持续扩大,避免单点噪声误判。",
|
||||
"可在 Agent 面板继续追问偏差来源和相关历史会话。"
|
||||
],
|
||||
escalationCriteria: [
|
||||
"连续两轮工况仍高于关注阈值。",
|
||||
"关键测点完整率继续下降或出现多源超时。",
|
||||
"同一分区出现用户投诉、低压或水质联动线索。"
|
||||
"同一汇水分区出现现场积水、倒灌或溢流联动线索。"
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ import type { WorkbenchScenario, WorkbenchUser } from "../types";
|
||||
export const WORKBENCH_SCENARIOS: WorkbenchScenario[] = [
|
||||
{
|
||||
id: "scenario-a",
|
||||
name: "方案 A",
|
||||
description: "关闭事故点上下游阀门,优先保障核心片区压力。",
|
||||
name: "泵站能力调整",
|
||||
description: "提前启用关联泵组,降低高水位片区溢流风险。",
|
||||
status: "active"
|
||||
},
|
||||
{
|
||||
id: "scenario-b",
|
||||
name: "方案 B",
|
||||
description: "扩大隔离范围,降低抢修期间二次爆管风险。",
|
||||
name: "管渠巡检疏通",
|
||||
description: "分段核查疑似淤堵管渠,安排清淤并复核上下游液位。",
|
||||
status: "review"
|
||||
},
|
||||
{
|
||||
id: "scenario-c",
|
||||
name: "夜间保供",
|
||||
description: "按夜间低峰工况调整泵站与分区调度策略。",
|
||||
name: "夜间泵组优化",
|
||||
description: "按夜间低流量工况优化泵组启停和汇水分区调度策略。",
|
||||
status: "draft"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { WATER_NETWORK_BOUNDS } from "../map/sources";
|
||||
import { impactAreaPolygon, mapAnnotations } from "./workbench-simulation";
|
||||
|
||||
function expectInsideDrainageBounds([longitude, latitude]: readonly number[]) {
|
||||
const [west, south, east, north] = WATER_NETWORK_BOUNDS;
|
||||
expect(longitude).toBeGreaterThanOrEqual(west);
|
||||
expect(longitude).toBeLessThanOrEqual(east);
|
||||
expect(latitude).toBeGreaterThanOrEqual(south);
|
||||
expect(latitude).toBeLessThanOrEqual(north);
|
||||
}
|
||||
|
||||
describe("drainage workbench simulation", () => {
|
||||
it("keeps every simulated point and polygon vertex inside the drainage extent", () => {
|
||||
mapAnnotations.forEach((annotation) => expectInsideDrainageBounds(annotation.coordinate));
|
||||
impactAreaPolygon.features[0].geometry.coordinates[0].forEach(expectInsideDrainageBounds);
|
||||
});
|
||||
|
||||
it("uses drainage-domain annotations", () => {
|
||||
expect(mapAnnotations.map((annotation) => annotation.kind)).toEqual([
|
||||
"high-water",
|
||||
"conduit-label",
|
||||
"risk-zone",
|
||||
"tooltip"
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -2,27 +2,27 @@ import type { MapAnnotation } from "../types";
|
||||
|
||||
export const mapAnnotations: MapAnnotation[] = [
|
||||
{
|
||||
id: "burst",
|
||||
label: "爆管位置",
|
||||
coordinate: [121.5215, 30.9084],
|
||||
kind: "burst"
|
||||
id: "high-water",
|
||||
label: "高水位点",
|
||||
coordinate: [120.699, 27.991],
|
||||
kind: "high-water"
|
||||
},
|
||||
{
|
||||
id: "dn600",
|
||||
label: "DN600",
|
||||
coordinate: [121.512, 30.9102],
|
||||
kind: "pipe-label"
|
||||
id: "trunk-conduit",
|
||||
label: "主干管渠",
|
||||
coordinate: [120.69, 27.996],
|
||||
kind: "conduit-label"
|
||||
},
|
||||
{
|
||||
id: "east-gate",
|
||||
label: "东直门小区",
|
||||
coordinate: [121.545, 30.951],
|
||||
kind: "district"
|
||||
id: "risk-zone",
|
||||
label: "高水位风险片区",
|
||||
coordinate: [120.712, 28.004],
|
||||
kind: "risk-zone"
|
||||
},
|
||||
{
|
||||
id: "impact",
|
||||
label: "影响范围 约 1.82 km²",
|
||||
coordinate: [121.567, 30.896],
|
||||
label: "影响范围 约 0.86 km²",
|
||||
coordinate: [120.721, 27.982],
|
||||
kind: "tooltip"
|
||||
}
|
||||
];
|
||||
@@ -35,20 +35,19 @@ export const impactAreaPolygon = {
|
||||
properties: {
|
||||
id: "impact-area",
|
||||
label: "影响范围",
|
||||
area: "1.82 km²"
|
||||
area: "0.86 km²"
|
||||
},
|
||||
geometry: {
|
||||
type: "Polygon" as const,
|
||||
coordinates: [
|
||||
[
|
||||
[121.5205, 30.9084],
|
||||
[121.548, 30.928],
|
||||
[121.604, 30.918],
|
||||
[121.626, 30.886],
|
||||
[121.584, 30.862],
|
||||
[121.538, 30.873],
|
||||
[121.506, 30.895],
|
||||
[121.5205, 30.9084]
|
||||
[120.687, 27.984],
|
||||
[120.7, 28.003],
|
||||
[120.721, 28.006],
|
||||
[120.733, 27.991],
|
||||
[120.72, 27.976],
|
||||
[120.697, 27.975],
|
||||
[120.687, 27.984]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -499,7 +499,7 @@ export function MapWorkbenchPage() {
|
||||
showMapNotice({
|
||||
tone: "info",
|
||||
title: "方案比较",
|
||||
message: "候选方案比较将汇总影响范围、阀门操作与保供风险。"
|
||||
message: "候选方案比较将汇总影响范围、管渠处置与输排风险。"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ export const simulationAnnotationLayers: StyleSpecification["layers"] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "simulation-burst-halo",
|
||||
id: "simulation-high-water-halo",
|
||||
type: "circle",
|
||||
source: SIMULATION_SOURCE_IDS.annotations,
|
||||
filter: ["==", ["get", "kind"], "burst"],
|
||||
filter: ["==", ["get", "kind"], "high-water"],
|
||||
paint: {
|
||||
"circle-color": MAP_STYLE_TOKENS.canvas.casing,
|
||||
"circle-radius": 18,
|
||||
@@ -53,10 +53,10 @@ export const simulationAnnotationLayers: StyleSpecification["layers"] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "simulation-burst-point",
|
||||
id: "simulation-high-water-point",
|
||||
type: "circle",
|
||||
source: SIMULATION_SOURCE_IDS.annotations,
|
||||
filter: ["==", ["get", "kind"], "burst"],
|
||||
filter: ["==", ["get", "kind"], "high-water"],
|
||||
paint: {
|
||||
"circle-color": MAP_STYLE_TOKENS.state.incident,
|
||||
"circle-radius": 7,
|
||||
@@ -65,10 +65,10 @@ export const simulationAnnotationLayers: StyleSpecification["layers"] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "simulation-pipe-label",
|
||||
id: "simulation-conduit-label",
|
||||
type: "symbol",
|
||||
source: SIMULATION_SOURCE_IDS.annotations,
|
||||
filter: ["==", ["get", "kind"], "pipe-label"],
|
||||
filter: ["==", ["get", "kind"], "conduit-label"],
|
||||
layout: {
|
||||
"text-field": ["get", "label"],
|
||||
"text-size": 13,
|
||||
@@ -100,10 +100,10 @@ export const simulationAnnotationLayers: StyleSpecification["layers"] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "simulation-district-label",
|
||||
id: "simulation-risk-zone-label",
|
||||
type: "symbol",
|
||||
source: SIMULATION_SOURCE_IDS.annotations,
|
||||
filter: ["==", ["get", "kind"], "district"],
|
||||
filter: ["==", ["get", "kind"], "risk-zone"],
|
||||
layout: {
|
||||
"text-field": ["get", "label"],
|
||||
"text-size": 13,
|
||||
@@ -117,12 +117,12 @@ export const simulationAnnotationLayers: StyleSpecification["layers"] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "simulation-burst-label",
|
||||
id: "simulation-high-water-label",
|
||||
type: "symbol",
|
||||
source: SIMULATION_SOURCE_IDS.annotations,
|
||||
filter: ["==", ["get", "kind"], "burst"],
|
||||
filter: ["==", ["get", "kind"], "high-water"],
|
||||
layout: {
|
||||
"text-field": "爆管位置\nDN600 给水管线\n压力:0.18 MPa\n时间:09:00",
|
||||
"text-field": "检查井高水位\n主干管渠输排受限\n液位:2.36 m\n时间:09:00",
|
||||
"text-size": 12,
|
||||
"text-font": ["Open Sans Regular"],
|
||||
"text-offset": [5.2, -3.4],
|
||||
|
||||
@@ -17,7 +17,8 @@ describe("workbench source layer controls", () => {
|
||||
"orifices",
|
||||
"outfalls",
|
||||
"pumps",
|
||||
"scada"
|
||||
"scada",
|
||||
"simulation"
|
||||
]);
|
||||
expect(items.slice(0, 5).map((item) => item.description)).toEqual([
|
||||
"wenzhou:geo_conduits_mat",
|
||||
@@ -26,10 +27,15 @@ describe("workbench source layer controls", () => {
|
||||
"wenzhou:geo_outfalls_mat",
|
||||
"wenzhou:geo_pumps_mat"
|
||||
]);
|
||||
expect(items.at(-1)).toMatchObject({
|
||||
expect(items.at(-2)).toMatchObject({
|
||||
label: "综合监测点",
|
||||
description: "wenzhou:geo_scadas_mat"
|
||||
});
|
||||
expect(items.at(-1)).toMatchObject({
|
||||
id: "simulation",
|
||||
label: "模拟结果",
|
||||
visible: false
|
||||
});
|
||||
});
|
||||
|
||||
it("finds every rendered layer that uses a source", () => {
|
||||
|
||||
@@ -14,12 +14,12 @@ export const WORKBENCH_LAYER_GROUPS: Record<string, string[]> = {
|
||||
simulation: [
|
||||
"simulation-impact-fill",
|
||||
"simulation-impact-outline",
|
||||
"simulation-burst-halo",
|
||||
"simulation-burst-point",
|
||||
"simulation-pipe-label",
|
||||
"simulation-high-water-halo",
|
||||
"simulation-high-water-point",
|
||||
"simulation-conduit-label",
|
||||
"simulation-impact-label",
|
||||
"simulation-district-label",
|
||||
"simulation-burst-label"
|
||||
"simulation-risk-zone-label",
|
||||
"simulation-high-water-label"
|
||||
]
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export const MAP_LEGEND_ITEMS: MapLegendItem[] = [
|
||||
{ id: "scada-integrated", label: "综合监测点", color: "#0E7490", imageSrc: "/icons/scada-integrated-monitoring.svg" },
|
||||
{ id: "inactive", label: "停用 / 关闭", color: MAP_STYLE_TOKENS.state.inactive, shape: "line" },
|
||||
{ id: "impact-area", label: "风险影响范围", color: MAP_STYLE_TOKENS.state.risk, shape: "square" },
|
||||
{ id: "incident", label: "事故 / 爆管", color: MAP_STYLE_TOKENS.state.incident, shape: "dot" },
|
||||
{ id: "incident", label: "高水位 / 溢流", color: MAP_STYLE_TOKENS.state.incident, shape: "dot" },
|
||||
{ id: "agent", label: "Agent 推断", color: MAP_STYLE_TOKENS.state.agent, shape: "dot" },
|
||||
{ id: "selected", label: "当前选中", color: MAP_STYLE_TOKENS.state.selected, shape: "ring" }
|
||||
];
|
||||
@@ -80,11 +80,19 @@ export const BASE_LAYER_OPTIONS: BaseLayerOption[] = [
|
||||
];
|
||||
|
||||
export function createLayerControlItems(layerVisibility: Record<string, boolean>): MapLayerControlItem[] {
|
||||
return WATER_NETWORK_SOURCE_IDS.map((sourceId) => ({
|
||||
id: sourceId,
|
||||
...SOURCE_CONTROL_LABELS[sourceId],
|
||||
visible: layerVisibility[sourceId]
|
||||
}));
|
||||
return [
|
||||
...WATER_NETWORK_SOURCE_IDS.map((sourceId) => ({
|
||||
id: sourceId,
|
||||
...SOURCE_CONTROL_LABELS[sourceId],
|
||||
visible: layerVisibility[sourceId]
|
||||
})),
|
||||
{
|
||||
id: "simulation",
|
||||
label: "模拟结果",
|
||||
description: "高水位风险与影响范围",
|
||||
visible: layerVisibility.simulation
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export function getWorkbenchLayerIds(
|
||||
|
||||
@@ -11,7 +11,7 @@ export const WATER_NETWORK_GLOBAL_VIEW = {
|
||||
]
|
||||
} as const;
|
||||
|
||||
const WATER_NETWORK_BOUNDS: [number, number, number, number] = [
|
||||
export const WATER_NETWORK_BOUNDS: [number, number, number, number] = [
|
||||
120.63483136963328,
|
||||
27.957404243937606,
|
||||
120.76346113516635,
|
||||
|
||||
@@ -130,7 +130,7 @@ export type MapAnnotation = {
|
||||
id: string;
|
||||
label: string;
|
||||
coordinate: [number, number];
|
||||
kind: "burst" | "pipe-label" | "district" | "tooltip";
|
||||
kind: "high-water" | "conduit-label" | "risk-zone" | "tooltip";
|
||||
};
|
||||
|
||||
export type WorkbenchMap = MapLibreMap;
|
||||
|
||||
@@ -73,7 +73,7 @@ const PROPERTY_LABELS: Record<string, string> = {
|
||||
status: "状态",
|
||||
material: "材质",
|
||||
elevation: "高程",
|
||||
demand: "需水量",
|
||||
demand: "来水量",
|
||||
pressure: "压力",
|
||||
flow: "流量",
|
||||
velocity: "流速",
|
||||
|
||||
@@ -104,7 +104,7 @@ export function createAlertQueueConversationPrompt({
|
||||
"1. 汇总这一组待复核工况之间的关联关系和处理顺序。",
|
||||
"2. 列出支撑判断的关键证据和不确定性。",
|
||||
"3. 给出处置方案,包含建议动作、适用条件、影响范围和需要人工确认的事项。",
|
||||
"4. 不要自动执行阀门、泵站或工单动作,先给调度员确认。",
|
||||
"4. 不要自动执行管渠、泵站或工单动作,先给调度员确认。",
|
||||
"",
|
||||
`待复核工况数量:${alerts.length}`,
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user