Files
ems/docs/ev-improvement-plan-2026-06-14.md
Dusan Vojacek 8ffe5460f1 fix(planner): živé EV SoC z integrálu power_w — konec phantom 11 kW oken
needed_wh i headroom z live_soc (soc_at_connect + integrál power_w), ne ze
zamrzlého soc_at_connect. energy_delivered_wh se během session nikdy nezapisoval
(→ needed konstantní, plánovač slepý k pokroku), counter energy_kwh (Telto reg 39)
je rozbitý (17.4 kWh nabito → counter 0.18). Nový fn_ev_session_delivered_wh
integruje power_w (dt cap 120 s), clamp 99 %, fallback drží staré chování bez
telemetrie. Ověřeno živě: needed_wh 18750→1329, live_soc 97.9 %.

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

12 KiB
Raw Blame History

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 0needed_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:3761.)

Klíč: živý progres už v DB MÁMEtelemetry_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 (~812 % 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,31,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 (23 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.