fix: align drainage frontend configuration

This commit is contained in:
2026-07-22 15:01:35 +08:00
parent 72850761ce
commit 0995172828
45 changed files with 853 additions and 339 deletions
@@ -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 -2
View File
@@ -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();
});
});
+40 -20
View File
@@ -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: "候选方案比较将汇总影响范围、管渠处置与输排风险。"
});
}
+11 -11
View File
@@ -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(
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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}`,
"",