feat(telemetry): idle-skip zápisů — neukládat 1min řádky idle zařízení
Slabý server: dict (tabulka, asset_id) → (signature, last_stored_at); _idle_skip ukládá vždy při změně signature, aktivitě, po startu procesu a heartbeat po > 840 s (každý 15min bucket má ≥ 1 řádek). - telemetry_ev_charger: aktivní = status != 'available' nebo power > 50 W; signature (status, výkon na 100 W) - telemetry_pool_pump: aktivní = is_on nebo power > 5 W (ON řádky 1/min kvůli on_minutes); signature (is_on, výkon na 10 W) - telemetry_loxone_sensor: jen změna hodnoty ≥ 0.1 / heartbeat - telemetry_heat_pump: aktivní = mode != 'off' nebo defrost; signature (mode, teploty na 0.2 °C) - telemetry_inverter: beze změny — NIKDY se nepřeskakuje (audit Wh split, baseline, SoC plánovače) Detekce příjezdu/odjezdu EV: previous_status přesunut z posledního řádku DB do in-memory _EV_LAST_STATUS (po startu seed z vw_latest_ev_charger — přechod během výpadku se pozná, prázdná DB nevystřelí falešný příjezd); fn_ev_session_transition se volá jen při změně statusu. PoolCard: staleness práh 5 → 16 min (> heartbeat 840 s). Docs: telemetry.md sekce „Idle-skip zápisů" (pravidla pro nové čtecí dotazy: sumy/gapfill, ne avg přes řádky), planning-changelog (TUV °C/min). Testy: tests/test_telemetry_idle_skip.py — _idle_skip jednotkově + EV arrival/departure přežije skip i restart procesu (303 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,57 @@ Implementace: `backend/services/telemetry_collector.py` — `poll_inverter()` po
|
||||
|
||||
---
|
||||
|
||||
## Idle-skip zápisů (úspora zápisů na slabém serveru)
|
||||
|
||||
Zařízení v klidu negeneruje nový 1min řádek — vzorek se zahodí, pokud je
|
||||
zařízení **idle** a **signature** (kvantizovaný stav) se nezměnila. Mechanismus:
|
||||
`_idle_skip(key, signature, is_active, now, max_gap_s=840)` v
|
||||
`telemetry_collector.py`, modulový stav `(tabulka, asset_id) → (signature,
|
||||
last_stored_at)`.
|
||||
|
||||
**Uloží se vždy, když:** signature se změnila; zařízení je aktivní; od
|
||||
posledního uložení uplynulo **> 840 s** (heartbeat — každý 15min bucket má
|
||||
≥ 1 řádek); nebo jde o první vzorek po startu procesu.
|
||||
|
||||
| Tabulka | Aktivní = ukládá se 1/min | Signature |
|
||||
|---|---|---|
|
||||
| `telemetry_ev_charger` | `status != 'available'` nebo `power_w > 50` | (status, výkon na 100 W) |
|
||||
| `telemetry_pool_pump` | `is_on` nebo `power_w > 5` | (is_on, výkon na 10 W) |
|
||||
| `telemetry_loxone_sensor` | nikdy (čidlo) — jen změna/heartbeat | hodnota na 0.1 |
|
||||
| `telemetry_heat_pump` | `operating_mode != 'off'` nebo defrost | (mode, teploty na 0.2 °C) |
|
||||
| `telemetry_inverter` | **vždy** — NIKDY se nepřeskakuje | — |
|
||||
|
||||
Střídač se nepřeskakuje: je vstupem auditu (per-minute Wh split, 7 směrových
|
||||
toků), baseline a SoC plánovače — hustá řada je nutná.
|
||||
|
||||
**Detekce příjezdu/odjezdu EV** už nečte předchozí status z posledního řádku
|
||||
DB (ten je při idle-skip řidší), ale z in-memory `_EV_LAST_STATUS`; po startu
|
||||
procesu se seeduje z `vw_latest_ev_charger` (přechod během výpadku backendu se
|
||||
pozná, prázdná DB nevystřelí falešný příjezd). `fn_ev_session_transition` se
|
||||
volá jen při změně statusu.
|
||||
|
||||
**Důsledky pro čtecí dotazy (POVINNÉ pravidlo):** nad idle-skip tabulkami
|
||||
nesmí nový dotaz počítat `avg(power)` přes přítomné řádky — chybějící minuta
|
||||
znamená „zařízení idle ≈ 0 W“ a avg by aktivitu části okna nadhodnotil.
|
||||
Správně: **suma / počet minut okna** (`sum(power_w) / 15.0` pro 15min slot),
|
||||
`time_bucket_gapfill`, nebo delta čítače energie. Poslední hodnota
|
||||
(`vw_latest_*`, TUV teplota, teplota bazénu) je díky heartbeatu max. ~14 min
|
||||
stará — staleness prahy musí být > 840 s (PoolCard používá 16 min).
|
||||
|
||||
Přizpůsobené čtecí cesty:
|
||||
|
||||
- `fn_fill_audit_interval` (R__019): EV a TČ `sum(power_w)/15` místo avg.
|
||||
- `fn_update_tuv_usage_stats` (R__018): delta TUV normalizovaná na **°C/min**
|
||||
délkou mezery mezi řádky (`gap_min`), mezery > 30 min vyloučeny.
|
||||
- `fn_update_baseline_stats` (R__003): beze změny — `coalesce(avg, 0)`
|
||||
v okně ±30 s; chybějící řádek = 0 W, což při idle-skip platí.
|
||||
- `vw_pool_pump_day_energy` (R__097): `on_minutes` počítá ON řádky — drží,
|
||||
protože zapnuté čerpadlo se ukládá každou minutu; kWh je delta čítače.
|
||||
- `fn_pool_daily_runtime_min`, `fn_planning_site_context` (TUV),
|
||||
`fn_load_planning_slots_full` (EV status): poslední hodnota — heartbeat stačí.
|
||||
|
||||
---
|
||||
|
||||
## Agregace 1min → 15min
|
||||
|
||||
Prováděna PostgreSQL funkcí `ems.fn_fill_audit_interval()` a navazujícími
|
||||
|
||||
@@ -5,6 +5,28 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-12 — idle-skip telemetrie: TUV delta normalizovaná na °C/min
|
||||
|
||||
**Problém:** telemetry_collector nově přeskakuje 1min zápisy idle zařízení
|
||||
(heartbeat 840 s — viz `telemetry.md`, sekce Idle-skip zápisů). Vstupy
|
||||
plánovače čtené z těchto tabulek nesmí předpokládat hustou 1min řadu.
|
||||
|
||||
**Mechanismus:** `fn_update_tuv_usage_stats` (R__018) počítá deltu TUV jako
|
||||
`(temp − lag(temp)) / gap_min` (°C/min, mezery > 30 min vyloučeny) — pro
|
||||
hustá 1min data numericky identické s původním per-row LAG; po idle-skip bez
|
||||
až 14× nadhodnocení delty. Ostatní vstupy solveru (poslední TUV teplota v
|
||||
`fn_planning_site_context`, poslední EV status v `fn_load_planning_slots_full`,
|
||||
baseline stats) pokrývá heartbeat beze změny. Audit: EV/TČ `sum/15` v R__019.
|
||||
|
||||
**Soubory:** `telemetry_collector.py`, `R__018_fn_extended_planning.sql`,
|
||||
`R__019_fn_fill_audit_interval.sql`, `R__097_vw_pool_pump.sql`, `PoolCard.tsx`,
|
||||
`docs/04-modules/telemetry.md`.
|
||||
|
||||
**Ověření:** `tests/test_telemetry_idle_skip.py` (změna/aktivita/heartbeat/
|
||||
start; EV arrival přežije skip i restart procesu); celá sada 303 passed.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-12 — v2 AKTIVNÍ v produkci + robustnostní trojice „nejistota jako cena"
|
||||
|
||||
**Přepnutí (847015f):** `PLANNING_ENGINE_VERSION` default **v2** v deploy compose; v1 běží
|
||||
|
||||
Reference in New Issue
Block a user