feat(api): standardize REST contracts and auth

This commit is contained in:
2026-07-30 20:38:51 +08:00
parent ae1a657554
commit ba947b616b
86 changed files with 54193 additions and 990 deletions
+24 -54
View File
@@ -32,24 +32,18 @@ def test_load_auth_context_supports_aliases(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_PROJECT_ID", "p1")
monkeypatch.setenv("TJWATER_USERNAME", "tester")
monkeypatch.setenv("TJWATER_NETWORK", "net1")
auth = core.load_auth_context(auth_stdin=False)
assert auth.server == "http://server"
assert auth.access_token == "abc"
assert auth.project_id == "p1"
assert auth.username == "tester"
assert auth.network == "net1"
def test_build_runtime_context_uses_default_server(monkeypatch):
monkeypatch.delenv("TJWATER_SERVER", raising=False)
monkeypatch.delenv("TJWATER_ACCESS_TOKEN", raising=False)
monkeypatch.delenv("TJWATER_PROJECT_ID", raising=False)
monkeypatch.delenv("TJWATER_USERNAME", raising=False)
monkeypatch.delenv("TJWATER_NETWORK", raising=False)
monkeypatch.delenv("TJWATER_EXTRA_HEADERS", raising=False)
runtime = core.build_runtime_context(
@@ -68,7 +62,7 @@ def test_auth_stdin_can_be_reused_with_runtime_context_cache(monkeypatch):
def fake_request_json(ctx, **kwargs):
observed_runtime_ids.append(id(ctx))
assert ctx.auth.access_token == "token-1"
assert kwargs["params"] == {"network": "tjwater", "junction": "11"}
assert kwargs["params"] == {"junction": "11"}
return {"id": "11"}, 5
monkeypatch.setattr(common, "request_json", fake_request_json)
@@ -81,7 +75,6 @@ def test_auth_stdin_can_be_reused_with_runtime_context_cache(monkeypatch):
"server": "http://server",
"access_token": "token-1",
"project_id": "project-1",
"network": "tjwater",
}
),
)
@@ -105,7 +98,6 @@ def test_network_get_junction_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-junction-properties", "--junction", "J1"])
@@ -116,8 +108,8 @@ def test_network_get_junction_properties_uses_network_context(monkeypatch):
assert payload["data"] == {"id": "J1"}
assert captured == {
"access_token": "abc",
"path": "/getjunctionproperties/",
"params": {"network": "tjwater", "junction": "J1"},
"path": "/junctions/properties",
"params": {"junction": "J1"},
}
@@ -132,7 +124,6 @@ def test_network_get_pipe_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-pipe-properties", "--pipe", "P1"])
@@ -143,8 +134,8 @@ def test_network_get_pipe_properties_uses_network_context(monkeypatch):
assert payload["data"] == {"id": "P1"}
assert captured == {
"access_token": "abc",
"path": "/getpipeproperties/",
"params": {"network": "tjwater", "pipe": "P1"},
"path": "/pipes/properties",
"params": {"pipe": "P1"},
}
@@ -159,7 +150,6 @@ def test_network_get_all_pipes_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-all-pipes-properties"])
@@ -170,8 +160,8 @@ def test_network_get_all_pipes_properties_uses_network_context(monkeypatch):
assert payload["data"] == [{"id": "P1"}]
assert captured == {
"access_token": "abc",
"path": "/getallpipeproperties/",
"params": {"network": "tjwater"},
"path": "/pipes",
"params": {},
}
@@ -186,7 +176,6 @@ def test_network_get_reservoir_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-reservoir-properties", "--reservoir", "R1"])
@@ -197,8 +186,8 @@ def test_network_get_reservoir_properties_uses_network_context(monkeypatch):
assert payload["data"] == {"id": "R1"}
assert captured == {
"access_token": "abc",
"path": "/getreservoirproperties/",
"params": {"network": "tjwater", "reservoir": "R1"},
"path": "/reservoirs/properties",
"params": {"reservoir": "R1"},
}
@@ -213,7 +202,6 @@ def test_network_get_all_reservoir_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-all-reservoirs-properties"])
@@ -224,8 +212,8 @@ def test_network_get_all_reservoir_properties_uses_network_context(monkeypatch):
assert payload["data"] == [{"id": "R1"}]
assert captured == {
"access_token": "abc",
"path": "/getallreservoirproperties/",
"params": {"network": "tjwater"},
"path": "/reservoirs",
"params": {},
}
@@ -240,7 +228,6 @@ def test_network_get_tank_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-tank-properties", "--tank", "T1"])
@@ -251,8 +238,8 @@ def test_network_get_tank_properties_uses_network_context(monkeypatch):
assert payload["data"] == {"id": "T1"}
assert captured == {
"access_token": "abc",
"path": "/gettankproperties/",
"params": {"network": "tjwater", "tank": "T1"},
"path": "/tanks/properties",
"params": {"tank": "T1"},
}
@@ -267,7 +254,6 @@ def test_network_get_all_tank_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-all-tanks-properties"])
@@ -278,8 +264,8 @@ def test_network_get_all_tank_properties_uses_network_context(monkeypatch):
assert payload["data"] == [{"id": "T1"}]
assert captured == {
"access_token": "abc",
"path": "/getalltankproperties/",
"params": {"network": "tjwater"},
"path": "/tanks",
"params": {},
}
@@ -294,7 +280,6 @@ def test_network_get_pump_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-pump-properties", "--pump", "PU1"])
@@ -305,8 +290,8 @@ def test_network_get_pump_properties_uses_network_context(monkeypatch):
assert payload["data"] == {"id": "PU1"}
assert captured == {
"access_token": "abc",
"path": "/getpumpproperties/",
"params": {"network": "tjwater", "pump": "PU1"},
"path": "/pumps/properties",
"params": {"pump": "PU1"},
}
@@ -321,7 +306,6 @@ def test_network_get_all_pump_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-all-pumps-properties"])
@@ -332,8 +316,8 @@ def test_network_get_all_pump_properties_uses_network_context(monkeypatch):
assert payload["data"] == [{"id": "PU1"}]
assert captured == {
"access_token": "abc",
"path": "/getallpumpproperties/",
"params": {"network": "tjwater"},
"path": "/pumps",
"params": {},
}
@@ -348,7 +332,6 @@ def test_network_get_valve_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-valve-properties", "--valve", "V1"])
@@ -359,8 +342,8 @@ def test_network_get_valve_properties_uses_network_context(monkeypatch):
assert payload["data"] == {"id": "V1"}
assert captured == {
"access_token": "abc",
"path": "/getvalveproperties/",
"params": {"network": "tjwater", "valve": "V1"},
"path": "/valves/properties",
"params": {"valve": "V1"},
}
@@ -375,7 +358,6 @@ def test_network_get_all_valve_properties_uses_network_context(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "tjwater")
monkeypatch.setattr(common, "request_json", fake_request_json)
result = runner.invoke(app, ["network", "get-all-valves-properties"])
@@ -386,8 +368,8 @@ def test_network_get_all_valve_properties_uses_network_context(monkeypatch):
assert payload["data"] == [{"id": "V1"}]
assert captured == {
"access_token": "abc",
"path": "/getallvalveproperties/",
"params": {"network": "tjwater"},
"path": "/valves",
"params": {},
}
@@ -525,7 +507,6 @@ def test_realtime_property_help_lists_supported_fields():
def test_analysis_burst_returns_next_step_to_fetch_scheme(monkeypatch, tmp_path: Path):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
burst_path = tmp_path / "burst.json"
burst_path.write_text('[{"id":"P1","size":3.5}]', encoding="utf-8")
@@ -559,7 +540,6 @@ def test_analysis_burst_returns_next_step_to_fetch_scheme(monkeypatch, tmp_path:
def test_analysis_contaminant_sends_required_scheme_name(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
captured = {}
def fake_request(**kwargs):
@@ -588,7 +568,6 @@ def test_analysis_contaminant_sends_required_scheme_name(monkeypatch):
assert result.exit_code == 0
assert captured["params"] == {
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"source": "N1",
"concentration": 10.0,
@@ -600,7 +579,6 @@ def test_analysis_contaminant_sends_required_scheme_name(monkeypatch):
def test_analysis_flushing_sends_required_scheme_name(monkeypatch, tmp_path: Path):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
captured = {}
valve_path = tmp_path / "valve.json"
valve_path.write_text('[{"valve":"V1","opening":0.5}]', encoding="utf-8")
@@ -633,11 +611,10 @@ def test_analysis_flushing_sends_required_scheme_name(monkeypatch, tmp_path: Pat
assert result.exit_code == 0
assert captured["params"] == {
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"valves": ["V1"],
"valves_k": [0.5],
"drainage_node_ID": "N1",
"drainage_node_id": "N1",
"flush_flow": 100.0,
"duration": 900,
"scheme_name": "flush_case_01",
@@ -647,7 +624,6 @@ def test_analysis_flushing_sends_required_scheme_name(monkeypatch, tmp_path: Pat
def test_analysis_valve_close_sends_required_scheme_name(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
captured = {}
def fake_request(**kwargs):
@@ -676,7 +652,6 @@ def test_analysis_valve_close_sends_required_scheme_name(monkeypatch):
assert result.exit_code == 0
assert captured["params"] == {
"network": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"valves": ["V1"],
"duration": 900,
@@ -687,7 +662,6 @@ def test_analysis_valve_close_sends_required_scheme_name(monkeypatch):
def test_analysis_contaminant_requires_scheme(monkeypatch, capsys):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
exit_code = main(
[
@@ -713,7 +687,6 @@ def test_analysis_contaminant_requires_scheme(monkeypatch, capsys):
def test_analysis_flushing_requires_scheme(monkeypatch, tmp_path: Path, capsys):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
valve_path = tmp_path / "valve.json"
valve_path.write_text('[{"valve":"V1","opening":0.5}]', encoding="utf-8")
@@ -741,7 +714,6 @@ def test_analysis_flushing_requires_scheme(monkeypatch, tmp_path: Path, capsys):
def test_analysis_valve_close_requires_scheme(monkeypatch, capsys):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
exit_code = main(
[
@@ -921,7 +893,6 @@ def test_main_bare_analysis_returns_typer_help_without_json_error(capsys):
def test_simulation_run_translates_rfc3339(monkeypatch):
monkeypatch.setenv("TJWATER_SERVER", "http://server")
monkeypatch.setenv("TJWATER_ACCESS_TOKEN", "abc")
monkeypatch.setenv("TJWATER_NETWORK", "demo")
captured = {}
def fake_request(**kwargs):
@@ -944,7 +915,6 @@ def test_simulation_run_translates_rfc3339(monkeypatch):
assert result.exit_code == 0
assert captured["json"] == {
"name": "demo",
"start_time": "2025-01-02T03:04:05+08:00",
"duration": 30,
}
+26 -72
View File
@@ -27,8 +27,6 @@ from .core import (
parse_time_with_timezone,
parse_valve_setting_file,
request_json,
require_network,
require_username,
resolve_scheme,
)
from .option_types import DataSource, ValveMode
@@ -41,11 +39,9 @@ def simulation_run(
duration: Annotated[int, typer.Option("--duration", help="持续分钟数")],
) -> None:
runtime = runtime_context(ctx)
network = require_network(runtime)
parsed = parse_time_with_timezone(start_time, option_name="--start-time")
end_time = (parsed + timedelta(minutes=duration)).isoformat()
body = {
"name": network,
"start_time": parsed.replace(microsecond=0).isoformat(),
"duration": duration,
}
@@ -53,10 +49,9 @@ def simulation_run(
ctx,
summary="触发模拟成功",
method="POST",
path="/simulations/run-by-date",
path="/simulation-runs",
json_body=body,
require_auth=True,
require_network_ctx=True,
next_commands=[
f"tjwater-cli data timeseries realtime links --start-time {parsed.isoformat()} --end-time {end_time}",
f"tjwater-cli data timeseries realtime nodes --start-time {parsed.isoformat()} --end-time {end_time}",
@@ -76,9 +71,8 @@ def analysis_burst(
ids, sizes = parse_burst_file(burst_file)
scheme_name = resolve_scheme(runtime, scheme, required=True)
params = {
"network": require_network(runtime),
"modify_pattern_start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"burst_ID": ids,
"burst_id": ids,
"burst_size": sizes,
"modify_total_duration": duration,
"scheme_name": scheme_name,
@@ -86,11 +80,10 @@ def analysis_burst(
emit_api(
ctx,
summary="爆管分析执行成功",
method="GET",
path="/burst-analysis",
method="POST",
path="/burst-analyses",
params=params,
require_auth=True,
require_network_ctx=True,
next_commands=[
f"tjwater-cli data scheme get --name {scheme_name}",
"tjwater-cli data scheme list",
@@ -110,7 +103,6 @@ def analysis_valve(
scheme: Annotated[str | None, typer.Option("--scheme", help="close 模式的方案名称")] = None,
) -> None:
runtime = runtime_context(ctx)
network = require_network(runtime)
if mode == ValveMode.CLOSE:
if not start_time or not valve:
raise CLIError(
@@ -120,7 +112,6 @@ def analysis_valve(
exit_code=2,
)
params = {
"network": network,
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"valves": valve,
"duration": duration or 900,
@@ -129,11 +120,10 @@ def analysis_valve(
emit_api(
ctx,
summary="阀门关闭分析执行成功",
method="GET",
path="/valve_close_analysis/",
method="POST",
path="/valve-isolation-analyses",
params=params,
require_auth=True,
require_network_ctx=True,
)
return
if mode == ValveMode.ISOLATION:
@@ -144,17 +134,16 @@ def analysis_valve(
message="isolation mode requires at least one --element",
exit_code=2,
)
params = {"network": network, "accident_element": element}
params = {"accident_element": element}
if disabled_valve:
params["disabled_valves"] = disabled_valve
emit_api(
ctx,
summary="阀门隔离分析执行成功",
method="GET",
path="/valve-isolation-analysis",
method="POST",
path="/valve-isolation-analyses",
params=params,
require_auth=True,
require_network_ctx=True,
)
return
raise AssertionError(f"unreachable valve mode: {mode}")
@@ -173,11 +162,10 @@ def analysis_flushing(
runtime = runtime_context(ctx)
valves, openings = parse_valve_setting_file(valve_setting_file)
params = {
"network": require_network(runtime),
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"valves": valves,
"valves_k": openings,
"drainage_node_ID": drainage_node,
"drainage_node_id": drainage_node,
"flush_flow": flow,
"duration": duration or 900,
"scheme_name": resolve_scheme(runtime, scheme, required=True),
@@ -185,11 +173,10 @@ def analysis_flushing(
emit_api(
ctx,
summary="冲洗分析执行成功",
method="GET",
path="/flushing-analysis",
method="POST",
path="/flushing-analyses",
params=params,
require_auth=True,
require_network_ctx=True,
)
@@ -203,15 +190,13 @@ def analysis_age(
emit_api(
ctx,
summary="水龄分析执行成功",
method="GET",
path="/age_analysis/",
method="POST",
path="/water-age-analyses",
params={
"network": require_network(runtime),
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"duration": duration,
},
require_auth=True,
require_network_ctx=True,
)
@@ -227,7 +212,6 @@ def analysis_contaminant(
) -> None:
runtime = runtime_context(ctx)
params = {
"network": require_network(runtime),
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"source": source_node,
"concentration": concentration,
@@ -239,11 +223,10 @@ def analysis_contaminant(
emit_api(
ctx,
summary="污染物模拟执行成功",
method="GET",
path="/contaminant-simulation",
method="POST",
path="/contaminant-simulations",
params=params,
require_auth=True,
require_network_ctx=True,
)
@@ -256,21 +239,17 @@ def analysis_sensor_placement_kmeans(
) -> None:
runtime = runtime_context(ctx)
body = {
"name": require_network(runtime),
"scheme_name": resolve_scheme(runtime, scheme, required=True),
"sensor_number": count,
"min_diameter": min_diameter,
"username": require_username(runtime),
}
emit_api(
ctx,
summary="传感器选址执行成功",
method="POST",
path="/pressure_sensor_placement_kmeans/",
path="/pressure-sensor-placement-kmeans",
json_body=body,
require_auth=True,
require_network_ctx=True,
require_username_ctx=True,
)
@@ -283,7 +262,6 @@ def analysis_leakage_identify(
) -> None:
runtime = runtime_context(ctx)
body = {
"network": require_network(runtime),
"scada_start": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"scada_end": parse_time_with_timezone(end_time, option_name="--end-time").isoformat(),
"scheme_name": resolve_scheme(runtime, scheme, required=True),
@@ -292,10 +270,9 @@ def analysis_leakage_identify(
ctx,
summary="漏损识别执行成功",
method="POST",
path="/leakage/identify/",
path="/leakage-identifications",
json_body=body,
require_auth=True,
require_network_ctx=True,
)
@@ -308,11 +285,9 @@ def analysis_leakage_schemes_list(ctx: typer.Context) -> None:
method="GET",
path="/schemes",
params={
"network": require_network(runtime),
"scheme_type": "dma_leak_identification",
},
require_auth=True,
require_network_ctx=True,
)
@@ -328,11 +303,9 @@ def analysis_leakage_schemes_get(
method="GET",
path=f"/schemes/{scheme_name}",
params={
"network": require_network(runtime),
"scheme_type": "dma_leak_identification",
},
require_auth=True,
require_network_ctx=True,
)
@@ -345,7 +318,6 @@ def analysis_burst_detection_detect(
) -> None:
runtime = runtime_context(ctx)
body = {
"network": require_network(runtime),
"scada_start": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"scada_end": parse_time_with_timezone(end_time, option_name="--end-time").isoformat(),
"scheme_name": resolve_scheme(runtime, scheme, required=True),
@@ -354,10 +326,9 @@ def analysis_burst_detection_detect(
ctx,
summary="爆管检测执行成功",
method="POST",
path="/burst-detection/detect/",
path="/burst-detections",
json_body=body,
require_auth=True,
require_network_ctx=True,
)
@@ -370,11 +341,9 @@ def analysis_burst_detection_schemes_list(ctx: typer.Context) -> None:
method="GET",
path="/schemes",
params={
"network": require_network(runtime),
"scheme_type": "burst_detection",
},
require_auth=True,
require_network_ctx=True,
)
@@ -390,11 +359,9 @@ def analysis_burst_detection_schemes_get(
method="GET",
path=f"/schemes/{scheme_name}",
params={
"network": require_network(runtime),
"scheme_type": "burst_detection",
},
require_auth=True,
require_network_ctx=True,
)
@@ -416,7 +383,6 @@ def analysis_burst_location_locate(
pressure_payload = parse_optional_dataset_file(pressure_file, label="pressure") or {}
flow_payload = parse_optional_dataset_file(flow_file, label="flow") or {}
body = {
"network": require_network(runtime),
"scheme_name": resolve_scheme(runtime, scheme, required=True),
"data_source": data_source.value,
"scada_burst_start": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
@@ -436,10 +402,9 @@ def analysis_burst_location_locate(
ctx,
summary="爆管定位执行成功",
method="POST",
path="/burst-location/locate/",
path="/burst-locations",
json_body=body,
require_auth=True,
require_network_ctx=True,
)
@@ -452,11 +417,9 @@ def analysis_burst_location_schemes_list(ctx: typer.Context) -> None:
method="GET",
path="/schemes",
params={
"network": require_network(runtime),
"scheme_type": "burst_location",
},
require_auth=True,
require_network_ctx=True,
)
@@ -472,11 +435,9 @@ def analysis_burst_location_schemes_get(
method="GET",
path=f"/schemes/{scheme_name}",
params={
"network": require_network(runtime),
"scheme_type": "burst_location",
},
require_auth=True,
require_network_ctx=True,
)
@@ -490,10 +451,9 @@ def analysis_risk_pipe_now(
ctx,
summary="读取当前管道风险成功",
method="GET",
path="/getpiperiskprobabilitynow/",
params={"network": require_network(runtime), "pipe_id": pipe},
path="/pipes/risk-probability-now",
params={"pipe_id": pipe},
require_auth=True,
require_network_ctx=True,
)
@@ -507,32 +467,26 @@ def analysis_risk_pipe_history(
ctx,
summary="读取历史管道风险成功",
method="GET",
path="/getpiperiskprobability/",
params={"network": require_network(runtime), "pipe_id": pipe},
path="/pipes/risk-probability",
params={"pipe_id": pipe},
require_auth=True,
require_network_ctx=True,
)
@analysis_risk_app.command("network")
def analysis_risk_network(ctx: typer.Context) -> None:
runtime = runtime_context(ctx)
network = require_network(runtime)
probabilities, duration_prob = request_json(
runtime,
method="GET",
path="/getnetworkpiperiskprobabilitynow/",
params={"network": network},
path="/network-pipe-risk-probability-nows",
require_auth=True,
require_network_ctx=True,
)
geometries, duration_geo = request_json(
runtime,
method="GET",
path="/getpiperiskprobabilitygeometries/",
params={"network": network},
path="/pipes/risk-probability-geometries",
require_auth=True,
require_network_ctx=True,
)
emit_success(
summary="读取全网风险成功",
+20 -30
View File
@@ -13,7 +13,7 @@ from .apps import (
data_timeseries_scheme_app,
)
from .common import emit_api, runtime_context
from .core import CLIError, parse_time_with_timezone, require_network, resolve_scheme
from .core import CLIError, parse_time_with_timezone, resolve_scheme
from .option_types import (
CompositeKind,
ElementType,
@@ -73,7 +73,7 @@ def data_realtime_links(
ctx,
summary="读取实时管道数据成功",
method="GET",
path="/realtime/links",
path="/timeseries/realtime/links",
params={
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"end_time": parse_time_with_timezone(end_time, option_name="--end-time").isoformat(),
@@ -93,7 +93,7 @@ def data_realtime_nodes(
ctx,
summary="读取实时节点数据成功",
method="GET",
path="/realtime/nodes",
path="/timeseries/realtime/nodes",
params={
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
"end_time": parse_time_with_timezone(end_time, option_name="--end-time").isoformat(),
@@ -114,7 +114,7 @@ def data_realtime_simulation_by_id_time(
ctx,
summary="读取实时模拟数据成功",
method="GET",
path="/realtime/query/by-id-time",
path="/timeseries/realtime/simulation-results",
params={
"id": id,
"type": type.value,
@@ -137,7 +137,7 @@ def data_realtime_simulation_by_time_property(
ctx,
summary="读取实时属性聚合数据成功",
method="GET",
path="/realtime/query/by-time-property",
path="/timeseries/realtime/records",
params={
"type": type.value,
"query_time": parse_time_with_timezone(time, option_name="--time").isoformat(),
@@ -161,7 +161,7 @@ def data_scheme_links(
ctx,
summary="读取方案管道数据成功",
method="GET",
path="/scheme/links",
path="/timeseries/schemes/links",
params={
"scheme_name": resolve_scheme(runtime, scheme, required=True),
"scheme_type": _scheme_type_option(scheme_type),
@@ -189,7 +189,7 @@ def data_scheme_node_field(
ctx,
summary="读取方案节点字段成功",
method="GET",
path=f"/scheme/nodes/{node}/field",
path=f"/timeseries/schemes/nodes/{node}/field",
params={
"field": field,
"scheme_name": resolve_scheme(runtime, scheme, required=True),
@@ -233,7 +233,7 @@ def data_scheme_simulation(
ctx,
summary="读取方案单点模拟数据成功",
method="GET",
path="/scheme/query/by-id-time",
path="/timeseries/schemes/simulation-results",
params=params,
require_auth=True,
require_project=True,
@@ -253,7 +253,7 @@ def data_scheme_simulation(
ctx,
summary="读取方案属性聚合数据成功",
method="GET",
path="/scheme/query/by-scheme-time-property",
path="/timeseries/schemes/records",
params=params,
require_auth=True,
require_project=True,
@@ -270,7 +270,7 @@ def data_scada_query(
end_time: Annotated[str, typer.Option("--end-time", help="结束时间")],
field: Annotated[str | None, typer.Option("--field", help="字段名,仅支持 monitored_value|cleaned_value")] = None,
) -> None:
path = "/scada/by-ids-field-time-range" if field else "/scada/by-ids-time-range"
path = "/timeseries/scada-readings/fields" if field else "/timeseries/scada-readings"
params = {
"device_ids": ",".join(device_id),
"start_time": parse_time_with_timezone(start_time, option_name="--start-time").isoformat(),
@@ -334,7 +334,7 @@ def data_timeseries_composite(
ctx,
summary="读取复合 SCADA-模拟数据成功",
method="GET",
path="/composite/scada-simulation",
path="/timeseries/views/scada-simulations",
params=params,
require_auth=True,
require_project=True,
@@ -357,7 +357,7 @@ def data_timeseries_composite(
ctx,
summary="读取复合元素模拟数据成功",
method="GET",
path="/composite/element-simulation",
path="/timeseries/views/element-simulations",
params=params,
require_auth=True,
require_project=True,
@@ -377,7 +377,7 @@ def data_timeseries_composite(
ctx,
summary="读取元素关联 SCADA 数据成功",
method="GET",
path="/composite/element-scada",
path="/timeseries/views/element-scada-readings",
params=params,
require_auth=True,
require_project=True,
@@ -398,21 +398,19 @@ def data_composite_pipeline_health(
ctx,
summary="读取管道健康预测成功",
method="GET",
path="/composite/pipeline-health-prediction",
path="/pipeline-health-predictions",
params={
"network_name": require_network(runtime_context(ctx)),
"query_time": parse_time_with_timezone(end_time, option_name="--end-time").isoformat(),
},
require_auth=True,
require_project=True,
require_network_ctx=True,
)
def _scada_mapping(kind: str, action: str) -> tuple[str, dict[str, str]]:
mapping = {
("info", "get"): ("/getscadainfo/", {"id_param": "id"}),
("info", "list"): ("/getallscadainfo/", {}),
("info", "get"): ("/scada-info/detail", {"id_param": "id"}),
("info", "list"): ("/scada-info", {}),
}
result = mapping.get((kind, action))
if result is None:
@@ -433,7 +431,7 @@ def data_scada_get(
) -> None:
runtime = runtime_context(ctx)
path, meta = _scada_mapping(kind.value, "get")
params = {"network": require_network(runtime), meta["id_param"]: id}
params = {meta["id_param"]: id}
emit_api(
ctx,
summary="读取 SCADA 数据成功",
@@ -441,7 +439,6 @@ def data_scada_get(
path=path,
params=params,
require_auth=True,
require_network_ctx=True,
)
@@ -457,9 +454,7 @@ def data_scada_list(
summary="读取 SCADA 列表成功",
method="GET",
path=path,
params={"network": require_network(runtime)},
require_auth=True,
require_network_ctx=True,
)
@@ -470,10 +465,8 @@ def data_scheme_schema(ctx: typer.Context) -> None:
ctx,
summary="读取方案 schema 成功",
method="GET",
path="/getschemeschema/",
params={"network": require_network(runtime)},
path="/network-schemas/scheme",
require_auth=True,
require_network_ctx=True,
)
@@ -487,10 +480,9 @@ def data_scheme_get(
ctx,
summary="读取方案成功",
method="GET",
path="/getscheme/",
params={"network": require_network(runtime), "schema_name": name},
path="/schemes/detail",
params={"schema_name": name},
require_auth=True,
require_network_ctx=True,
)
@@ -502,7 +494,5 @@ def data_scheme_list(ctx: typer.Context) -> None:
summary="读取方案列表成功",
method="GET",
path="/schemes",
params={"network": require_network(runtime)},
require_auth=True,
require_network_ctx=True,
)
+34 -60
View File
@@ -5,8 +5,8 @@ from typing import Annotated
import typer
from .apps import component_option_app, network_app
from .common import emit_api, runtime_context
from .core import CLIError, require_network
from .common import emit_api
from .core import CLIError
from .option_types import ComponentOptionKind
@@ -15,15 +15,13 @@ def network_get_junction_properties(
ctx: typer.Context,
junction: Annotated[str, typer.Option("--junction", help="节点 ID")],
) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取节点属性成功",
method="GET",
path="/getjunctionproperties/",
params={"network": require_network(runtime), "junction": junction},
path="/junctions/properties",
params={"junction": junction},
require_auth=True,
require_network_ctx=True,
)
@@ -32,29 +30,25 @@ def network_get_pipe_properties(
ctx: typer.Context,
pipe: Annotated[str, typer.Option("--pipe", help="管道 ID")],
) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取管道属性成功",
method="GET",
path="/getpipeproperties/",
params={"network": require_network(runtime), "pipe": pipe},
path="/pipes/properties",
params={"pipe": pipe},
require_auth=True,
require_network_ctx=True,
)
@network_app.command("get-all-pipes-properties")
def network_get_all_pipes_properties(ctx: typer.Context) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取全部管道属性成功",
method="GET",
path="/getallpipeproperties/",
params={"network": require_network(runtime)},
path="/pipes",
params={},
require_auth=True,
require_network_ctx=True,
)
@@ -63,29 +57,25 @@ def network_get_reservoir_properties(
ctx: typer.Context,
reservoir: Annotated[str, typer.Option("--reservoir", help="水库 ID")],
) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取水库属性成功",
method="GET",
path="/getreservoirproperties/",
params={"network": require_network(runtime), "reservoir": reservoir},
path="/reservoirs/properties",
params={"reservoir": reservoir},
require_auth=True,
require_network_ctx=True,
)
@network_app.command("get-all-reservoirs-properties")
def network_get_all_reservoir_properties(ctx: typer.Context) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取全部水库属性成功",
method="GET",
path="/getallreservoirproperties/",
params={"network": require_network(runtime)},
path="/reservoirs",
params={},
require_auth=True,
require_network_ctx=True,
)
@@ -94,29 +84,25 @@ def network_get_tank_properties(
ctx: typer.Context,
tank: Annotated[str, typer.Option("--tank", help="水箱 ID")],
) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取水箱属性成功",
method="GET",
path="/gettankproperties/",
params={"network": require_network(runtime), "tank": tank},
path="/tanks/properties",
params={"tank": tank},
require_auth=True,
require_network_ctx=True,
)
@network_app.command("get-all-tanks-properties")
def network_get_all_tank_properties(ctx: typer.Context) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取全部水箱属性成功",
method="GET",
path="/getalltankproperties/",
params={"network": require_network(runtime)},
path="/tanks",
params={},
require_auth=True,
require_network_ctx=True,
)
@@ -125,29 +111,25 @@ def network_get_pump_properties(
ctx: typer.Context,
pump: Annotated[str, typer.Option("--pump", help="水泵 ID")],
) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取水泵属性成功",
method="GET",
path="/getpumpproperties/",
params={"network": require_network(runtime), "pump": pump},
path="/pumps/properties",
params={"pump": pump},
require_auth=True,
require_network_ctx=True,
)
@network_app.command("get-all-pumps-properties")
def network_get_all_pump_properties(ctx: typer.Context) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取全部水泵属性成功",
method="GET",
path="/getallpumpproperties/",
params={"network": require_network(runtime)},
path="/pumps",
params={},
require_auth=True,
require_network_ctx=True,
)
@@ -156,29 +138,25 @@ def network_get_valve_properties(
ctx: typer.Context,
valve: Annotated[str, typer.Option("--valve", help="阀门 ID")],
) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取阀门属性成功",
method="GET",
path="/getvalveproperties/",
params={"network": require_network(runtime), "valve": valve},
path="/valves/properties",
params={"valve": valve},
require_auth=True,
require_network_ctx=True,
)
@network_app.command("get-all-valves-properties")
def network_get_all_valve_properties(ctx: typer.Context) -> None:
runtime = runtime_context(ctx)
emit_api(
ctx,
summary="读取全部阀门属性成功",
method="GET",
path="/getallvalveproperties/",
params={"network": require_network(runtime)},
path="/valves",
params={},
require_auth=True,
require_network_ctx=True,
)
@@ -188,9 +166,8 @@ def component_option_schema(
kind: Annotated[ComponentOptionKind, typer.Option("--kind", help="选项类型,仅支持 time|energy|pump-energy|network")],
pump: Annotated[str | None, typer.Option("--pump", help="pump-energy 时需要的泵 ID")] = None,
) -> None:
runtime = runtime_context(ctx)
path = _component_option_path(kind.value, schema=True)
params = {"network": require_network(runtime)}
params: dict[str, str] = {}
if kind == ComponentOptionKind.PUMP_ENERGY and pump:
params["pump"] = pump
emit_api(
@@ -200,7 +177,6 @@ def component_option_schema(
path=path,
params=params,
require_auth=True,
require_network_ctx=True,
)
@@ -210,9 +186,8 @@ def component_option_get(
kind: Annotated[ComponentOptionKind, typer.Option("--kind", help="选项类型,仅支持 time|energy|pump-energy|network")],
pump: Annotated[str | None, typer.Option("--pump", help="pump-energy 时需要的泵 ID")] = None,
) -> None:
runtime = runtime_context(ctx)
path = _component_option_path(kind.value, schema=False)
params = {"network": require_network(runtime)}
params: dict[str, str] = {}
if kind == ComponentOptionKind.PUMP_ENERGY:
if not pump:
raise CLIError(
@@ -229,20 +204,19 @@ def component_option_get(
path=path,
params=params,
require_auth=True,
require_network_ctx=True,
)
def _component_option_path(kind: str, *, schema: bool) -> str:
routes = {
("time", True): "/gettimeschema",
("time", False): "/gettimeproperties/",
("energy", True): "/getenergyschema/",
("energy", False): "/getenergyproperties/",
("pump-energy", True): "/getpumpenergyschema/",
("pump-energy", False): "/getpumpenergyproperties//",
("network", True): "/getoptionschema/",
("network", False): "/getoptionproperties/",
("time", True): "/network-schemas/time",
("time", False): "/network-options/time",
("energy", True): "/network-schemas/energy",
("energy", False): "/network-options/energy",
("pump-energy", True): "/network-schemas/pump-energy",
("pump-energy", False): "/network-options/pump-energy",
("network", True): "/network-schemas/option",
("network", False): "/network-options",
}
path = routes.get((kind, schema))
if path is None:
-4
View File
@@ -38,8 +38,6 @@ def emit_api(
json_body: Any = None,
require_auth: bool = True,
require_project: bool = False,
require_network_ctx: bool = False,
require_username_ctx: bool = False,
next_commands: list[str] | None = None,
) -> None:
runtime = runtime_context(ctx)
@@ -51,8 +49,6 @@ def emit_api(
json_body=json_body,
require_auth=require_auth,
require_project=require_project,
require_network_ctx=require_network_ctx,
require_username_ctx=require_username_ctx,
)
emit_success(
summary=summary,
+23 -59
View File
@@ -17,8 +17,6 @@ SCHEMA_VERSION = "tjwater-cli/v1"
CLI_NAME = "tjwater-cli"
DEFAULT_TIMEOUT = 180
DEFAULT_SERVER = "http://192.168.1.114:8000"
class CLIError(Exception):
def __init__(
self,
@@ -46,8 +44,6 @@ class AuthContext:
server: str | None = None
access_token: str | None = None
project_id: str | None = None
username: str | None = None
network: str | None = None
headers: dict[str, str] = field(default_factory=dict)
@@ -97,8 +93,6 @@ def load_auth_context(auth_stdin: bool = False) -> AuthContext:
"server": os.getenv("TJWATER_SERVER"),
"access_token": os.getenv("TJWATER_ACCESS_TOKEN"),
"project_id": os.getenv("TJWATER_PROJECT_ID"),
"username": os.getenv("TJWATER_USERNAME"),
"network": os.getenv("TJWATER_NETWORK"),
"headers": json.loads(extra_headers) if extra_headers else {},
}
@@ -115,8 +109,6 @@ def load_auth_context(auth_stdin: bool = False) -> AuthContext:
server=_pick(raw, "server", "base_url"),
access_token=_pick(raw, "access_token", "token", "accessToken"),
project_id=_pick(raw, "project_id", "projectId", "x_project_id"),
username=_pick(raw, "username", "preferred_username"),
network=_pick(raw, "network", "project_code", "projectCode", "project"),
headers={str(key): str(value) for key, value in headers.items()},
)
@@ -175,30 +167,6 @@ def require_project_id(ctx: RuntimeContext) -> str:
)
def require_network(ctx: RuntimeContext) -> str:
if ctx.auth.network:
return ctx.auth.network
raise CLIError(
"认证失败",
code="NETWORK_CONTEXT_REQUIRED",
message="missing network in auth context for legacy network-based endpoints",
exit_code=3,
next_commands=["add network to auth context"],
)
def require_username(ctx: RuntimeContext) -> str:
if ctx.auth.username:
return ctx.auth.username
raise CLIError(
"认证失败",
code="USERNAME_CONTEXT_REQUIRED",
message="missing username in auth context",
exit_code=3,
next_commands=["add username to auth context"],
)
def resolve_scheme(ctx: RuntimeContext, explicit_scheme: str | None, *, required: bool = False) -> str | None:
scheme = explicit_scheme or ctx.scheme
if required and not scheme:
@@ -254,14 +222,14 @@ def parse_burst_file(path: Path) -> tuple[list[str], list[float]]:
raw = read_json_input(path, label="burst")
if isinstance(raw, dict) and "bursts" in raw:
raw = raw["bursts"]
if isinstance(raw, dict) and "burst_ID" in raw and "burst_size" in raw:
ids = [str(item) for item in raw["burst_ID"]]
if isinstance(raw, dict) and "burst_id" in raw and "burst_size" in raw:
ids = [str(item) for item in raw["burst_id"]]
sizes = [float(item) for item in raw["burst_size"]]
if len(ids) != len(sizes):
raise CLIError(
"CLI 参数错误",
code="BURST_FILE_INVALID",
message="burst file burst_ID and burst_size must have the same length",
message="burst file burst_id and burst_size must have the same length",
exit_code=2,
)
return ids, sizes
@@ -282,7 +250,7 @@ def parse_burst_file(path: Path) -> tuple[list[str], list[float]]:
raise CLIError(
"CLI 参数错误",
code="BURST_FILE_INVALID",
message="burst file must be a JSON array or object with burst_ID/burst_size",
message="burst file must be a JSON array or object with burst_id/burst_size",
exit_code=2,
)
@@ -404,12 +372,13 @@ def _parse_response_body(response: requests.Response) -> Any:
return {}
def _with_network_param(params: dict[str, Any] | None, network: str) -> dict[str, Any]:
params = dict(params or {})
if "network" in params or "network_name" in params or "name" in params:
return params
params["network"] = network
return params
def _prepare_public_request(
method: str,
path: str,
params: dict[str, Any] | None,
json_body: Any,
) -> tuple[str, str, dict[str, Any] | None, Any]:
return method.upper(), path.rstrip("/") or "/", params or None, json_body
def request_json(
@@ -421,18 +390,14 @@ def request_json(
json_body: Any = None,
require_auth: bool = True,
require_project: bool = False,
require_network_ctx: bool = False,
require_username_ctx: bool = False,
) -> tuple[Any, int]:
require_server(ctx)
network = None
if require_network_ctx:
network = require_network(ctx)
if require_username_ctx:
require_username(ctx)
if network and (params is not None or json_body is None):
params = _with_network_param(params, network)
method, path, params, json_body = _prepare_public_request(
method,
path,
params,
json_body,
)
url = f"{require_server(ctx)}/api/v1{path}"
headers = build_headers(ctx, require_auth=require_auth, require_project=require_project)
started = time.monotonic()
@@ -482,15 +447,14 @@ def request_bytes(
params: dict[str, Any] | None = None,
require_auth: bool = True,
require_project: bool = False,
require_network_ctx: bool = False,
) -> tuple[bytes, int]:
require_server(ctx)
network = None
if require_network_ctx:
network = require_network(ctx)
if network:
params = _with_network_param(params, network)
method, path, params, _ = _prepare_public_request(
method,
path,
params,
None,
)
url = f"{require_server(ctx)}/api/v1{path}"
headers = build_headers(ctx, require_auth=require_auth, require_project=require_project)
started = time.monotonic()
+35 -35
View File
@@ -35,73 +35,73 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("network", "get-junction-properties"): CommandDoc(
path=("network", "get-junction-properties"),
summary="读取节点属性",
description="调用 /getjunctionproperties/",
description="调用 GET /api/v1/junctions/{junction_id}/properties。",
options=(CommandOptionDoc("junction", "节点 ID", required=True),),
examples=("tjwater-cli network get-junction-properties --junction J1",),
),
("network", "get-pipe-properties"): CommandDoc(
path=("network", "get-pipe-properties"),
summary="读取管道属性",
description="调用 /getpipeproperties/",
description="调用 GET /api/v1/pipes/{pipe_id}/properties。",
options=(CommandOptionDoc("pipe", "管道 ID", required=True),),
examples=("tjwater-cli network get-pipe-properties --pipe P1",),
),
("network", "get-all-pipes-properties"): CommandDoc(
path=("network", "get-all-pipes-properties"),
summary="读取全部管道属性",
description="调用 /getallpipeproperties/",
description="调用 GET /api/v1/pipes/properties。",
examples=("tjwater-cli network get-all-pipes-properties",),
),
("network", "get-reservoir-properties"): CommandDoc(
path=("network", "get-reservoir-properties"),
summary="读取水库属性",
description="调用 /getreservoirproperties/",
description="调用 GET /api/v1/reservoirs/{reservoir_id}/properties。",
options=(CommandOptionDoc("reservoir", "水库 ID", required=True),),
examples=("tjwater-cli network get-reservoir-properties --reservoir R1",),
),
("network", "get-all-reservoirs-properties"): CommandDoc(
path=("network", "get-all-reservoirs-properties"),
summary="读取全部水库属性",
description="调用 /getallreservoirproperties/",
description="调用 GET /api/v1/reservoirs/properties。",
examples=("tjwater-cli network get-all-reservoirs-properties",),
),
("network", "get-tank-properties"): CommandDoc(
path=("network", "get-tank-properties"),
summary="读取水箱属性",
description="调用 /gettankproperties/",
description="调用 GET /api/v1/tanks/{tank_id}/properties。",
options=(CommandOptionDoc("tank", "水箱 ID", required=True),),
examples=("tjwater-cli network get-tank-properties --tank T1",),
),
("network", "get-all-tanks-properties"): CommandDoc(
path=("network", "get-all-tanks-properties"),
summary="读取全部水箱属性",
description="调用 /getalltankproperties/",
description="调用 GET /api/v1/tanks/properties。",
examples=("tjwater-cli network get-all-tanks-properties",),
),
("network", "get-pump-properties"): CommandDoc(
path=("network", "get-pump-properties"),
summary="读取水泵属性",
description="调用 /getpumpproperties/",
description="调用 GET /api/v1/pumps/{pump_id}/properties。",
options=(CommandOptionDoc("pump", "水泵 ID", required=True),),
examples=("tjwater-cli network get-pump-properties --pump PU1",),
),
("network", "get-all-pumps-properties"): CommandDoc(
path=("network", "get-all-pumps-properties"),
summary="读取全部水泵属性",
description="调用 /getallpumpproperties/",
description="调用 GET /api/v1/pumps/properties。",
examples=("tjwater-cli network get-all-pumps-properties",),
),
("network", "get-valve-properties"): CommandDoc(
path=("network", "get-valve-properties"),
summary="读取阀门属性",
description="调用 /getvalveproperties/",
description="调用 GET /api/v1/valves/{valve_id}/properties。",
options=(CommandOptionDoc("valve", "阀门 ID", required=True),),
examples=("tjwater-cli network get-valve-properties --valve V1",),
),
("network", "get-all-valves-properties"): CommandDoc(
path=("network", "get-all-valves-properties"),
summary="读取全部阀门属性",
description="调用 /getallvalveproperties/",
description="调用 GET /api/v1/valves/properties。",
examples=("tjwater-cli network get-all-valves-properties",),
),
("component", "option", "schema"): CommandDoc(
@@ -137,7 +137,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("simulation", "run"): CommandDoc(
path=("simulation", "run"),
summary="触发指定绝对时间的模拟运行",
description="把显式带时区的 RFC3339 start-time 直接传给 /simulations/run-by-date;服务端按带时区时间处理并统一按 UTC 存储结果,实时数据需后续通过 data timeseries 在对应时间段查询。duration 单位为分钟。",
description="把显式带时区的 RFC3339 start-time 直接传给 POST /api/v1/simulation-runs;服务端按带时区时间处理并统一按 UTC 存储结果,实时数据需后续通过 data timeseries 在对应时间段查询。duration 单位为分钟。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("duration", "持续分钟数", required=True),
@@ -152,7 +152,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("analysis", "burst"): CommandDoc(
path=("analysis", "burst"),
summary="执行爆管分析",
description="读取 burst-file 并转换为 burst_ID[] / burst_size[];接口本身只返回分析执行结果,方案数据需后续通过 data scheme 命令获取。duration 单位为秒。",
description="读取 burst-file burst_id[] / burst_size[] 并调用 POST /api/v1/burst-analyses;接口本身只返回分析执行结果,方案数据需后续通过 data scheme 命令获取。duration 单位为秒。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("duration", "持续秒数", required=True),
@@ -201,7 +201,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("analysis", "age"): CommandDoc(
path=("analysis", "age"),
summary="执行水龄分析",
description="调用 /age_analysis/。duration 单位为秒。",
description="调用 POST /api/v1/water-age-analyses。duration 单位为秒。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("duration", "持续秒数", required=True),
@@ -211,7 +211,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("analysis", "contaminant"): CommandDoc(
path=("analysis", "contaminant"),
summary="执行污染物模拟",
description="调用 /contaminant-simulation。duration 单位为秒。",
description="调用 POST /api/v1/contaminant-simulations。duration 单位为秒。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("duration", "持续秒数", required=True),
@@ -247,19 +247,19 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("analysis", "leakage", "schemes", "list"): CommandDoc(
path=("analysis", "leakage", "schemes", "list"),
summary="列出漏损方案",
description="调用 /schemes,并传入 scheme_type=dma_leak_identification。",
description="调用 GET /api/v1/schemes,并传入 scheme_type=dma_leak_identification。",
examples=("tjwater-cli analysis leakage schemes list",),
),
("analysis", "leakage", "schemes", "get"): CommandDoc(
path=("analysis", "leakage", "schemes", "get"),
summary="读取漏损方案详情",
description="调用 /schemes/{scheme_name},并传入 scheme_type=dma_leak_identification。",
description="调用 GET /api/v1/schemes/{scheme_name},并传入 scheme_type=dma_leak_identification。",
examples=("tjwater-cli analysis leakage schemes get my_scheme",),
),
("analysis", "burst-detection", "detect"): CommandDoc(
path=("analysis", "burst-detection", "detect"),
summary="执行爆管检测",
description="调用 /burst-detection/detect/",
description="调用 POST /api/v1/burst-detections",
options=(
CommandOptionDoc("start-time", "显式带时区的 SCADA 开始时间", required=True),
CommandOptionDoc("end-time", "显式带时区的 SCADA 结束时间", required=True),
@@ -270,19 +270,19 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("analysis", "burst-detection", "schemes", "list"): CommandDoc(
path=("analysis", "burst-detection", "schemes", "list"),
summary="列出爆管检测方案",
description="调用 /schemes,并传入 scheme_type=burst_detection。",
description="调用 GET /api/v1/schemes,并传入 scheme_type=burst_detection。",
examples=("tjwater-cli analysis burst-detection schemes list",),
),
("analysis", "burst-detection", "schemes", "get"): CommandDoc(
path=("analysis", "burst-detection", "schemes", "get"),
summary="读取爆管检测方案详情",
description="调用 /schemes/{scheme_name},并传入 scheme_type=burst_detection。",
description="调用 GET /api/v1/schemes/{scheme_name},并传入 scheme_type=burst_detection。",
examples=("tjwater-cli analysis burst-detection schemes get my_scheme",),
),
("analysis", "burst-location", "locate"): CommandDoc(
path=("analysis", "burst-location", "locate"),
summary="执行爆管定位",
description="调用 /burst-location/locate/;需要 burst-leakage。支持 monitoring 和 simulation 两种数据源。",
description="调用 POST /api/v1/burst-locations;需要 burst-leakage。支持 monitoring 和 simulation 两种数据源。",
options=(
CommandOptionDoc("start-time", "显式带时区的 SCADA 开始时间", required=True),
CommandOptionDoc("end-time", "显式带时区的 SCADA 结束时间", required=True),
@@ -303,26 +303,26 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("analysis", "burst-location", "schemes", "list"): CommandDoc(
path=("analysis", "burst-location", "schemes", "list"),
summary="列出爆管定位方案",
description="调用 /schemes,并传入 scheme_type=burst_location。",
description="调用 GET /api/v1/schemes,并传入 scheme_type=burst_location。",
examples=("tjwater-cli analysis burst-location schemes list",),
),
("analysis", "burst-location", "schemes", "get"): CommandDoc(
path=("analysis", "burst-location", "schemes", "get"),
summary="读取爆管定位方案详情",
description="调用 /schemes/{scheme_name},并传入 scheme_type=burst_location。",
description="调用 GET /api/v1/schemes/{scheme_name},并传入 scheme_type=burst_location。",
examples=("tjwater-cli analysis burst-location schemes get my_scheme",),
),
("analysis", "risk", "pipe-now"): CommandDoc(
path=("analysis", "risk", "pipe-now"),
summary="读取单条管道当前风险",
description="调用 /getpiperiskprobabilitynow/",
description="调用 GET /api/v1/pipes/risk-probability-now。",
options=(CommandOptionDoc("pipe", "管道 ID", required=True),),
examples=("tjwater-cli analysis risk pipe-now --pipe P1",),
),
("analysis", "risk", "pipe-history"): CommandDoc(
path=("analysis", "risk", "pipe-history"),
summary="读取单条管道历史风险",
description="调用 /getpiperiskprobability/",
description="调用 GET /api/v1/pipes/risk-probability。",
options=(CommandOptionDoc("pipe", "管道 ID", required=True),),
examples=("tjwater-cli analysis risk pipe-history --pipe P1",),
),
@@ -335,7 +335,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "realtime", "links"): CommandDoc(
path=("data", "timeseries", "realtime", "links"),
summary="查询实时管道时序",
description="调用 /realtime/links。",
description="调用 GET /api/v1/timeseries/realtime/links。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("end-time", "显式带时区的结束时间", required=True),
@@ -345,7 +345,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "realtime", "nodes"): CommandDoc(
path=("data", "timeseries", "realtime", "nodes"),
summary="查询实时节点时序",
description="调用 /realtime/nodes。",
description="调用 GET /api/v1/timeseries/realtime/nodes。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("end-time", "显式带时区的结束时间", required=True),
@@ -355,7 +355,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "realtime", "simulation-by-id-time"): CommandDoc(
path=("data", "timeseries", "realtime", "simulation-by-id-time"),
summary="按元素和时间查询实时模拟结果",
description="调用 /realtime/query/by-id-time",
description="调用 GET /api/v1/timeseries/realtime/by-element",
options=(
CommandOptionDoc("id", "元素 ID", required=True),
CommandOptionDoc("type", "元素类型:pipe 或 junctionlinks/nodes 是独立子命令,不是 type 取值", required=True),
@@ -369,7 +369,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "realtime", "simulation-by-time-property"): CommandDoc(
path=("data", "timeseries", "realtime", "simulation-by-time-property"),
summary="按时间和属性查询实时模拟结果",
description="调用 /realtime/query/by-time-property。pipe 属性:flow、friction、headloss、quality、reaction、setting、status、velocityjunction 属性:actual_demand、total_head、pressure、quality。",
description="调用 GET /api/v1/timeseries/realtime/by-property。pipe 属性:flow、friction、headloss、quality、reaction、setting、status、velocityjunction 属性:actual_demand、total_head、pressure、quality。",
options=(
CommandOptionDoc("type", "元素类型:pipe 或 junctionlinks/nodes 是独立子命令,不是 type 取值", required=True),
CommandOptionDoc("time", "显式带时区的查询时间", required=True),
@@ -380,7 +380,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "scheme", "links"): CommandDoc(
path=("data", "timeseries", "scheme", "links"),
summary="查询方案管道时序",
description="调用 /scheme/links。",
description="调用 GET /api/v1/timeseries/schemes/links。",
options=(
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
CommandOptionDoc("end-time", "显式带时区的结束时间", required=True),
@@ -392,7 +392,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "scheme", "node-field"): CommandDoc(
path=("data", "timeseries", "scheme", "node-field"),
summary="查询方案节点字段时序",
description="调用 /scheme/nodes/{node_id}/field。field 仅支持 actual_demand、total_head、pressure、quality。",
description="调用 GET /api/v1/timeseries/schemes/nodes/{node_id}/{field}。field 仅支持 actual_demand、total_head、pressure、quality。",
options=(
CommandOptionDoc("node", "节点 ID", required=True),
CommandOptionDoc("field", "字段名:actual_demand、total_head、pressure、quality", required=True),
@@ -458,7 +458,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "timeseries", "composite", "pipeline-health"): CommandDoc(
path=("data", "timeseries", "composite", "pipeline-health"),
summary="查询管道健康预测",
description="调用 /composite/pipeline-health-prediction。",
description="调用 GET /api/v1/pipeline-health-predictions",
options=(
CommandOptionDoc("pipe", "管道 ID", required=True),
CommandOptionDoc("start-time", "显式带时区的开始时间", required=True),
@@ -486,20 +486,20 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
("data", "scheme", "schema"): CommandDoc(
path=("data", "scheme", "schema"),
summary="读取方案 schema",
description="调用 /getschemeschema/",
description="调用 GET /api/v1/network-schemas/scheme",
examples=("tjwater-cli data scheme schema",),
),
("data", "scheme", "get"): CommandDoc(
path=("data", "scheme", "get"),
summary="读取单条方案",
description="调用 /getscheme/",
description="调用 GET /api/v1/schemes/detail",
options=(CommandOptionDoc("name", "方案名称", required=True),),
examples=("tjwater-cli data scheme get --name my_scheme",),
),
("data", "scheme", "list"): CommandDoc(
path=("data", "scheme", "list"),
summary="列出方案",
description="调用 /schemes。",
description="调用 GET /api/v1/schemes。",
examples=("tjwater-cli data scheme list",),
),
}