qapu-cli 0.7.2__tar.gz → 0.7.3__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.7.2/qapu_cli.egg-info → qapu_cli-0.7.3}/PKG-INFO +4 -2
- qapu_cli-0.7.2/PKG-INFO → qapu_cli-0.7.3/README.md +3 -13
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/pyproject.toml +1 -1
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli/main.py +88 -23
- qapu_cli-0.7.2/README.md → qapu_cli-0.7.3/qapu_cli.egg-info/PKG-INFO +15 -1
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli/__init__.py +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli/client.py +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli.egg-info/SOURCES.txt +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli.egg-info/dependency_links.txt +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli.egg-info/entry_points.txt +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli.egg-info/requires.txt +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/qapu_cli.egg-info/top_level.txt +0 -0
- {qapu_cli-0.7.2 → qapu_cli-0.7.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qapu-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
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
|
|
@@ -59,7 +59,7 @@ Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`
|
|
|
59
59
|
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), or an explicit --start/--end range (GET /hermes/data/{device_id}). |
|
|
60
60
|
| `qapu energy` | One day's active/reactive energy breakdown per phase (GET /hermes/energy/{device_id}). |
|
|
61
61
|
| `qapu health` | Check CLI/server version, connectivity, and server status (GET /health, no auth required). |
|
|
62
|
-
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). |
|
|
62
|
+
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). `--watch` live-refreshes in place. |
|
|
63
63
|
| `qapu power` | Power synthesis parameters, grouped (active/reactive/apparent, imbalance, power factor) (GET /hermes/data/{device_id}). |
|
|
64
64
|
| `qapu timeline` | Get one device's timeline events (GET /hermes/timeline/{device_id}). |
|
|
65
65
|
| `qapu trend` | Trend for one variable on one device: recent readings, daily min/avg/max, or an explicit --start/--end range (GET /hermes/trend/{device_id}/{variable_id}). |
|
|
@@ -368,6 +368,8 @@ qapu pipeline --json
|
|
|
368
368
|
```
|
|
369
369
|
Not device-scoped - a fleet-wide summary across every service in the `ingest -> stream -> calibration -> raw-writer -> synthesis -> ... -> rule` pipeline. Deliberately reads from the real tables each stage actually writes to (`raw_data.stream_time`/`valid_pack` for `hardware`'s ingest, `streams.stream_time` for `data`'s calibration+synthesis completion, `device_timeline.create_time` for `rule`'s observable output, `blockchain.create_time` for mined blocks) rather than any service's own `/health` endpoint - those are point-in-time snapshots (uptime, CPU, last-message-age), not a windowed throughput count. New `common/qapu_common/domain/pipeline.py` (`Pipeline_Stats.get_stats(minutes)`, four plain `SELECT count(*) ... WHERE <time column> >= now() - interval` queries, no caching - these are meant to be live) + `GET /hermes/pipeline/stats` (`services/api/src/routers/hermes.py`, `minutes` query param, 1 to 10080/7 days). **`Rule_Events` deliberately does NOT count rule evaluations** - `rule` evaluates every single stream message 1:1, the same count `Data_Processed` already reports, so repeating it would be redundant; it counts `device_timeline` rows instead, rule's actual observable output (a trigger/reset transition), not every evaluation pass. The CLI prints a bordered summary table plus a soft backlog hint (yellow note, not an error) if `Data_Processed` looks meaningfully behind `Ingest_Valid` (`<80%`) - a sign of consumer lag worth checking, not itself proof of one. Live-tested against real prod DB: `--minutes 60` correctly returned all zeros (no test traffic in the preceding hour at that point in the session), `--minutes 1440` correctly returned real 24h counts, and a fresh real packet sent immediately before a `--minutes 5` check showed up as exactly `+1` across Ingest/Data_Processed/Blockchain_Mined (Rule_Events stayed `0`, correctly - that packet's only variable, `PCB_T`, didn't cross any rule threshold).
|
|
370
370
|
|
|
371
|
+
**`--watch`/`-w` added the same day**, per direct follow-up request ("pipe line canlı takip edilebilecek bir tool yapabilirmiyiz") - polls the same endpoint every `--interval` seconds (default `5.0`) and redraws the summary in place via Rich's `Live`, instead of printing once and exiting; `Ctrl+C` stops cleanly. Each poll after the first also shows a `DEĞİŞİM` column - green `+N` if a stage's count grew since the last poll, red `-N` if it somehow shrank (shouldn't happen for these monotonic counters within a fixed window, but rendered anyway rather than assumed impossible), `·` if unchanged - a bare redrawn count doesn't read as "live" the way a visible delta does. Refactored the table-building logic into a shared `_build_pipeline_group()` (title + table + optional backlog note, returned as one `rich.console.Group`) used by both the one-shot path and the `--watch` loop, so they can't drift apart. Not combinable with `--json` (a live-refreshed raw-JSON dump isn't a coherent thing to build - errors clearly if both are passed). The delta/redraw logic itself was verified directly (calling `_build_pipeline_group()` with two synthetic consecutive polls and confirming the `+4`/`+2`/`·` rendering, plus the empty-result error path) rather than through a full `--watch` session end to end - `rich.Live`'s screen-redraw behavior depends on `console.is_terminal`, which this environment's piped/non-TTY test harness can't exercise the same way a real interactive terminal does; confirm the actual live redraw looks right in a real terminal session if this is touched again.
|
|
372
|
+
|
|
371
373
|
`qapu trend` shows a per-variable history plus a simple linear-regression trend line (slope + direction). Three modes, picked by which options you pass:
|
|
372
374
|
- **Raw readings** (`--last N`, default `N=20`): the measurement cache's rolling buffer - bounded to its own depth (~50 most recent packets), *not* a time window. Good for "what's it doing right now."
|
|
373
375
|
- **Daily aggregates** (`--days N`): one row per calendar day (min/avg/max/count), from the same cache the `/measurement/{device_id}/history` endpoint reads - this is what actually covers multi-day/month windows, since the raw buffer doesn't go back that far. `--days` wins over `--last` if both are given (and `--start`/`--end` isn't).
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: qapu-cli
|
|
3
|
-
Version: 0.7.2
|
|
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
|
-
Author: OVOO Technology
|
|
6
|
-
Classifier: Programming Language :: Python :: 3
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: typer<1.0,>=0.12
|
|
10
|
-
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
-
Requires-Dist: rich<16.0,>=13.0
|
|
12
|
-
|
|
13
1
|
# Qapu CLI
|
|
14
2
|
|
|
15
3
|
A thin command-line client for the Qapu API (`api.ovoo.com.tr`), built for the Hermes agent (runs outside this Swarm, in a separate datacenter, and only ever talks to Qapu through this public API) - but usable by anything else that needs to script against Qapu from outside the private network.
|
|
@@ -59,7 +47,7 @@ Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`
|
|
|
59
47
|
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), or an explicit --start/--end range (GET /hermes/data/{device_id}). |
|
|
60
48
|
| `qapu energy` | One day's active/reactive energy breakdown per phase (GET /hermes/energy/{device_id}). |
|
|
61
49
|
| `qapu health` | Check CLI/server version, connectivity, and server status (GET /health, no auth required). |
|
|
62
|
-
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). |
|
|
50
|
+
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). `--watch` live-refreshes in place. |
|
|
63
51
|
| `qapu power` | Power synthesis parameters, grouped (active/reactive/apparent, imbalance, power factor) (GET /hermes/data/{device_id}). |
|
|
64
52
|
| `qapu timeline` | Get one device's timeline events (GET /hermes/timeline/{device_id}). |
|
|
65
53
|
| `qapu trend` | Trend for one variable on one device: recent readings, daily min/avg/max, or an explicit --start/--end range (GET /hermes/trend/{device_id}/{variable_id}). |
|
|
@@ -368,6 +356,8 @@ qapu pipeline --json
|
|
|
368
356
|
```
|
|
369
357
|
Not device-scoped - a fleet-wide summary across every service in the `ingest -> stream -> calibration -> raw-writer -> synthesis -> ... -> rule` pipeline. Deliberately reads from the real tables each stage actually writes to (`raw_data.stream_time`/`valid_pack` for `hardware`'s ingest, `streams.stream_time` for `data`'s calibration+synthesis completion, `device_timeline.create_time` for `rule`'s observable output, `blockchain.create_time` for mined blocks) rather than any service's own `/health` endpoint - those are point-in-time snapshots (uptime, CPU, last-message-age), not a windowed throughput count. New `common/qapu_common/domain/pipeline.py` (`Pipeline_Stats.get_stats(minutes)`, four plain `SELECT count(*) ... WHERE <time column> >= now() - interval` queries, no caching - these are meant to be live) + `GET /hermes/pipeline/stats` (`services/api/src/routers/hermes.py`, `minutes` query param, 1 to 10080/7 days). **`Rule_Events` deliberately does NOT count rule evaluations** - `rule` evaluates every single stream message 1:1, the same count `Data_Processed` already reports, so repeating it would be redundant; it counts `device_timeline` rows instead, rule's actual observable output (a trigger/reset transition), not every evaluation pass. The CLI prints a bordered summary table plus a soft backlog hint (yellow note, not an error) if `Data_Processed` looks meaningfully behind `Ingest_Valid` (`<80%`) - a sign of consumer lag worth checking, not itself proof of one. Live-tested against real prod DB: `--minutes 60` correctly returned all zeros (no test traffic in the preceding hour at that point in the session), `--minutes 1440` correctly returned real 24h counts, and a fresh real packet sent immediately before a `--minutes 5` check showed up as exactly `+1` across Ingest/Data_Processed/Blockchain_Mined (Rule_Events stayed `0`, correctly - that packet's only variable, `PCB_T`, didn't cross any rule threshold).
|
|
370
358
|
|
|
359
|
+
**`--watch`/`-w` added the same day**, per direct follow-up request ("pipe line canlı takip edilebilecek bir tool yapabilirmiyiz") - polls the same endpoint every `--interval` seconds (default `5.0`) and redraws the summary in place via Rich's `Live`, instead of printing once and exiting; `Ctrl+C` stops cleanly. Each poll after the first also shows a `DEĞİŞİM` column - green `+N` if a stage's count grew since the last poll, red `-N` if it somehow shrank (shouldn't happen for these monotonic counters within a fixed window, but rendered anyway rather than assumed impossible), `·` if unchanged - a bare redrawn count doesn't read as "live" the way a visible delta does. Refactored the table-building logic into a shared `_build_pipeline_group()` (title + table + optional backlog note, returned as one `rich.console.Group`) used by both the one-shot path and the `--watch` loop, so they can't drift apart. Not combinable with `--json` (a live-refreshed raw-JSON dump isn't a coherent thing to build - errors clearly if both are passed). The delta/redraw logic itself was verified directly (calling `_build_pipeline_group()` with two synthetic consecutive polls and confirming the `+4`/`+2`/`·` rendering, plus the empty-result error path) rather than through a full `--watch` session end to end - `rich.Live`'s screen-redraw behavior depends on `console.is_terminal`, which this environment's piped/non-TTY test harness can't exercise the same way a real interactive terminal does; confirm the actual live redraw looks right in a real terminal session if this is touched again.
|
|
360
|
+
|
|
371
361
|
`qapu trend` shows a per-variable history plus a simple linear-regression trend line (slope + direction). Three modes, picked by which options you pass:
|
|
372
362
|
- **Raw readings** (`--last N`, default `N=20`): the measurement cache's rolling buffer - bounded to its own depth (~50 most recent packets), *not* a time window. Good for "what's it doing right now."
|
|
373
363
|
- **Daily aggregates** (`--days N`): one row per calendar day (min/avg/max/count), from the same cache the `/measurement/{device_id}/history` endpoint reads - this is what actually covers multi-day/month windows, since the raw buffer doesn't go back that far. `--days` wins over `--last` if both are given (and `--start`/`--end` isn't).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "qapu-cli"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.3"
|
|
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"
|
|
@@ -15,7 +15,8 @@ from typing import Any, Optional
|
|
|
15
15
|
import httpx
|
|
16
16
|
import typer
|
|
17
17
|
from rich.columns import Columns
|
|
18
|
-
from rich.console import Console
|
|
18
|
+
from rich.console import Console, Group
|
|
19
|
+
from rich.live import Live
|
|
19
20
|
from rich.panel import Panel
|
|
20
21
|
from rich.table import Table
|
|
21
22
|
from rich.tree import Tree
|
|
@@ -1256,32 +1257,49 @@ def device_energy(
|
|
|
1256
1257
|
# Get One Variable's Trend on One Device - Either the Last N Raw Buffered
|
|
1257
1258
|
# Readings, or Daily Min/Avg/Max Over the Last N Days (Mutually Exclusive -
|
|
1258
1259
|
# --days Wins if Both are Given)
|
|
1259
|
-
|
|
1260
|
-
def pipeline_stats(
|
|
1261
|
-
minutes: int = typer.Option(60, "--minutes", help="Window size in minutes (default 60, max 10080/7 days)."),
|
|
1262
|
-
as_json: bool = typer.Option(False, "--json", help="Print raw JSON instead of the summary table."),
|
|
1263
|
-
):
|
|
1264
|
-
"""Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats)."""
|
|
1265
|
-
result: dict[str, Any] = client.get("/hermes/pipeline/stats", params={"minutes": minutes})
|
|
1260
|
+
def _window_label(minutes: int) -> str:
|
|
1266
1261
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
return
|
|
1262
|
+
# Format a Window Size as "N dk" or "N sa" (Whole Hours Only)
|
|
1263
|
+
if minutes < 60 or minutes % 60 != 0:
|
|
1264
|
+
return f"{minutes} dk"
|
|
1265
|
+
return f"{minutes // 60} sa"
|
|
1270
1266
|
|
|
1271
|
-
if not result:
|
|
1272
|
-
typer.echo("İstatistikler alınamadı.")
|
|
1273
|
-
return
|
|
1274
1267
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1268
|
+
# Build the Pipeline Summary as a Single Renderable (Title + Table +
|
|
1269
|
+
# Optional Backlog Note) - Shared by the One-Shot Print and the --watch
|
|
1270
|
+
# Live Loop Below, so Both Stay in Sync as the Table's Shape Evolves.
|
|
1271
|
+
# `previous` (the Last Poll's raw stats dict, or None) Lets --watch Show a
|
|
1272
|
+
# "+N since last check" Delta per Stage, Which is What Actually Makes a
|
|
1273
|
+
# Polling Loop Feel "Live" Rather than Just a Table that Happens to
|
|
1274
|
+
# Redraw - a Bare Count with No Motion Indicator Doesn't Read as
|
|
1275
|
+
# Streaming.
|
|
1276
|
+
def _build_pipeline_group(result: dict[str, Any], minutes: int, previous: Optional[dict[str, Any]] = None, as_of: Optional[str] = None) -> Group:
|
|
1277
|
+
|
|
1278
|
+
title = f"[bold]Pipeline Özeti[/bold] - son {_window_label(minutes)}"
|
|
1279
|
+
if as_of:
|
|
1280
|
+
title += f" [dim](güncellendi: {as_of})[/dim]"
|
|
1277
1281
|
|
|
1278
|
-
|
|
1282
|
+
if not result:
|
|
1283
|
+
return Group(title, "\n[red]İstatistikler alınamadı.[/red]")
|
|
1279
1284
|
|
|
1280
1285
|
table = Table(show_header=True, header_style="bold")
|
|
1281
1286
|
table.add_column("AŞAMA")
|
|
1282
1287
|
table.add_column("SAYI", justify="right")
|
|
1288
|
+
if previous is not None:
|
|
1289
|
+
table.add_column("DEĞİŞİM", justify="right")
|
|
1283
1290
|
table.add_column("DETAY")
|
|
1284
1291
|
|
|
1292
|
+
def delta_str(key: str, current: int) -> str:
|
|
1293
|
+
if previous is None:
|
|
1294
|
+
return ""
|
|
1295
|
+
prev_value = previous.get(key, 0) or 0
|
|
1296
|
+
diff = current - prev_value
|
|
1297
|
+
if diff > 0:
|
|
1298
|
+
return f"[green]+{diff}[/green]"
|
|
1299
|
+
if diff < 0:
|
|
1300
|
+
return f"[red]{diff}[/red]"
|
|
1301
|
+
return "·"
|
|
1302
|
+
|
|
1285
1303
|
ingest_total = result.get("Ingest_Total", 0)
|
|
1286
1304
|
ingest_valid = result.get("Ingest_Valid", 0)
|
|
1287
1305
|
ingest_invalid = result.get("Ingest_Invalid", 0)
|
|
@@ -1289,18 +1307,65 @@ def pipeline_stats(
|
|
|
1289
1307
|
rule_events = result.get("Rule_Events", 0)
|
|
1290
1308
|
blocks_mined = result.get("Blockchain_Mined", 0)
|
|
1291
1309
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1310
|
+
rows = [
|
|
1311
|
+
("Ingest (hardware)", ingest_total, "Ingest_Total", f"{ingest_valid} geçerli, {ingest_invalid} geçersiz"),
|
|
1312
|
+
("Data İşlendi (data)", data_processed, "Data_Processed", "kalibrasyon + sentez tamamlanan paket"),
|
|
1313
|
+
("Kural Olayları (rule)", rule_events, "Rule_Events", "tetiklenen/sıfırlanan olay (device_timeline)"),
|
|
1314
|
+
("Blockchain Kazıldı", blocks_mined, "Blockchain_Mined", "yeni blok"),
|
|
1315
|
+
]
|
|
1316
|
+
for label, value, key, detail in rows:
|
|
1317
|
+
row = [label, str(value)]
|
|
1318
|
+
if previous is not None:
|
|
1319
|
+
row.append(delta_str(key, value))
|
|
1320
|
+
row.append(detail)
|
|
1321
|
+
table.add_row(*row)
|
|
1296
1322
|
|
|
1297
|
-
|
|
1323
|
+
parts: list[Any] = [title, table]
|
|
1298
1324
|
|
|
1299
1325
|
# Simple Backlog Hint - not an Alert, Just a Note if the Numbers Look
|
|
1300
1326
|
# Off (data_processed Should Track ingest_valid Closely Since data
|
|
1301
1327
|
# Consumes qapu.raw 1:1)
|
|
1302
1328
|
if ingest_valid > 0 and data_processed < ingest_valid * 0.8:
|
|
1303
|
-
|
|
1329
|
+
parts.append(f"[yellow]Not: Data işlenen paket sayısı ({data_processed}), geçerli ingest sayısından ({ingest_valid}) belirgin şekilde düşük - olası birikme/gecikme, kontrol edilebilir.[/yellow]")
|
|
1330
|
+
|
|
1331
|
+
return Group(*parts)
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
@app.command("pipeline")
|
|
1335
|
+
def pipeline_stats(
|
|
1336
|
+
minutes: int = typer.Option(60, "--minutes", help="Window size in minutes (default 60, max 10080/7 days)."),
|
|
1337
|
+
watch: bool = typer.Option(False, "--watch", "-w", help="Live-refresh the summary in place every --interval seconds (Ctrl+C to stop), instead of printing once."),
|
|
1338
|
+
interval: float = typer.Option(5.0, "--interval", help="Seconds between refreshes in --watch mode."),
|
|
1339
|
+
as_json: bool = typer.Option(False, "--json", help="Print raw JSON instead of the summary table. Not combinable with --watch."),
|
|
1340
|
+
):
|
|
1341
|
+
"""Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats)."""
|
|
1342
|
+
|
|
1343
|
+
if as_json and watch:
|
|
1344
|
+
typer.echo("Error: --json ve --watch birlikte kullanılamaz.", err=True)
|
|
1345
|
+
raise typer.Exit(code=1)
|
|
1346
|
+
|
|
1347
|
+
# --watch: Poll on a Live-Refreshed Loop Until Ctrl+C
|
|
1348
|
+
if watch:
|
|
1349
|
+
previous: Optional[dict[str, Any]] = None
|
|
1350
|
+
try:
|
|
1351
|
+
with Live(console=console, refresh_per_second=4) as live:
|
|
1352
|
+
while True:
|
|
1353
|
+
result: dict[str, Any] = client.get("/hermes/pipeline/stats", params={"minutes": minutes})
|
|
1354
|
+
as_of = datetime.now().strftime("%H:%M:%S")
|
|
1355
|
+
live.update(_build_pipeline_group(result, minutes, previous=previous, as_of=as_of))
|
|
1356
|
+
previous = result or previous
|
|
1357
|
+
time.sleep(interval)
|
|
1358
|
+
except KeyboardInterrupt:
|
|
1359
|
+
return
|
|
1360
|
+
|
|
1361
|
+
# One-Shot Fetch
|
|
1362
|
+
result: dict[str, Any] = client.get("/hermes/pipeline/stats", params={"minutes": minutes})
|
|
1363
|
+
|
|
1364
|
+
if as_json:
|
|
1365
|
+
typer.echo(json.dumps(result, indent=2))
|
|
1366
|
+
return
|
|
1367
|
+
|
|
1368
|
+
console.print(_build_pipeline_group(result, minutes))
|
|
1304
1369
|
|
|
1305
1370
|
|
|
1306
1371
|
@app.command("trend")
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qapu-cli
|
|
3
|
+
Version: 0.7.3
|
|
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
|
+
Author: OVOO Technology
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
10
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
12
|
+
|
|
1
13
|
# Qapu CLI
|
|
2
14
|
|
|
3
15
|
A thin command-line client for the Qapu API (`api.ovoo.com.tr`), built for the Hermes agent (runs outside this Swarm, in a separate datacenter, and only ever talks to Qapu through this public API) - but usable by anything else that needs to script against Qapu from outside the private network.
|
|
@@ -47,7 +59,7 @@ Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`
|
|
|
47
59
|
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), or an explicit --start/--end range (GET /hermes/data/{device_id}). |
|
|
48
60
|
| `qapu energy` | One day's active/reactive energy breakdown per phase (GET /hermes/energy/{device_id}). |
|
|
49
61
|
| `qapu health` | Check CLI/server version, connectivity, and server status (GET /health, no auth required). |
|
|
50
|
-
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). |
|
|
62
|
+
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). `--watch` live-refreshes in place. |
|
|
51
63
|
| `qapu power` | Power synthesis parameters, grouped (active/reactive/apparent, imbalance, power factor) (GET /hermes/data/{device_id}). |
|
|
52
64
|
| `qapu timeline` | Get one device's timeline events (GET /hermes/timeline/{device_id}). |
|
|
53
65
|
| `qapu trend` | Trend for one variable on one device: recent readings, daily min/avg/max, or an explicit --start/--end range (GET /hermes/trend/{device_id}/{variable_id}). |
|
|
@@ -356,6 +368,8 @@ qapu pipeline --json
|
|
|
356
368
|
```
|
|
357
369
|
Not device-scoped - a fleet-wide summary across every service in the `ingest -> stream -> calibration -> raw-writer -> synthesis -> ... -> rule` pipeline. Deliberately reads from the real tables each stage actually writes to (`raw_data.stream_time`/`valid_pack` for `hardware`'s ingest, `streams.stream_time` for `data`'s calibration+synthesis completion, `device_timeline.create_time` for `rule`'s observable output, `blockchain.create_time` for mined blocks) rather than any service's own `/health` endpoint - those are point-in-time snapshots (uptime, CPU, last-message-age), not a windowed throughput count. New `common/qapu_common/domain/pipeline.py` (`Pipeline_Stats.get_stats(minutes)`, four plain `SELECT count(*) ... WHERE <time column> >= now() - interval` queries, no caching - these are meant to be live) + `GET /hermes/pipeline/stats` (`services/api/src/routers/hermes.py`, `minutes` query param, 1 to 10080/7 days). **`Rule_Events` deliberately does NOT count rule evaluations** - `rule` evaluates every single stream message 1:1, the same count `Data_Processed` already reports, so repeating it would be redundant; it counts `device_timeline` rows instead, rule's actual observable output (a trigger/reset transition), not every evaluation pass. The CLI prints a bordered summary table plus a soft backlog hint (yellow note, not an error) if `Data_Processed` looks meaningfully behind `Ingest_Valid` (`<80%`) - a sign of consumer lag worth checking, not itself proof of one. Live-tested against real prod DB: `--minutes 60` correctly returned all zeros (no test traffic in the preceding hour at that point in the session), `--minutes 1440` correctly returned real 24h counts, and a fresh real packet sent immediately before a `--minutes 5` check showed up as exactly `+1` across Ingest/Data_Processed/Blockchain_Mined (Rule_Events stayed `0`, correctly - that packet's only variable, `PCB_T`, didn't cross any rule threshold).
|
|
358
370
|
|
|
371
|
+
**`--watch`/`-w` added the same day**, per direct follow-up request ("pipe line canlı takip edilebilecek bir tool yapabilirmiyiz") - polls the same endpoint every `--interval` seconds (default `5.0`) and redraws the summary in place via Rich's `Live`, instead of printing once and exiting; `Ctrl+C` stops cleanly. Each poll after the first also shows a `DEĞİŞİM` column - green `+N` if a stage's count grew since the last poll, red `-N` if it somehow shrank (shouldn't happen for these monotonic counters within a fixed window, but rendered anyway rather than assumed impossible), `·` if unchanged - a bare redrawn count doesn't read as "live" the way a visible delta does. Refactored the table-building logic into a shared `_build_pipeline_group()` (title + table + optional backlog note, returned as one `rich.console.Group`) used by both the one-shot path and the `--watch` loop, so they can't drift apart. Not combinable with `--json` (a live-refreshed raw-JSON dump isn't a coherent thing to build - errors clearly if both are passed). The delta/redraw logic itself was verified directly (calling `_build_pipeline_group()` with two synthetic consecutive polls and confirming the `+4`/`+2`/`·` rendering, plus the empty-result error path) rather than through a full `--watch` session end to end - `rich.Live`'s screen-redraw behavior depends on `console.is_terminal`, which this environment's piped/non-TTY test harness can't exercise the same way a real interactive terminal does; confirm the actual live redraw looks right in a real terminal session if this is touched again.
|
|
372
|
+
|
|
359
373
|
`qapu trend` shows a per-variable history plus a simple linear-regression trend line (slope + direction). Three modes, picked by which options you pass:
|
|
360
374
|
- **Raw readings** (`--last N`, default `N=20`): the measurement cache's rolling buffer - bounded to its own depth (~50 most recent packets), *not* a time window. Good for "what's it doing right now."
|
|
361
375
|
- **Daily aggregates** (`--days N`): one row per calendar day (min/avg/max/count), from the same cache the `/measurement/{device_id}/history` endpoint reads - this is what actually covers multi-day/month windows, since the raw buffer doesn't go back that far. `--days` wins over `--last` if both are given (and `--start`/`--end` isn't).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|