Files
ems/docs/improvement-backlog-2026-06-14.md
Dusan Vojacek 17147ca412 docs(backlog): export-constrained lokalita — curtailment-min test use-case
Tier 3 test: malý export limit + velký instal → ověřit, že MILP drží baterce
rezervu na polední peak místo naivního plnění ráno. Závislé na PV forecast review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:47:20 +02:00

128 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- Noční analýza (workflow ems-improvement-backlog, 16 agentů), 2026-06-14. Read-only, k review. -->
# EMS — Prioritizovaný implementovatelný backlog
_Syntéza nálezů (6 dimenzí) + dvě kritiky (kompletnost, riziko), dedup + sloučení. Ověřeno proti kódu na větvi `main`/`dev` (k 2026-06-14). Filozofie v2: ceny, ne heuristiky; žádná sezónní okna._
---
## 1. TL;DR
Systém řídí produkci přes v2 solver, ale backlog stojí na **jedné tiché iluzi a několika single-point-of-failure dírách**.
**Iluze:** golden gate je v dokumentaci „tvrdá brána", ale **neběží v CI** (`.gitea/workflows/deploy.yml` dělá jen migration-immutability + flyway-validate + deploy). Navíc **7. fixture `home-01_2026-06-13_ev_neg_day.json` nemá snapshot** (`fixtures/`=7, `snapshots/`=6) → ten test buď failuje, nebo nikdy neběžel na zelenou. A **CI golden replay (kdyby běžel) testuje default `v1`** (`planning_engine.py:239 "or v1"`), ne produkční v2 — pokud nemá `PLANNING_ENGINE_VERSION=v2` v env. Proto je **CI golden gate enforcement nový Tier 1 PŘED jakoukoli změnou solveru** — bez něj je každá solver-touching položka (TUV fix, terminal clamp, dead-penalty delete, arbitráž, perf) reálně nechráněná a regrese se nasadí tiše.
**Díry SPOF (tiché, dokud se nestane neštěstí):** chybí **DB záloha** (jediná Postgres v Docker volume, žádné pg_dump/WAL); **Open-Meteo je jediný PV forecast provider** bez fallbacku/staleness alertu (výpadek zneplatní celý plán tiše); **price importer** bez staleness alertu (§16 horizont se tiše zkrátí); **FastAPI zápisová vrstva je BEZ JAKÉKOLI autentizace** (`POST /sites/{id}/mode` mění fyzické setpointy střídače, CORS `allow_methods=['*']`, main.py:46-50) — kdokoli na síti přepne režim; **signal `abandoned`** (vč. `EXPORT_BAN_ACTIVE` na Loxone) padá bez notify; kritické joby v lifespan.py mají jen `logger.exception`. A **Discord dedup/rate-limit primitive NEEXISTUJE** — je předpokladem všech alert itemů, ne poznámka.
**Konkrétní chyba s číslem:** TUV ochlazování je **60× podhodnocené** (`solver_v2.py:348 delta * INTERVAL_H`, kde `delta` je °C/min × 0.25 h). Fix `* 15` je matematicky správný, ale `tuv_heat_t`/`tuv_emergency` jsou HARD constraints (solver_v2.py:354,356) → 60× silnější signál může vynutit ohřev v drahých slotech nebo udělat deep-neg-sell den **Infeasible** (§6 zákaz exportu + plná baterie + vynucený ohřev). To **není low-risk** a vyžaduje **napřed TČ/TUV golden fixture** (žádná ze 7 současných ho nemá).
**Druhá zbývající díra v řízení:** `write_heat_pump_setpoint()` (outputs.py:255-278) je čistý logger — solver_v2 počítá TUV look-ahead, ale nikdy nezapíše reg 73/74; HP navíc vůbec není ve `verify.py`/`modbus_journal.py` (jiné zařízení MIM-B19N) → je to nová verify větev + failsafe rozhodnutí, ne příloha k write stubu.
**Co NEDĚLAT:** nepřidávat penalty/binárky bez (CI) golden gate a time-budget revize (v2 už na 10s limitu u 2 fixtures); neobcházet §6 (zákaz exportu při sell<0); neměnit historická data (`tuv_usage_stats` = °C/min, `audit_interval` do 2026-06-12 = CT reg 619 legacy, `forecast_pv_*`, `ev_arrival_stats`) — opravovat dopředně/filtrovaně; nemazat penalty z `constants.py` hromadně (sdílené s živým v1 shadow peer); `max_connections` na slabém nočním serveru nešvihat tvrdě na 250.
**Korekce předchozího draftu:** `git log main..dev` je prázdný → export-guard carve-out (`521a365`) i reg15 re-assert (`54288ee`) jsou na main, ne „čekají v dev". Ty fixy jsou shipped.
---
## 2. Tier 1 — Quick wins + tiché SPOF díry (S/M, hodnota nebo pojistka teď)
| Položka | Co | Hodnota | První krok | Co ohlídat |
|---|---|---|---|---|
| **CI golden gate enforcement** ⚠️ PŘEDŘAZENÉ | `test_golden_replay.py` neběží v CI (`deploy.yml` = jen migrace+validate+deploy). Solver-touching změny jdou do prod nechráněné. | Reálná regresní brána místo iluzorní lokální. Předpoklad VŠECH solver položek níže. | Přidat CI step `pytest backend/tests/golden` s **`PLANNING_ENGINE_VERSION=v2`** (jinak testuje default v1). Vyřešit 7. chybějící snapshot napřed (viz níže). | Snapshoty neukládají verzi enginu → CI env MUSÍ explicitně v2. Slabý server: golden běh nesmí kolidovat s 15:00 daily / OTE okny. |
| **7. fixture chybí snapshot** | `fixtures/`=7, `snapshots/`=6; `home-01_2026-06-13_ev_neg_day.json` bez baseline → test failuje (test_golden_replay.py:177-180). „EV+neg pokrytý/zelený" je nepravda. | Skutečná regresní ochrana EV+neg scénáře. | Vygenerovat baseline snapshot **vědomě** (přečíst plán, ne slepě accept), pak ekonomický assert `battery_arbitrage_czk > 0`. | Snapshot generovat pod v2; ověřit že plán dává smysl, ne jen „zelená". |
| **TČ/TUV golden fixture** ⚠️ PŘED TUV fixem | Žádná ze 7 fixtures nemá aktivní TČ/TUV (nenulový `tuv_delta_stats` + `current_tuv_temp_c`) → `tuv_heat_t` se nikde netestuje. | Ekonomická brána pro TUV 60× fix i budoucí TČ write. Bez ní jsou oba fixy slepé. | Extrahovat home-01 zimní/přechodový den s aktivním TČ; přidat do golden setu (→ 8 fixtures). | Slot s neg-sell + plná baterie + nucený ohřev = Infeasible kandidát; fixture to musí umět zachytit. |
| **TUV delta 60× fix** | solver_v2.py:348 `tuv_pred += delta * INTERVAL_H` sčítá °C/min × 0.25 h. Opravit na `delta * 15` (°C/15min) na **straně solveru**. | Solver „vidí" chladnutí → předehřev TUV před buy<0. GAP +1-3 %, hlavně zima. | **Až po TČ/TUV fixture.** Edit jen solver_v2.py (čtecí vrstva), pak `solver_v2_eval` + golden gate. | **Riziko medium, ne low:** `tuv_heat`/`tuv_emergency` HARD (řádky 354,356) → 60× signál může vynutit ohřev v drahých slotech NEBO Infeasible v deep-neg-sell. Historické `tuv_usage_stats` NEMĚNIT (°C/min). Δcashflow vysvětlit. |
| **API-key gate na FastAPI ZÁPISOVÉ endpointy** | 0 auth (žádný `Depends`/`Security`/JWT, grep prázdný), CORS `allow_methods=['*']` (main.py:46-50). `POST /sites/{id}/mode`, `PATCH /configuration`, EV patche mění fyzické řízení. | Okamžitá redukce attack surface; kdokoli na síti dnes přepne režim střídače. | Shared-secret header dependency na write routes. | **Server-internal cesty (scheduler, lifespan, mismatch→SELF_SUSTAIN přes `fn_set_mode`/`fn_expire_modes`) NESMÍ projít přes gate** ani Loxone callback (§4.4). HTTP gate v Pythonu je OK; **autorizační LOGIKA/RLS zůstává mimo Python** (§2.2). |
| **Discord dedup/rate-limit primitive** | `discord_bot.py` nemá cooldown/throttle (grep: jen EV handlery). Předpoklad VŠECH alert itemů. | Bez něj job-fail loop (rolling každých 15 min) spamuje kanál → alerty se ignorují. | Sdílená cooldown registry (max 1×/N min per alert-key). MUSÍ být ve stejném PR jako první alert. | Vlastní mini-task, ne poznámka v „Co ohlídat". |
| **Discord alert na kritické joby** | Joby v lifespan.py mají jen `logger.exception`; rolling_replan Infeasible/timeout se zjistí druhý den. | Selhání plánu/exportu v minutách, ne dnech. | Decorator `@async_discord_alert(job, level)`. Severity: plán/export/verify=CRITICAL, audit/forecast/stats=WARNING. `DISCORD_WEBHOOK_URL` existuje. | **Realisticky M, ne S** (dedup primitive napřed). Rozšířit na VŠECHNY joby (lifespan.py:86-237), ne jen 3: audit_filler, signal_send/verify, verify_modbus, forecast, price_importer, baseline/price/tuv stats. |
| **Discord alert na signal `abandoned`** | `signal_service.py` přepíná do `abandoned` na 5+ místech (330,359,372,523,658,683) bez notify. `EXPORT_BAN_ACTIVE` na Loxone (§6-relevant, loxone-integration.md:30,48-49) tiše umře → EMS neví o desync. | EMS ví, že Loxone nedostal export-ban stav. | Notify v okamžiku přechodu do `abandoned`, dedup per `signal_code`. | Signal infra a per-endpoint backoff jsou bezpečnostní featury — zachovat. |
| **Forecast staleness watchdog** | Open-Meteo je **jediný** PV provider (forecast.md:62,234 — Solcast jen „budoucí"); `forecast_service.py` try/except (63,108,111,118,132) bez fallbacku/alertu → plán běží na zastaralém PV tiše. | Detekce výpadku feedu, který zneplatní CELÝ plán (větší dopad než EUR/CZK). | Job: stáří nejnovějšího `forecast_pv_run.created_at` per site > X h (např. 8 h) → Discord WARNING. Read-only. | **Cenový fallback do LP je ZAKÁZÁN (§16)** — fallback PV forecastu OK (vstup), ale žádná predikovaná CENA do solveru. |
| **Price/OTE staleness watchdog** | `price_importer.py` jen retryuje (65-98); při trvalém failu §16 horizont (`fn_planning_horizon_end`) tiše zkrátí. | Detekce výpadku cen místo tichého zkrácení horizontu. | Job: poslední `market_interval_price.interval_start` per zdroj starší než očekávaný horizont → Discord. | Fallback na `market_price_stats` jen jako diagnostika/UI, **nikdy do LP** (§16). |
| **DB pg_dump cron + retence** | Žádná záloha/WAL/PITR; Docker volume = SPOF (deployment-self-hosted.md §10 je jen reset). | Pojistka proti ztrátě 6 měsíců telemetrie/auditu (faktura, NZÚ evidence). | Cron `pg_dump \| gzip``/opt/ems-deploy/backups/dump-DATE.sql.gz`, keep 30 d, rsync na NAS. | Read-only; bez dopadu na běh. Test restore 1×/měsíc. |
| **Terminal SoC shadow price ≥ 0 clamp (Python)** | R__063 SQL clampuje `charge_acquisition` na `greatest(acq,0)`, ale Python NE: planning_engine.py:1163 `terminal_soc_kcz_per_wh = avg_buy_terminal * terminal_factor / 1000` bez `max(0,)`, objektiv :1928 `- … * soc[T-1]`. Při avg buy<0 v 24h → záporná terminal value → solver vybíjí na konci horizontu. | Invariant „energie má kladnou hodnotu" i v sustained neg-buy (§16). | Přidat clamp na `avg_buy_terminal`/`terminal_soc_kcz_per_wh`. | **Interakce s `terminal_neg_buy_weight`** (cap 0.95, constants:82, heuristics.py:890): `max(0, avg_buy*factor)``max(0, avg_buy)*factor` — pevný clamp na 0 může zrušit záměrné škálování faktoru v neg-buy. **Effort spíš S→M (návrh interakce dvou §16 mechanismů).** Clamp musí být ATOMICKY na všech místech (989-997, 1163, 710 `_recompute`) jinak solve≠audit (§8). Relevantní jen když 24h-průměr buy<0 (vzácné) — ověřit na fixture `extreme_neg_buy` (2026-05-01) + normální den. |
| **Telemetry backoff per-gateway dedup alert** | Jeden WB (172.16.1.16) v backoffu blokuje čtení druhého na stejné bráně; tichý drift. | EV nepojede na 15-20 min staré telemetrii bez upozornění. | Dedup alert (1×/10 min/endpoint) + endpoint-level health do `fn_health_detailed_db`. | Per-endpoint backoff i watchdog reg 19/20 ZACHOVAT (bezpečnostní). |
| **CI: Flyway validate retry + timeout** | `flyway validate` proti sdílené prod DB padá na transient connection (`2590eeb`) → falešné negativy. | Méně flaky CI. | Timeout 10→30 s, retry 2× na connect-timeout v `ci_flyway_validate_remote.sh`. | Retry jen na connect chyby — nezaměnit transient za skutečný migration bug. |
| **CI duplicate-SHA guard** | Stejný SHA dev→main / prázdný commit → skip s combined status `success`, deploy se tiše neaplikuje (deployment.md §11). | Konec „myslím že nasazeno, ale neběží". | Step `git rev-parse HEAD vs origin/main`, error při shodě bez deploye. | Re-trigger přes `workflow_dispatch`; doc v CLAUDE.md §Kadence. |
---
## 3. Tier 2 — Střední (M)
| Položka | Co | Hodnota | První krok | Co ohlídat |
|---|---|---|---|---|
| **Samsung TČ Modbus zápis (reg 73/74) + verify větev** | `write_heat_pump_setpoint` (outputs.py:255-278) je stub (logger). **HP NENÍ ve `verify.py` ani `modbus_journal.py`** (grep prázdný) → celá nová verify větev, ne příloha. Design: tuv-control-design.md. | Letní využití přebytků na ohřev + zimní dohřev v levných slotech; NZÚ RD compliance. | Implementovat zápis → `modbus_command` journal; postavit vlastní HP verify cestu. **Effort horní M / spodní L.** | **Samsung = jiné zařízení (MIM-B19N): §18 FC 0x10 platí jen pro Deye 60-499 — ověřit vlastní FC Samsungu.** NECPAT do `DEYE_CRITICAL_REGS_SELF_SUSTAIN`. **Rozhodnout failsafe semantiku: mismatch TČ → SELF_SUSTAIN nedává smysl** (spíš Discord + ponechat Samsung autonomní, analogicky výjimce hodin 62-64 §17). Špatný zápis = mráz/legionella, ne jen ekonomika. |
| **Arbitráž: acquisition z okna, ne min(buy)** | `charge_acquisition` = vážený průměr N nejlevnějších slotů (home-01 nabíjí 2-4 h), ne jeden min(buy). Two-pass už existuje (planning_engine.py:705-720, `_recompute` :710). | Korektní ekonomika baterie (+6-10 % GAP). | Ověřit/dokončit vážení v `_recompute_charge_acquisition_from_results`; eval. | §8: neúčtovat buy/sell téže kWh v jednom slotu; min(buy)≠cena nabití; SQL clamp `greatest(acq,0)` R__063:1177. **Two-pass má `ACQUISITION_TWO_PASS_EPS_KWH` — agresivnější vážení může rozbít konvergenci → oscilace acq → nedeterministický plán.** Ladit solver I audit současně. Golden gate. |
| **Solver v2 výkon — méně binárek** | 2 ze 7 fixtures naráží na 10 s limit (`SOLVER_TIME_LIMIT=10`, constants:18); `y_imp`/`z_exp`/`z_gen` na všech slotech (solver_v2.py:155-158). | Rolling replan <2 s spolehlivě, žádný fallback na v1 při daily 15:00. | Profilovat. Redukovat binárky strukturálně (např. fyzikální nemožnost), **NE podle ceny/forecastu**. | **Redukce „jen sloty kde import/export reálně možný podle ceny" ZAVÁDÍ cenovou heuristiku do struktury LP = porušení v2 filozofie (§4)** a může vynechat slot kde je export optimální. `z_exp[t]` drží §19 export reserve (soc≥arb_floor, :250), `y_imp` exkluzivitu. Při time-limit HiGHS vrátí suboptimum bez varování. Golden gate. |
| **EUR/CZK kurz — ČNB denní fetch** | `eur_czk_rate` fixní 25.0 (config.py:31); v 36h horizontu EUR oscilace 1-2 %. | Přesnější efektivní ceny OTE (drobný vstup — nižší dopad než forecast staleness). | Denní fetch z ČNB API + cache + fallback na fixní. | Jen vstupní parametr; fallback při výpadku API; golden gate. |
| **Loxone heartbeat readback + alert** | EMS NEVÍ, že Loxone přepnul na SELF_SUSTAIN po výpadku → posílá AUTO setpointy, které Loxone ignoruje. | EMS ví o desync; export se neztratí tiše. | Job čte `site_heartbeat.last_control_heartbeat_ts`, po 5 min bez ACK → Discord INFO. | §4 — **NEMĚNIT watchdog logiku Loxone**; jen čtecí/alert job. |
| **Lehká observabilita (`/metrics` nebo rozšířený health)** | Žádný Prometheus/`/metrics`/OTel (grep prázdný); jen `/health` + `/health/detailed` (main.py:113-131). Reaktivní alerty neukáží trend. | Proaktivní signál: solve-time trend (v2 u 2 fixtures na 10s limitu), hloubka fronty `modbus_command`/`signal_outbound_journal` (pending), stáří telemetrie/forecastu. | Rozšířit `fn_health_detailed_db` o pending counts + stáří forecastu/cen + poslední rolling_replan trvání/stav; volitelně `/metrics`. | Read-only čtení; SQL-first (rozšíření fn, ne ad-hoc dotazy v Pythonu). |
| **fn_plan_current_bundle výkon (3.8 s)** | 90 % času v `fn_forecast_pv_slots_range_canonical_ab` (audit 2026-06-11). #1 endpoint plánu. | Rychlejší UI. | LATERAL fix view (vzor FE perf memo); virtualizace tabulky. | Stejné řádky/`planning_interval`; jen SQL refaktor. |
---
## 4. Tier 3 — Velké sázky (L / vyžaduje rozhodnutí uživatele)
| Položka | Co | Hodnota | První krok | Co ohlídat / **co rozhodne uživatel** |
|---|---|---|---|---|
| **Bazén jako flexible_load v LP** | Shelly asset live (V087/V088), telemetrie + on/off signál; LP integrace návrh (pool-shelly.md §5+). ~1-4 kW. | Filtrace v přebytkových/levných slotech místo noci za 4 Kč; ~100-200 Kč/měsíc. | Asset jako `flexible_load`, constraint min X h/den, preference E_surplus. | NEpřebít GFCI port sdílený s EV; Loxone fallback. **Rozhodnout:** min h/den, UI paradigma (open-q §32, blocker UI workshop). |
| **Spirála TUV v LP (`z_spiral[t]`)** | Dnes jen Loxone signál (dump bez ceny). Kaskáda neg-sell (baterie→EV→TČ→spirála) je emergentní, ne řízená. | +1-2 % v neg-sell dnech; cílený dohřev místo 1 kW dumpu. | Binárka `z_spiral` podmíněná bilancí (neg-sell okno, SoC≥reserve), bez min-run. | Loxone watchdog nesmí zůstat na spirále při výpadku. **Rozhodnout:** priorita vs TČ reg 74; NZÚ skepse k dohřevu bez COP. |
| **Onboarding harness pro novou lokalitu** | Dnes ruční copy-paste SQL (new-site-setup-template.md §8) do Flyway migrace, bez validace (chybějící endpoint, špatný azimut, zapomenutý `block_export`). | Bezchybný multi-site rollout (Fáze 2, open-q §69). | Skript: z parametrů → migrace ze šablony + post-deploy ověření (endpoint reachable, telemetrie teče, forecast běží, první plán feasible). | SQL-first; ověření nesmí psát do prod naslepo. **Rozhodnout:** kdy přijde 2. lokalita. |
| **FastAPI write auth → plný RBAC + PostgREST RLS/JWT** | API-key gate (Tier 1) je dočasná záplata. `ems_anon` read-only na views bez RLS → vidí všechny sites. | Bezpečnost před multi-user produkcí. | RLS policy per site + JWT; `GET /me/sites` filtr. | §2.2 — **autorizační logika/RLS NESMÍ do Pythonu**. **Rozhodnout:** kdy 2. tenant / externí přístup (jinak parkovat, ale Tier 1 API-key gate udělat hned). |
| **pgbouncer connection pooling** | `max_connections` (deploy/docker-compose.yml:17 `${POSTGRES_MAX_CONNECTIONS:-100}`) na slabém nočním serveru: skok na 250 = +~1.5 GB RAM (250×~10 MB) → OOM/swap riziko místo občasného timeoutu. | Řeší „remaining connection slots" bez RAM nárůstu. | Zavést pgbouncer; `max_connections` může zůstat nízko. | **Nešvihat tvrdě na 250 na slabém serveru.** Pooling je správné řešení; mezitím ops-checklist: zvednout na 150-180 + sledovat `pg_stat_activity` a RAM. |
| **Termo-flex blok (TČ + spirála + bazén)** | TČ reg 74 + spirála + bazén = jeden produktový balík „flexibilní zátěže". | Konzistentní řízení flex zátěží. | Pořadí: TČ zápis (Tier 2) → spirála → bazén. | Žádná sezónní okna (v2 filozofie); každá zátěž opt-in per site config. |
| **Export-constrained lokalita — curtailment-min use-case (TEST)** | Hypotetická lokalita: **malý export limit (~4.5 kW), velký instal (~10 kW)**, konečná baterka. Otestovat, že MILP **drží baterce rezervu na polední peak** (ráno export na limitu + nabíjení zbytkem, baterka se plní pomalu) místo naivního „plná baterka ráno → v poledne se peak curtailuje". Přes kladné ceny: export limit + zbytek do baterky; přes záporné/peak: export off, vše do baterky + curtail zbytku. | Ověření, že na export-omezených sitech **minimalizujeme curtailment vs naivní Deye** (méně „škoda na střeše"); připravenost na 2. typ lokality. | Syntetický golden fixture (bell-curve PV >> export limit, konečná kapacita+rychlost baterky) + assert: `Σ curtailment < naivní baseline` a `SoC nenajede na plno před peakem`. | **Stojí a padá na kvalitě forecastu peaku** (podstřel → málo rezervy → zbytečný curtail; viz PV forecast review — canonical rolling_factor/delta) → **až po něm**. Curtail nad `(export+load+battery_charge_RATE)` je fyzicky nevyhnutelný, ne bug. Reaktivní řez nechat na Deye (CT smyčka), EMS jen strategie (viz [[ems-not-realtime-inverter-battery-buffer]]). v2 filozofie (žádná sezónní okna). |
---
## 5. Rozdělané k dotažení (půl hotové — chybí poslední krok)
| Co | Stav | Chybí poslední krok |
|---|---|---|
| **CI golden gate** | Test existuje (`test_golden_replay.py`), lokálně běží. | Zařadit do `.gitea/workflows/deploy.yml` s `PLANNING_ENGINE_VERSION=v2` (viz Tier 1). |
| **7. EV+neg fixture** | `home-01_2026-06-13_ev_neg_day.json` existuje. | Chybí baseline snapshot (`snapshots/`=6 vs `fixtures/`=7) → vygenerovat + ekonomický assert. |
| **TČ Modbus zápis** | Telemetrie MIM-B19N live (V096/V101), design hotový, solver TUV look-ahead počítá. | `write_heat_pump_setpoint` (outputs.py:274 stub) → reg 73/74 + **celá nová verify větev** (HP ve `verify.py`/`modbus_journal.py` chybí). |
| **EV oportunismus z cen** | Flag `target_soc_forecast_enabled` zaveden; session bez deadline viditelná (BUG2 fix, R__038). | Heuristika P50 nejlevnějších oken z `market_price_stats` místo konstanty 1 Kč/kWh. Za flagem → v1 netknut. |
| **Bazén** | Asset, endpoint, telemetrie, čidlo, on/off signál live (V087-V092). | LP model (výběr ON slotů). |
| **Spirála TUV** | `signal_def`/`signal_route` infra (V085), Loxone dump funguje. | LP binárka. |
| **Reg 340 (max solar)** | DB sloty `deye_reg340_max_solar_w` + logika setpoints.py:143-167; není v critical regs (správně). | Ověřit firmware limit na SUN-20K (V082, todo.md ř.67) — jinak možná mrtvý registr. |
| **Externí CT reg 619** | Doinstalován 2026-06-12 (V100), nová pole `inverter_grid_port_w`/`ups_load_w`. | Baseline rebuild po týdnu dat (`fn_rebuild`); audit do 2026-06-12 nechat legacy. |
| **WB2 + Chint elektroměr** | WB2 deaktivován (V105, reaktivace v SQL komentáři); Chint plánován unit 3. | Fyzická instalace na majiteli (HW, mimo kód). |
| **Evening-push override (v54-v55)** | Oprava `Infeasible` na relaxed retry v produkci. | Robustnější test + finální doc (brittle `stale_evening_push_override`). |
---
## 6. Rizika / „NErozbít"
- **CI golden gate (NEběží!).** Nejzávažnější: golden gate je dnes lokální-only → ochrana solveru je v nasazení iluzorní. Dokud neběží v CI (s env v2), KAŽDÁ solver-touching změna může regrese nasadit tiše. Snapshoty neukládají verzi enginu; default je v1 (`planning_engine.py:239`).
- **v1 je STÁLE živý shadow peer** (`PLANNING_ENGINE_COMPARE_ENABLED`) a sdílí `constants.py` s v2 (`solver_v2.py:62`). **Nemazat penalty hromadně**`penalty_audit` Δ=0 je měřeno jen na v2 a jen na 7 fixtures (ne v1, ne edge dny mimo fixtures). Per-penalta grep v1 cesty + vědomá regenerace snapshotů. Hodnota „rychlejší v2" je navíc malá (mrtvé penalty obvykle nemají binárky → nezrychlí MILP). **→ spíš parkovat než Tier 2.**
- **§6 — zákaz exportu při sell<0.** Export-guard carve-out (`521a365`, setpoints.py:313) řeší false positive u nabíjení — carve-out jen pro `pm=='CHARGE'` nebo `grid_sp>0 & bat_sp>0` (import). Žádná další úprava nesmí povolit reálný export při sell<0. Ověřit i na BA81/KV1 (`block_export_on_negative_sell=true`, V074).
- **§7 — buy<0 import cap.** Záporná nákupní cena nesmí dělat „nekonečný" import (`solve_dispatch`).
- **§8 — arbitráž.** Neúčtovat buy/sell téže kWh v jednom slotu; min(buy)≠cena nabití. Acquisition ≥ 0 (SQL `greatest` R__063:1177 + Python clamp na všech 3 místech atomicky). Two-pass konvergence (`ACQUISITION_TWO_PASS_EPS_KWH`) — agresivnější vážení může oscilovat. Solver i audit musí ladit.
- **§16/§19 — terminal SoC & export reserve.** Shadow price ekonomická kotva ≥ 0; clamp musí být sladěn s `terminal_neg_buy_weight` (jinak se dva mechanismy perou). `z_export` drží `soc ≥ arb_base_wh` při exportu. **Žádná predikovaná CENA do LP** — forecast/price fallbacky jsou jen diagnostika/vstup, ne cena ve solveru.
- **§17/§18 — Modbus journal & FC 0x10.** Nové TČ registry (73/74) přes journal + **vlastní** verify větev (HP ≠ Deye); FC 0x10 pravidlo §18 platí jen pro Deye 60-499 — Samsung ověřit zvlášť. Reg 15 už re-assert každý tick (`54288ee`) — nevracet na write-on-change. TČ mismatch → SELF_SUSTAIN NEDÁVÁ smysl.
- **FastAPI auth gate nesmí blokovat server-internal cesty** (scheduler, lifespan, mismatch→SELF_SUSTAIN, `fn_expire_modes`) ani Loxone callback (§4.4). HTTP gate v Pythonu OK; autorizační logika/RLS mimo Python (§2.2).
- **Discord alerty bez dedup = spam.** Dedup primitive NEEXISTUJE (`discord_bot.py`) — musí být ve stejném PR jako první alert, jinak rolling-fail loop (15 min) zaplaví kanál a alerty se začnou ignorovat.
- **TUV 60× fix mění chování VŠECH slotů s TČ, ne jen zimních.** HARD constraints (solver_v2.py:354,356) → riziko nuceného ohřevu v drahých slotech i Infeasible v deep-neg-sell. `tuv_delta_stats` filtr `abs<5`/min = až 75°C/15min (R__018:100). **Bez TČ/TUV fixture neimplementovat.**
- **max_connections na slabém nočním serveru.** 250×~10 MB = +1.5 GB → OOM/swap riziko. `${POSTGRES_MAX_CONNECTIONS:-100}` je jen .env změna (ops-checklist, ne feature). Cíl 150-180 + pgbouncer, ne tvrdých 250.
- **Historická data NEPŘEPISOVAT.** `tuv_usage_stats` (°C/min normalizace), `audit_interval` do 2026-06-12 (CT reg 619 legacy), `forecast_pv_run/interval` (§12), `ev_arrival_stats` (§14) — fix dopředně/filtrovaně.
- **Loxone watchdog = bezpečnostní feature.** Žádný readback/alert job nesmí měnit autonomní fallback do SELF_SUSTAIN.
- **v2 filozofie.** Ceny, ne heuristiky; žádná sezónní okna. Redukce binárek podle ceny = zakázaná heuristika ve struktuře LP. Nové penalty podezřelé (16/26 mrtvých).
---
## 7. Co dozraje časem (data/zima — jen sledovat)
- **Zimní termální cykly.** Žádná vlastní zimní data (telemetrie od 3/2026); 2 zimy jen raw OTE. TUV delta fix (Tier 1) a TČ zápis (Tier 2) jsou klíčové AŽ s reálnými zimními daty — malé spready / žádné neg dny → TČ track rozhoduje. Neladit sezónně teď.
- **Současně buy<0 AND sell<0 v jednom horizontu.** Home-01 viděl 1-2×; gray zone (§57+§59 interakce). Přidat invariant test až bude reálný fixture — neimplementovat naslepo.
- **Sustained neg-buy blok (24h-průměr buy<0).** Vzácný; jediný kandidát `extreme_neg_buy` (2026-05-01). Bez fixtury s tímto profilem nelze terminal SoC clamp (Tier 1) bezpečně ověřit.
- **Tesla deadline drift +36h.** Root cause neuzavřen (open-q), symptom ošetřen (`03b7396`: charge_limit = strop, ne cíl). Sledovat návrat.
- **Idle-skip 840 s heartbeat.** Práh nevalidovaný proti reálnému poll intervalu; ovlivňuje TUV delta agregaci. Sledovat, neměnit dokud nevadí.
- **Clock drift reg 62-64.** Discord-only bez auto SELF_SUSTAIN (§17 výjimka, záměr). Sledovat lidskou reakci.
- **PV pole B v auditu.** Politika „neřízená výroba vs ignorovat" (open-q §59) — jen mix A+B; nízká frekvence. Rozhodnout až bude potřeba pro green-bonus výkaz.
- **OCPP vs Tesla REST.** Tesla Fleet API běží živě; rozhodnutí protokolu lze odložit (obě cesty za flagem koexistují).
- **HP verify failsafe semantika.** Až se reg 73/74 zapisuje: mismatch → co? (SELF_SUSTAIN nedává smysl; spíš Discord + Samsung autonomní, analogicky výjimce hodin 62-64 §17).
---
_Poznámka k ověření: `git log main..dev` prázdný — export-guard carve-out (`521a365`) i reg15 re-assert (`54288ee`) jsou na main, ne „čekají v dev". Golden gate má `fixtures/`=7 ale `snapshots/`=6 (7. fixture bez baseline) a **neběží v CI** → „7 fixtures = tvrdá brána" je v nasazení iluzorní. CI golden enforcement (s env v2) je nový Tier 1 PŘED jakoukoli změnou solveru._