feat(flushing): 添加阀门状态与设置值控制

This commit is contained in:
2026-08-17 18:29:00 +08:00
parent 1ac7372585
commit 9e79d52dc8
7 changed files with 451 additions and 152 deletions
+70 -9
View File
@@ -11038,7 +11038,7 @@
}, },
"/api/v1/flushing-analyses": { "/api/v1/flushing-analyses": {
"post": { "post": {
"description": "高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。", "description": "高级版本的冲洗分析,支持按状态和设置值控制多个可选阀门,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。",
"operationId": "post_flushing_analyses", "operationId": "post_flushing_analyses",
"parameters": [ "parameters": [
{ {
@@ -11053,31 +11053,92 @@
} }
}, },
{ {
"description": "要开启的阀门ID列表", "description": "参与控制的阀门ID列表(可选)",
"in": "query", "in": "query",
"name": "valves", "name": "valves",
"required": true, "required": false,
"schema": { "schema": {
"description": "要开启的阀门ID列表", "anyOf": [
{
"items": { "items": {
"type": "string" "type": "string"
}, },
"title": "Valves",
"type": "array" "type": "array"
},
{
"type": "null"
}
],
"description": "参与控制的阀门ID列表(可选)",
"title": "Valves"
} }
}, },
{ {
"description": "对应各阀门的开度列表(0-1", "description": "对应各阀门的开度列表(0-1,可选,与valves同时提供",
"in": "query", "in": "query",
"name": "valves_k", "name": "valves_k",
"required": true, "required": false,
"schema": { "schema": {
"description": "对应各阀门的开度列表(0-1", "anyOf": [
{
"items": { "items": {
"type": "number" "type": "number"
}, },
"title": "Valves K",
"type": "array" "type": "array"
},
{
"type": "null"
}
],
"description": "对应各阀门的开度列表(0-1,可选,与valves同时提供)",
"title": "Valves K"
}
},
{
"description": "对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选)",
"in": "query",
"name": "valve_statuses",
"required": false,
"schema": {
"anyOf": [
{
"items": {
"enum": [
"OPEN",
"CLOSED",
"ACTIVE"
],
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"description": "对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选)",
"title": "Valve Statuses"
}
},
{
"description": "对应各阀门的设置值列表(ACTIVE状态下必填)",
"in": "query",
"name": "valve_settings",
"required": false,
"schema": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"description": "对应各阀门的设置值列表(ACTIVE状态下必填)",
"title": "Valve Settings"
} }
}, },
{ {
@@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useRef } from "react";
import { import {
Box, Box,
TextField, TextField,
@@ -8,8 +8,8 @@ import {
Typography, Typography,
IconButton, IconButton,
Stack, Stack,
Alert,
Divider, Divider,
MenuItem,
} from "@mui/material"; } from "@mui/material";
import { import {
AdjustOutlined, AdjustOutlined,
@@ -33,14 +33,25 @@ import {
import Feature, { FeatureLike } from "ol/Feature"; import Feature, { FeatureLike } from "ol/Feature";
import { useNotification } from "@refinedev/core"; import { useNotification } from "@refinedev/core";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { config, NETWORK_NAME } from "@/config/config"; import { config } from "@/config/config";
import { useControllableObjectState } from "@components/olmap/core/useControllableState"; import { useControllableObjectState } from "@components/olmap/core/useControllableState";
import { useSessionRecoveryDraft } from "@/lib/sessionRecoveryDraft"; import { useSessionRecoveryDraft } from "@/lib/sessionRecoveryDraft";
import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; import PanelEmptyState from "@components/olmap/common/PanelEmptyState";
import {
type LinkStatus,
getValveSettingHelperText,
isLinkStatus,
normalizeValveSetting,
VALVE_STATUS_OPTIONS,
validateValveSetting,
} from "@components/olmap/core/Controls/valveControl";
export interface ValveItem { export interface ValveItem {
id: string; id: string;
k: number; setting?: string | null;
vType?: string | null;
status?: LinkStatus | null;
detailsLoaded?: boolean;
} }
export interface FlushingAnalysisParametersState { export interface FlushingAnalysisParametersState {
@@ -92,6 +103,7 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
const [selectionMode, setSelectionMode] = useState<'none' | 'valve' | 'drainage'>('none'); const [selectionMode, setSelectionMode] = useState<'none' | 'valve' | 'drainage'>('none');
const [analyzing, setAnalyzing] = useState<boolean>(false); const [analyzing, setAnalyzing] = useState<boolean>(false);
const valveSettingRequests = useRef(new Set<string>());
const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null); const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null);
@@ -119,7 +131,7 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
if (!feature) return; if (!feature) return;
const layer = feature.getId()?.toString().split(".")[0]; const layer = feature.getId()?.toString().split(".")[0];
const featureId = feature.getProperties().id; const featureId = String(feature.getProperties().id);
if (selectionMode === 'valve') { if (selectionMode === 'valve') {
if (layer !== 'geo_valves') { if (layer !== 'geo_valves') {
@@ -139,7 +151,16 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
return prev; return prev;
} }
setValveFeatures((features) => [...features, feature]); setValveFeatures((features) => [...features, feature]);
return [...prev, { id: featureId, k: 1.0 }]; return [
...prev,
{
id: featureId,
setting: undefined,
vType: undefined,
status: undefined,
detailsLoaded: false,
},
];
}); });
} else if (selectionMode === 'drainage') { } else if (selectionMode === 'drainage') {
@@ -253,6 +274,75 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
}); });
}, [valveFeatures.length, valves]); }, [valveFeatures.length, valves]);
useEffect(() => {
valves.forEach((valve) => {
if (valve.detailsLoaded || valveSettingRequests.current.has(valve.id)) {
return;
}
valveSettingRequests.current.add(valve.id);
void Promise.allSettled([
api.get(`${config.BACKEND_URL}/api/v1/valves/properties`, {
params: { valve: valve.id },
}),
api.get(`${config.BACKEND_URL}/api/v1/status`, {
params: { link: valve.id },
}),
])
.then(([propertiesResult, statusResult]) => {
const properties =
propertiesResult.status === "fulfilled"
? propertiesResult.value.data
: null;
const rawSetting = properties?.setting;
const rawStatus =
statusResult.status === "fulfilled"
? statusResult.value.data?.status
: null;
const status = isLinkStatus(rawStatus) ? rawStatus : null;
setValves((previous) =>
previous.map((item) =>
item.id === valve.id
? {
...item,
setting: normalizeValveSetting(rawSetting),
vType: properties?.v_type
? String(properties.v_type)
: null,
status,
detailsLoaded: true,
}
: item,
),
);
if (
propertiesResult.status === "rejected" ||
statusResult.status === "rejected"
) {
console.error("读取阀门属性失败", {
propertiesError:
propertiesResult.status === "rejected"
? propertiesResult.reason
: undefined,
statusError:
statusResult.status === "rejected"
? statusResult.reason
: undefined,
});
open?.({
type: "error",
message: `阀门 ${valve.id} 的部分属性读取失败`,
});
}
})
.finally(() => {
valveSettingRequests.current.delete(valve.id);
});
});
}, [open, setValves, valves]);
useEffect(() => { useEffect(() => {
if (!drainageNode) { if (!drainageNode) {
setDrainageFeature(null); setDrainageFeature(null);
@@ -292,9 +382,21 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
); );
}; };
const handleValveKChange = (id: string, k: string) => { const handleValveStatusChange = (id: string, status: string) => {
const numK = parseFloat(k); if (!isLinkStatus(status)) return;
setValves(prev => prev.map(v => v.id === id ? { ...v, k: isNaN(numK) ? 0 : numK } : v)); setValves((previous) =>
previous.map((valve) =>
valve.id === id ? { ...valve, status } : valve,
),
);
};
const handleValveSettingChange = (id: string, setting: string) => {
setValves((previous) =>
previous.map((valve) =>
valve.id === id ? { ...valve, setting } : valve,
),
);
}; };
const handleAnalyze = async () => { const handleAnalyze = async () => {
@@ -307,6 +409,33 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
return; return;
} }
if (valves.some((valve) => !isLinkStatus(valve.status))) {
open?.({
type: "error",
message: "阀门开关状态未设置",
description: "请为所有参与阀门选择开启、关闭或激活状态",
});
return;
}
const invalidActiveValve = valves.find(
(valve) =>
valve.status === "ACTIVE" &&
validateValveSetting(valve.vType, valve.setting ?? ""),
);
if (invalidActiveValve) {
open?.({
type: "error",
message: `阀门 ${invalidActiveValve.id} 的设置值无效`,
description:
validateValveSetting(
invalidActiveValve.vType,
invalidActiveValve.setting ?? "",
) ?? undefined,
});
return;
}
setAnalyzing(true); setAnalyzing(true);
try { try {
@@ -315,8 +444,13 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
const params = { const params = {
scheme_name: schemeName, scheme_name: schemeName,
start_time: formattedTime, start_time: formattedTime,
...(valves.length > 0 && {
valves: valves.map(v => v.id), valves: valves.map(v => v.id),
valves_k: valves.map(v => v.k), valve_statuses: valves.map((v) => v.status),
valve_settings: valves.map((v) =>
v.status === "ACTIVE" ? (v.setting ?? "").trim() : "",
),
}),
drainage_node_id: drainageNode, drainage_node_id: drainageNode,
flush_flow: flushFlow, flush_flow: flushFlow,
duration: duration duration: duration
@@ -355,62 +489,11 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
return ( return (
<Box className="flex flex-col h-full gap-4 pb-4"> <Box className="flex flex-col h-full gap-4 pb-4">
{/* 1. Valve Selection */} {/* 1. Drainage Node Selection */}
<Box> <Box>
<Box className="flex items-center justify-between mb-2"> <Box className="flex items-center justify-between mb-2">
<Typography variant="subtitle2" className="font-medium"> <Typography variant="subtitle2" className="font-medium">
</Typography>
<Button
variant={selectionMode === 'valve' ? "contained" : "outlined"}
color={selectionMode === 'valve' ? "error" : "primary"}
size="small"
onClick={() => toggleSelection('valve')}
>
{selectionMode === 'valve' ? "停止选择" : "选择阀门"}
</Button>
</Box>
{selectionMode === 'valve' && (
<Box className="mb-2 p-2 bg-blue-50 text-xs text-blue-700 rounded">
💡
</Box>
)}
<Stack spacing={1} className="max-h-50 h-48 overflow-auto">
{valves.map((valve) => (
<Box key={valve.id} className="flex items-center gap-2 p-2 bg-gray-50 rounded">
<Typography className="text-sm flex-1 pl-1">{valve.id}</Typography>
<TextField
label="开度"
size="small"
type="number"
value={valve.k}
onChange={(e) => handleValveKChange(valve.id, e.target.value)}
className="w-20"
slotProps={{ htmlInput: { step: 0.1, min: 0, max: 1 } }}
/>
<IconButton size="small" onClick={() => handleRemoveValve(valve.id)}>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
))}
{valves.length === 0 && (
<PanelEmptyState
variant="compact"
icon={<AdjustOutlined />}
title="尚未选择阀门"
description="点击“选择阀门”,然后在地图上添加。"
/>
)}
</Stack>
</Box>
<Divider />
{/* 2. Drainage Node Selection */}
<Box>
<Box className="flex items-center justify-between mb-2">
<Typography variant="subtitle2" className="font-medium">
</Typography> </Typography>
<Button <Button
variant={selectionMode === 'drainage' ? "contained" : "outlined"} variant={selectionMode === 'drainage' ? "contained" : "outlined"}
@@ -454,6 +537,106 @@ const AnalysisParameters: React.FC<AnalysisParametersProps> = ({
<Divider /> <Divider />
{/* 2. Optional Valve Selection */}
<Box>
<Box className="flex items-center justify-between mb-2">
<Typography variant="subtitle2" className="font-medium">
</Typography>
<Button
variant={selectionMode === 'valve' ? "contained" : "outlined"}
color={selectionMode === 'valve' ? "error" : "primary"}
size="small"
onClick={() => toggleSelection('valve')}
>
{selectionMode === 'valve' ? "停止选择" : "选择阀门"}
</Button>
</Box>
{selectionMode === 'valve' && (
<Box className="mb-2 p-2 bg-blue-50 text-xs text-blue-700 rounded">
💡
</Box>
)}
<Stack spacing={1} className="max-h-50 h-48 overflow-auto">
{valves.map((valve) => {
const settingValidation =
valve.status === "ACTIVE"
? validateValveSetting(valve.vType, valve.setting ?? "")
: null;
const isSettingDisabled =
!valve.detailsLoaded ||
valve.status === "OPEN" ||
valve.status === "CLOSED";
return (
<Box key={valve.id} className="p-2 bg-gray-50 rounded">
<Box className="flex items-center gap-2 mb-2">
<Typography className="text-sm min-w-0 flex-1 pl-1">
{valve.id}
</Typography>
<IconButton
size="small"
aria-label={`移除阀门 ${valve.id}`}
onClick={() => handleRemoveValve(valve.id)}
>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
<Box className="grid grid-cols-2 gap-2">
<TextField
select
fullWidth
size="small"
label="开关状态"
value={valve.status ?? ""}
disabled={!valve.detailsLoaded}
onChange={(event) =>
handleValveStatusChange(valve.id, event.target.value)
}
>
{VALVE_STATUS_OPTIONS.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<TextField
fullWidth
size="small"
label="阀门设置值"
value={valve.setting ?? ""}
disabled={isSettingDisabled}
error={Boolean(settingValidation)}
helperText={
valve.detailsLoaded
? settingValidation ??
getValveSettingHelperText(
valve.vType,
valve.status,
)
: "加载中"
}
onChange={(event) =>
handleValveSettingChange(valve.id, event.target.value)
}
/>
</Box>
</Box>
);
})}
{valves.length === 0 && (
<PanelEmptyState
variant="compact"
icon={<AdjustOutlined />}
title="未选择参与阀门"
description="无需调整阀门时可跳过,也可点击“选择阀门”添加。"
/>
)}
</Stack>
</Box>
<Divider />
{/* 3. Parameters */} {/* 3. Parameters */}
<Box className="flex flex-col gap-3"> <Box className="flex flex-col gap-3">
<Box> <Box>
+6 -37
View File
@@ -25,6 +25,12 @@ import {
} from "./toolbarFeatureHelpers"; } from "./toolbarFeatureHelpers";
import { useToolbarChatActions } from "./useToolbarChatActions"; import { useToolbarChatActions } from "./useToolbarChatActions";
import { useStyleEditor } from "./useStyleEditor"; import { useStyleEditor } from "./useStyleEditor";
import {
type LinkStatus,
isLinkStatus,
normalizeValveSetting,
validateValveSetting,
} from "./valveControl";
import { config, NETWORK_NAME } from "@/config/config"; import { config, NETWORK_NAME } from "@/config/config";
import { useProject } from "@/contexts/ProjectContext"; import { useProject } from "@/contexts/ProjectContext";
@@ -41,7 +47,6 @@ interface ToolbarProps {
enableCompare?: boolean; enableCompare?: boolean;
} }
type LinkStatus = "OPEN" | "CLOSED" | "ACTIVE";
type ValveProperties = { type ValveProperties = {
vType: string | null; vType: string | null;
setting: string | null; setting: string | null;
@@ -50,42 +55,6 @@ type ValveProperties = {
const isValveLayer = (layerId: string | undefined) => const isValveLayer = (layerId: string | undefined) =>
layerId === "geo_valves_mat" || layerId === "geo_valves"; layerId === "geo_valves_mat" || layerId === "geo_valves";
const isLinkStatus = (value: unknown): value is LinkStatus =>
value === "OPEN" || value === "CLOSED" || value === "ACTIVE";
const normalizeValveSetting = (value: unknown): string | null => {
if (value === undefined || value === null) return null;
return String(value);
};
const validateValveSetting = (
valveType: string | null,
value: string,
): string | null => {
const normalizedType = valveType?.toUpperCase();
const trimmedValue = value.trim();
const numericTypes = new Set(["PRV", "PSV", "PBV", "FCV", "TCV"]);
if (normalizedType === "GPV") {
return trimmedValue ? null : "GPV 阀门设置值必须是非空曲线 ID。";
}
if (numericTypes.has(normalizedType ?? "")) {
if (!trimmedValue) {
return "阀门设置值必须是 0 或正数。";
}
const numericValue = Number(trimmedValue);
if (!Number.isFinite(numericValue) || numericValue < 0) {
return "阀门设置值必须是有限数字,且大于或等于 0。";
}
return null;
}
return trimmedValue ? null : "阀门设置值不能为空。";
};
const Toolbar: React.FC<ToolbarProps> = ({ const Toolbar: React.FC<ToolbarProps> = ({
hiddenButtons, hiddenButtons,
queryType, queryType,
@@ -1,6 +1,10 @@
import Feature from "ol/Feature"; import Feature from "ol/Feature";
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units"; import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
import {
getValveSettingHelperText,
VALVE_STATUS_OPTIONS,
} from "./valveControl";
type ToolbarBaseProperty = { type ToolbarBaseProperty = {
label: string; label: string;
@@ -66,30 +70,6 @@ export type ValveSettingPropertyOptions = {
onSave: (value: string) => Promise<void>; onSave: (value: string) => Promise<void>;
}; };
const getValveSettingHelperText = (
vType: string | null,
status?: string | null,
) => {
if (status === "OPEN" || status === "CLOSED") {
return "开启/关闭状态下 EPANET 会忽略阀门设置值";
}
switch (vType?.toUpperCase()) {
case "PRV":
case "PSV":
case "PBV":
return "压力设置值,需为 0 或正数";
case "FCV":
return "流量设置值,需为 0 或正数";
case "TCV":
return "损失系数,需为 0 或正数";
case "GPV":
return "水头损失曲线 ID";
default:
return "阀门类型相关设置值";
}
};
const getFeatureHistoryType = (feature: Feature): string | null => { const getFeatureHistoryType = (feature: Feature): string | null => {
const layerId = feature.getId()?.toString().split(".")[0] || ""; const layerId = feature.getId()?.toString().split(".")[0] || "";
if (layerId.includes("pipe")) return "pipe"; if (layerId.includes("pipe")) return "pipe";
@@ -366,11 +346,7 @@ export const buildFeatureProperties = (
type: "select" as const, type: "select" as const,
label: "开关状态", label: "开关状态",
value: valveStatus.value ?? "", value: valveStatus.value ?? "",
options: [ options: VALVE_STATUS_OPTIONS,
{ label: "开启", value: "OPEN" },
{ label: "关闭", value: "CLOSED" },
{ label: "激活", value: "ACTIVE" },
],
placeholder: valveStatus.loading ? "加载中" : "未设置", placeholder: valveStatus.loading ? "加载中" : "未设置",
disabled: valveStatus.loading, disabled: valveStatus.loading,
saving: valveStatus.saving, saving: valveStatus.saving,
@@ -0,0 +1,36 @@
import {
getValveSettingHelperText,
isLinkStatus,
normalizeValveSetting,
VALVE_STATUS_OPTIONS,
validateValveSetting,
} from "./valveControl";
describe("valveControl", () => {
it("provides the status translations shared by valve editors", () => {
expect(VALVE_STATUS_OPTIONS).toEqual([
{ label: "开启", value: "OPEN" },
{ label: "关闭", value: "CLOSED" },
{ label: "激活", value: "ACTIVE" },
]);
expect(isLinkStatus("ACTIVE")).toBe(true);
expect(isLinkStatus("UNKNOWN")).toBe(false);
});
it("normalizes setting values without dropping zero", () => {
expect(normalizeValveSetting(0)).toBe("0");
expect(normalizeValveSetting(null)).toBeNull();
});
it("validates numeric and curve settings by valve type", () => {
expect(validateValveSetting("PRV", "2.5")).toBeNull();
expect(validateValveSetting("PRV", "-1")).toContain("大于或等于 0");
expect(validateValveSetting("GPV", "curve-1")).toBeNull();
expect(validateValveSetting("GPV", " ")).toContain("曲线 ID");
});
it("explains that OPEN and CLOSED ignore the setting", () => {
expect(getValveSettingHelperText("PRV", "OPEN")).toContain("忽略");
expect(getValveSettingHelperText("FCV", "ACTIVE")).toContain("流量");
});
});
@@ -0,0 +1,70 @@
export type LinkStatus = "OPEN" | "CLOSED" | "ACTIVE";
export const VALVE_STATUS_OPTIONS: Array<{
label: string;
value: LinkStatus;
}> = [
{ label: "开启", value: "OPEN" },
{ label: "关闭", value: "CLOSED" },
{ label: "激活", value: "ACTIVE" },
];
export const isLinkStatus = (value: unknown): value is LinkStatus =>
value === "OPEN" || value === "CLOSED" || value === "ACTIVE";
export const normalizeValveSetting = (value: unknown): string | null => {
if (value === undefined || value === null) return null;
return String(value);
};
export const validateValveSetting = (
valveType: string | null | undefined,
value: string,
): string | null => {
const normalizedType = valveType?.toUpperCase();
const trimmedValue = value.trim();
const numericTypes = new Set(["PRV", "PSV", "PBV", "FCV", "TCV"]);
if (normalizedType === "GPV") {
return trimmedValue ? null : "GPV 阀门设置值必须是非空曲线 ID。";
}
if (numericTypes.has(normalizedType ?? "")) {
if (!trimmedValue) {
return "阀门设置值必须是 0 或正数。";
}
const numericValue = Number(trimmedValue);
if (!Number.isFinite(numericValue) || numericValue < 0) {
return "阀门设置值必须是有限数字,且大于或等于 0。";
}
return null;
}
return trimmedValue ? null : "阀门设置值不能为空。";
};
export const getValveSettingHelperText = (
valveType: string | null | undefined,
status?: LinkStatus | string | null,
) => {
if (status === "OPEN" || status === "CLOSED") {
return "开启/关闭状态下 EPANET 会忽略阀门设置值";
}
switch (valveType?.toUpperCase()) {
case "PRV":
case "PSV":
case "PBV":
return "压力设置值,需为 0 或正数";
case "FCV":
return "流量设置值,需为 0 或正数";
case "TCV":
return "损失系数,需为 0 或正数";
case "GPV":
return "水头损失曲线 ID";
default:
return "阀门类型相关设置值";
}
};
+9 -5
View File
@@ -1121,7 +1121,7 @@ export interface paths {
put?: never; put?: never;
/** /**
* *
* @description * @description
*/ */
post: operations["post_flushing_analyses"]; post: operations["post_flushing_analyses"];
delete?: never; delete?: never;
@@ -14004,10 +14004,14 @@ export interface operations {
query: { query: {
/** @description 冲洗开始时间(ISO 8601格式) */ /** @description 冲洗开始时间(ISO 8601格式) */
start_time: string; start_time: string;
/** @description 要开启的阀门ID列表 */ /** @description 参与控制的阀门ID列表(可选) */
valves: string[]; valves?: string[] | null;
/** @description 对应各阀门的开度列表(0-1) */ /** @description 对应各阀门的开度列表(0-1,可选,与valves同时提供 */
valves_k: number[]; valves_k?: number[] | null;
/** @description 对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选) */
valve_statuses?: ("OPEN" | "CLOSED" | "ACTIVE")[] | null;
/** @description 对应各阀门的设置值列表(ACTIVE状态下必填) */
valve_settings?: string[] | null;
/** @description 排污节点ID */ /** @description 排污节点ID */
drainage_node_id: string; drainage_node_id: string;
/** @description 冲洗流量(L/s),0表示自动计算 */ /** @description 冲洗流量(L/s),0表示自动计算 */