# EV plán zlepšení — konsolidace (2026-06-14) Triáž z živého provozu home-01 (Tesla na TeltoCharge). Sjednocuje pět propojených pozorování do jednoho prioritizovaného plánu. Cílem je odstranit phantom nabíjení, fragmentaci a slepotu plánovače k reálnému stavu auta — **bez nové „přežvykovací" vrstvy nad plánem** (bolístka z v1). Veškerá logika zůstává v solveru / SQL, control vrstva je hloupý vykonavatel. ## Průřezové zásady (platí pro všechny body) - **Nebudit auto.** Tesla `vehicle_data` (SoC/odometer/poloha) se čte jen když je auto vzhůru z vlastní vůle — `get_vehicle_api_state` (`tesla_client.py:183`, online/asleep/offline, nebudí) gatuje budicí `get_charge_state` (`tesla_client.py:125`). Výjimka bez buzení: **auto, které aktivně nabíjí, je vzhůru** → během session lze SoC číst bezpečně. - **SQL-first.** Živé SoC i „full" stav musí být v DB sloupci/tabulce; čte je `fn_ev_session_planning_json` / `fn_ev_session_defaults`. Žádné skládání v Pythonu. - **Golden gate.** Cokoliv mění `planning_interval` (solver, needed_wh) musí projít golden gate. Solver změny za DB flagem s default = no-op, pak kalibrace harnessem. Pozor: golden fixtures dnes EV **nulují** → nutná EV fixture z home-01 (chce DB). - **Žádné hardcoded kódy zařízení.** Vybírat podle `site_id` + `id`. - **Import/export tvrdé limity** (§7/§19): control nesmí jednostranně zvednout EV výkon nad plán (rozbilo by garanci `import ≤ max_import`). --- ## 1. Živé SoC auta do plánovače — ✅ IMPLEMENTOVÁNO na dev (2026-06-14) > **KOREKCE po ověření na živé DB:** plánovaný coulomb z `vw_latest_ev_charger.energy_kwh` > NEFUNGUJE — ten counter (Telto reg 39) je **rozbitý** (17.4 kWh reálně nabito → > counter stál na 0.18). Fix proto integruje **`power_w`** (spolehlivý signál), ne counter. > Hotovo v `R__038`: nový `fn_ev_session_delivered_wh` (time-weighted integrál power_w, > dt cap 120 s) + přepočet needed_wh/headroom z `live_soc = soc_at_connect + delivered/cap` > (clamp 99 %), fallback `coalesce(live, energy_delivered_wh, 0)`. **Ověřeno živě:** > needed_wh 18750 → **1329 Wh**, live_soc 97.9 %. **Nenasazeno na prod** (čeká deploy). > Detail: `docs/planning-changelog.md` 2026-06-14. **Pozorování:** auto na 99 %, ale plán do rána ukazuje 4× 11 kW okna (phantom). **Příčina (ostrá, z workflow `wqikxa47f` — horší, než vypadala):** plánovač počítá `needed_wh` i headroom **výhradně ze zamrzlého `soc_at_connect_pct`** (zapsán jednou při příjezdu) mínus `energy_delivered_wh` — JENŽE **`energy_delivered_wh` se během session NIKDY nezapíše** (V006:53, NOT NULL DEFAULT 0; `fn_ev_session_transition` jen otevře/zavře, `telemetry_collector` píše kumulativní energii jen do `telemetry_ev_charger.energy_kwh`, ne do session). Takže delivered je **trvale 0** → **`needed_wh = (target − soc_at_connect)/100 × cap` je KONSTANTA po celou session, neklesá jak auto nabíjí.** Plánovač je k pokroku nabíjení **úplně slepý** → rolling replan každých 15 min znovu emituje plný deficit (4× 11 kW okno). (R__038:37–61.) **Klíč: živý progres už v DB MÁME** — `telemetry_ev_charger.energy_kwh` (Teltonika reg 39, kumulativní kWh per session, reset na novou session, poll 60s), vystavený přes `vw_latest_ev_charger.energy_kwh`. **Hardwarově měřený, funguje pro všechna auta (i Zoe), bez Tesla API, bez buzení.** **Fix #1 (primární — čistě SQL, žádná nová vrstva/tabulka/job, žádné buzení):** v `fn_ev_session_planning_json` (R__038) nahradit zamrzlý `coalesce(es.energy_delivered_wh, 0)` **živým** `coalesce((select energy_kwh from vw_latest_ev_charger … otevřená session) × 1000, es.energy_delivered_wh, 0)`. Odvodit `live_soc = soc_at_connect + delivered/(cap_wh)×100`, **clamp 99 %** (taper ignorujeme). `needed_wh = greatest(0, (target − live_soc)/100 × cap_wh)` a `headroom = greatest(0, (99 − live_soc)/100 × cap_wh)` z živého SoC. → needed i headroom klesají s nabíjením a **kolabují na 0 při plném autě** → phantom okna zmizí. - **Fallback `coalesce(vw, es.energy_delivered_wh, 0)`** drží staré golden fixtures beze změny (bez WB telemetrie = delivered 0 = soc_at_connect = dnešní chování) → **golden gate zůstane zelená by-construction.** Tj. #1 lze nakódovat a lokálně dokázat ne-regresi **bez živé DB**; DB chce jen živé ověření na home-01. **Komplement — ne-Tesla (Zoe):** dnes se session při `soc_at_connect_pct IS NULL` **úplně vyřadí z LP** (R__038:29). Změkčit: startovní SoC z kaskády (ruční UI patch přes R__015 → zděděný `soc_at_disconnect` minulé session → konzervativní default ~20 %), pak coulomb delta dá použitelné absolutní SoC. *(samostatný krok po ověření #1.)* **Odloženo (Fix #3, opt-in):** mid-session Tesla refresh živého SoC — JEN když coulomb counter nestačí (auto nabito mimo WB / chybí WB telemetrie). Budí auto (vampire drain, proti dnešní zásadě) → nedělat, dokud se coulomb fix neukáže jako nedostatečný. Wallbox `charging_state` „full" je univerzální brzda zdarma navrch (auto přestalo brát → needed spadne i kdyby coulomb plaval). **Soubory:** `R__038_fn_ev_session_planning_json.sql` (jádro), `R__015` (Zoe patch), `docs/04-modules/ev-charging.md`, `docs/planning-changelog.md`. **Golden:** ANO (mění needed_wh) — fallback drží fixtures bez EV telemetrie identické; fixtures s nenulovou WB telemetrií se přegenerují (phantom byl bug, nová čísla správná). **Roll-forward deficitu** vyjde emergentně: nenabito dnes → SoC nízký → další deadline dožene. **Ověřit na živé DB (chce IP):** že `vw_latest_ev_charger.energy_kwh` sedí na AKTUÁLNÍ session (counter per connector — spolehlivě resetuje na session?); reálná AC→DC účinnost (~8–12 % ztrát → live SoC mírně optimistické, žádoucí směr — méně phantom); porovnat odvozené `live_soc` vs 99 % na displeji auta a že needed_wh/headroom spadnou na ~0. --- ## 2. Předehřev / 0 A logika — PRIORITA Č. 2 (control, bez golden) **Princip:** wallbox neumí oddělit „proud na topení" od „proudu na nabití". - **SoC ≥ target → NEřezat na 0 A.** Pusť proud → Tesla se předehřeje z WB (levná síť/baterka) místo z vlastní (vožené, drahé) baterie; protože je na targetu, baterku stejně nenabije → nulové riziko. Hlavní zimní případ. - **SoC < target → řídí plán** (nabít v levných, 0 A v drahých). Konflikt předehřevu v drahém slotu je vzácný (auto obvykle dosáhne targetu přes noc) → nepřekomplikovávat. - **Operačně:** odpojení → **jednorázové 0 A** (auto pryč, failsafe je jedno, žádné periodické psaní); připojení → notifikace → plán + amps; po dobu připojení → re-assert amps každý tick (Fáze-0 oprava proti driftu WB watchdogu na failsafe). - **Fáze z DB** (`asset_ev_charger.phases`), ne hardcoded 3/1 (`setpoints.py:185-186`). **Závisí na #1** (potřebuje znát SoC ≥ target). **Soubory:** `setpoints.py`, `outputs.py`, `docs/04-modules/ev-charging.md`. **Golden:** NE (jen překlad watt→amp při zápisu; control nečte/nepíše planning_interval). --- ## 3. Anti-fragmentace + plný výkon v solveru — PRIORITA Č. 3 (za flagem) **Pozorování:** nabíjení rozsekané přes 21:15 / 1:30 / 1:45 / 5:30 / 6:00, navíc dílčí 1,3–1,4 kW. „Z baterky je solveru jedno kdy" (uživatel) = indiference → náhodný scatter. **Příčina:** EV je v solveru spojitá energie po slotech bez jakéhokoliv časového členu — žádná start/stop ani commitment penalta (tu má jen baterie). Pro LP je souvislý i roztříštěný profil ekonomicky identický (`solver_v2.py:292-337`). Dílčí výkon = marginální slot dolitý na zbytek (spojitá proměnná, `:162-175`). **Fix (jeden člen v objektivu, žádná nová vrstva):** - **Block-start penalta:** `ev_start[t] ≥ on[t] − on[t-1]`, objektiv `+ Σ ev_start × planner_ev_start_penalty_czk`. Min počet startů = jedna várka. Protože scatter z baterie je čistá remíza, **malá penalta slepí blok zadarmo** a **nikdy nepřebije reálný cenový spread** (auto-splnění obavy „ať to nehrne přes extrémní cenu"). DB param na `asset_ev_charger`, default 0 = no-op. - **`min_power_w` → třífázový floor** (6 A × 3 × 230 ≈ 4140 W) místo jednofázových 1380 → zruší sub-6 A drobky i tiché shození pod minimem (`outputs.py:49` je správně, problém je nefyzikální setpoint z plánu). **Soubory:** `solver_v2.py`, `db/migration/V1xx__asset_ev_charger_ev_start_penalty.sql`, `R__039`, `db_io.py`, golden fixtures, `docs/04-modules/planning.md`, `docs/planning-changelog.md`. **Golden:** ANO (za flagem default 0 → no-op). **Odloženo zvlášť:** explicitní round-trip cena EV-z-baterie v LP (citlivé na arbitráž §8; na scatter nemá vliv). --- ## 4. Trip/usage forecast — aktivace (PRIORITA Č. 4, většinou jen config) **Stav: postaveno** (V089 + R__096), chytřejší než původní nápad: - `ev_vehicle_obs` (Tesla obs při příjezdu/odjezdu), `ev_trip` (km z odometru, kWh z ΔSoC, `charged_away` vyloučí nabíjení cestou), `ev_usage_stats` (týdenní DOW rytmus), job 00:50 (`lifespan.py:276 fn_update_ev_usage_stats`). - `fn_ev_required_soc` = **P80 spotřeby toho DOW + 10 p.b.**, clamp `[min_target_soc_pct, 100]`; `fn_ev_next_departure` = typický odjezd. - Model je **DOW-based, ne GPS-route** — GPS okruhy zatím nedělá (refinement, nízká priorita; DOW na dojíždění většinou stačí). **Co dotáhnout:** - Ověřit **objem dat** (≥4 vzorky/DOW; telemetrie od ~3/2026 → po 3 měsících by mělo stačit) — chce živou DB. - Zvážit zapnutí `asset_vehicle.target_soc_forecast_enabled` (default false = sbírá, ale session jede na defaultech). **Golden:** NE (jen nastaví target/deadline session). **Soubory:** žádné nové, jen verifikace + flag. --- ## 5. Geofence arrival trigger — PRIORITA Č. 5 (schváleno uživatelem) **Motivace:** dnes je celý arrival/trip ukotvený na **píchnutí do wallboxu**. Když uživatel nepíchne (zaparkuje doma bez nabíjení), wallbox nevidí nic → žádný trip, žádná obs. Presence cesta (V095) přitom „je doma" **detekuje přes GPS geofence** i bez píchnutí (`telemetry_collector.py:840-849 at_home`). **Fix:** přechod `at_home` false→true (auto vzhůru, nepíchnuté) brát jako **arrival home event**: - zapsat obs pro trip-building (i bez píchnutí), s `trigger` rozlišujícím zdroj (wallbox vs geofence); - umožnit proaktivní notifikaci (bod #6) i bez píchnutí. **Caveaty:** oportunistické (jen když je auto vzhůru → ne instantní); **debounce** (2–3 vzorky); **dedup s wallbox arrival** (když píchneš, wallbox je autoritativní, geofence se nepočítá dvakrát); trip se páruje s nejbližším relevantním odjezd-eventem. **Soubory:** `telemetry_collector.py` (presence cesta), `R__096` (`fn_ev_build_trips` přijme geofence arrivals), případně nový `trigger` enum ve `V089` schématu (nová migrace). **Golden:** NE (jen sběr dat/notifikace). --- ## 6. Proaktivní notifikace „doma + nenabito + levné" — PRIORITA Č. 6 **Datový základ existuje** (`ev_presence_obs`: `at_home`, `charging_state`, SoC, vše bez buzení). Logika: `at_home=true` ∧ nepíchnuto (`charging_state` disconnected) ∧ SoC < target ∧ (přebytek PV NEBO záporná/levná cena) → Discord nudge „píchni ho, je levno". Oportunistické (čeká, až je auto vzhůru). Napojí se na #5 (geofence arrival) a stávající `ev_notify` / `discord_bot`. **Golden:** NE. --- ## Pořadí nasazení 1. **#1 živé SoC** — odstraní phantom okna a plýtvání; enabler pro #2. (golden) 2. **#2 předehřev/0 A** — control, hned po #1, bez golden. 3. **#3 anti-fragmentace** — za flagem default 0, kalibrace harnessem + EV fixture. 4. **#5 geofence arrival** + **#6 notifikace** — sběr/notifikace, samostatné PR. 5. **#4 forecast aktivace** — až je dat dost (verifikace na DB). **Blokery:** #1, #3, #4 chtějí **živou DB** (EV fixture, objem dat, ověření) — potřebuju IP serveru (`frank` se neresolvuje). Lokálně umím dokázat jen ne-regresi (golden default off) + unit testy. ## Rozhodnutí (z rozhovoru 2026-06-14) - 3 fáze (ne 1f surplus — pokryje velká baterka). - Anti-fragmentace = malá ekonomická penalta v solveru, ne tvrdá priorita ani nová vrstva; control zůstává hloupý (žádný max-amps override — rozbil by §7). - Geofence arrival ANO (robustnost bez píchnutí). - DOW forecast stačí; GPS-route clustering odloženo.