Merge hotfix: planovac hardcoded wallbox kody (oslepnuti po rename)
All checks were successful
CI and deploy / migration-check (push) Successful in 19s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-06-14 11:26:41 +02:00
3 changed files with 145 additions and 8 deletions

View File

@@ -126,7 +126,8 @@ begin
select ai.deye_gen_microinverter_cutoff_enabled
from ems.asset_inverter ai
where ai.site_id = p_site_id
and ai.code = 'deye-main'
and ai.controllable = true
order by ai.id
limit 1
),
false
@@ -169,22 +170,30 @@ begin
'battery_capacity_kwh', v.battery_capacity_kwh,
'default_target_soc_pct', v.default_target_soc_pct
)
order by ch.code
order by ch.id
),
'[]'::jsonb
)
into v_veh
from ems.asset_vehicle v
join ems.asset_ev_charger ch on ch.id = v.default_charger_id
where v.site_id = p_site_id
and ch.code in ('ev-charger-1', 'ev-charger-2');
where v.site_id = p_site_id;
-- EV session per wallbox — logika v ems.fn_ev_session_planning_json
-- (R__038): session se NEvyřazuje při needed_wh=0 (auto nad targetem),
-- zůstává v plánu kvůli oportunistickému headroomu i jako známá zátěž.
-- Wallboxy se vybírají DYNAMICKY podle site (řazeno ch.id = stabilní pořadí
-- ev1/ev2), NE podle natvrdo kódu — uživatel může wallbox přejmenovat
-- (2026-06-14: rename 'ev-charger-1' → 'vt-ev-charger-1' oslepil plánovač).
v_ev := jsonb_build_array(
ems.fn_ev_session_planning_json(p_site_id, 'ev-charger-1'),
ems.fn_ev_session_planning_json(p_site_id, 'ev-charger-2')
ems.fn_ev_session_planning_json(
p_site_id,
(select code from ems.asset_ev_charger where site_id = p_site_id order by id limit 1)
),
ems.fn_ev_session_planning_json(
p_site_id,
(select code from ems.asset_ev_charger where site_id = p_site_id order by id offset 1 limit 1)
)
);
select ti.battery_soc_percent

View File

@@ -209,7 +209,8 @@ begin
select t.status
from ems.telemetry_ev_charger t
join ems.asset_ev_charger ch on ch.id = t.charger_id
where t.site_id = p_site_id and ch.code = 'ev-charger-1'
where t.site_id = p_site_id
and ch.id = (select id from ems.asset_ev_charger where site_id = p_site_id order by id limit 1)
order by t.measured_at desc
limit 1
) ev1 on true
@@ -217,7 +218,8 @@ begin
select t.status
from ems.telemetry_ev_charger t
join ems.asset_ev_charger ch on ch.id = t.charger_id
where t.site_id = p_site_id and ch.code = 'ev-charger-2'
where t.site_id = p_site_id
and ch.id = (select id from ems.asset_ev_charger where site_id = p_site_id order by id offset 1 limit 1)
order by t.measured_at desc
limit 1
) ev2 on true;

View File

@@ -0,0 +1,126 @@
<!-- 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. |
---
## 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._