Files
TJWaterServerBinary/cli/tjwater_cli/registry.py
T

453 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from .core import CommandDoc, CommandOptionDoc, SCHEMA_VERSION
GROUP_SUMMARIES: dict[tuple[str, ...], str] = {
("project",): "项目与项目级元数据相关命令。",
("network",): "管网节点、管线等基础属性查询命令。",
("component",): "组件选项与配置读取命令。",
("component", "option"): "组件选项查询命令。",
("simulation",): "模拟运行与调度相关命令。",
("analysis",): "分析计算与诊断相关命令。",
("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", "timeseries"): "时序数据查询命令。",
("data", "timeseries", "realtime"): "实时模拟时序查询命令。",
("data", "timeseries", "scheme"): "方案时序查询命令。",
("data", "timeseries", "scada"): "SCADA 时序查询命令。",
("data", "timeseries", "composite"): "复合时序查询命令。",
("data", "scada"): "SCADA 元数据查询命令。",
("data", "scheme"): "方案数据查询命令。",
("data", "extension"): "扩展数据查询命令。",
("data", "misc"): "其他结果数据查询命令。",
}
HIDDEN_PATH_PREFIXES: tuple[tuple[str, ...], ...] = (
("analysis", "burst-location"),
("analysis", "risk"),
)
COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("project", "list"): CommandDoc(
path=("project", "list"),
summary="列出当前用户可访问项目",
description="调用 /meta/projects 返回项目列表。",
examples=("tjwater-cli project list",),
next_commands=("tjwater-cli project info",),
),
("project", "info"): CommandDoc(
path=("project", "info"),
summary="查看当前项目摘要信息。",
description="查看当前项目的基础信息。",
examples=("tjwater-cli project info",),
output="项目元数据",
),
("project", "db-health"): CommandDoc(
path=("project", "db-health"),
summary="检查当前项目数据库健康状态",
description="调用 /meta/db/health 返回 PostgreSQL 与 Timescale 健康状态。",
),
("project", "export-inp"): CommandDoc(
path=("project", "export-inp"),
summary="导出当前项目 INP 到本地文件",
description="先调用 /dumpinp/ 在服务端生成 INP,再通过 /downloadinp/ 下载到本地。",
options=(
CommandOptionDoc("output", "本地输出路径", required=True),
),
output="本地文件路径和下载摘要",
),
("project", "data"): CommandDoc(
path=("project", "data"),
summary="读取当前项目业务数据",
description="kind 支持 scada-info、scheme-list、burst-locate-result。",
options=(CommandOptionDoc("kind", "数据类型", required=True),),
),
("network", "get-node-properties"): CommandDoc(
path=("network", "get-node-properties"),
summary="读取节点属性",
description="调用 /getnodeproperties/。",
options=(CommandOptionDoc("node", "节点 ID", required=True),),
),
("network", "get-link-properties"): CommandDoc(
path=("network", "get-link-properties"),
summary="读取管线属性",
description="调用 /getlinkproperties/。",
options=(CommandOptionDoc("link", "管线 ID", required=True),),
),
("component", "option", "schema"): CommandDoc(
path=("component", "option", "schema"),
summary="读取选项 schema",
description="kind 支持 time、energy、pump-energy、network。",
options=(
CommandOptionDoc("kind", "选项类型", required=True),
CommandOptionDoc("pump", "pump-energy 时需要的泵 ID"),
),
),
("component", "option", "get"): CommandDoc(
path=("component", "option", "get"),
summary="读取选项属性",
description="kind 支持 time、energy、pump-energy、network。",
options=(
CommandOptionDoc("kind", "选项类型", required=True),
CommandOptionDoc("pump", "pump-energy 时需要的泵 ID"),
),
),
("simulation", "run"): CommandDoc(
path=("simulation", "run"),
summary="触发指定绝对时间的模拟运行",
description="把 RFC3339 start-time 拆成 simulation_date 与 start_time 后调用 /runsimulationmanuallybydate/;接口本身只负责触发运行,结果需后续通过 data timeseries 在对应时间段查询。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("duration", "持续分钟数", required=True),
),
next_commands=(
"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",
),
output="模拟触发结果;实时数据需通过 data timeseries 命令按时间段查询",
),
("analysis", "burst"): CommandDoc(
path=("analysis", "burst"),
summary="执行爆管分析",
description="读取 burst-file 并转换为 burst_ID[] / burst_size[];接口本身只返回分析执行结果,方案数据需后续通过 data scheme 命令获取。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("duration", "持续秒数", required=True),
CommandOptionDoc("burst-file", "爆管输入 JSON 文件", required=True),
CommandOptionDoc("scheme", "方案名称"),
),
examples=(
"tjwater-cli analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 30 --burst-file ./burst.json --scheme burst_case_01",
"tjwater-cli data scheme get --name burst_case_01",
"tjwater-cli data scheme list",
),
),
("analysis", "valve"): CommandDoc(
path=("analysis", "valve"),
summary="阀门工况分析。",
description="指定阀门采取关闭/开启等操作逻辑,并执行定时长模拟。结果写入时序库。",
options=(
CommandOptionDoc(name="mode", description="阀门操作模式:'close''open'", required=True),
CommandOptionDoc(name="start-time", description="起始绝对时间,必须显式带时区偏移", required=True),
CommandOptionDoc(name="valve", description="阀门 ID(可多次指定)", required=True, repeated=True),
CommandOptionDoc(name="duration", description="模拟持续分钟数", required=True),
),
examples=(
"tjwater-cli analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --duration 900",
),
),
("analysis", "flushing"): CommandDoc(
path=("analysis", "flushing"),
summary="执行冲洗分析",
description="读取 valve-setting-file 并转换为 valves[] / valves_k[]。",
),
("analysis", "age"): CommandDoc(
path=("analysis", "age"),
summary="执行水龄分析",
description="调用 /age_analysis/。",
),
("analysis", "contaminant"): CommandDoc(
path=("analysis", "contaminant"),
summary="执行污染物模拟",
description="调用 /contaminant_simulation/。",
),
("analysis", "sensor-placement", "kmeans"): CommandDoc(
path=("analysis", "sensor-placement", "kmeans"),
summary="执行 KMeans 传感器选址",
description="使用 POST /pressure_sensor_placement_kmeans/,补齐 username 和 min_diameter。",
),
("analysis", "leakage", "identify"): CommandDoc(
path=("analysis", "leakage", "identify"),
summary="执行漏损识别",
description="把 CLI 时间映射到 scada_start / scada_end。",
),
("analysis", "leakage", "schemes", "list"): CommandDoc(
path=("analysis", "leakage", "schemes", "list"),
summary="列出漏损方案",
description="调用 /leakage/schemes/。",
),
("analysis", "leakage", "schemes", "get"): CommandDoc(
path=("analysis", "leakage", "schemes", "get"),
summary="读取漏损方案详情",
description="调用 /leakage/schemes/{scheme_name}",
),
("analysis", "burst-detection", "detect"): CommandDoc(
path=("analysis", "burst-detection", "detect"),
summary="执行爆管检测",
description="调用 /burst-detection/detect/。",
),
("analysis", "burst-detection", "schemes", "list"): CommandDoc(
path=("analysis", "burst-detection", "schemes", "list"),
summary="列出爆管检测方案",
description="调用 /burst-detection/schemes/。",
),
("analysis", "burst-detection", "schemes", "get"): CommandDoc(
path=("analysis", "burst-detection", "schemes", "get"),
summary="读取爆管检测方案详情",
description="调用 /burst-detection/schemes/{scheme_name}",
),
("analysis", "burst-location", "locate"): CommandDoc(
path=("analysis", "burst-location", "locate"),
summary="执行爆管定位",
description="调用 /burst-location/locate/;需要 burst-leakage。",
),
("analysis", "burst-location", "schemes", "list"): CommandDoc(
path=("analysis", "burst-location", "schemes", "list"),
summary="列出爆管定位方案",
description="调用 /burst-location/schemes/。",
),
("analysis", "burst-location", "schemes", "get"): CommandDoc(
path=("analysis", "burst-location", "schemes", "get"),
summary="读取爆管定位方案详情",
description="调用 /burst-location/schemes/{scheme_name}",
),
("analysis", "risk", "pipe-now"): CommandDoc(
path=("analysis", "risk", "pipe-now"),
summary="读取单条管道当前风险",
description="调用 /getpiperiskprobabilitynow/。",
),
("analysis", "risk", "pipe-history"): CommandDoc(
path=("analysis", "risk", "pipe-history"),
summary="读取单条管道历史风险",
description="调用 /getpiperiskprobability/。",
),
("analysis", "risk", "network"): CommandDoc(
path=("analysis", "risk", "network"),
summary="读取全网风险",
description="组合 /getnetworkpiperiskprobabilitynow/ 与 /getpiperiskprobabilitygeometries/。",
),
("data", "timeseries", "realtime", "links"): CommandDoc(
path=("data", "timeseries", "realtime", "links"),
summary="查询实时管道时序",
description="调用 /realtime/links。",
),
("data", "timeseries", "realtime", "nodes"): CommandDoc(
path=("data", "timeseries", "realtime", "nodes"),
summary="查询实时节点时序",
description="调用 /realtime/nodes。",
),
("data", "timeseries", "realtime", "simulation-by-id-time"): CommandDoc(
path=("data", "timeseries", "realtime", "simulation-by-id-time"),
summary="按元素和时间查询实时模拟结果",
description="调用 /realtime/query/by-id-time。",
),
("data", "timeseries", "realtime", "simulation-by-time-property"): CommandDoc(
path=("data", "timeseries", "realtime", "simulation-by-time-property"),
summary="按时间和属性查询实时模拟结果",
description="调用 /realtime/query/by-time-property。",
),
("data", "timeseries", "scheme", "links"): CommandDoc(
path=("data", "timeseries", "scheme", "links"),
summary="查询方案管道时序",
description="调用 /scheme/links。",
),
("data", "timeseries", "scheme", "node-field"): CommandDoc(
path=("data", "timeseries", "scheme", "node-field"),
summary="查询方案节点字段时序",
description="调用 /scheme/nodes/{node_id}/field。",
),
("data", "timeseries", "scheme", "simulation"): CommandDoc(
path=("data", "timeseries", "scheme", "simulation"),
summary="查询方案模拟数据",
description="支持 by-id-time 与 by-scheme-time-property 两种查询。",
),
("data", "timeseries", "scada", "query"): CommandDoc(
path=("data", "timeseries", "scada", "query"),
summary="查询 SCADA 时序",
description="device-id 会被转换成后端逗号分隔参数。",
),
("data", "timeseries", "composite"): CommandDoc(
path=("data", "timeseries", "composite"),
summary="执行复合时序查询",
description="kind 支持 scada-simulation、element-simulation、element-scada。",
),
("data", "timeseries", "composite", "pipeline-health"): CommandDoc(
path=("data", "timeseries", "composite", "pipeline-health"),
summary="查询管道健康预测",
description="调用 /composite/pipeline-health-prediction。",
),
("data", "scada", "schema"): CommandDoc(
path=("data", "scada", "schema"),
summary="读取 SCADA schema",
description="kind 支持 device、device-data、element、info。",
),
("data", "scada", "get"): CommandDoc(
path=("data", "scada", "get"),
summary="读取单条 SCADA 元数据",
description="kind 支持 device、device-data、element、info。",
),
("data", "scada", "list"): CommandDoc(
path=("data", "scada", "list"),
summary="列出 SCADA 元数据",
description="kind 支持 device、element、infodevice-data 当前后端无 list 接口。",
),
("data", "scheme", "schema"): CommandDoc(
path=("data", "scheme", "schema"),
summary="读取方案 schema",
description="调用 /getschemeschema/。",
),
("data", "scheme", "get"): CommandDoc(
path=("data", "scheme", "get"),
summary="读取单条方案",
description="调用 /getscheme/。",
),
("data", "scheme", "list"): CommandDoc(
path=("data", "scheme", "list"),
summary="列出方案",
description="调用 /getallschemes/。",
),
("data", "extension", "keys"): CommandDoc(
path=("data", "extension", "keys"),
summary="列出扩展数据键",
description="调用 /getallextensiondatakeys/。",
),
("data", "extension", "get"): CommandDoc(
path=("data", "extension", "get"),
summary="读取扩展数据",
description="调用 /getextensiondata/。",
),
("data", "extension", "list"): CommandDoc(
path=("data", "extension", "list"),
summary="列出扩展数据",
description="调用 /getallextensiondata/。",
),
("data", "misc", "sensor-placements"): CommandDoc(
path=("data", "misc", "sensor-placements"),
summary="列出传感器布置结果",
description="调用 /getallsensorplacements/。",
),
("data", "misc", "burst-location-results"): CommandDoc(
path=("data", "misc", "burst-location-results"),
summary="列出爆管定位结果",
description="调用 /getallburstlocateresults/。",
),
}
def _build_examples(doc: CommandDoc) -> list[str]:
return list(doc.examples) if doc.examples else [_build_usage(doc)]
def _is_hidden_path(path: tuple[str, ...]) -> bool:
return any(path[: len(prefix)] == prefix for prefix in HIDDEN_PATH_PREFIXES)
def is_hidden_path(path: tuple[str, ...]) -> bool:
return _is_hidden_path(path)
def has_subcommands(path_prefix: tuple[str, ...]) -> bool:
return any(
not _is_hidden_path(doc.path)
and doc.path[: len(path_prefix)] == path_prefix
and len(doc.path) > len(path_prefix)
for doc in COMMAND_DOCS.values()
)
def get_group_summary(path_prefix: tuple[str, ...]) -> str:
return GROUP_SUMMARIES.get(path_prefix, f"{' '.join(path_prefix)} 可用子命令")
def list_capabilities() -> dict[str, object]:
seen: set[tuple[str, ...]] = set()
commands: list[dict[str, str]] = []
for doc in sorted(COMMAND_DOCS.values(), key=lambda item: item.path):
if _is_hidden_path(doc.path):
continue
prefix = doc.path[:1]
if prefix in seen:
continue
seen.add(prefix)
commands.append(
{
"command": " ".join(prefix),
"summary": get_group_summary(prefix),
}
)
return {
"ok": True,
"schema_version": SCHEMA_VERSION,
"summary": "可用一级菜单",
"menu_level": 1,
"commands": commands,
}
def get_command_doc(path: tuple[str, ...]) -> dict[str, object] | None:
if _is_hidden_path(path):
return None
doc = COMMAND_DOCS.get(path)
if doc is None:
return None
return {
"ok": True,
"schema_version": SCHEMA_VERSION,
"summary": doc.summary,
"command": " ".join(doc.path),
"description": doc.description,
"usage": _build_usage(doc),
"options": [
{
"name": option.name,
"description": option.description,
"required": option.required,
"repeated": option.repeated,
"default": option.default,
}
for option in doc.options
],
"examples": _build_examples(doc),
"next_commands": list(doc.next_commands),
"output": doc.output,
}
def list_subcommands(path_prefix: tuple[str, ...], summary: str | None = None) -> dict[str, object]:
seen: set[str] = set()
commands: list[dict[str, str]] = []
for doc in sorted(COMMAND_DOCS.values(), key=lambda item: item.path):
if _is_hidden_path(doc.path):
continue
if doc.path[: len(path_prefix)] != path_prefix or len(doc.path) <= len(path_prefix):
continue
subcommand = doc.path[len(path_prefix)]
if subcommand in seen:
continue
seen.add(subcommand)
current_path = (*path_prefix, subcommand)
is_group = has_subcommands(current_path)
usage = f"tjwater-cli {' '.join(current_path)} help" if is_group else (doc.examples[0] if doc.examples else _build_usage(doc))
commands.append(
{
"command": " ".join(current_path),
"summary": get_group_summary(current_path) if is_group else doc.summary,
"usage": usage,
"example": f"tjwater-cli {' '.join(current_path)} help" if is_group else _build_examples(doc)[0],
}
)
return {
"ok": True,
"schema_version": SCHEMA_VERSION,
"summary": summary or get_group_summary(path_prefix),
"commands": commands,
}
def _build_usage(doc: CommandDoc) -> str:
parts = ["tjwater-cli", *doc.path]
for option in doc.options:
placeholder = option.name.upper().replace("-", "_")
if option.required:
parts.extend([f"--{option.name}", f"<{placeholder}>"])
else:
parts.append(f"[--{option.name} <{placeholder}>]")
return " ".join(parts)