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:
@@ -35,15 +35,18 @@ description: tjwater-cli 命令行工具使用说明,涵盖命令发现、输
|
||||
|
||||
Agent 通过 `help` 动态发现可用命令,而非依赖硬编码清单。
|
||||
|
||||
**重要:命令分为两类——触发动作与数据获取。**
|
||||
**重要:命令分为三类——触发动作、运行/结果查询与时序数据获取。**
|
||||
|
||||
- **触发动作**(`simulation`、`analysis`):向服务端发起计算请求,返回任务状态/ID,**不直接返回分析结果**。
|
||||
- **数据获取**(`data timeseries`):所有计算结果(仿真压力、分析指标等)的唯一数据出口,需在触发动作完成后调用。
|
||||
- **触发动作**(`simulation`、各类 `analysis`):向服务端发起计算请求。
|
||||
- **运行/结果查询**(`analysis runs`):按 `run_id` 查询运行元数据和非时序结果。
|
||||
- **时序数据获取**(`data timeseries`):实时结果或按 `run_id` 查询节点、管道时序。
|
||||
|
||||
```
|
||||
simulation/analysis → 触发计算 → 返回状态/任务ID
|
||||
↓
|
||||
data timeseries → 获取计算结果
|
||||
analysis → 触发计算 → analysis runs list/get/results
|
||||
↓ run_id
|
||||
data timeseries analysis → 获取元素时序
|
||||
|
||||
simulation → 触发实时模拟 → data timeseries realtime → 获取实时结果
|
||||
```
|
||||
|
||||
通过 `help` 发现命令:
|
||||
@@ -63,11 +66,11 @@ tjwater-cli help COMMAND → 子命令与参数详情
|
||||
|
||||
| 命令族 | 典型子命令 | 用途 |
|
||||
|------|-----------|------|
|
||||
| `project` | `list`, `db-health` | 项目管理、数据库健康检查 |
|
||||
| `data` | `timeseries realtime links / nodes`, `timeseries scada query` | **时序数据查询**(实时/SCADA),所有分析结果的唯一获取渠道 |
|
||||
| `network` | `get-pipe-properties`, `get-all-pipes-properties` | 管网元素查询 |
|
||||
| `component` | `option get`, `option schema` | 模型选项和结构查询 |
|
||||
| `data` | `timeseries realtime`, `timeseries analysis`, `timeseries scada`, `scada` | 实时、分析时序和 SCADA 查询 |
|
||||
| `simulation` | 通过 `help simulation` 发现 | **触发水力仿真计算**(执行成功返回状态,实际结果需走 `data timeseries` 获取) |
|
||||
| `analysis` | 通过 `help analysis` 发现 | **触发分析计算**(执行成功返回状态,实际结果需走 `data timeseries` 获取) |
|
||||
| `net` | `list-pipes` | 管网拓扑查询 |
|
||||
| `analysis` | `runs`, `sensor-placement` 及各类分析命令 | 触发分析,并按运行 ID 查询元数据与结果 |
|
||||
| `help` | (无子命令) | 命令发现入口 |
|
||||
|
||||
> 完整命令清单始终以 `tjwater-cli help` 实时输出为准。
|
||||
@@ -128,11 +131,11 @@ tjwater-cli help COMMAND → 子命令与参数详情
|
||||
|
||||
1. **禁止猜测命令** — 执行任何命令前必须先 `tjwater_cli(command="help ...")` 确认命令存在及参数签名,参数均已写在 help 中,禁止凭经验拼写
|
||||
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
|
||||
5. **结果验证** — 始终检查 `ok` 字段,失败时先处理错误码再重试
|
||||
6. **大结果集** — 优先过滤/采样,不要一次性拉取全部数据
|
||||
7. **模拟时长控制** — 模拟(`simulation`)或方案模拟的 `--duration` 不宜过长,建议每次仿真时间跨度控制在一小时以内,避免计算耗时过长或结果数据量过大
|
||||
7. **模拟时长控制** — 实时模拟或分析运行的 `--duration` 不宜过长,建议每次仿真时间跨度控制在一小时以内,避免计算耗时过长或结果数据量过大
|
||||
|
||||
## 示例
|
||||
|
||||
@@ -145,11 +148,11 @@ tjwater-cli help COMMAND → 子命令与参数详情
|
||||
```
|
||||
> `data timeseries realtime nodes` 仅接受 `--start-time` / `--end-time`,返回全量节点数据。
|
||||
|
||||
### 按节点查询方案时序字段
|
||||
### 按节点查询分析运行时序字段
|
||||
```json
|
||||
{
|
||||
"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"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export default tool({
|
||||
command: tool.schema
|
||||
.string()
|
||||
.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
|
||||
.number()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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 { optionalNumber, optionalString, optionalStringArray, parseOptions, requiredNumber, requiredString, validateChoice } from "../core/options.js";
|
||||
import { resolveScheme } from "../core/runtime.js";
|
||||
import { parseTime } from "../core/time.js";
|
||||
import { success } from "../core/output.js";
|
||||
import type { HandlerMap, RuntimeContext } from "../core/types.js";
|
||||
|
||||
function analysisBurst(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
@@ -22,30 +21,13 @@ function analysisBurst(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
scheme_name: schemeName,
|
||||
},
|
||||
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> {
|
||||
const { values } = parseOptions(argv, { valve: "repeat", element: "repeat", "disabled-valve": "repeat", duration: "integer" });
|
||||
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,
|
||||
});
|
||||
}
|
||||
function analysisValveIsolation(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv, { element: "repeat", "disabled-valve": "repeat" });
|
||||
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, "阀门隔离分析执行成功", {
|
||||
method: "POST",
|
||||
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 });
|
||||
}
|
||||
|
||||
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" });
|
||||
return emitApi(ctx, "传感器选址执行成功", {
|
||||
method: "POST",
|
||||
path: "/pressure-sensor-placement-kmeans",
|
||||
path: "/sensor-placement-runs",
|
||||
body: {
|
||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
||||
sensor_number: requiredNumber(values, "count"),
|
||||
run_name: requiredString(values, "run-name"),
|
||||
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,
|
||||
},
|
||||
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> {
|
||||
return emitApi(ctx, summary, { method: "GET", path: "/schemes", params: { scheme_type: schemeType }, requireProject: true });
|
||||
function analysisRunGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
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> {
|
||||
const { positionals } = parseOptions(argv);
|
||||
if (!positionals[0]) throw new CliError("CLI 参数错误", "MISSING_ARGUMENT", "Missing argument 'SCHEME_NAME'", 2);
|
||||
return emitApi(ctx, summary, { method: "GET", path: `/schemes/${positionals[0]}`, params: { scheme_type: schemeType }, requireProject: true });
|
||||
function analysisRunResults(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv);
|
||||
const runId = requiredString(values, "run-id");
|
||||
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> {
|
||||
@@ -156,34 +151,22 @@ function burstLocation(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
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 = {
|
||||
"analysis burst": analysisBurst,
|
||||
"analysis valve": analysisValve,
|
||||
"analysis valve isolation": analysisValveIsolation,
|
||||
"analysis flushing": analysisFlushing,
|
||||
"analysis age": analysisAge,
|
||||
"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 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 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 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
@@ -1,8 +1,8 @@
|
||||
import { SCADA_FIELDS, type ElementType } from "../core/constants.js";
|
||||
import { CliError } from "../core/errors.js";
|
||||
import { emitApi } from "../core/http.js";
|
||||
import { fieldsFor, optionalString, parseOptions, requiredString, requiredStringArray, validateChoice } from "../core/options.js";
|
||||
import { resolveScheme } from "../core/runtime.js";
|
||||
import { emitApi, requestAllPages } from "../core/http.js";
|
||||
import { fieldsFor, optionalNumber, optionalString, parseOptions, requiredString, requiredStringArray, validateChoice } from "../core/options.js";
|
||||
import { success } from "../core/output.js";
|
||||
import { parseTime } from "../core/time.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);
|
||||
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",
|
||||
path: "/timeseries/schemes/links",
|
||||
path: `/timeseries/analysis/runs/${encodeURIComponent(runId)}/${elementPath}/${encodeURIComponent(elementId)}`,
|
||||
params: {
|
||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
||||
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"),
|
||||
},
|
||||
@@ -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);
|
||||
return emitApi(ctx, "读取方案节点字段成功", {
|
||||
const type = validateChoice(requiredString(values, "type"), ["pipe", "junction"] as const, "--type");
|
||||
return emitApi(ctx, "读取分析运行指定时刻结果成功", {
|
||||
method: "GET",
|
||||
path: `/timeseries/schemes/nodes/${requiredString(values, "node")}/field`,
|
||||
path: `/timeseries/analysis/runs/${encodeURIComponent(requiredString(values, "run-id"))}/values`,
|
||||
params: {
|
||||
field: validateChoice(requiredString(values, "field"), fieldsFor("junction"), "--field"),
|
||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
||||
scheme_type: optionalString(values, "scheme-type") || "simulation",
|
||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
||||
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
|
||||
result_time: parseTime(requiredString(values, "time"), "--time"),
|
||||
element_type: backendElementType(type),
|
||||
field: validateChoice(requiredString(values, "field"), fieldsFor(type), "--field"),
|
||||
},
|
||||
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> {
|
||||
const { values } = parseOptions(argv, { "device-id": "repeat" });
|
||||
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"),
|
||||
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
|
||||
};
|
||||
const schemeName = resolveScheme(ctx, optionalString(values, "scheme"));
|
||||
if (schemeName) Object.assign(params, { scheme_name: schemeName, scheme_type: optionalString(values, "scheme-type") || "simulation" });
|
||||
const runId = optionalString(values, "run-id");
|
||||
if (runId && kind !== "element-scada") params.run_id = runId;
|
||||
if (kind === "scada-simulation") params.device_ids = requiredStringArray(values, "feature").join(",");
|
||||
else if (kind === "element-simulation") params.feature_infos = requiredStringArray(values, "feature").join(",");
|
||||
else {
|
||||
@@ -130,31 +114,28 @@ function composite(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
|
||||
function pipelineHealth(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv);
|
||||
requiredString(values, "pipe");
|
||||
requiredString(values, "start-time");
|
||||
return emitApi(ctx, "读取管道健康预测成功", {
|
||||
method: "GET",
|
||||
path: "/pipeline-health-predictions",
|
||||
params: { query_time: parseTime(requiredString(values, "end-time"), "--end-time") },
|
||||
params: { query_time: parseTime(requiredString(values, "time"), "--time") },
|
||||
requireProject: true,
|
||||
});
|
||||
}
|
||||
|
||||
function dataScadaGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv);
|
||||
validateChoice(requiredString(values, "kind"), ["info"] as const, "--kind");
|
||||
return emitApi(ctx, "读取 SCADA 数据成功", { method: "GET", path: "/scada-info/detail", params: { id: requiredString(values, "id") }, requireProject: true });
|
||||
return emitApi(ctx, "读取 SCADA 设备成功", { method: "GET", path: "/scada-devices/detail", params: { device_id: requiredString(values, "device-id") }, requireProject: true });
|
||||
}
|
||||
|
||||
function dataScadaList(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv);
|
||||
validateChoice(requiredString(values, "kind"), ["info"] as const, "--kind");
|
||||
return emitApi(ctx, "读取 SCADA 列表成功", { method: "GET", path: "/scada-info", requireProject: true });
|
||||
}
|
||||
|
||||
function dataSchemeGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv);
|
||||
return emitApi(ctx, "读取方案成功", { method: "GET", path: "/schemes/detail", params: { schema_name: requiredString(values, "name") }, requireProject: true });
|
||||
async function dataScadaList(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
||||
const { values } = parseOptions(argv, { "page-size": "integer" });
|
||||
const pageSize = Math.min(1000, Math.max(1, optionalNumber(values, "page-size") ?? 1000));
|
||||
const [data, durationMs] = await requestAllPages(
|
||||
ctx,
|
||||
{ method: "GET", path: "/scada-devices", requireProject: true },
|
||||
pageSize,
|
||||
);
|
||||
success("读取 SCADA 设备列表成功", data, ctx, durationMs);
|
||||
}
|
||||
|
||||
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 simulation-by-id-time": realtimeByIdTime,
|
||||
"data timeseries realtime simulation-by-time-property": realtimeByTimeProperty,
|
||||
"data timeseries scheme links": schemeLinks,
|
||||
"data timeseries scheme node-field": schemeNodeField,
|
||||
"data timeseries scheme simulation": schemeSimulation,
|
||||
"data timeseries analysis link-field": (ctx, argv) => analysisSeries(ctx, argv, "pipe"),
|
||||
"data timeseries analysis node-field": (ctx, argv) => analysisSeries(ctx, argv, "junction"),
|
||||
"data timeseries analysis values": analysisValues,
|
||||
"data timeseries scada query": scadaQuery,
|
||||
"data timeseries composite": composite,
|
||||
"data timeseries composite pipeline-health": pipelineHealth,
|
||||
"data pipeline-health": pipelineHealth,
|
||||
"data scada get": dataScadaGet,
|
||||
"data scada list": dataScadaList,
|
||||
"data scheme schema": (ctx) => emitApi(ctx, "读取方案 schema 成功", { method: "GET", path: "/network-schemas/scheme", requireProject: true }),
|
||||
"data scheme get": dataSchemeGet,
|
||||
"data scheme list": (ctx) => emitApi(ctx, "读取方案列表成功", { method: "GET", path: "/schemes", requireProject: true }),
|
||||
"data scada schema": (ctx) => emitApi(ctx, "读取 SCADA 设备 schema 成功", { method: "GET", path: "/network-schemas/scada-device", requireProject: true }),
|
||||
};
|
||||
|
||||
+21
-31
@@ -7,25 +7,21 @@ export const GROUP_SUMMARIES: Record<string, string> = {
|
||||
"component option": "组件选项查询命令。",
|
||||
simulation: "模拟运行与调度相关命令。",
|
||||
analysis: "分析计算与诊断相关命令。",
|
||||
"analysis runs": "分析运行及结果查询命令。",
|
||||
"analysis leakage": "漏损分析相关命令。",
|
||||
"analysis leakage schemes": "漏损方案查询命令。",
|
||||
"analysis burst-detection": "爆管检测相关命令。",
|
||||
"analysis burst-detection schemes": "爆管检测方案查询命令。",
|
||||
"analysis burst-location": "爆管定位相关命令。",
|
||||
"analysis burst-location schemes": "爆管定位方案查询命令。",
|
||||
"analysis risk": "风险分析相关命令。",
|
||||
"analysis sensor-placement": "传感器选址相关命令。",
|
||||
data: "时序、SCADA 和方案数据查询命令。",
|
||||
data: "时序、SCADA 和分析数据查询命令。",
|
||||
"data timeseries": "时序数据查询命令。",
|
||||
"data timeseries realtime": "实时模拟时序查询命令。",
|
||||
"data timeseries scheme": "方案时序查询命令。",
|
||||
"data timeseries analysis": "按分析运行 ID 查询历史时序命令。",
|
||||
"data timeseries scada": "SCADA 时序查询命令。",
|
||||
"data timeseries composite": "复合时序查询命令。",
|
||||
"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[]];
|
||||
|
||||
@@ -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 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"]],
|
||||
["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 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 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 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 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 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 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 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 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 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-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 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 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 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 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 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 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 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 scada get", "读取单条 SCADA 元数据", ["--kind <KIND>", "--id <ID>"], ["tjwater-cli data scada get --kind info --id SCADA-001"]],
|
||||
["data scada list", "列出 SCADA 元数据", ["--kind <KIND>"], ["tjwater-cli data scada list --kind info"]],
|
||||
["data scheme schema", "读取方案 schema", [], ["tjwater-cli data scheme schema"]],
|
||||
["data scheme get", "读取单条方案", ["--name <NAME>"], ["tjwater-cli data scheme get --name my_scheme"]],
|
||||
["data scheme list", "列出方案", [], ["tjwater-cli data scheme list"]],
|
||||
["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 pipeline-health", "查询指定时刻的管道健康预测", ["--time <TIME>"], ["tjwater-cli data pipeline-health --time 2025-01-02T04:00:00+08:00"]],
|
||||
["data scada get", "读取单条 SCADA 设备", ["--device-id <DEVICE_ID>"], ["tjwater-cli data scada get --device-id SCADA-001"]],
|
||||
["data scada list", "列出 SCADA 设备", ["[--page-size <PAGE_SIZE>]"], ["tjwater-cli data scada list"]],
|
||||
["data scada schema", "读取 SCADA 设备 schema", [], ["tjwater-cli data scada schema"]],
|
||||
];
|
||||
|
||||
export const commandDocs = new Map<string, CommandDoc>(
|
||||
@@ -107,7 +97,7 @@ function optionDoc(path: string, token: string): CommandOptionDoc {
|
||||
const clean = token.replace(/^\[/, "").replace(/\]$/, "");
|
||||
const name = clean.slice(2).split(/\s+/)[0]!;
|
||||
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"],
|
||||
"data timeseries scada query": ["device-id"],
|
||||
"data timeseries composite": ["feature"],
|
||||
|
||||
@@ -13,32 +13,32 @@ const visibleCommandPaths = [
|
||||
"analysis age",
|
||||
"analysis burst",
|
||||
"analysis burst-detection detect",
|
||||
"analysis burst-detection schemes get",
|
||||
"analysis burst-detection schemes list",
|
||||
"analysis burst-location locate",
|
||||
"analysis contaminant",
|
||||
"analysis flushing",
|
||||
"analysis leakage identify",
|
||||
"analysis leakage schemes get",
|
||||
"analysis leakage schemes list",
|
||||
"analysis sensor-placement kmeans",
|
||||
"analysis valve",
|
||||
"analysis runs get",
|
||||
"analysis runs list",
|
||||
"analysis runs results",
|
||||
"analysis sensor-placement get",
|
||||
"analysis sensor-placement list",
|
||||
"analysis sensor-placement run",
|
||||
"analysis valve isolation",
|
||||
"component option get",
|
||||
"component option schema",
|
||||
"data scada get",
|
||||
"data scada list",
|
||||
"data scheme get",
|
||||
"data scheme list",
|
||||
"data scheme schema",
|
||||
"data scada schema",
|
||||
"data pipeline-health",
|
||||
"data timeseries analysis link-field",
|
||||
"data timeseries analysis node-field",
|
||||
"data timeseries analysis values",
|
||||
"data timeseries composite",
|
||||
"data timeseries composite pipeline-health",
|
||||
"data timeseries realtime links",
|
||||
"data timeseries realtime nodes",
|
||||
"data timeseries realtime simulation-by-id-time",
|
||||
"data timeseries realtime simulation-by-time-property",
|
||||
"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-pumps-properties",
|
||||
"network get-all-reservoirs-properties",
|
||||
@@ -53,14 +53,7 @@ const visibleCommandPaths = [
|
||||
"simulation run",
|
||||
];
|
||||
|
||||
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",
|
||||
];
|
||||
const hiddenCommandPaths = [];
|
||||
|
||||
function runCommand(command, args, input, options = {}) {
|
||||
return new Promise((resolveRun, reject) => {
|
||||
@@ -144,7 +137,7 @@ function normalizeSeenRequest(request) {
|
||||
|
||||
function defaultContractResponse(req) {
|
||||
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 {
|
||||
items: [],
|
||||
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 [
|
||||
["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", "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);
|
||||
assert.equal(result.exitCode, 0, result.stderr);
|
||||
}
|
||||
const queries = server.seen.map(normalizeSeenRequest).map((request) => request.query.type);
|
||||
assert.deepEqual(queries, ["node", "link", "link"]);
|
||||
const requests = server.seen.map(normalizeSeenRequest);
|
||||
assert.deepEqual(
|
||||
[requests[0].query.type, requests[1].query.type, requests[2].query.element_type],
|
||||
["node", "link", "link"],
|
||||
);
|
||||
} finally {
|
||||
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 () => {
|
||||
const tempDir = await mkdtemp(join(tmpdir(), "tjwater-cli-parity-"));
|
||||
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 end = "2025-01-02T04:00:00+08:00";
|
||||
const at = "2025-01-02T03:30:00+08:00";
|
||||
const runId = "00000000-0000-0000-0000-000000000001";
|
||||
const cases = [
|
||||
["network get-junction-properties", ["network", "get-junction-properties", "--junction", "J1"]],
|
||||
["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"]],
|
||||
["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 valve close", ["analysis", "valve", "--mode", "close", "--start-time", start, "--valve", "V1", "--valve", "V2", "--duration", "900", "--scheme", "valve_case"]],
|
||||
["analysis valve isolation", ["analysis", "valve", "--mode", "isolation", "--element", "E1", "--disabled-valve", "V3"]],
|
||||
["analysis valve isolation", ["analysis", "valve", "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 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 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 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 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 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 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-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 scheme node-field", ["data", "timeseries", "scheme", "node-field", "--node", "J1", "--field", "pressure", "--start-time", start, "--end-time", end, "--scheme", "scheme_case"]],
|
||||
["data scheme simulation by-id", ["data", "timeseries", "scheme", "simulation", "--query", "by-id-time", "--id", "J1", "--time", at, "--type", "junction", "--scheme", "scheme_case"]],
|
||||
["data scheme simulation by-property", ["data", "timeseries", "scheme", "simulation", "--query", "by-scheme-time-property", "--time", at, "--type", "pipe", "--property", "flow", "--scheme", "scheme_case"]],
|
||||
["data analysis link-field", ["data", "timeseries", "analysis", "link-field", "--run-id", runId, "--link", "P1", "--field", "flow", "--start-time", start, "--end-time", end]],
|
||||
["data analysis node-field", ["data", "timeseries", "analysis", "node-field", "--run-id", runId, "--node", "J1", "--field", "pressure", "--start-time", start, "--end-time", end]],
|
||||
["data analysis values", ["data", "timeseries", "analysis", "values", "--run-id", runId, "--type", "pipe", "--time", at, "--field", "flow"]],
|
||||
["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-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 scada get", ["data", "scada", "get", "--kind", "info", "--id", "SCADA-001"]],
|
||||
["data scada list", ["data", "scada", "list", "--kind", "info"]],
|
||||
["data scheme schema", ["data", "scheme", "schema"]],
|
||||
["data scheme get", ["data", "scheme", "get", "--name", "scheme_case"]],
|
||||
["data scheme list", ["data", "scheme", "list"]],
|
||||
["data pipeline-health", ["data", "pipeline-health", "--time", end]],
|
||||
["data scada get", ["data", "scada", "get", "--device-id", "SCADA-001"]],
|
||||
["data scada list", ["data", "scada", "list"]],
|
||||
["data scada schema", ["data", "scada", "schema"]],
|
||||
];
|
||||
|
||||
for (const [name, args] of cases) {
|
||||
|
||||
Reference in New Issue
Block a user