From ad4b52c9ce5e780ccad3925795f23854fa1842d7 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Thu, 11 Jun 2026 14:45:16 +0200 Subject: [PATCH] Dokumentace refaktoru a delta-triage skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .claude/skills/ems-delta-triage/SKILL.md | 63 +++++++++++++++++++ CLAUDE.md | 3 + docs/04-modules/planning.md | 16 +++++ .../audits/frontend-performance-2026-06-11.md | 7 +++ docs/audits/frontend-responsive-2026-06-11.md | 5 ++ docs/planning-changelog.md | 22 +++++++ docs/refactor-clean-planner.md | 62 ++++++++++++++++++ 7 files changed, 178 insertions(+) create mode 100644 .claude/skills/ems-delta-triage/SKILL.md create mode 100644 docs/refactor-clean-planner.md diff --git a/.claude/skills/ems-delta-triage/SKILL.md b/.claude/skills/ems-delta-triage/SKILL.md new file mode 100644 index 0000000..07bfbed --- /dev/null +++ b/.claude/skills/ems-delta-triage/SKILL.md @@ -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 --day --tag triage_ +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 --from --to +``` +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í). diff --git a/CLAUDE.md b/CLAUDE.md index 80cf251..418a0be 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` | diff --git a/docs/04-modules/planning.md b/docs/04-modules/planning.md index 990a12b..5079ab9 100644 --- a/docs/04-modules/planning.md +++ b/docs/04-modules/planning.md @@ -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`. diff --git a/docs/audits/frontend-performance-2026-06-11.md b/docs/audits/frontend-performance-2026-06-11.md index 74729d5..11a2efb 100644 --- a/docs/audits/frontend-performance-2026-06-11.md +++ b/docs/audits/frontend-performance-2026-06-11.md @@ -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í. diff --git a/docs/audits/frontend-responsive-2026-06-11.md b/docs/audits/frontend-responsive-2026-06-11.md index c55ed63..6fbe78e 100644 --- a/docs/audits/frontend-responsive-2026-06-11.md +++ b/docs/audits/frontend-responsive-2026-06-11.md @@ -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: ~220–250 řá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). diff --git a/docs/planning-changelog.md b/docs/planning-changelog.md index f7cb6a1..c3900f6 100644 --- a/docs/planning-changelog.md +++ b/docs/planning-changelog.md @@ -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 0–3) + +**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,4–10 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 %**). diff --git a/docs/refactor-clean-planner.md b/docs/refactor-clean-planner.md new file mode 100644 index 0000000..293e271 --- /dev/null +++ b/docs/refactor-clean-planner.md @@ -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 1–5 %, volatilní/neg-sell 50–160 %). +- 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.