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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qapu-cli
3
- Version: 0.7.2
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.2"
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
- @app.command("pipeline")
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
- if as_json:
1268
- typer.echo(json.dumps(result, indent=2))
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
- window = result.get("Window_Minutes", minutes)
1276
- window_label = f"{window} dk" if window < 60 else (f"{window // 60} sa" if window % 60 == 0 else f"{window} dk")
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
- console.print(f"[bold]Pipeline Özeti[/bold] - son {window_label}\n")
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
- table.add_row("Ingest (hardware)", str(ingest_total), f"{ingest_valid} geçerli, {ingest_invalid} geçersiz")
1293
- table.add_row("Data İşlendi (data)", str(data_processed), "kalibrasyon + sentez tamamlanan paket")
1294
- table.add_row("Kural Olayları (rule)", str(rule_events), "tetiklenen/sıfırlanan olay (device_timeline)")
1295
- table.add_row("Blockchain Kazıldı", str(blocks_mined), "yeni blok")
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
- console.print(table)
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
- console.print(f"\n[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]")
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