Dokumentace refaktoru a delta-triage skill

- docs/refactor-clean-planner.md: plán Fází 0-4, stav, závazná pravidla
  (golden gate), návod nasazení v2 (shadow → vyhodnocení → přepnutí)
- docs/planning-changelog.md: záznam 2026-06-11 (Fáze 0-3 kompletní)
- docs/04-modules/planning.md: sekce Verze enginu v1/v2 + env flagy
- docs/audits/*: stav implementace FE fixů
- .claude/skills/ems-delta-triage: postup triáže neekonomického chování
  (realita vs plán vs shadow peer vs oracle, verdikt s Kč)
- CLAUDE.md: ukazatele na refaktor, solver_v2 a delta-triage v 'Kde hledat co'

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-11 14:45:16 +02:00
parent b5dbc8cf0a
commit ad4b52c9ce
7 changed files with 178 additions and 0 deletions

View File

@@ -799,3 +799,19 @@ Planner v2 má dělat přesně toto:
- PV A škrtit jen když je to nutné
- PV B nikdy neškrtit
- BA81 řešit přes GEN cutoff
---
## Verze enginu: v1 (heuristický) vs v2 (čisté jádro) — od 2026-06-11
Plánovač má dvě implementace, přepínané env proměnnými (`backend/app/config.py`):
| Env | Default | Význam |
|-----|---------|--------|
| `PLANNING_ENGINE_VERSION` | `v1` | Aktivní engine pro daily i rolling plán |
| `PLANNING_ENGINE_COMPARE_ENABLED` | `false` | Shadow režim: druhá verze se počítá paralelně, diff se ukládá do `planning_run.solver_params.comparison` (status `comparison`) |
- **v1** = `solve_dispatch_two_pass` (heuristické fáze/okna/kotvy + penalty; popsáno výše v tomto dokumentu).
- **v2** = `services/planning/solver_v2.py`: objective = jen reálné peníze (cash + degradace terminal SoC value z `asset_battery.planner_terminal_soc_value_factor`); tvrdá pravidla (CLAUDE.md 5/6/7/19), EV deadline (placený slack), TUV look-ahead, provozní režimy. SQL masky `allow_charge`/`allow_discharge_export` **ignoruje**.
- Router: `_solve_dispatch_for_version` v `planning_engine.py`; chyby v2 jdou do standardní failure pipeline (`fn_planning_run_fail`).
- Regresní brána a měření: `scripts/harness/README.md` (golden replay, economics report, penalty audit, `solver_v2_eval.py`); plán refaktoru: `docs/refactor-clean-planner.md`.

View File

@@ -24,3 +24,10 @@ Měřeno na živé DB (site_id=2) + statická analýza kódu a bundle. Plný kon
1. **Backend SQL**: fn_plan_current_bundle + fn_site_full_status (největší dopad, řeší se samostatně).
2. **FE quick wins**: polling 60/15 s, telemetry limit 420, lazy routes + manualChunks.
3. **FE větší**: 2-vlnové načítání, virtualizace Planning tabulky, memoizace.
## Stav implementace (2026-06-11)
- ✅ Quick wins (polling 60/15/120 s, payload okna grafu, manualChunks + lazy routes, 2 vlny načítání) — merge `60f5f77`, build ověřen.
-`vw_latest_inverter` / `vw_latest_ev_charger` → LATERAL (508→56 ms, 460→75 ms živě) — commit `1d5b97c`, projeví se deployem.
-`fn_plan_current_bundle` (90 % času ve `fn_forecast_pv_slots_range_canonical_ab`) — vyžaduje hlubší zásah.
- ⬜ Virtualizace Planning tabulky, Recharts Cell mapování.

View File

@@ -22,3 +22,8 @@ Hlášené problémy: grafy na mobilu špatně zobrazené; tooltip při dotyku k
3. **Střední**: grid breakpointy všude, ControlPanel, font scaling, touch targets.
Odhad: ~220250 řádků změn napříč ~13 soubory.
## Stav implementace (2026-06-11)
- ✅ Kritické + vysoké: responsive výšky grafů (tailwind chart-*), StatePanel mobile, PlanSlotDetail sticky řádek, tap-to-pin tooltip (Chart.js panel / Recharts trigger click, hook `useIsCoarsePointer`), viewport-fit, touch targets, grid breakpointy — merge `60f5f77` + fix `b5dbc8c`, build ověřen.
- ⬜ Otestovat na reálném mobilu (tap-to-pin chování, scroll Planning tabulky).

View File

@@ -5,6 +5,28 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
---
## 2026-06-11 — Refaktor „Čistý plánovač“: harness, dekompozice, solver_v2 (Fáze 03)
**Kontext:** Ekonomický audit potvrdil systémový problém heuristické vrstvy: na neg-sell dnech Σ heuristických penalt v objective 13× převažuje reálný cashflow; GAP actual vs perfect-hindsight oracle za 29 dní home-01 = **2 185 Kč ≈ 27 %**. Plný plán a stav: `docs/refactor-clean-planner.md`.
**Fáze 0 — harness (`scripts/harness/`, `backend/tests/golden/`):**
- `extract_fixtures.py` (vstupy solveru z DB → JSON), **golden replay gate** `test_golden_replay.py` (bit-perfektní diff, `GOLDEN_UPDATE=1` jen vědomě), `economics_report.py` (actual vs oracle, SoC-adjusted), `penalty_audit.py`.
- 6 fixtures vč. **`home-01_2026-05-01_extreme_neg_buy`** (buy 13,26): v1 **Infeasible po všech 8 relax krocích** — zmrazeno jako golden failure snapshot.
**Fáze 1 — dekompozice:** `planning_engine.py` 6 345 → 3 960 ř.; nový balíček `services/planning/` (`constants` — všech 59 konstant vč. penalt, `types`, `forecast`, `db_io`, `heuristics` — 88 pre-solver funkcí). Engine = fasáda, importy beze změny, golden gate zelená po každém kroku.
**Fáze 2 — audit:** 16 z 26 ekonomických penalt **mrtvých** na všech fixtures (vč. `EVENING_PUSH_Z_EXPORT_BONUS` na evening-push dni); aktivní penalty silně interagují. 4 trvale failující testy = **stale** (chování před retry-chain v5) → `@unittest.expectedFailure` se zdůvodněním; suite poprvé zelená (120 passed, 4 xfailed).
**Fáze 3 — `services/planning/solver_v2.py` (čisté jádro):**
- Objective = cash + degradace terminal SoC value (DB faktor); tvrdá pravidla (bilance, breaker, curtail jen A, GEN cutoff binárka, neg-buy/neg-sell bloky, export z baterie ⇒ arb floor, zákaz souč. imp+exp), EV deadline s placeným slackem (50 Kč/kWh), TUV look-ahead, režimy. **SQL masky `allow_*` ignoruje** (heuristika, ne fyzika).
- **Výsledky (`solver_v2_eval.py`):** lepší než v1 na všech 5 řešitelných fixtures (**+231,5 Kč ≈ +22 %**, SoC-fér); extreme_neg_buy den v1=INFEASIBLE → v2 OK. Časy 0,410 s (2 extrémy na time limitu — TODO méně binárek).
- **Router verzí:** `_solve_dispatch_for_version` v engine; env `PLANNING_ENGINE_COMPARE_ENABLED=true` = shadow (v1 aktivní, v2 peer, diff v `planning_run.solver_params.comparison`); `PLANNING_ENGINE_VERSION=v2` = přepnutí. Default v1 — beze změny chování.
**Soubory:** `services/planning/*`, `planning_engine.py`, `tests/test_solver_v2.py` (11), `tests/test_golden_replay.py`, `scripts/harness/*`.
**Ověření:** plná sada 245 passed / 4 xfailed (1 předexistující reg340 fail); golden 7/7; `solver_v2_eval.py`.
---
## 2026-06-06 — Pozdní replan večer: Infeasible při vysokém SoC (home-01)
**Problém:** Po přepnutí na AUTO a ručním replanem (~21:00, SoC **~74 %**, zítra `buy<0` + `sell<0`): všechny retry včetně `neg_sell_phases_fallback`**`Solver: Infeasible`**. Aktivní zůstal starý plán z 17:00 (import místo večerního vývozu k **reserve ~20 %**).

View File

@@ -0,0 +1,62 @@
# Refaktor „Čistý plánovač“ — plán a stav
Cíl: odstranit příčinu neekonomického provozu — heuristickou vrstvu okolo MILP
solveru (pre-solver fáze/okna/kotvy + ~26 ručně laděných penalt v objective),
která se vzájemně pere a převažuje reálné peníze. Strategie: **ne big-bang
přepis projektu** (predikce, Modbus, telemetrie, audit, DB jsou odladěné),
ale řízená náhrada jádra plánovače za regresním harnessem.
## Diagnóza (měřeno 2026-06-11)
- `planning_engine.py` (před refaktorem 6 345 ř., 112 funkcí): ~35 % ekonomické
logiky v heuristikách PŘED solverem, ~60 % jako měkké penalty v objective
s ~20 konstantami natvrdo. Solver = „vykonavatel heuristického plánu“.
- Na neg-sell dni Σ penalt 2 119 Kč při cashflow 163 Kč (13×).
- GAP actual vs perfect-hindsight oracle, home-01 29 dní: **2 185 Kč ≈ 27 %**
(stabilní dny 15 %, volatilní/neg-sell 50160 %).
- Den 2026-05-01 (buy 13,26): v1 Infeasible po všech 8 relax krocích.
- Penalty audit: **16/26 penalt mrtvých** na 6 reprezentativních fixtures.
## Fáze a stav
| Fáze | Obsah | Stav |
|------|-------|------|
| 0 | Ekonomický harness: golden replay gate, fixtures z reálné DB, economics report (actual vs oracle), penalty audit | ✅ hotovo |
| 1 | Dekompozice `planning_engine.py``services/planning/` (constants/types/forecast/db_io/heuristics), fasáda, identita chování | ✅ hotovo |
| 2 | Penalty audit, stale testy → xfail, rozšíření fixtures (extrémní dny) | ✅ hotovo |
| 3 | `solver_v2` (čisté jádro) + router verzí + shadow porovnání | ✅ hotovo (kód); **čeká na shadow data z produkce** |
| 4 | Slupka: FE výkon + responsivita | ✅ první vlna (viz `docs/audits/`) |
## Jak se pracuje (závazná pravidla)
1. **Golden gate** (`backend/tests/test_golden_replay.py`) musí projít po každé
změně plánovače. Snapshoty se regenerují (`GOLDEN_UPDATE=1`) jen při vědomé
změně chování, s odůvodněním v commitu a s nezhoršeným GAPem
(`scripts/harness/economics_report.py`).
2. Ekonomické parametry patří do DB (CLAUDE.md pravidlo 16), ne do Pythonu.
3. v2 nikdy neměnit bez `solver_v2_eval.py` (v2 vs v1 na fixtures).
## Nasazení v2 (návod)
1. **Shadow**: do prod env `PLANNING_ENGINE_COMPARE_ENABLED=true` → v1 řídí,
v2 se počítá paralelně, diff v `planning_run.solver_params.comparison`.
2. Po ~týdnu vyhodnotit: `select solver_params->'comparison' from ems.planning_run …`
+ `economics_report.py` (trend GAPu).
3. **Přepnutí**: `PLANNING_ENGINE_VERSION=v2`; golden snapshoty vědomě
zregenerovat; heuristics.py + mrtvé penalty postupně mazat.
## Klíčové výsledky v2 (fixtures, SoC-fér)
v2 lepší na všech 5 řešitelných fixtures, **+231,5 Kč ≈ +22 %**; den
2026-05-01 v1=INFEASIBLE → v2 řeší (674,5 Kč). Detail:
`scripts/harness/solver_v2_eval.py`, changelog 2026-06-11.
## Otevřené body
- v2 výkon na extrémních dnech (10 s time limit) — omezit binárky
`y_imp`/`z_exp` jen na sloty, kde dávají smysl.
- `fn_plan_current_bundle` 3,8 s (90 % v `fn_forecast_pv_slots_range_canonical_ab`)
— viz `docs/audits/frontend-performance-2026-06-11.md`.
- Virtualizace Planning tabulky; Recharts Cell mapování.
- Po přepnutí na v2: smazat mrtvé heuristiky/penalty, přepsat 4 xfail testy
na ekonomické asserty.