refactor(cli)!: align commands with analysis run APIs

Replace legacy scheme commands with run-id based analysis and timeseries queries. Update sensor placement and SCADA routes, and refresh CLI help, tests, and usage guidance.

BREAKING CHANGE: legacy scheme, risk, and valve-close CLI command paths are removed.
This commit is contained in:
2026-08-26 17:28:41 +08:00
parent 4ec010455b
commit a438433068
6 changed files with 215 additions and 206 deletions
+17 -14
View File
@@ -35,15 +35,18 @@ description: tjwater-cli 命令行工具使用说明,涵盖命令发现、输
Agent 通过 `help` 动态发现可用命令,而非依赖硬编码清单。 Agent 通过 `help` 动态发现可用命令,而非依赖硬编码清单。
**重要:命令分为类——触发动作数据获取。** **重要:命令分为类——触发动作、运行/结果查询与时序数据获取。**
- **触发动作**`simulation``analysis`):向服务端发起计算请求,返回任务状态/ID,**不直接返回分析结果** - **触发动作**`simulation`各类 `analysis`):向服务端发起计算请求。
- **数据获取**`data timeseries`):所有计算结果(仿真压力、分析指标等)的唯一数据出口,需在触发动作完成后调用 - **运行/结果查询**`analysis runs`):按 `run_id` 查询运行元数据和非时序结果
- **时序数据获取**`data timeseries`):实时结果或按 `run_id` 查询节点、管道时序。
``` ```
simulation/analysis → 触发计算 → 返回状态/任务ID analysis → 触发计算 → analysis runs list/get/results
run_id
data timeseries → 获取计算结果 data timeseries analysis → 获取元素时序
simulation → 触发实时模拟 → data timeseries realtime → 获取实时结果
``` ```
通过 `help` 发现命令: 通过 `help` 发现命令:
@@ -63,11 +66,11 @@ tjwater-cli help COMMAND → 子命令与参数详情
| 命令族 | 典型子命令 | 用途 | | 命令族 | 典型子命令 | 用途 |
|------|-----------|------| |------|-----------|------|
| `project` | `list`, `db-health` | 项目管理、数据库健康检查 | | `network` | `get-pipe-properties`, `get-all-pipes-properties` | 管网元素查询 |
| `data` | `timeseries realtime links / nodes`, `timeseries scada query` | **时序数据查询**(实时/SCADA),所有分析结果的唯一获取渠道 | | `component` | `option get`, `option schema` | 模型选项和结构查询 |
| `data` | `timeseries realtime`, `timeseries analysis`, `timeseries scada`, `scada` | 实时、分析时序和 SCADA 查询 |
| `simulation` | 通过 `help simulation` 发现 | **触发水力仿真计算**(执行成功返回状态,实际结果需走 `data timeseries` 获取) | | `simulation` | 通过 `help simulation` 发现 | **触发水力仿真计算**(执行成功返回状态,实际结果需走 `data timeseries` 获取) |
| `analysis` | 通过 `help analysis` 发现 | **触发分析计算**(执行成功返回状态,实际结果需走 `data timeseries` 获取) | | `analysis` | `runs`, `sensor-placement` 及各类分析命令 | 触发分析,并按运行 ID 查询元数据与结果 |
| `net` | `list-pipes` | 管网拓扑查询 |
| `help` | (无子命令) | 命令发现入口 | | `help` | (无子命令) | 命令发现入口 |
> 完整命令清单始终以 `tjwater-cli help` 实时输出为准。 > 完整命令清单始终以 `tjwater-cli help` 实时输出为准。
@@ -128,11 +131,11 @@ tjwater-cli help COMMAND → 子命令与参数详情
1. **禁止猜测命令** — 执行任何命令前必须先 `tjwater_cli(command="help ...")` 确认命令存在及参数签名,参数均已写在 help 中,禁止凭经验拼写 1. **禁止猜测命令** — 执行任何命令前必须先 `tjwater_cli(command="help ...")` 确认命令存在及参数签名,参数均已写在 help 中,禁止凭经验拼写
2. **reason 必填** — 每次调用必须说明具体理由 2. **reason 必填** — 每次调用必须说明具体理由
3. **触发后取数据**`simulation`/`analysis` 仅触发计算,结果必须从 `data timeseries` 获取,勿将触发返回的状态信息当作分析结果 3. **按运行 ID 取结果** — 分析完成后先用 `analysis runs list/get/results` 获取 `run_id` 和非时序结果;元素时序再用 `data timeseries analysis` 查询
4. **文件分析** — workflow 脚本需要文件时使用 `store_result=true`,不得从 Bash 直接联网调用 CLI 4. **文件分析** — workflow 脚本需要文件时使用 `store_result=true`,不得从 Bash 直接联网调用 CLI
5. **结果验证** — 始终检查 `ok` 字段,失败时先处理错误码再重试 5. **结果验证** — 始终检查 `ok` 字段,失败时先处理错误码再重试
6. **大结果集** — 优先过滤/采样,不要一次性拉取全部数据 6. **大结果集** — 优先过滤/采样,不要一次性拉取全部数据
7. **模拟时长控制**模拟(`simulation`)或方案模拟`--duration` 不宜过长,建议每次仿真时间跨度控制在一小时以内,避免计算耗时过长或结果数据量过大 7. **模拟时长控制**实时模拟或分析运行`--duration` 不宜过长,建议每次仿真时间跨度控制在一小时以内,避免计算耗时过长或结果数据量过大
## 示例 ## 示例
@@ -145,11 +148,11 @@ tjwater-cli help COMMAND → 子命令与参数详情
``` ```
> `data timeseries realtime nodes` 仅接受 `--start-time` / `--end-time`,返回全量节点数据。 > `data timeseries realtime nodes` 仅接受 `--start-time` / `--end-time`,返回全量节点数据。
### 按节点查询方案时序字段 ### 按节点查询分析运行时序字段
```json ```json
{ {
"reason": "查询节点 J-001 最近1小时的压力数据", "reason": "查询节点 J-001 最近1小时的压力数据",
"command": "data timeseries scheme node-field --node J-001 --field pressure --start-time 2026-06-03T08:00:00+08:00 --end-time 2026-06-03T09:00:00+08:00" "command": "data timeseries analysis node-field --run-id 00000000-0000-0000-0000-000000000001 --node J-001 --field pressure --start-time 2026-06-03T08:00:00+08:00 --end-time 2026-06-03T09:00:00+08:00"
} }
``` ```
+1 -1
View File
@@ -14,7 +14,7 @@ export default tool({
command: tool.schema command: tool.schema
.string() .string()
.describe( .describe(
"tjwater-cli 子命令,不含二进制路径。示例:'data scheme list'、'data timeseries realtime links --start-time 2025-01-01T00:00:00+08:00 --end-time 2025-01-01T01:00:00+08:00'", "tjwater-cli 子命令,不含二进制路径。示例:'analysis runs list'、'data timeseries realtime links --start-time 2025-01-01T00:00:00+08:00 --end-time 2025-01-01T01:00:00+08:00'",
), ),
timeout: tool.schema timeout: tool.schema
.number() .number()
+38 -55
View File
@@ -1,10 +1,9 @@
import { CliError } from "../core/errors.js"; import { CliError } from "../core/errors.js";
import { emitApi, requestJson } from "../core/http.js"; import { emitApi } from "../core/http.js";
import { assignDatasetKeys, parseBurstFile, parseValveSettingFile } from "../core/files.js"; import { assignDatasetKeys, parseBurstFile, parseValveSettingFile } from "../core/files.js";
import { optionalNumber, optionalString, optionalStringArray, parseOptions, requiredNumber, requiredString, validateChoice } from "../core/options.js"; import { optionalNumber, optionalString, optionalStringArray, parseOptions, requiredNumber, requiredString, validateChoice } from "../core/options.js";
import { resolveScheme } from "../core/runtime.js"; import { resolveScheme } from "../core/runtime.js";
import { parseTime } from "../core/time.js"; import { parseTime } from "../core/time.js";
import { success } from "../core/output.js";
import type { HandlerMap, RuntimeContext } from "../core/types.js"; import type { HandlerMap, RuntimeContext } from "../core/types.js";
function analysisBurst(ctx: RuntimeContext, argv: string[]): Promise<void> { function analysisBurst(ctx: RuntimeContext, argv: string[]): Promise<void> {
@@ -22,30 +21,13 @@ function analysisBurst(ctx: RuntimeContext, argv: string[]): Promise<void> {
scheme_name: schemeName, scheme_name: schemeName,
}, },
requireProject: true, requireProject: true,
}, [`tjwater-cli data scheme get --name ${schemeName}`, "tjwater-cli data scheme list"]); }, ["tjwater-cli analysis runs list"]);
} }
function analysisValve(ctx: RuntimeContext, argv: string[]): Promise<void> { function analysisValveIsolation(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv, { valve: "repeat", element: "repeat", "disabled-valve": "repeat", duration: "integer" }); const { values } = parseOptions(argv, { element: "repeat", "disabled-valve": "repeat" });
const mode = validateChoice(requiredString(values, "mode"), ["close", "isolation"] as const, "--mode");
if (mode === "close") {
const valves = optionalStringArray(values, "valve");
const startTime = optionalString(values, "start-time");
if (!startTime || !valves) throw new CliError("CLI 参数错误", "INVALID_VALVE_CLOSE_ARGS", "close mode requires --start-time and at least one --valve", 2);
return emitApi(ctx, "阀门关闭分析执行成功", {
method: "POST",
path: "/valve-isolation-analyses",
params: {
start_time: parseTime(startTime, "--start-time"),
valves,
duration: optionalNumber(values, "duration") || 900,
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
},
requireProject: true,
});
}
const elements = optionalStringArray(values, "element"); const elements = optionalStringArray(values, "element");
if (!elements) throw new CliError("CLI 参数错误", "INVALID_VALVE_ISOLATION_ARGS", "isolation mode requires at least one --element", 2); if (!elements) throw new CliError("CLI 参数错误", "INVALID_VALVE_ISOLATION_ARGS", "at least one --element is required", 2);
return emitApi(ctx, "阀门隔离分析执行成功", { return emitApi(ctx, "阀门隔离分析执行成功", {
method: "POST", method: "POST",
path: "/valve-isolation-analyses", path: "/valve-isolation-analyses",
@@ -97,14 +79,16 @@ function analysisContaminant(ctx: RuntimeContext, argv: string[]): Promise<void>
return emitApi(ctx, "污染物模拟执行成功", { method: "POST", path: "/contaminant-simulations", params, requireProject: true }); return emitApi(ctx, "污染物模拟执行成功", { method: "POST", path: "/contaminant-simulations", params, requireProject: true });
} }
function sensorKmeans(ctx: RuntimeContext, argv: string[]): Promise<void> { function sensorPlacementRun(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv, { count: "integer", "min-diameter": "integer" }); const { values } = parseOptions(argv, { count: "integer", "min-diameter": "integer" });
return emitApi(ctx, "传感器选址执行成功", { return emitApi(ctx, "传感器选址执行成功", {
method: "POST", method: "POST",
path: "/pressure-sensor-placement-kmeans", path: "/sensor-placement-runs",
body: { body: {
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true), run_name: requiredString(values, "run-name"),
sensor_number: requiredNumber(values, "count"), sensor_type: "pressure",
method: validateChoice(requiredString(values, "method"), ["sensitivity", "kmeans"] as const, "--method"),
sensor_count: requiredNumber(values, "count"),
min_diameter: optionalNumber(values, "min-diameter") || 0, min_diameter: optionalNumber(values, "min-diameter") || 0,
}, },
requireProject: true, requireProject: true,
@@ -125,14 +109,25 @@ function schemeAnalysis(ctx: RuntimeContext, argv: string[], summary: string, pa
}); });
} }
function schemeList(ctx: RuntimeContext, summary: string, schemeType: string): Promise<void> { function analysisRunGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
return emitApi(ctx, summary, { method: "GET", path: "/schemes", params: { scheme_type: schemeType }, requireProject: true }); const { values } = parseOptions(argv);
const runId = requiredString(values, "run-id");
return emitApi(ctx, "读取分析运行成功", {
method: "GET",
path: `/analysis/runs/${encodeURIComponent(runId)}`,
requireProject: true,
});
} }
function schemeGet(ctx: RuntimeContext, argv: string[], summary: string, schemeType: string): Promise<void> { function analysisRunResults(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { positionals } = parseOptions(argv); const { values } = parseOptions(argv);
if (!positionals[0]) throw new CliError("CLI 参数错误", "MISSING_ARGUMENT", "Missing argument 'SCHEME_NAME'", 2); const runId = requiredString(values, "run-id");
return emitApi(ctx, summary, { method: "GET", path: `/schemes/${positionals[0]}`, params: { scheme_type: schemeType }, requireProject: true }); return emitApi(ctx, "读取分析结果成功", {
method: "GET",
path: `/analysis/runs/${encodeURIComponent(runId)}/results`,
params: { result_type: optionalString(values, "result-type") },
requireProject: true,
});
} }
function burstLocation(ctx: RuntimeContext, argv: string[]): Promise<void> { function burstLocation(ctx: RuntimeContext, argv: string[]): Promise<void> {
@@ -156,34 +151,22 @@ function burstLocation(ctx: RuntimeContext, argv: string[]): Promise<void> {
return emitApi(ctx, "爆管定位执行成功", { method: "POST", path: "/burst-locations", body, requireProject: true }); return emitApi(ctx, "爆管定位执行成功", { method: "POST", path: "/burst-locations", body, requireProject: true });
} }
function riskPipe(ctx: RuntimeContext, argv: string[], summary: string, path: string): Promise<void> {
const { values } = parseOptions(argv);
return emitApi(ctx, summary, { method: "GET", path, params: { pipe_id: requiredString(values, "pipe") }, requireProject: true });
}
async function riskNetwork(ctx: RuntimeContext): Promise<void> {
const [probabilities, a] = await requestJson(ctx, { method: "GET", path: "/network-pipe-risk-probability-nows", requireProject: true });
const [geometries, b] = await requestJson(ctx, { method: "GET", path: "/pipes/risk-probability-geometries", requireProject: true });
success("读取全网风险成功", { probabilities, geometries }, ctx, a + b);
}
export const analysisHandlers: HandlerMap = { export const analysisHandlers: HandlerMap = {
"analysis burst": analysisBurst, "analysis burst": analysisBurst,
"analysis valve": analysisValve, "analysis valve isolation": analysisValveIsolation,
"analysis flushing": analysisFlushing, "analysis flushing": analysisFlushing,
"analysis age": analysisAge, "analysis age": analysisAge,
"analysis contaminant": analysisContaminant, "analysis contaminant": analysisContaminant,
"analysis sensor-placement kmeans": sensorKmeans, "analysis sensor-placement run": sensorPlacementRun,
"analysis sensor-placement list": (ctx) => emitApi(ctx, "读取传感器选址运行列表成功", { method: "GET", path: "/sensor-placement-runs", requireProject: true }),
"analysis sensor-placement get": (ctx, argv) => {
const { values } = parseOptions(argv);
return emitApi(ctx, "读取传感器选址运行成功", { method: "GET", path: `/sensor-placement-runs/${encodeURIComponent(requiredString(values, "run-id"))}`, requireProject: true });
},
"analysis runs list": (ctx) => emitApi(ctx, "读取分析运行列表成功", { method: "GET", path: "/analysis/runs", requireProject: true }),
"analysis runs get": analysisRunGet,
"analysis runs results": analysisRunResults,
"analysis leakage identify": (ctx, argv) => schemeAnalysis(ctx, argv, "漏损识别执行成功", "/leakage-identifications", "scada_start", "scada_end"), "analysis leakage identify": (ctx, argv) => schemeAnalysis(ctx, argv, "漏损识别执行成功", "/leakage-identifications", "scada_start", "scada_end"),
"analysis leakage schemes list": (ctx) => schemeList(ctx, "读取漏损方案列表成功", "dma_leak_identification"),
"analysis leakage schemes get": (ctx, argv) => schemeGet(ctx, argv, "读取漏损方案详情成功", "dma_leak_identification"),
"analysis burst-detection detect": (ctx, argv) => schemeAnalysis(ctx, argv, "爆管检测执行成功", "/burst-detections", "scada_start", "scada_end"), "analysis burst-detection detect": (ctx, argv) => schemeAnalysis(ctx, argv, "爆管检测执行成功", "/burst-detections", "scada_start", "scada_end"),
"analysis burst-detection schemes list": (ctx) => schemeList(ctx, "读取爆管检测方案列表成功", "burst_detection"),
"analysis burst-detection schemes get": (ctx, argv) => schemeGet(ctx, argv, "读取爆管检测方案详情成功", "burst_detection"),
"analysis burst-location locate": burstLocation, "analysis burst-location locate": burstLocation,
"analysis burst-location schemes list": (ctx) => schemeList(ctx, "读取爆管定位方案列表成功", "burst_location"),
"analysis burst-location schemes get": (ctx, argv) => schemeGet(ctx, argv, "读取爆管定位方案详情成功", "burst_location"),
"analysis risk pipe-now": (ctx, argv) => riskPipe(ctx, argv, "读取当前管道风险成功", "/pipes/risk-probability-now"),
"analysis risk pipe-history": (ctx, argv) => riskPipe(ctx, argv, "读取历史管道风险成功", "/pipes/risk-probability"),
"analysis risk network": riskNetwork,
}; };
+36 -57
View File
@@ -1,8 +1,8 @@
import { SCADA_FIELDS, type ElementType } from "../core/constants.js"; import { SCADA_FIELDS, type ElementType } from "../core/constants.js";
import { CliError } from "../core/errors.js"; import { CliError } from "../core/errors.js";
import { emitApi } from "../core/http.js"; import { emitApi, requestAllPages } from "../core/http.js";
import { fieldsFor, optionalString, parseOptions, requiredString, requiredStringArray, validateChoice } from "../core/options.js"; import { fieldsFor, optionalNumber, optionalString, parseOptions, requiredString, requiredStringArray, validateChoice } from "../core/options.js";
import { resolveScheme } from "../core/runtime.js"; import { success } from "../core/output.js";
import { parseTime } from "../core/time.js"; import { parseTime } from "../core/time.js";
import type { HandlerMap, RuntimeContext } from "../core/types.js"; import type { HandlerMap, RuntimeContext } from "../core/types.js";
@@ -42,14 +42,17 @@ function realtimeByTimeProperty(ctx: RuntimeContext, argv: string[]): Promise<vo
}); });
} }
function schemeLinks(ctx: RuntimeContext, argv: string[]): Promise<void> { function analysisSeries(ctx: RuntimeContext, argv: string[], type: ElementType): Promise<void> {
const { values } = parseOptions(argv); const { values } = parseOptions(argv);
return emitApi(ctx, "读取方案管道数据成功", { const runId = requiredString(values, "run-id");
const idOption = type === "pipe" ? "link" : "node";
const elementId = requiredString(values, idOption);
const elementPath = type === "pipe" ? "links" : "nodes";
return emitApi(ctx, type === "pipe" ? "读取分析管道字段成功" : "读取分析节点字段成功", {
method: "GET", method: "GET",
path: "/timeseries/schemes/links", path: `/timeseries/analysis/runs/${encodeURIComponent(runId)}/${elementPath}/${encodeURIComponent(elementId)}`,
params: { params: {
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true), field: validateChoice(requiredString(values, "field"), fieldsFor(type), "--field"),
scheme_type: optionalString(values, "scheme-type") || "simulation",
start_time: parseTime(requiredString(values, "start-time"), "--start-time"), start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
end_time: parseTime(requiredString(values, "end-time"), "--end-time"), end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
}, },
@@ -57,40 +60,21 @@ function schemeLinks(ctx: RuntimeContext, argv: string[]): Promise<void> {
}); });
} }
function schemeNodeField(ctx: RuntimeContext, argv: string[]): Promise<void> { function analysisValues(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv); const { values } = parseOptions(argv);
return emitApi(ctx, "读取方案节点字段成功", { const type = validateChoice(requiredString(values, "type"), ["pipe", "junction"] as const, "--type");
return emitApi(ctx, "读取分析运行指定时刻结果成功", {
method: "GET", method: "GET",
path: `/timeseries/schemes/nodes/${requiredString(values, "node")}/field`, path: `/timeseries/analysis/runs/${encodeURIComponent(requiredString(values, "run-id"))}/values`,
params: { params: {
field: validateChoice(requiredString(values, "field"), fieldsFor("junction"), "--field"), result_time: parseTime(requiredString(values, "time"), "--time"),
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true), element_type: backendElementType(type),
scheme_type: optionalString(values, "scheme-type") || "simulation", field: validateChoice(requiredString(values, "field"), fieldsFor(type), "--field"),
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
}, },
requireProject: true, requireProject: true,
}); });
} }
function schemeSimulation(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv);
const query = validateChoice(requiredString(values, "query"), ["by-id-time", "by-scheme-time-property"] as const, "--query");
const type = validateChoice(optionalString(values, "type") || "pipe", ["pipe", "junction"] as const, "--type") as ElementType;
const params: Record<string, unknown> = {
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
scheme_type: optionalString(values, "scheme-type") || "simulation",
query_time: parseTime(requiredString(values, "time"), "--time"),
type: backendElementType(type),
};
if (query === "by-id-time") {
params.id = requiredString(values, "id");
return emitApi(ctx, "读取方案单点模拟数据成功", { method: "GET", path: "/timeseries/schemes/simulation-results", params, requireProject: true });
}
params.property = validateChoice(requiredString(values, "property"), fieldsFor(type), "--property");
return emitApi(ctx, "读取方案属性聚合数据成功", { method: "GET", path: "/timeseries/schemes/records", params, requireProject: true });
}
function scadaQuery(ctx: RuntimeContext, argv: string[]): Promise<void> { function scadaQuery(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv, { "device-id": "repeat" }); const { values } = parseOptions(argv, { "device-id": "repeat" });
const params: Record<string, unknown> = { const params: Record<string, unknown> = {
@@ -110,8 +94,8 @@ function composite(ctx: RuntimeContext, argv: string[]): Promise<void> {
start_time: parseTime(requiredString(values, "start-time"), "--start-time"), start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
end_time: parseTime(requiredString(values, "end-time"), "--end-time"), end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
}; };
const schemeName = resolveScheme(ctx, optionalString(values, "scheme")); const runId = optionalString(values, "run-id");
if (schemeName) Object.assign(params, { scheme_name: schemeName, scheme_type: optionalString(values, "scheme-type") || "simulation" }); if (runId && kind !== "element-scada") params.run_id = runId;
if (kind === "scada-simulation") params.device_ids = requiredStringArray(values, "feature").join(","); if (kind === "scada-simulation") params.device_ids = requiredStringArray(values, "feature").join(",");
else if (kind === "element-simulation") params.feature_infos = requiredStringArray(values, "feature").join(","); else if (kind === "element-simulation") params.feature_infos = requiredStringArray(values, "feature").join(",");
else { else {
@@ -130,31 +114,28 @@ function composite(ctx: RuntimeContext, argv: string[]): Promise<void> {
function pipelineHealth(ctx: RuntimeContext, argv: string[]): Promise<void> { function pipelineHealth(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv); const { values } = parseOptions(argv);
requiredString(values, "pipe");
requiredString(values, "start-time");
return emitApi(ctx, "读取管道健康预测成功", { return emitApi(ctx, "读取管道健康预测成功", {
method: "GET", method: "GET",
path: "/pipeline-health-predictions", path: "/pipeline-health-predictions",
params: { query_time: parseTime(requiredString(values, "end-time"), "--end-time") }, params: { query_time: parseTime(requiredString(values, "time"), "--time") },
requireProject: true, requireProject: true,
}); });
} }
function dataScadaGet(ctx: RuntimeContext, argv: string[]): Promise<void> { function dataScadaGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv); const { values } = parseOptions(argv);
validateChoice(requiredString(values, "kind"), ["info"] as const, "--kind"); return emitApi(ctx, "读取 SCADA 设备成功", { method: "GET", path: "/scada-devices/detail", params: { device_id: requiredString(values, "device-id") }, requireProject: true });
return emitApi(ctx, "读取 SCADA 数据成功", { method: "GET", path: "/scada-info/detail", params: { id: requiredString(values, "id") }, requireProject: true });
} }
function dataScadaList(ctx: RuntimeContext, argv: string[]): Promise<void> { async function dataScadaList(ctx: RuntimeContext, argv: string[]): Promise<void> {
const { values } = parseOptions(argv); const { values } = parseOptions(argv, { "page-size": "integer" });
validateChoice(requiredString(values, "kind"), ["info"] as const, "--kind"); const pageSize = Math.min(1000, Math.max(1, optionalNumber(values, "page-size") ?? 1000));
return emitApi(ctx, "读取 SCADA 列表成功", { method: "GET", path: "/scada-info", requireProject: true }); const [data, durationMs] = await requestAllPages(
} ctx,
{ method: "GET", path: "/scada-devices", requireProject: true },
function dataSchemeGet(ctx: RuntimeContext, argv: string[]): Promise<void> { pageSize,
const { values } = parseOptions(argv); );
return emitApi(ctx, "读取方案成功", { method: "GET", path: "/schemes/detail", params: { schema_name: requiredString(values, "name") }, requireProject: true }); success("读取 SCADA 设备列表成功", data, ctx, durationMs);
} }
export const dataHandlers: HandlerMap = { export const dataHandlers: HandlerMap = {
@@ -162,15 +143,13 @@ export const dataHandlers: HandlerMap = {
"data timeseries realtime nodes": (ctx, argv) => rangeGet(ctx, argv, "读取实时节点数据成功", "/timeseries/realtime/nodes"), "data timeseries realtime nodes": (ctx, argv) => rangeGet(ctx, argv, "读取实时节点数据成功", "/timeseries/realtime/nodes"),
"data timeseries realtime simulation-by-id-time": realtimeByIdTime, "data timeseries realtime simulation-by-id-time": realtimeByIdTime,
"data timeseries realtime simulation-by-time-property": realtimeByTimeProperty, "data timeseries realtime simulation-by-time-property": realtimeByTimeProperty,
"data timeseries scheme links": schemeLinks, "data timeseries analysis link-field": (ctx, argv) => analysisSeries(ctx, argv, "pipe"),
"data timeseries scheme node-field": schemeNodeField, "data timeseries analysis node-field": (ctx, argv) => analysisSeries(ctx, argv, "junction"),
"data timeseries scheme simulation": schemeSimulation, "data timeseries analysis values": analysisValues,
"data timeseries scada query": scadaQuery, "data timeseries scada query": scadaQuery,
"data timeseries composite": composite, "data timeseries composite": composite,
"data timeseries composite pipeline-health": pipelineHealth, "data pipeline-health": pipelineHealth,
"data scada get": dataScadaGet, "data scada get": dataScadaGet,
"data scada list": dataScadaList, "data scada list": dataScadaList,
"data scheme schema": (ctx) => emitApi(ctx, "读取方案 schema 成功", { method: "GET", path: "/network-schemas/scheme", requireProject: true }), "data scada schema": (ctx) => emitApi(ctx, "读取 SCADA 设备 schema 成功", { method: "GET", path: "/network-schemas/scada-device", requireProject: true }),
"data scheme get": dataSchemeGet,
"data scheme list": (ctx) => emitApi(ctx, "读取方案列表成功", { method: "GET", path: "/schemes", requireProject: true }),
}; };
+21 -31
View File
@@ -7,25 +7,21 @@ export const GROUP_SUMMARIES: Record<string, string> = {
"component option": "组件选项查询命令。", "component option": "组件选项查询命令。",
simulation: "模拟运行与调度相关命令。", simulation: "模拟运行与调度相关命令。",
analysis: "分析计算与诊断相关命令。", analysis: "分析计算与诊断相关命令。",
"analysis runs": "分析运行及结果查询命令。",
"analysis leakage": "漏损分析相关命令。", "analysis leakage": "漏损分析相关命令。",
"analysis leakage schemes": "漏损方案查询命令。",
"analysis burst-detection": "爆管检测相关命令。", "analysis burst-detection": "爆管检测相关命令。",
"analysis burst-detection schemes": "爆管检测方案查询命令。",
"analysis burst-location": "爆管定位相关命令。", "analysis burst-location": "爆管定位相关命令。",
"analysis burst-location schemes": "爆管定位方案查询命令。",
"analysis risk": "风险分析相关命令。",
"analysis sensor-placement": "传感器选址相关命令。", "analysis sensor-placement": "传感器选址相关命令。",
data: "时序、SCADA 和方案数据查询命令。", data: "时序、SCADA 和分析数据查询命令。",
"data timeseries": "时序数据查询命令。", "data timeseries": "时序数据查询命令。",
"data timeseries realtime": "实时模拟时序查询命令。", "data timeseries realtime": "实时模拟时序查询命令。",
"data timeseries scheme": "方案时序查询命令。", "data timeseries analysis": "按分析运行 ID 查询历史时序命令。",
"data timeseries scada": "SCADA 时序查询命令。", "data timeseries scada": "SCADA 时序查询命令。",
"data timeseries composite": "复合时序查询命令。", "data timeseries composite": "复合时序查询命令。",
"data scada": "SCADA 元数据查询命令。", "data scada": "SCADA 元数据查询命令。",
"data scheme": "方案数据查询命令。",
}; };
export const HIDDEN_PATH_PREFIXES = ["analysis burst-location", "analysis risk"]; export const HIDDEN_PATH_PREFIXES: string[] = [];
type CommandSpec = readonly [path: string, summary: string, options: readonly string[], examples: readonly string[], nextCommands?: readonly string[]]; type CommandSpec = readonly [path: string, summary: string, options: readonly string[], examples: readonly string[], nextCommands?: readonly string[]];
@@ -44,39 +40,33 @@ const commandSpecs: readonly CommandSpec[] = [
["component option schema", "读取选项 schema", ["--kind <KIND>", "[--pump <PUMP>]"], ["tjwater-cli component option schema --kind time", "tjwater-cli component option schema --kind energy", "tjwater-cli component option schema --kind pump-energy --pump PUMP1", "tjwater-cli component option schema --kind network"]], ["component option schema", "读取选项 schema", ["--kind <KIND>", "[--pump <PUMP>]"], ["tjwater-cli component option schema --kind time", "tjwater-cli component option schema --kind energy", "tjwater-cli component option schema --kind pump-energy --pump PUMP1", "tjwater-cli component option schema --kind network"]],
["component option get", "读取选项属性", ["--kind <KIND>", "[--pump <PUMP>]"], ["tjwater-cli component option get --kind time", "tjwater-cli component option get --kind energy", "tjwater-cli component option get --kind pump-energy --pump PUMP1", "tjwater-cli component option get --kind network"]], ["component option get", "读取选项属性", ["--kind <KIND>", "[--pump <PUMP>]"], ["tjwater-cli component option get --kind time", "tjwater-cli component option get --kind energy", "tjwater-cli component option get --kind pump-energy --pump PUMP1", "tjwater-cli component option get --kind network"]],
["simulation run", "触发指定绝对时间的模拟运行", ["--start-time <START_TIME>", "--duration <DURATION>"], ["tjwater-cli simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30"], ["tjwater-cli data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00", "tjwater-cli data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"]], ["simulation run", "触发指定绝对时间的模拟运行", ["--start-time <START_TIME>", "--duration <DURATION>"], ["tjwater-cli simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30"], ["tjwater-cli data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00", "tjwater-cli data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"]],
["analysis burst", "执行爆管分析", ["--start-time <START_TIME>", "--duration <DURATION>", "--burst-file <BURST_FILE>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 900 --burst-file ./burst.json --scheme burst_case_01", "tjwater-cli data scheme get --name burst_case_01", "tjwater-cli data scheme list"]], ["analysis burst", "执行爆管分析", ["--start-time <START_TIME>", "--duration <DURATION>", "--burst-file <BURST_FILE>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 900 --burst-file ./burst.json --scheme burst_case_01", "tjwater-cli analysis runs list"]],
["analysis valve", "阀门工况分析", ["--mode <MODE>", "[--start-time <START_TIME>]", "[--valve <VALVE>]", "[--element <ELEMENT>]", "[--disabled-valve <DISABLED_VALVE>]", "[--duration <DURATION>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --valve V2 --duration 900 --scheme valve_case_01", "tjwater-cli analysis valve --mode isolation --element E1 --element E2", "tjwater-cli analysis valve --mode isolation --element E1 --disabled-valve V3"]], ["analysis valve isolation", "执行阀门隔离分析", ["--element <ELEMENT>", "[--disabled-valve <DISABLED_VALVE>]"], ["tjwater-cli analysis valve isolation --element E1 --element E2", "tjwater-cli analysis valve isolation --element E1 --disabled-valve V3"]],
["analysis flushing", "执行冲洗分析", ["--start-time <START_TIME>", "--valve-setting-file <VALVE_SETTING_FILE>", "--drainage-node <DRAINAGE_NODE>", "--flow <FLOW>", "[--duration <DURATION>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis flushing --start-time 2025-01-02T03:04:05+08:00 --valve-setting-file ./valve.json --drainage-node N1 --flow 100.0 --duration 900 --scheme flush_case_01"]], ["analysis flushing", "执行冲洗分析", ["--start-time <START_TIME>", "--valve-setting-file <VALVE_SETTING_FILE>", "--drainage-node <DRAINAGE_NODE>", "--flow <FLOW>", "[--duration <DURATION>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis flushing --start-time 2025-01-02T03:04:05+08:00 --valve-setting-file ./valve.json --drainage-node N1 --flow 100.0 --duration 900 --scheme flush_case_01"]],
["analysis age", "执行水龄分析", ["--start-time <START_TIME>", "--duration <DURATION>"], ["tjwater-cli analysis age --start-time 2025-01-02T03:04:05+08:00 --duration 900"]], ["analysis age", "执行水龄分析", ["--start-time <START_TIME>", "--duration <DURATION>"], ["tjwater-cli analysis age --start-time 2025-01-02T03:04:05+08:00 --duration 900"]],
["analysis contaminant", "执行污染物模拟", ["--start-time <START_TIME>", "--duration <DURATION>", "--source-node <SOURCE_NODE>", "--concentration <CONCENTRATION>", "[--pattern <PATTERN>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis contaminant --start-time 2025-01-02T03:04:05+08:00 --duration 900 --source-node N1 --concentration 10.0 --scheme contam_case_01"]], ["analysis contaminant", "执行污染物模拟", ["--start-time <START_TIME>", "--duration <DURATION>", "--source-node <SOURCE_NODE>", "--concentration <CONCENTRATION>", "[--pattern <PATTERN>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis contaminant --start-time 2025-01-02T03:04:05+08:00 --duration 900 --source-node N1 --concentration 10.0 --scheme contam_case_01"]],
["analysis sensor-placement kmeans", "执行 KMeans 传感器选址", ["--count <COUNT>", "[--min-diameter <MIN_DIAMETER>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis sensor-placement kmeans --count 5 --min-diameter 100 --scheme placement_case_01"]], ["analysis sensor-placement run", "执行传感器选址", ["--run-name <RUN_NAME>", "--method <METHOD>", "--count <COUNT>", "[--min-diameter <MIN_DIAMETER>]"], ["tjwater-cli analysis sensor-placement run --run-name placement_case_01 --method kmeans --count 5 --min-diameter 100", "tjwater-cli analysis sensor-placement run --run-name placement_case_02 --method sensitivity --count 5"]],
["analysis sensor-placement list", "列出传感器选址运行", [], ["tjwater-cli analysis sensor-placement list"]],
["analysis sensor-placement get", "读取传感器选址运行", ["--run-id <RUN_ID>"], ["tjwater-cli analysis sensor-placement get --run-id 00000000-0000-0000-0000-000000000001"]],
["analysis runs list", "列出分析运行", [], ["tjwater-cli analysis runs list"]],
["analysis runs get", "读取分析运行", ["--run-id <RUN_ID>"], ["tjwater-cli analysis runs get --run-id 00000000-0000-0000-0000-000000000001"]],
["analysis runs results", "读取分析运行结果", ["--run-id <RUN_ID>", "[--result-type <RESULT_TYPE>]"], ["tjwater-cli analysis runs results --run-id 00000000-0000-0000-0000-000000000001", "tjwater-cli analysis runs results --run-id 00000000-0000-0000-0000-000000000001 --result-type leakage_identification"]],
["analysis leakage identify", "执行漏损识别", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis leakage identify --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme leak_case_01"]], ["analysis leakage identify", "执行漏损识别", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis leakage identify --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme leak_case_01"]],
["analysis leakage schemes list", "列出漏损方案", [], ["tjwater-cli analysis leakage schemes list"]],
["analysis leakage schemes get", "读取漏损方案详情", ["<SCHEME_NAME>"], ["tjwater-cli analysis leakage schemes get my_scheme"]],
["analysis burst-detection detect", "执行爆管检测", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst-detection detect --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme detect_case_01"]], ["analysis burst-detection detect", "执行爆管检测", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst-detection detect --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme detect_case_01"]],
["analysis burst-detection schemes list", "列出爆管检测方案", [], ["tjwater-cli analysis burst-detection schemes list"]],
["analysis burst-detection schemes get", "读取爆管检测方案详情", ["<SCHEME_NAME>"], ["tjwater-cli analysis burst-detection schemes get my_scheme"]],
["analysis burst-location locate", "执行爆管定位", ["--start-time <START_TIME>", "--end-time <END_TIME>", "--burst-leakage <BURST_LEAKAGE>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst-location locate --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --burst-leakage 100.0 --scheme locate_case_01"]], ["analysis burst-location locate", "执行爆管定位", ["--start-time <START_TIME>", "--end-time <END_TIME>", "--burst-leakage <BURST_LEAKAGE>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst-location locate --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --burst-leakage 100.0 --scheme locate_case_01"]],
["analysis burst-location schemes list", "列出爆管定位方案", [], ["tjwater-cli analysis burst-location schemes list"]],
["analysis burst-location schemes get", "读取爆管定位方案详情", ["<SCHEME_NAME>"], ["tjwater-cli analysis burst-location schemes get my_scheme"]],
["analysis risk pipe-now", "读取单条管道当前风险", ["--pipe <PIPE>"], ["tjwater-cli analysis risk pipe-now --pipe P1"]],
["analysis risk pipe-history", "读取单条管道历史风险", ["--pipe <PIPE>"], ["tjwater-cli analysis risk pipe-history --pipe P1"]],
["analysis risk network", "读取全网风险", [], ["tjwater-cli analysis risk network"]],
["data timeseries realtime links", "查询实时管道时序", ["--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries realtime links --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]], ["data timeseries realtime links", "查询实时管道时序", ["--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries realtime links --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
["data timeseries realtime nodes", "查询实时节点时序", ["--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries realtime nodes --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]], ["data timeseries realtime nodes", "查询实时节点时序", ["--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries realtime nodes --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
["data timeseries realtime simulation-by-id-time", "按元素和时间查询实时模拟结果", ["--id <ID>", "--type <TYPE>", "--time <TIME>"], ["tjwater-cli data timeseries realtime simulation-by-id-time --id J1 --type junction --time 2025-01-02T03:30:00+08:00", "tjwater-cli data timeseries realtime simulation-by-id-time --id P1 --type pipe --time 2025-01-02T03:30:00+08:00"]], ["data timeseries realtime simulation-by-id-time", "按元素和时间查询实时模拟结果", ["--id <ID>", "--type <TYPE>", "--time <TIME>"], ["tjwater-cli data timeseries realtime simulation-by-id-time --id J1 --type junction --time 2025-01-02T03:30:00+08:00", "tjwater-cli data timeseries realtime simulation-by-id-time --id P1 --type pipe --time 2025-01-02T03:30:00+08:00"]],
["data timeseries realtime simulation-by-time-property", "按时间和属性查询实时模拟结果", ["--type <TYPE>", "--time <TIME>", "--property <PROPERTY>"], ["tjwater-cli data timeseries realtime simulation-by-time-property --type pipe --time 2025-01-02T03:30:00+08:00 --property flow"]], ["data timeseries realtime simulation-by-time-property", "按时间和属性查询实时模拟结果", ["--type <TYPE>", "--time <TIME>", "--property <PROPERTY>"], ["tjwater-cli data timeseries realtime simulation-by-time-property --type pipe --time 2025-01-02T03:30:00+08:00 --property flow"]],
["data timeseries scheme links", "查询方案管道时序", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]"], ["tjwater-cli data timeseries scheme links --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme"]], ["data timeseries analysis link-field", "查询分析运行的管道字段时序", ["--run-id <RUN_ID>", "--link <LINK>", "--field <FIELD>", "--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries analysis link-field --run-id 00000000-0000-0000-0000-000000000001 --link P1 --field flow --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
["data timeseries scheme node-field", "查询方案节点字段时序", ["--node <NODE>", "--field <FIELD>", "--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]"], ["tjwater-cli data timeseries scheme node-field --node J1 --field pressure --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme"]], ["data timeseries analysis node-field", "查询分析运行的节点字段时序", ["--run-id <RUN_ID>", "--node <NODE>", "--field <FIELD>", "--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries analysis node-field --run-id 00000000-0000-0000-0000-000000000001 --node J1 --field pressure --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
["data timeseries scheme simulation", "查询方案模拟数据", ["--query <QUERY>", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]", "[--id <ID>]", "[--time <TIME>]", "[--type <TYPE>]", "[--property <PROPERTY>]"], ["tjwater-cli data timeseries scheme simulation --query by-id-time --id J1 --time 2025-01-02T03:30:00+08:00 --type junction --scheme my_scheme", "tjwater-cli data timeseries scheme simulation --query by-scheme-time-property --time 2025-01-02T03:30:00+08:00 --type pipe --property flow --scheme my_scheme"]], ["data timeseries analysis values", "查询分析运行指定时刻的全部元素字段", ["--run-id <RUN_ID>", "--type <TYPE>", "--time <TIME>", "--field <FIELD>"], ["tjwater-cli data timeseries analysis values --run-id 00000000-0000-0000-0000-000000000001 --type junction --time 2025-01-02T03:30:00+08:00 --field pressure"]],
["data timeseries scada query", "查询 SCADA 时序", ["--device-id <DEVICE_ID>", "--start-time <START_TIME>", "--end-time <END_TIME>", "[--field <FIELD>]"], ["tjwater-cli data timeseries scada query --device-id D1 --device-id D2 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00", "tjwater-cli data timeseries scada query --device-id D1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --field monitored_value"]], ["data timeseries scada query", "查询 SCADA 时序", ["--device-id <DEVICE_ID>", "--start-time <START_TIME>", "--end-time <END_TIME>", "[--field <FIELD>]"], ["tjwater-cli data timeseries scada query --device-id D1 --device-id D2 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00", "tjwater-cli data timeseries scada query --device-id D1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --field monitored_value"]],
["data timeseries composite", "执行复合时序查询", ["[--kind <KIND>]", "[--feature <FEATURE>]", "[--start-time <START_TIME>]", "[--end-time <END_TIME>]", "[--pipe <PIPE>]", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]", "[--use-cleaned]"], ["tjwater-cli data timeseries composite --kind scada-simulation --feature D1 --feature D2 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme", "tjwater-cli data timeseries composite --kind element-simulation --feature J1:pressure --feature P1:flow --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme", "tjwater-cli data timeseries composite --kind element-scada --feature J1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --use-cleaned"]], ["data timeseries composite", "执行复合时序查询", ["--kind <KIND>", "--feature <FEATURE>", "--start-time <START_TIME>", "--end-time <END_TIME>", "[--run-id <RUN_ID>]", "[--use-cleaned]"], ["tjwater-cli data timeseries composite --kind scada-simulation --feature D1 --feature D2 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --run-id 00000000-0000-0000-0000-000000000001", "tjwater-cli data timeseries composite --kind element-simulation --feature J1:pressure --feature P1:flow --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --run-id 00000000-0000-0000-0000-000000000001", "tjwater-cli data timeseries composite --kind element-scada --feature J1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --use-cleaned"]],
["data timeseries composite pipeline-health", "查询管道健康预测", ["--pipe <PIPE>", "--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries composite pipeline-health --pipe P1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]], ["data pipeline-health", "查询指定时刻的管道健康预测", ["--time <TIME>"], ["tjwater-cli data pipeline-health --time 2025-01-02T04:00:00+08:00"]],
["data scada get", "读取单条 SCADA 元数据", ["--kind <KIND>", "--id <ID>"], ["tjwater-cli data scada get --kind info --id SCADA-001"]], ["data scada get", "读取单条 SCADA 设备", ["--device-id <DEVICE_ID>"], ["tjwater-cli data scada get --device-id SCADA-001"]],
["data scada list", "列出 SCADA 元数据", ["--kind <KIND>"], ["tjwater-cli data scada list --kind info"]], ["data scada list", "列出 SCADA 设备", ["[--page-size <PAGE_SIZE>]"], ["tjwater-cli data scada list"]],
["data scheme schema", "读取方案 schema", [], ["tjwater-cli data scheme schema"]], ["data scada schema", "读取 SCADA 设备 schema", [], ["tjwater-cli data scada schema"]],
["data scheme get", "读取单条方案", ["--name <NAME>"], ["tjwater-cli data scheme get --name my_scheme"]],
["data scheme list", "列出方案", [], ["tjwater-cli data scheme list"]],
]; ];
export const commandDocs = new Map<string, CommandDoc>( export const commandDocs = new Map<string, CommandDoc>(
@@ -107,7 +97,7 @@ function optionDoc(path: string, token: string): CommandOptionDoc {
const clean = token.replace(/^\[/, "").replace(/\]$/, ""); const clean = token.replace(/^\[/, "").replace(/\]$/, "");
const name = clean.slice(2).split(/\s+/)[0]!; const name = clean.slice(2).split(/\s+/)[0]!;
const repeatedOptions: Record<string, string[]> = { const repeatedOptions: Record<string, string[]> = {
"analysis valve": ["valve", "element", "disabled-valve"], "analysis valve isolation": ["element", "disabled-valve"],
"analysis burst-location locate": ["pressure-scada-id", "flow-scada-id"], "analysis burst-location locate": ["pressure-scada-id", "flow-scada-id"],
"data timeseries scada query": ["device-id"], "data timeseries scada query": ["device-id"],
"data timeseries composite": ["feature"], "data timeseries composite": ["feature"],
+102 -48
View File
@@ -13,32 +13,32 @@ const visibleCommandPaths = [
"analysis age", "analysis age",
"analysis burst", "analysis burst",
"analysis burst-detection detect", "analysis burst-detection detect",
"analysis burst-detection schemes get", "analysis burst-location locate",
"analysis burst-detection schemes list",
"analysis contaminant", "analysis contaminant",
"analysis flushing", "analysis flushing",
"analysis leakage identify", "analysis leakage identify",
"analysis leakage schemes get", "analysis runs get",
"analysis leakage schemes list", "analysis runs list",
"analysis sensor-placement kmeans", "analysis runs results",
"analysis valve", "analysis sensor-placement get",
"analysis sensor-placement list",
"analysis sensor-placement run",
"analysis valve isolation",
"component option get", "component option get",
"component option schema", "component option schema",
"data scada get", "data scada get",
"data scada list", "data scada list",
"data scheme get", "data scada schema",
"data scheme list", "data pipeline-health",
"data scheme schema", "data timeseries analysis link-field",
"data timeseries analysis node-field",
"data timeseries analysis values",
"data timeseries composite", "data timeseries composite",
"data timeseries composite pipeline-health",
"data timeseries realtime links", "data timeseries realtime links",
"data timeseries realtime nodes", "data timeseries realtime nodes",
"data timeseries realtime simulation-by-id-time", "data timeseries realtime simulation-by-id-time",
"data timeseries realtime simulation-by-time-property", "data timeseries realtime simulation-by-time-property",
"data timeseries scada query", "data timeseries scada query",
"data timeseries scheme links",
"data timeseries scheme node-field",
"data timeseries scheme simulation",
"network get-all-pipes-properties", "network get-all-pipes-properties",
"network get-all-pumps-properties", "network get-all-pumps-properties",
"network get-all-reservoirs-properties", "network get-all-reservoirs-properties",
@@ -53,14 +53,7 @@ const visibleCommandPaths = [
"simulation run", "simulation run",
]; ];
const hiddenCommandPaths = [ const hiddenCommandPaths = [];
"analysis burst-location locate",
"analysis burst-location schemes get",
"analysis burst-location schemes list",
"analysis risk network",
"analysis risk pipe-history",
"analysis risk pipe-now",
];
function runCommand(command, args, input, options = {}) { function runCommand(command, args, input, options = {}) {
return new Promise((resolveRun, reject) => { return new Promise((resolveRun, reject) => {
@@ -144,7 +137,7 @@ function normalizeSeenRequest(request) {
function defaultContractResponse(req) { function defaultContractResponse(req) {
const url = new URL(req.url, "http://127.0.0.1"); const url = new URL(req.url, "http://127.0.0.1");
if (["/api/v1/pipes", "/api/v1/reservoirs", "/api/v1/tanks", "/api/v1/pumps", "/api/v1/valves"].includes(url.pathname)) { if (["/api/v1/pipes", "/api/v1/reservoirs", "/api/v1/tanks", "/api/v1/pumps", "/api/v1/valves", "/api/v1/scada-devices"].includes(url.pathname)) {
return { return {
items: [], items: [],
limit: Number(url.searchParams.get("limit")), limit: Number(url.searchParams.get("limit")),
@@ -338,18 +331,86 @@ test("maps CLI pipe and junction types to backend link and node types", async ()
for (const args of [ for (const args of [
["data", "timeseries", "realtime", "simulation-by-id-time", "--id", "J1", "--type", "junction", "--time", at], ["data", "timeseries", "realtime", "simulation-by-id-time", "--id", "J1", "--type", "junction", "--time", at],
["data", "timeseries", "realtime", "simulation-by-time-property", "--type", "pipe", "--time", at, "--property", "flow"], ["data", "timeseries", "realtime", "simulation-by-time-property", "--type", "pipe", "--time", at, "--property", "flow"],
["data", "timeseries", "scheme", "simulation", "--query", "by-id-time", "--id", "P1", "--type", "pipe", "--time", at, "--scheme", "scheme_case"], ["data", "timeseries", "analysis", "values", "--run-id", "00000000-0000-0000-0000-000000000001", "--type", "pipe", "--time", at, "--field", "flow"],
]) { ]) {
const result = await runCli(["--auth-stdin", ...args], auth); const result = await runCli(["--auth-stdin", ...args], auth);
assert.equal(result.exitCode, 0, result.stderr); assert.equal(result.exitCode, 0, result.stderr);
} }
const queries = server.seen.map(normalizeSeenRequest).map((request) => request.query.type); const requests = server.seen.map(normalizeSeenRequest);
assert.deepEqual(queries, ["node", "link", "link"]); assert.deepEqual(
[requests[0].query.type, requests[1].query.type, requests[2].query.element_type],
["node", "link", "link"],
);
} finally { } finally {
await server.close(); await server.close();
} }
}); });
test("uses run-id based analysis, sensor placement, and SCADA contracts", async () => {
const server = await startJsonServer(defaultContractResponse);
const auth = { server: server.url, access_token: "token-4", project_id: "project-1" };
const runId = "00000000-0000-0000-0000-000000000001";
const start = "2025-01-02T03:00:00+08:00";
const end = "2025-01-02T04:00:00+08:00";
try {
for (const args of [
["analysis", "sensor-placement", "run", "--run-name", "placement-1", "--method", "kmeans", "--count", "5", "--min-diameter", "100"],
["analysis", "runs", "results", "--run-id", runId, "--result-type", "leakage_identification"],
["data", "timeseries", "analysis", "node-field", "--run-id", runId, "--node", "J1", "--field", "pressure", "--start-time", start, "--end-time", end],
["data", "scada", "get", "--device-id", "SCADA-001"],
]) {
const result = await runCli(["--auth-stdin", ...args], auth);
assert.equal(result.exitCode, 0, result.stderr);
}
const requests = server.seen.map(normalizeSeenRequest);
assert.deepEqual(requests[0], {
body: {
run_name: "placement-1",
sensor_type: "pressure",
method: "kmeans",
sensor_count: 5,
min_diameter: 100,
},
headers: { authorization: "Bearer token-4", "x-project-id": "project-1" },
method: "POST",
path: "/api/v1/sensor-placement-runs",
query: {},
});
assert.equal(requests[1].path, `/api/v1/analysis/runs/${runId}/results`);
assert.deepEqual(requests[1].query, { result_type: "leakage_identification" });
assert.equal(requests[2].path, `/api/v1/timeseries/analysis/runs/${runId}/nodes/J1`);
assert.deepEqual(requests[2].query, {
end_time: end,
field: "pressure",
start_time: start,
});
assert.equal(requests[3].path, "/api/v1/scada-devices/detail");
assert.deepEqual(requests[3].query, { device_id: "SCADA-001" });
} finally {
await server.close();
}
});
test("does not expose removed scheme, risk, or valve-close commands", async () => {
for (const command of [
"analysis leakage schemes list",
"analysis risk network",
"data scheme list",
"data timeseries scheme simulation",
]) {
const result = await runCli(["help", ...command.split(" ")]);
assert.equal(result.exitCode, 0, result.stderr);
const payload = parseJsonResult(result);
assert.equal(payload.ok, false, command);
assert.equal(payload.error.code, "COMMAND_NOT_FOUND", command);
}
const valveClose = await runCli(["analysis", "valve", "--mode", "close"]);
assert.equal(valveClose.exitCode, 2, valveClose.stderr);
assert.equal(parseJsonResult(valveClose).error.code, "COMMAND_NOT_FOUND");
});
test("executes every command and key variant against the backend contract", async () => { test("executes every command and key variant against the backend contract", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "tjwater-cli-parity-")); const tempDir = await mkdtemp(join(tmpdir(), "tjwater-cli-parity-"));
try { try {
@@ -372,6 +433,7 @@ test("executes every command and key variant against the backend contract", asyn
const start = "2025-01-02T03:00:00+08:00"; const start = "2025-01-02T03:00:00+08:00";
const end = "2025-01-02T04:00:00+08:00"; const end = "2025-01-02T04:00:00+08:00";
const at = "2025-01-02T03:30:00+08:00"; const at = "2025-01-02T03:30:00+08:00";
const runId = "00000000-0000-0000-0000-000000000001";
const cases = [ const cases = [
["network get-junction-properties", ["network", "get-junction-properties", "--junction", "J1"]], ["network get-junction-properties", ["network", "get-junction-properties", "--junction", "J1"]],
["network get-pipe-properties", ["network", "get-pipe-properties", "--pipe", "P1"]], ["network get-pipe-properties", ["network", "get-pipe-properties", "--pipe", "P1"]],
@@ -388,42 +450,34 @@ test("executes every command and key variant against the backend contract", asyn
["component option get", ["component", "option", "get", "--kind", "pump-energy", "--pump", "P1"]], ["component option get", ["component", "option", "get", "--kind", "pump-energy", "--pump", "P1"]],
["simulation run", ["simulation", "run", "--start-time", start, "--duration", "60"]], ["simulation run", ["simulation", "run", "--start-time", start, "--duration", "60"]],
["analysis burst", ["analysis", "burst", "--start-time", start, "--duration", "900", "--burst-file", burstFile, "--scheme", "burst_case"]], ["analysis burst", ["analysis", "burst", "--start-time", start, "--duration", "900", "--burst-file", burstFile, "--scheme", "burst_case"]],
["analysis valve close", ["analysis", "valve", "--mode", "close", "--start-time", start, "--valve", "V1", "--valve", "V2", "--duration", "900", "--scheme", "valve_case"]], ["analysis valve isolation", ["analysis", "valve", "isolation", "--element", "E1", "--disabled-valve", "V3"]],
["analysis valve isolation", ["analysis", "valve", "--mode", "isolation", "--element", "E1", "--disabled-valve", "V3"]],
["analysis flushing", ["analysis", "flushing", "--start-time", start, "--valve-setting-file", valveFile, "--drainage-node", "N1", "--flow", "100.5", "--duration", "900", "--scheme", "flush_case"]], ["analysis flushing", ["analysis", "flushing", "--start-time", start, "--valve-setting-file", valveFile, "--drainage-node", "N1", "--flow", "100.5", "--duration", "900", "--scheme", "flush_case"]],
["analysis age", ["analysis", "age", "--start-time", start, "--duration", "900"]], ["analysis age", ["analysis", "age", "--start-time", start, "--duration", "900"]],
["analysis contaminant", ["analysis", "contaminant", "--start-time", start, "--duration", "900", "--source-node", "N1", "--concentration", "10.5", "--pattern", "P1", "--scheme", "contam_case"]], ["analysis contaminant", ["analysis", "contaminant", "--start-time", start, "--duration", "900", "--source-node", "N1", "--concentration", "10.5", "--pattern", "P1", "--scheme", "contam_case"]],
["analysis sensor-placement kmeans", ["analysis", "sensor-placement", "kmeans", "--count", "5", "--min-diameter", "100", "--scheme", "place_case"]], ["analysis sensor-placement run", ["analysis", "sensor-placement", "run", "--run-name", "place_case", "--method", "kmeans", "--count", "5", "--min-diameter", "100"]],
["analysis sensor-placement list", ["analysis", "sensor-placement", "list"]],
["analysis sensor-placement get", ["analysis", "sensor-placement", "get", "--run-id", runId]],
["analysis runs list", ["analysis", "runs", "list"]],
["analysis runs get", ["analysis", "runs", "get", "--run-id", runId]],
["analysis runs results", ["analysis", "runs", "results", "--run-id", runId, "--result-type", "leakage_identification"]],
["analysis leakage identify", ["analysis", "leakage", "identify", "--start-time", start, "--end-time", end, "--scheme", "leak_case"]], ["analysis leakage identify", ["analysis", "leakage", "identify", "--start-time", start, "--end-time", end, "--scheme", "leak_case"]],
["analysis leakage schemes list", ["analysis", "leakage", "schemes", "list"]],
["analysis leakage schemes get", ["analysis", "leakage", "schemes", "get", "leak_case"]],
["analysis burst-detection detect", ["analysis", "burst-detection", "detect", "--start-time", start, "--end-time", end, "--scheme", "detect_case"]], ["analysis burst-detection detect", ["analysis", "burst-detection", "detect", "--start-time", start, "--end-time", end, "--scheme", "detect_case"]],
["analysis burst-detection schemes list", ["analysis", "burst-detection", "schemes", "list"]],
["analysis burst-detection schemes get", ["analysis", "burst-detection", "schemes", "get", "detect_case"]],
["analysis burst-location locate", ["analysis", "burst-location", "locate", "--start-time", start, "--end-time", end, "--burst-leakage", "50.5", "--scheme", "locate_case", "--data-source", "simulation", "--pressure-file", pressureFile, "--flow-file", flowFile, "--use-scada-flow"]], ["analysis burst-location locate", ["analysis", "burst-location", "locate", "--start-time", start, "--end-time", end, "--burst-leakage", "50.5", "--scheme", "locate_case", "--data-source", "simulation", "--pressure-file", pressureFile, "--flow-file", flowFile, "--use-scada-flow"]],
["analysis burst-location schemes list", ["analysis", "burst-location", "schemes", "list"]],
["analysis burst-location schemes get", ["analysis", "burst-location", "schemes", "get", "locate_case"]],
["analysis risk pipe-now", ["analysis", "risk", "pipe-now", "--pipe", "P1"]],
["analysis risk pipe-history", ["analysis", "risk", "pipe-history", "--pipe", "P1"]],
["analysis risk network", ["analysis", "risk", "network"]],
["data realtime links", ["data", "timeseries", "realtime", "links", "--start-time", start, "--end-time", end]], ["data realtime links", ["data", "timeseries", "realtime", "links", "--start-time", start, "--end-time", end]],
["data realtime nodes", ["data", "timeseries", "realtime", "nodes", "--start-time", start, "--end-time", end]], ["data realtime nodes", ["data", "timeseries", "realtime", "nodes", "--start-time", start, "--end-time", end]],
["data realtime simulation-by-id-time", ["data", "timeseries", "realtime", "simulation-by-id-time", "--id", "J1", "--type", "junction", "--time", at]], ["data realtime simulation-by-id-time", ["data", "timeseries", "realtime", "simulation-by-id-time", "--id", "J1", "--type", "junction", "--time", at]],
["data realtime simulation-by-time-property", ["data", "timeseries", "realtime", "simulation-by-time-property", "--type", "pipe", "--time", at, "--property", "flow"]], ["data realtime simulation-by-time-property", ["data", "timeseries", "realtime", "simulation-by-time-property", "--type", "pipe", "--time", at, "--property", "flow"]],
["data scheme links", ["data", "timeseries", "scheme", "links", "--start-time", start, "--end-time", end, "--scheme", "scheme_case", "--scheme-type", "simulation"]], ["data analysis link-field", ["data", "timeseries", "analysis", "link-field", "--run-id", runId, "--link", "P1", "--field", "flow", "--start-time", start, "--end-time", end]],
["data scheme node-field", ["data", "timeseries", "scheme", "node-field", "--node", "J1", "--field", "pressure", "--start-time", start, "--end-time", end, "--scheme", "scheme_case"]], ["data analysis node-field", ["data", "timeseries", "analysis", "node-field", "--run-id", runId, "--node", "J1", "--field", "pressure", "--start-time", start, "--end-time", end]],
["data scheme simulation by-id", ["data", "timeseries", "scheme", "simulation", "--query", "by-id-time", "--id", "J1", "--time", at, "--type", "junction", "--scheme", "scheme_case"]], ["data analysis values", ["data", "timeseries", "analysis", "values", "--run-id", runId, "--type", "pipe", "--time", at, "--field", "flow"]],
["data scheme simulation by-property", ["data", "timeseries", "scheme", "simulation", "--query", "by-scheme-time-property", "--time", at, "--type", "pipe", "--property", "flow", "--scheme", "scheme_case"]],
["data scada query", ["data", "timeseries", "scada", "query", "--device-id", "D1", "--device-id", "D2", "--start-time", start, "--end-time", end, "--field", "monitored_value"]], ["data scada query", ["data", "timeseries", "scada", "query", "--device-id", "D1", "--device-id", "D2", "--start-time", start, "--end-time", end, "--field", "monitored_value"]],
["data composite scada-simulation", ["data", "timeseries", "composite", "--kind", "scada-simulation", "--feature", "D1", "--feature", "D2", "--start-time", start, "--end-time", end, "--scheme", "scheme_case"]], ["data composite scada-simulation", ["data", "timeseries", "composite", "--kind", "scada-simulation", "--feature", "D1", "--feature", "D2", "--start-time", start, "--end-time", end, "--run-id", runId]],
["data composite element-simulation", ["data", "timeseries", "composite", "--kind", "element-simulation", "--feature", "J1:pressure", "--start-time", start, "--end-time", end]], ["data composite element-simulation", ["data", "timeseries", "composite", "--kind", "element-simulation", "--feature", "J1:pressure", "--start-time", start, "--end-time", end]],
["data composite element-scada", ["data", "timeseries", "composite", "--kind", "element-scada", "--feature", "J1", "--start-time", start, "--end-time", end, "--use-cleaned"]], ["data composite element-scada", ["data", "timeseries", "composite", "--kind", "element-scada", "--feature", "J1", "--start-time", start, "--end-time", end, "--use-cleaned"]],
["data composite pipeline-health", ["data", "timeseries", "composite", "pipeline-health", "--pipe", "P1", "--start-time", start, "--end-time", end]], ["data pipeline-health", ["data", "pipeline-health", "--time", end]],
["data scada get", ["data", "scada", "get", "--kind", "info", "--id", "SCADA-001"]], ["data scada get", ["data", "scada", "get", "--device-id", "SCADA-001"]],
["data scada list", ["data", "scada", "list", "--kind", "info"]], ["data scada list", ["data", "scada", "list"]],
["data scheme schema", ["data", "scheme", "schema"]], ["data scada schema", ["data", "scada", "schema"]],
["data scheme get", ["data", "scheme", "get", "--name", "scheme_case"]],
["data scheme list", ["data", "scheme", "list"]],
]; ];
for (const [name, args] of cases) { for (const [name, args] of cases) {