qapu-cli 0.74.0__tar.gz → 0.76.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/PKG-INFO +1 -1
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/pyproject.toml +1 -1
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/command_packages.py +12 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/main.py +287 -3
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli.egg-info/PKG-INFO +1 -1
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/README.md +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/__init__.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/client.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/__init__.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/app.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/app.tcss +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/data.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/rendering.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/__init__.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/catalog.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/catalog_form.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/catalog_picker.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/confirm.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/dashboard.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/device_authorization.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/device_edit.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/device_list.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/equipment_form.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/group_detail.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/group_form.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/group_list.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/screens/infrastructure.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/widgets/__init__.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/tui/widgets/device_table.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli/visibility.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli.egg-info/SOURCES.txt +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli.egg-info/dependency_links.txt +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli.egg-info/entry_points.txt +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli.egg-info/requires.txt +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/qapu_cli.egg-info/top_level.txt +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/setup.cfg +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_command_packages_complete.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_login.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_logout.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_tui_authorization.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_tui_catalog.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_tui_device_edit.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_tui_groups.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_tui_infrastructure.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_tui_smoke.py +0 -0
- {qapu_cli-0.74.0 → qapu_cli-0.76.0}/tests/test_visibility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qapu-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.76.0
|
|
4
4
|
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
5
|
Author: OVOO Technology
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "qapu-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.76.0"
|
|
4
4
|
description = "CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -63,6 +63,17 @@ COMMAND_REQUIRED_PACKAGES: dict[str, frozenset[str]] = {
|
|
|
63
63
|
"calibration_get": frozenset({"calibration:read"}),
|
|
64
64
|
"calibration_list": frozenset({"calibration:read"}),
|
|
65
65
|
"calibration_update": frozenset({"calibration:write"}),
|
|
66
|
+
"calibration_assignment_add": frozenset({"calibration:write"}),
|
|
67
|
+
"calibration_assignment_delete": frozenset({"calibration:write"}),
|
|
68
|
+
"calibration_assignment_get": frozenset({"calibration:read"}),
|
|
69
|
+
"calibration_assignment_list": frozenset({"calibration:read"}),
|
|
70
|
+
"calibration_assignment_retire": frozenset({"calibration:write"}),
|
|
71
|
+
"calibration_assignment_update": frozenset({"calibration:write"}),
|
|
72
|
+
"calibration_profile_add": frozenset({"calibration:write"}),
|
|
73
|
+
"calibration_profile_delete": frozenset({"calibration:write"}),
|
|
74
|
+
"calibration_profile_get": frozenset({"calibration:read"}),
|
|
75
|
+
"calibration_profile_list": frozenset({"calibration:read"}),
|
|
76
|
+
"calibration_profile_update": frozenset({"calibration:write"}),
|
|
66
77
|
"cinga_ai_list": frozenset({"mcp:read-only"}),
|
|
67
78
|
"cinga_ai_log": frozenset({"observability:write"}),
|
|
68
79
|
"crop_type_add": frozenset({"operations-catalog:write"}),
|
|
@@ -90,6 +101,7 @@ COMMAND_REQUIRED_PACKAGES: dict[str, frozenset[str]] = {
|
|
|
90
101
|
"device_power": frozenset({"telemetry:read"}),
|
|
91
102
|
"device_purge_history": frozenset({"devices:destructive"}),
|
|
92
103
|
"device_reliability": frozenset({"telemetry:read"}),
|
|
104
|
+
"device_windows": frozenset({"telemetry:read"}),
|
|
93
105
|
"device_trend": frozenset({"telemetry:read"}),
|
|
94
106
|
"device_voltage": frozenset({"telemetry:read"}),
|
|
95
107
|
"equipment_add": frozenset({"infrastructure:write"}),
|
|
@@ -78,7 +78,11 @@ app.add_typer(equipment_relation_app, name="equipment-relation", rich_help_panel
|
|
|
78
78
|
blockchain_app = typer.Typer(no_args_is_help=True, help="Device blockchain commands (mined block ledger).")
|
|
79
79
|
app.add_typer(blockchain_app, name="blockchain", rich_help_panel="Analiz & Skor")
|
|
80
80
|
calibration_app = typer.Typer(no_args_is_help=True, help="Device calibration commands (Gain/Offset per variable).")
|
|
81
|
+
calibration_profile_app = typer.Typer(no_args_is_help=True, help="Reusable calibration profile commands (2026-10-15 redesign - share one Gain/Offset definition across many assignments).")
|
|
82
|
+
calibration_assignment_app = typer.Typer(no_args_is_help=True, help="Calibration assignment commands (2026-10-15 redesign - DEVICE/GROUP/GLOBAL scope, priority, time-windowed validity).")
|
|
81
83
|
app.add_typer(calibration_app, name="calibration", rich_help_panel="Analiz & Skor")
|
|
84
|
+
app.add_typer(calibration_profile_app, name="calibration-profile", rich_help_panel="Analiz & Skor")
|
|
85
|
+
app.add_typer(calibration_assignment_app, name="calibration-assignment", rich_help_panel="Analiz & Skor")
|
|
82
86
|
score_app = typer.Typer(no_args_is_help=True, help="Cinga Score commands (cinga_score_snapshot rows - list/get/bundle/revisions/calculate/analysis-request/narrative/delete).")
|
|
83
87
|
app.add_typer(score_app, name="score", rich_help_panel="Analiz & Skor")
|
|
84
88
|
crop_type_app = typer.Typer(no_args_is_help=True, help="Crop type catalog commands.")
|
|
@@ -1161,6 +1165,91 @@ def device_reliability(
|
|
|
1161
1165
|
]))
|
|
1162
1166
|
|
|
1163
1167
|
|
|
1168
|
+
_WINDOWS_PERIOD_TYPE_TO_CODE = {"day": 1, "week": 7, "month": 30}
|
|
1169
|
+
_WINDOWS_PERIOD_CODE_TO_LABEL = {1: "Günlük", 7: "Haftalık", 30: "Aylık"}
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
@device_app.command("windows")
|
|
1173
|
+
def device_windows(
|
|
1174
|
+
device_id: str = typer.Argument(..., help="Device ID."),
|
|
1175
|
+
variable_id: Optional[str] = typer.Option(None, "--variable-id", help="Restrict to one variable (e.g. VRMS_A). Omit for every in-scope variable."),
|
|
1176
|
+
period: Optional[str] = typer.Option(None, "--period", help="day | week | month. Omit for all three."),
|
|
1177
|
+
days: Optional[int] = typer.Option(None, "--days", help="Window size in days, by window_start, ending now. Ignored if --start/--end are given."),
|
|
1178
|
+
start: Optional[str] = typer.Option(None, "--start", help="ISO date/datetime lower bound on window_start, inclusive. Takes priority over --days."),
|
|
1179
|
+
end: Optional[str] = typer.Option(None, "--end", help="ISO date/datetime upper bound on window_start, exclusive. Takes priority over --days."),
|
|
1180
|
+
limit: Optional[int] = typer.Option(None, "--limit", help="Cap the number of rows shown. Omit to page through everything interactively."),
|
|
1181
|
+
as_json: bool = typer.Option(False, "--json", help="Print raw JSON instead of a human-readable table."),
|
|
1182
|
+
):
|
|
1183
|
+
"""Pre-computed daily/weekly/monthly statistics for a device, voltage/current/power/energy only (GET /hermes/windows/{device_id}). Not real-time - see `qapu trend`/`qapu data` for live readings."""
|
|
1184
|
+
|
|
1185
|
+
# Translate --period's Friendly Name Into the Raw int the API Expects
|
|
1186
|
+
period_type: Optional[int] = None
|
|
1187
|
+
if period is not None:
|
|
1188
|
+
period_type = _WINDOWS_PERIOD_TYPE_TO_CODE.get(period.strip().lower())
|
|
1189
|
+
if period_type is None:
|
|
1190
|
+
console.print(f"[red]Geçersiz --period değeri: {period!r} (day, week veya month olmalı)[/red]")
|
|
1191
|
+
raise typer.Exit(code=1)
|
|
1192
|
+
|
|
1193
|
+
# Fetch the Rows
|
|
1194
|
+
params: dict[str, Any] = {}
|
|
1195
|
+
if variable_id:
|
|
1196
|
+
params["variable_id"] = variable_id
|
|
1197
|
+
if period_type is not None:
|
|
1198
|
+
params["period_type"] = period_type
|
|
1199
|
+
if start or end:
|
|
1200
|
+
if start:
|
|
1201
|
+
params["start"] = start
|
|
1202
|
+
if end:
|
|
1203
|
+
params["end"] = end
|
|
1204
|
+
elif days is not None:
|
|
1205
|
+
params["days"] = days
|
|
1206
|
+
rows: list[dict[str, Any]] = client.get(f"/hermes/windows/{device_id}", params=params)
|
|
1207
|
+
|
|
1208
|
+
# JSON Output - Nothing Further to Format
|
|
1209
|
+
if as_json:
|
|
1210
|
+
typer.echo(json.dumps(rows, indent=2))
|
|
1211
|
+
return
|
|
1212
|
+
|
|
1213
|
+
# No Rows - Say so Plainly
|
|
1214
|
+
if not rows:
|
|
1215
|
+
console.print(f"[yellow]{device_id} için kayıt bulunamadı.[/yellow]")
|
|
1216
|
+
return
|
|
1217
|
+
|
|
1218
|
+
# Build and Print the Table, Paginated Like Every Other Long List in
|
|
1219
|
+
# This CLI (device/variable/synthesis list) - a Device's own Window
|
|
1220
|
+
# History Can Easily Run Into the Hundreds of Rows Once Every Variable
|
|
1221
|
+
# and Period Type is Included.
|
|
1222
|
+
build_table = _simple_table_builder([
|
|
1223
|
+
("DÖNEM", "period_type"),
|
|
1224
|
+
("DEĞİŞKEN", "variable_id"),
|
|
1225
|
+
("BAŞLANGIÇ", "window_start"),
|
|
1226
|
+
("ORT", "mean"),
|
|
1227
|
+
("MIN", "min"),
|
|
1228
|
+
("MAKS", "max"),
|
|
1229
|
+
("ÖRNEK", "sample_count"),
|
|
1230
|
+
("TAMLIK", "completeness_ratio"),
|
|
1231
|
+
])
|
|
1232
|
+
|
|
1233
|
+
# Pretty-Print Each Row's period_type/window_start/completeness_ratio
|
|
1234
|
+
# Before Handing off to the Generic Table Builder - Raw int Period
|
|
1235
|
+
# Codes and ISO Timestamps/Ratios Read Poorly Side by Side With Every
|
|
1236
|
+
# Other Column's Already-Human Values.
|
|
1237
|
+
display_rows = []
|
|
1238
|
+
for row in rows:
|
|
1239
|
+
display_row = dict(row)
|
|
1240
|
+
display_row["period_type"] = _WINDOWS_PERIOD_CODE_TO_LABEL.get(row.get("period_type"), row.get("period_type"))
|
|
1241
|
+
display_row["window_start"] = _format_time(row.get("window_start"))
|
|
1242
|
+
ratio = row.get("completeness_ratio")
|
|
1243
|
+
display_row["completeness_ratio"] = f"%{ratio * 100:.0f}" if ratio is not None else "-"
|
|
1244
|
+
for numeric_col in ("mean", "min", "max"):
|
|
1245
|
+
value = row.get(numeric_col)
|
|
1246
|
+
if isinstance(value, (int, float)):
|
|
1247
|
+
display_row[numeric_col] = round(value, 3)
|
|
1248
|
+
display_rows.append(display_row)
|
|
1249
|
+
|
|
1250
|
+
_print_paginated(display_rows, build_table, limit, "pencere")
|
|
1251
|
+
|
|
1252
|
+
|
|
1164
1253
|
@device_app.command("purge-history")
|
|
1165
1254
|
def device_purge_history(
|
|
1166
1255
|
device_id: str = typer.Argument(..., help="Device ID."),
|
|
@@ -5419,9 +5508,14 @@ def calibration_list(device_id: str = typer.Argument(...), as_json: bool = typer
|
|
|
5419
5508
|
typer.echo("Bu cihaz için kalibrasyon kaydı yok.")
|
|
5420
5509
|
return
|
|
5421
5510
|
|
|
5422
|
-
# Build the Table
|
|
5511
|
+
# Build the Table - SCOPE (2026-10-15, calibration_profiles/
|
|
5512
|
+
# calibration_assignments Redesign) Shows Which Scope Tier This
|
|
5513
|
+
# Device's Effective Value Actually Came From; ID is Blank for a
|
|
5514
|
+
# GROUP/GLOBAL-Inherited row (not Directly Editable at This Device's
|
|
5515
|
+
# Level - See Calibration.list()'s own Docstring)
|
|
5423
5516
|
table = Table(show_header=True, header_style="bold")
|
|
5424
5517
|
table.add_column("ID", justify="right")
|
|
5518
|
+
table.add_column("SCOPE")
|
|
5425
5519
|
table.add_column("VARIABLE")
|
|
5426
5520
|
table.add_column("GAIN", justify="right")
|
|
5427
5521
|
table.add_column("OFFSET", justify="right")
|
|
@@ -5430,7 +5524,8 @@ def calibration_list(device_id: str = typer.Argument(...), as_json: bool = typer
|
|
|
5430
5524
|
for item in items:
|
|
5431
5525
|
variable = item.get("Variable") or {}
|
|
5432
5526
|
table.add_row(
|
|
5433
|
-
str(item.get("ID")),
|
|
5527
|
+
str(item.get("ID")) if item.get("ID") is not None else "-",
|
|
5528
|
+
item.get("Scope") or "-",
|
|
5434
5529
|
variable.get("ID") or "-",
|
|
5435
5530
|
str(item.get("Gain")) if item.get("Gain") is not None else "-",
|
|
5436
5531
|
str(item.get("Offset")) if item.get("Offset") is not None else "-",
|
|
@@ -5496,6 +5591,195 @@ def calibration_delete(calibration_id: int = typer.Argument(...)):
|
|
|
5496
5591
|
typer.echo(result.get("Message", result))
|
|
5497
5592
|
|
|
5498
5593
|
|
|
5594
|
+
# Calibration Profile Commands (2026-10-15) - Reusable Gain/Offset
|
|
5595
|
+
# Definitions, Genuinely Shareable Across Many Calibration_Assignment rows
|
|
5596
|
+
|
|
5597
|
+
@calibration_profile_app.command("list")
|
|
5598
|
+
def calibration_profile_list(variable_id: Optional[str] = typer.Option(None, "--variable-id"), as_json: bool = typer.Option(False, "--json")):
|
|
5599
|
+
"""List reusable calibration profiles (GET /hermes/calibration-profiles)."""
|
|
5600
|
+
items: list[dict[str, Any]] = client.get("/hermes/calibration-profiles", params={"variable_id": variable_id} if variable_id else None)
|
|
5601
|
+
if as_json:
|
|
5602
|
+
typer.echo(json.dumps(items, indent=2))
|
|
5603
|
+
return
|
|
5604
|
+
if not items:
|
|
5605
|
+
typer.echo("Hiç kalibrasyon profili yok.")
|
|
5606
|
+
return
|
|
5607
|
+
table = Table(show_header=True, header_style="bold")
|
|
5608
|
+
table.add_column("ID", justify="right")
|
|
5609
|
+
table.add_column("VARIABLE")
|
|
5610
|
+
table.add_column("GAIN", justify="right")
|
|
5611
|
+
table.add_column("OFFSET", justify="right")
|
|
5612
|
+
table.add_column("ACTIVE")
|
|
5613
|
+
table.add_column("DESCRIPTION")
|
|
5614
|
+
for item in items:
|
|
5615
|
+
variable = item.get("Variable") or {}
|
|
5616
|
+
table.add_row(str(item.get("ID")), variable.get("ID") or "-", str(item.get("Gain")), str(item.get("Offset")), "✓" if item.get("Is_Active") else "-", item.get("Description") or "-")
|
|
5617
|
+
console.print(table)
|
|
5618
|
+
|
|
5619
|
+
|
|
5620
|
+
@calibration_profile_app.command("get")
|
|
5621
|
+
def calibration_profile_get(profile_id: int = typer.Argument(...), as_json: bool = typer.Option(False, "--json")):
|
|
5622
|
+
"""Get one calibration profile's detail (GET /hermes/calibration-profiles/{profile_id})."""
|
|
5623
|
+
result = client.get(f"/hermes/calibration-profiles/{profile_id}")
|
|
5624
|
+
if as_json:
|
|
5625
|
+
typer.echo(json.dumps(result, indent=2))
|
|
5626
|
+
return
|
|
5627
|
+
_print_detail(result)
|
|
5628
|
+
|
|
5629
|
+
|
|
5630
|
+
@calibration_profile_app.command("add")
|
|
5631
|
+
def calibration_profile_add(
|
|
5632
|
+
variable_id: str = typer.Argument(..., help="Variable ID this profile applies to."),
|
|
5633
|
+
gain: float = typer.Option(..., "--gain"),
|
|
5634
|
+
offset: float = typer.Option(..., "--offset"),
|
|
5635
|
+
description: Optional[str] = typer.Option(None, "--description"),
|
|
5636
|
+
):
|
|
5637
|
+
"""Create a new, standalone calibration profile - not assigned to anything until a calibration-assignment points at it (POST /hermes/calibration-profiles)."""
|
|
5638
|
+
body: dict[str, Any] = {"Variable": {"ID": variable_id}, "Gain": gain, "Offset": offset}
|
|
5639
|
+
if description is not None:
|
|
5640
|
+
body["Description"] = description
|
|
5641
|
+
result = client.post("/hermes/calibration-profiles", json_body=body)
|
|
5642
|
+
typer.echo(result.get("Message", result))
|
|
5643
|
+
|
|
5644
|
+
|
|
5645
|
+
@calibration_profile_app.command("update")
|
|
5646
|
+
def calibration_profile_update(
|
|
5647
|
+
profile_id: int = typer.Argument(...),
|
|
5648
|
+
gain: Optional[float] = typer.Option(None, "--gain"),
|
|
5649
|
+
offset: Optional[float] = typer.Option(None, "--offset"),
|
|
5650
|
+
is_active: Optional[bool] = typer.Option(None, "--active/--inactive"),
|
|
5651
|
+
description: Optional[str] = typer.Option(None, "--description"),
|
|
5652
|
+
):
|
|
5653
|
+
"""Update a calibration profile - only the fields passed are changed. Affects every assignment pointing at it (PUT /hermes/calibration-profiles/{profile_id})."""
|
|
5654
|
+
body: dict[str, Any] = {}
|
|
5655
|
+
if gain is not None:
|
|
5656
|
+
body["Gain"] = gain
|
|
5657
|
+
if offset is not None:
|
|
5658
|
+
body["Offset"] = offset
|
|
5659
|
+
if is_active is not None:
|
|
5660
|
+
body["Is_Active"] = is_active
|
|
5661
|
+
if description is not None:
|
|
5662
|
+
body["Description"] = description
|
|
5663
|
+
result = client.put(f"/hermes/calibration-profiles/{profile_id}", json_body=body)
|
|
5664
|
+
typer.echo(result.get("Message", result))
|
|
5665
|
+
|
|
5666
|
+
|
|
5667
|
+
@calibration_profile_app.command("delete")
|
|
5668
|
+
def calibration_profile_delete(profile_id: int = typer.Argument(...)):
|
|
5669
|
+
"""Delete a calibration profile - refuses if any assignment still points at it (DELETE /hermes/calibration-profiles/{profile_id})."""
|
|
5670
|
+
result = client.delete(f"/hermes/calibration-profiles/{profile_id}")
|
|
5671
|
+
typer.echo(result.get("Message", result))
|
|
5672
|
+
|
|
5673
|
+
|
|
5674
|
+
# Calibration Assignment Commands (2026-10-15) - DEVICE/GROUP/GLOBAL
|
|
5675
|
+
# Scope, Priority, Time-Windowed Validity. The Only way to Create a
|
|
5676
|
+
# GROUP/GLOBAL Assignment, or to Point a new Assignment at an Existing
|
|
5677
|
+
# Profile for Real Reuse.
|
|
5678
|
+
|
|
5679
|
+
@calibration_assignment_app.command("list")
|
|
5680
|
+
def calibration_assignment_list(
|
|
5681
|
+
variable_id: Optional[str] = typer.Option(None, "--variable-id"),
|
|
5682
|
+
device_id: Optional[str] = typer.Option(None, "--device-id"),
|
|
5683
|
+
group_id: Optional[int] = typer.Option(None, "--group-id"),
|
|
5684
|
+
scope_type: Optional[str] = typer.Option(None, "--scope", help="DEVICE / GROUP / GLOBAL"),
|
|
5685
|
+
calibration_id: Optional[int] = typer.Option(None, "--profile-id"),
|
|
5686
|
+
as_json: bool = typer.Option(False, "--json"),
|
|
5687
|
+
):
|
|
5688
|
+
"""List calibration assignments (GET /hermes/calibration-assignments)."""
|
|
5689
|
+
params = {"variable_id": variable_id, "device_id": device_id, "group_id": group_id, "scope_type": scope_type, "calibration_id": calibration_id}
|
|
5690
|
+
items: list[dict[str, Any]] = client.get("/hermes/calibration-assignments", params={k: v for k, v in params.items() if v is not None})
|
|
5691
|
+
if as_json:
|
|
5692
|
+
typer.echo(json.dumps(items, indent=2))
|
|
5693
|
+
return
|
|
5694
|
+
if not items:
|
|
5695
|
+
typer.echo("Hiç kalibrasyon ataması yok.")
|
|
5696
|
+
return
|
|
5697
|
+
table = Table(show_header=True, header_style="bold")
|
|
5698
|
+
table.add_column("ID", justify="right")
|
|
5699
|
+
table.add_column("PROFILE", justify="right")
|
|
5700
|
+
table.add_column("VARIABLE")
|
|
5701
|
+
table.add_column("SCOPE")
|
|
5702
|
+
table.add_column("TARGET")
|
|
5703
|
+
table.add_column("PRIORITY", justify="right")
|
|
5704
|
+
table.add_column("ACTIVE")
|
|
5705
|
+
table.add_column("EFFECTIVE FROM")
|
|
5706
|
+
table.add_column("EFFECTIVE TO")
|
|
5707
|
+
for item in items:
|
|
5708
|
+
target = item.get("Device_ID") or (f"group:{item.get('Group_ID')}" if item.get("Group_ID") else "-")
|
|
5709
|
+
table.add_row(
|
|
5710
|
+
str(item.get("ID")), str(item.get("Calibration_ID")), item.get("Variable_ID") or "-", item.get("Scope_Type") or "-",
|
|
5711
|
+
target, str(item.get("Priority_Override")) if item.get("Priority_Override") is not None else "-",
|
|
5712
|
+
"✓" if item.get("Is_Active") else "-", item.get("Effective_From") or "-", item.get("Effective_To") or "-",
|
|
5713
|
+
)
|
|
5714
|
+
console.print(table)
|
|
5715
|
+
|
|
5716
|
+
|
|
5717
|
+
@calibration_assignment_app.command("get")
|
|
5718
|
+
def calibration_assignment_get(assignment_id: int = typer.Argument(...), as_json: bool = typer.Option(False, "--json")):
|
|
5719
|
+
"""Get one calibration assignment's detail (GET /hermes/calibration-assignments/{assignment_id})."""
|
|
5720
|
+
result = client.get(f"/hermes/calibration-assignments/{assignment_id}")
|
|
5721
|
+
if as_json:
|
|
5722
|
+
typer.echo(json.dumps(result, indent=2))
|
|
5723
|
+
return
|
|
5724
|
+
_print_detail(result)
|
|
5725
|
+
|
|
5726
|
+
|
|
5727
|
+
@calibration_assignment_app.command("add")
|
|
5728
|
+
def calibration_assignment_add(
|
|
5729
|
+
profile_id: int = typer.Argument(..., help="The calibration-profile ID this assignment points at."),
|
|
5730
|
+
scope: str = typer.Option(..., "--scope", help="DEVICE / GROUP / GLOBAL"),
|
|
5731
|
+
device_id: Optional[str] = typer.Option(None, "--device-id", help="Required for --scope DEVICE."),
|
|
5732
|
+
group_id: Optional[int] = typer.Option(None, "--group-id", help="Required for --scope GROUP."),
|
|
5733
|
+
priority: Optional[int] = typer.Option(None, "--priority", help="Lower value wins ties (e.g. a device belonging to multiple groups)."),
|
|
5734
|
+
):
|
|
5735
|
+
"""Create a new calibration assignment - the only way to create a GROUP/GLOBAL-scope assignment, or to point a new assignment at an existing profile for real reuse (POST /hermes/calibration-assignments)."""
|
|
5736
|
+
body: dict[str, Any] = {"Calibration_ID": profile_id, "Scope_Type": scope.upper()}
|
|
5737
|
+
if device_id is not None:
|
|
5738
|
+
body["Device_ID"] = device_id
|
|
5739
|
+
if group_id is not None:
|
|
5740
|
+
body["Group_ID"] = group_id
|
|
5741
|
+
if priority is not None:
|
|
5742
|
+
body["Priority_Override"] = priority
|
|
5743
|
+
result = client.post("/hermes/calibration-assignments", json_body=body)
|
|
5744
|
+
typer.echo(result.get("Message", result))
|
|
5745
|
+
|
|
5746
|
+
|
|
5747
|
+
@calibration_assignment_app.command("update")
|
|
5748
|
+
def calibration_assignment_update(
|
|
5749
|
+
assignment_id: int = typer.Argument(...),
|
|
5750
|
+
priority: Optional[int] = typer.Option(None, "--priority"),
|
|
5751
|
+
effective_from: Optional[str] = typer.Option(None, "--effective-from"),
|
|
5752
|
+
effective_to: Optional[str] = typer.Option(None, "--effective-to"),
|
|
5753
|
+
is_active: Optional[bool] = typer.Option(None, "--active/--inactive"),
|
|
5754
|
+
):
|
|
5755
|
+
"""Update an assignment's priority/validity window/active flag - scope/target/profile are not editable here (delete+add instead) (PUT /hermes/calibration-assignments/{assignment_id})."""
|
|
5756
|
+
body: dict[str, Any] = {}
|
|
5757
|
+
if priority is not None:
|
|
5758
|
+
body["Priority_Override"] = priority
|
|
5759
|
+
if effective_from is not None:
|
|
5760
|
+
body["Effective_From"] = effective_from
|
|
5761
|
+
if effective_to is not None:
|
|
5762
|
+
body["Effective_To"] = effective_to
|
|
5763
|
+
if is_active is not None:
|
|
5764
|
+
body["Is_Active"] = is_active
|
|
5765
|
+
result = client.put(f"/hermes/calibration-assignments/{assignment_id}", json_body=body)
|
|
5766
|
+
typer.echo(result.get("Message", result))
|
|
5767
|
+
|
|
5768
|
+
|
|
5769
|
+
@calibration_assignment_app.command("retire")
|
|
5770
|
+
def calibration_assignment_retire(assignment_id: int = typer.Argument(...)):
|
|
5771
|
+
"""Retire an assignment (Effective_To=now(), Is_Active=false) - preserves it as real history instead of deleting it (POST /hermes/calibration-assignments/{assignment_id}/retire)."""
|
|
5772
|
+
result = client.post(f"/hermes/calibration-assignments/{assignment_id}/retire", json_body={})
|
|
5773
|
+
typer.echo(result.get("Message", result))
|
|
5774
|
+
|
|
5775
|
+
|
|
5776
|
+
@calibration_assignment_app.command("delete")
|
|
5777
|
+
def calibration_assignment_delete(assignment_id: int = typer.Argument(...)):
|
|
5778
|
+
"""Delete a calibration assignment outright - loses history, prefer 'retire' when the point is to preserve it (DELETE /hermes/calibration-assignments/{assignment_id})."""
|
|
5779
|
+
result = client.delete(f"/hermes/calibration-assignments/{assignment_id}")
|
|
5780
|
+
typer.echo(result.get("Message", result))
|
|
5781
|
+
|
|
5782
|
+
|
|
5499
5783
|
# Device Trigger-Rule Commands (2026-10-10) - This is the Real Device-
|
|
5500
5784
|
# Side Rule Engine services/rule Evaluates on Every qapu.stream Message
|
|
5501
5785
|
# (Low Voltage/High Current/Pump Active/etc.) - Distinct From `synthesis`
|
|
@@ -7661,7 +7945,7 @@ def _console_fetch(device_id: str) -> dict[str, Any]:
|
|
|
7661
7945
|
@auth_token_app.command("create")
|
|
7662
7946
|
def auth_token_create(
|
|
7663
7947
|
label: str = typer.Argument(..., help="A human-readable label for this token, e.g. 'macbook-mcp'."),
|
|
7664
|
-
packages: str = typer.Option(..., "--packages", help="Comma-separated permission package codes, e.g.
|
|
7948
|
+
packages: str = typer.Option(..., "--packages", help="Comma-separated permission package codes, e.g. devices:read,telemetry:read,infrastructure:read - must be a subset of your own granted packages (see `qapu user tiers` for the current catalog; `catalog:read`/`infrastructure:*`/`telemetry:write` are the RETIRED pre-2026-10-10 names and no longer unlock any route)."),
|
|
7665
7949
|
expires_in_days: int = typer.Option(90, "--expires-in-days"),
|
|
7666
7950
|
no_save: bool = typer.Option(False, "--no-save", help="Skip the 'save as active Hermes token?' prompt entirely - print the raw token and nothing else."),
|
|
7667
7951
|
):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qapu-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.76.0
|
|
4
4
|
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
5
|
Author: OVOO Technology
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|