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

@@ -0,0 +1,63 @@
---
name: ems-delta-triage
description: Triáž neekonomického chování plánovače po nasazení — vysvětlit PROČ plán udělal co udělal, porovnat v1 vs v2 (shadow), vyčíslit ztrátu proti oracle. Použít když uživatel hlásí "divné/neekonomické chování", "proč to v X hodin nabíjelo/exportovalo", nebo chce vyhodnotit shadow data v1 vs v2.
---
# EMS delta-triáž (v1 vs v2 vs realita vs oracle)
Cíl: z konkrétního dne/situace vyrobit vysvětlení s čísly, ne dojmy. Vždy
pracuj v pořadí: (1) co se REÁLNĚ stalo, (2) co chtěl plán, (3) co chtěl peer
(shadow), (4) co bylo optimum, (5) proč se liší.
## 0. Vstupy od uživatele
site code (home-01/BA81/KV1/…), den či časové okno (Prague), co je „divné".
## 1. Realita (audit) — MCP `query` na `user-postgres-ems`
```sql
select interval_start, actual_grid_power_w, actual_battery_power_w,
actual_battery_soc_pct, actual_pv_power_w, actual_load_power_w,
actual_cost_czk, deviation_cost_czk, planning_run_id
from ems.audit_interval
where site_id = :id and interval_start >= :od and interval_start < :do
order by interval_start;
```
+ efektivní ceny: `ems.vw_site_effective_price` (stejné okno). Hledej sloty,
kde tok jde PROTI ceně (import za draho při nabité baterii, export při sell<0…).
## 2. Plán a jeho zdůvodnění
- Aktivní run pro slot: `audit_interval.planning_run_id``ems.planning_run`
(`solver_params`: `version`, `relax_chain`, `neg_sell_*`, `evening_push_ts`…)
a `ems.planning_interval` (setpointy, expected_cost).
- `ems.fn_plan_explain_bundle` + skill `.cursor/skills/ems-plan-explain`.
- v1 vs v2 shadow diff: `planning_run.solver_params->'comparison'`
(`diff.total_expected_cost_czk`, `slot_diffs` — kde se verze rozcházejí).
## 3. Replay lokálně (přesná rekonstrukce)
```bash
python3 scripts/harness/extract_fixtures.py --site-code <code> --day <YYYY-MM-DD> --tag triage_<duvod>
cd backend && python3 ../scripts/harness/solver_v2_eval.py # v1 (golden) vs v2 na fixture
```
Pozor: context = AKTUÁLNÍ konfigurace; pro historickou věrnost srovnej
`planning_run.solver_params.inputs` (battery parametry tehdy).
## 4. Optimum (kolik se nechalo na stole)
```bash
EMS_DB_DSN=… python3 scripts/harness/economics_report.py --site-code <code> --from <den> --to <den>
```
GAP = forecast error + neefektivita dispatche. Pro oddělení: porovnej plán
(forecast vstupy) vs oracle (skutečné PV/load) — velký rozdíl plán/oracle při
malém rozdílu plán/realita ⇒ chyba forecastu, ne dispatche.
## 5. Verdikt — vždy jedna z kategorií + číslo v Kč
- **forecast error** (PV/load se netrefil; plán byl na svá data racionální),
- **heuristika v1** (penalty/maska vynutila neekonomický tok — ukaž kterou:
vypni ji přes `penalty_audit.py --only NAZEV` na fixture dne),
- **tvrdé pravidlo** (block_export, arb floor, breaker, režim — správné chování),
- **chyba modelu v2** (jen pokud aktivní v2; ověř `solver_v2_eval.py` + unit testy),
- **exekuce** (plán dobrý, zařízení neposlechlo — `ems.modbus_command` journal,
skill ems-planner-bug-triage).
## Zásady
- Žádné závěry bez čísel ze SQL/harnessu; vždy uveď sloty a Kč.
- Nikdy neměnit plánovač bez golden gate (viz docs/refactor-clean-planner.md).
- Nálezy zapsat do docs/planning-changelog.md (formát: datum · problém · příčina · ověření).

View File

@@ -218,6 +218,9 @@ Specifikace z `docs/02-architecture.md`, modulových docs a komentářů v `plan
| Reset DB / restore z dumpu (Docker volume, Timescale) | `docs/database-reset-and-restore.md`, `scripts/import_ems_db.sh` |
| Nespecifikované chování | `docs/06-open-questions.md` (přidat otázku, neimpl. naslepo) |
| **MCP read-only SQL na EMS DB** | **`docs/07-mcp-postgres-ems.md`** — server ID **`user-postgres-ems`**, nástroj **`query`**, `{"sql":"…"}`. Pravidlo **`.cursor/rules/mcp-postgres-ems.mdc`**. |
| **Refaktor „Čistý plánovač“ (fáze, stav, nasazení v2)** | **`docs/refactor-clean-planner.md`**; verze enginu v1/v2 + env flagy: `docs/04-modules/planning.md` (sekce Verze enginu); changelog 2026-06-11 |
| **Čisté jádro plánovače v2** | `backend/services/planning/solver_v2.py`, testy `backend/tests/test_solver_v2.py`, eval `scripts/harness/solver_v2_eval.py` |
| **Delta-triáž neekonomického chování (agent skill)** | **`.claude/skills/ems-delta-triage/`** — realita vs plán vs shadow peer vs oracle, verdikt s Kč |
| **Vysvětlení plánu (agent skill)** | **`.cursor/skills/ems-plan-explain/`** — `fn_plan_explain_bundle`, sloty, proč nabíjí/exportuje |
| **Triáž bugů plánovače (agent skill)** | **`.cursor/skills/ems-planner-bug-triage/`** — Infeasible/relaxed solve, večerní export, neg den, BA81/KV1; MCP SQL v `reference.md` |

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.