From 3161421d5cf3adce7e2cea05222a88cdd7e3c547 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Sat, 6 Jun 2026 22:41:56 +0200 Subject: [PATCH] skill pro debug --- .cursor/skills/ems-plan-explain/SKILL.md | 4 + .../skills/ems-planner-bug-triage/SKILL.md | 106 +++++++++ .../ems-planner-bug-triage/reference.md | 202 ++++++++++++++++++ CLAUDE.md | 2 + docs/07-mcp-postgres-ems.md | 3 + 5 files changed, 317 insertions(+) create mode 100644 .cursor/skills/ems-planner-bug-triage/SKILL.md create mode 100644 .cursor/skills/ems-planner-bug-triage/reference.md diff --git a/.cursor/skills/ems-plan-explain/SKILL.md b/.cursor/skills/ems-plan-explain/SKILL.md index 5088331..3c4be0d 100644 --- a/.cursor/skills/ems-plan-explain/SKILL.md +++ b/.cursor/skills/ems-plan-explain/SKILL.md @@ -87,6 +87,10 @@ Krátce a v pořadí: → [reference.md](reference.md) +## Související skill + +- **Bug / incident plánovače** (422 Infeasible, degradovaný relaxed solve, večerní export, multi-site): [ems-planner-bug-triage](../ems-planner-bug-triage/SKILL.md) — triáž a návrh fix větve; tento skill zůstává pro vysvětlení slotů. + ## Anti-patterns - **Hromadná analýza** (`fn_plan_explain_bundle`, `planning_interval` pro více `site_id`) jen proto, že uživatel **neřekl kterou** lokalitu — vždy se **nejprve** zeptat. diff --git a/.cursor/skills/ems-planner-bug-triage/SKILL.md b/.cursor/skills/ems-planner-bug-triage/SKILL.md new file mode 100644 index 0000000..4ba2063 --- /dev/null +++ b/.cursor/skills/ems-planner-bug-triage/SKILL.md @@ -0,0 +1,106 @@ +--- +name: ems-planner-bug-triage +description: >- + Triages EMS planner bugs from live Postgres (MCP): degraded Infeasible retry chain, + evening export vs battery hold, neg-buy/neg-sell days, fixed-tariff sites (BA81/KV1), + failed replan vs stale active plan. Use when the user reports planner errors, wrong + evening export, battery held while buy ~5 Kč/kWh, relaxed_neg_prep_window, + evening_push_hard_suppressed, or multi-site planner comparison. Complements + ems-plan-explain — use this skill for incident/bug classification and fix-branch hints. +--- + +# EMS — triáž bugů plánovače + +## Kdy použít (vs. ems-plan-explain) + +| Situace | Skill | +|---------|--------| +| „Proč v tom slotu nabíjí / neexportuje?“ | [ems-plan-explain](../ems-plan-explain/SKILL.md) | +| „Plánovač blbne / neprodává večer / 422 Infeasible / BA81 vs home-01“ | **tento skill** | +| Degradovaný plán (relaxed solve) ale run `active` | **tento skill** | +| Porovnání více lokalit uživatel **explicitně** jmenoval | **tento skill** (jinak jedna site) | + +Typické spouštěče: *neprodává večer*, *drží baterku @ 5 Kč*, *nepřeplánovalo po OTE*, *Solver: Infeasible*, *evening_push prázdný*, *sell<0 a výroba do site*. + +## Tvrdá pravidla + +1. **MCP first** — server `user-postgres-ems`, nástroj `query`, jen `SELECT`. Po chybě: přesný text + VPN/MCP zapnutý. +2. **Selhání plánu není v `planning_run`** — status je jen `active` / `superseded` / `comparison` / `draft`. API **422** a scheduler exception = logy backendu; aktivní run může být **starý** nebo **degradovaný**. +3. Rozliš: **plán v DB** vs **exekuce** — `site_operating_mode != AUTO` → rolling replan se **přeskakuje** (ne nutně solver bug). +4. **Dvě podlahy SoC:** export / strategie → **`reserve_soc_percent`** (~20 %); dům v noci (Deye PASSIVE) → může jít k **`min_soc_percent`** (~10 %). Před návrhem večerního vývoje se u exportu domluv **`reserve`**, ne `min_soc`, pokud uživatel neřekne jinak. +5. **`neg sell` ≠ `buy < 0`:** vyprázdnit před **`sell < 0`** (headroom FVE) není totéž co strategie před **`buy < 0`** (levný import). + +## Checklist triáže + +``` +- [ ] site_id (kód / id / výběr ze seznamu — viz ems-plan-explain reference §0) +- [ ] site_operating_mode + poslední řádky site_operating_mode_log +- [ ] active planning_run: id, created_at, run_type, triggered_by, soc_at_replan_wh +- [ ] solver_params.inputs: relaxed_*, evening_push_hard_suppressed, neg_evening_*, + pre_neg_pv_export_forecast_ok, charge_acquisition_buy_czk_kwh +- [ ] Večerní okno: planning_interval (battery/grid setpoint, soc target, ceny) +- [ ] Zítra: neg sloty ve vw_site_effective_price; snap neg_sell_day_pv_usable_wh +- [ ] Klasifikace bug typu A–E (reference.md §1) +- [ ] Návrh fix větve + ověření po fixu +``` + +## Rozhodovací strom + +``` +Replan 422 / žádný nový run po OTE? +├─ mode != AUTO → provozní (MANUAL/SELF_SUSTAIN), plán zastaralý +├─ poslední run any_relaxed + evening_push_hard_suppressed +│ └─ BUG typ A/B: degradovaný solve — Branch 1 + 2 +└─ žádný řádek v planning_run → backend log „Infeasible“ (Branch 1) + +Večer neprodává, drží vysoké SoC? +├─ evening_push_hard_suppressed = true → tvrdý push vypnutý (Branch 2) +├─ grid import @ drahý buy + vysoké SoC → terminal SoC + pos_sell_pre_neg_buy (Branch 2/5) +├─ zítra buy<0 + vysoká FVE, pre_neg ok = false +│ └─ měl jít večer k reserve, ne držet ~80 % (Branch 2) +└─ fixed tarif + evening_push_ts = [] → v58 / charge-slot-budget (Branch 3) + +BA81: sell<0, výroba „do site“ / export? +└─ deye_gen_microinverter_cutoff + pole B — exekuce GEN (Branch 4) +``` + +## Retry řetězec (solve_dispatch) + +Při Infeasible solver postupně zapíná (viz `planning_engine.py` ~4216): + +1. `relaxed_expensive_import` +2. `relaxed_neg_buy_charge` +3. `relaxed_neg_prep_window` → **vypne** neg-evening push, kotvy, prep hold; **`evening_push_hard_suppressed = true`** +4. `neg_sell_phases_fallback` (prep_soc = 100 %) + +**Symptom degradace:** run `active`, ale ve špičce **import ze sítě** místo exportu baterie; `neg_evening_push_ts` prázdné; plán drží SoC nad očekávání před neg dnem. + +## Výstup pro uživatele (šablona) + +1. **Fakta z DB** — run id, čas, mode, 3–5 klíčových flags, 2–3 večerní sloty (W, SoC %, buy/sell). +2. **Root cause** — jedna věta: degradovaný retry / konfigurace site / režim / exekuce. +3. **Bug typ** — A–E z [reference.md](reference.md). +4. **Doporučená větev opravy** — Branch 1–5 + soubor v repu. +5. **Ověření** — MCP dotaz nebo `pytest backend/tests/test_planning_dispatch_milp.py -k "…"`. + +## Kód a dokumentace + +| Téma | Soubor | +|------|--------| +| LP + retry | `backend/services/planning_engine.py` — `solve_dispatch` | +| `evening_push_hard_suppressed` | ~2810 | +| `pos_sell_pre_neg_buy` → `ge=0` | ~3929 | +| SQL masky | `db/routines/R__063_fn_load_planning_slots_full.sql` | +| Charge-slot budget (plán) | `docs/04-modules/planning-charge-slot-budget.md` | +| Changelog v55–v57 | `docs/planning-changelog.md` | +| Bisect fixture | `scripts/diagnose_home01_infeasible.py` | + +## SQL a archetypy site + +→ [reference.md](reference.md) + +## Anti-patterns + +- Diagnostikovat Infeasible **bez** `solver_params.inputs` z posledního úspěšného runu — může být právě ten degradovaný. +- Zaměnit „plán neexportuje“ s „exporter neběží“ — v MANUAL/SELF_SUSTAIN se plán nemusí aktualizovat. +- U fixního tarifu očekávat stejné `evening_push_ts` jako u home-01 — BA81/KV1 mají jinou větev (viz reference §3). diff --git a/.cursor/skills/ems-planner-bug-triage/reference.md b/.cursor/skills/ems-planner-bug-triage/reference.md new file mode 100644 index 0000000..9e843e1 --- /dev/null +++ b/.cursor/skills/ems-planner-bug-triage/reference.md @@ -0,0 +1,202 @@ +# EMS planner bug triage — reference (MCP) + +Všechno jen **read-only** `SELECT`. Server: **`user-postgres-ems`**, nástroj **`query`**. + +Seznam lokalit a `fn_plan_explain_bundle` → [ems-plan-explain/reference.md](../ems-plan-explain/reference.md). + +--- + +## 1) Bug typy (klasifikace) + +| Typ | Popis | Typické flags / signály | Fix větev | +|-----|--------|-------------------------|-----------| +| **A** | Degradovaný solve — Infeasible retry krok 3+ | `any_relaxed_solve`, `relaxed_neg_prep_window`, `evening_push_hard_suppressed` | Branch 1: granulární relaxace + failed-run journal | +| **B** | Večerní export chybí před neg dnem | Vysoké SoC ve špičce, `grid_setpoint_w > 0` @ ~5 Kč buy, `neg_evening_push_ts` prázdné, zítra `buy<0` / vysoká FVE | Branch 2: neg-večer k `reserve_soc` | +| **C** | Fixní tarif — špatné nabíjení / žádný večerní push | `evening_push_ts: []`, SoC ~60–80 % ve slunci, v58 `bc_pv=0` | Branch 3: charge-slot-budget | +| **D** | Provoz / exekuce, ne LP | `mode != AUTO`, starý `active` run, ruční MANUAL | Návrat do AUTO, ruční replan po fixu | +| **E** | GEN port / pole B při sell<0 | BA81 + `deye_gen_microinverter_cutoff`, audit export v sell<0 | Branch 4: cutoff exekuce | + +--- + +## 2) MCP — aktivní run + relax flags + +Nahraď `$site_id` (např. 2 = home-01): + +```sql +select pr.id, + pr.created_at, + pr.run_type, + pr.triggered_by, + pr.soc_at_replan_wh, + pr.solver_params->'inputs'->>'any_relaxed_solve' as relaxed, + pr.solver_params->'inputs'->>'relaxed_expensive_import' as r_exp_import, + pr.solver_params->'inputs'->>'relaxed_neg_buy_charge' as r_neg_buy, + pr.solver_params->'inputs'->>'relaxed_neg_prep_window' as r_neg_prep, + pr.solver_params->'inputs'->>'neg_sell_phases_fallback' as r_phases_off, + pr.solver_params->'inputs'->>'evening_push_hard_suppressed' as push_suppressed, + pr.solver_params->'inputs'->>'pre_neg_pv_export_forecast_ok' as pre_neg_ok, + pr.solver_params->'inputs'->>'neg_evening_push_ts' as neg_eve_push, + pr.solver_params->'inputs'->>'evening_push_ts' as evening_push, + pr.solver_params->'inputs'->>'charge_acquisition_buy_czk_kwh' as acq, + pr.solver_params->'inputs'->>'neg_sell_day_pv_usable_wh' as neg_pv_wh, + pr.solver_params->>'planner_build_tag' as tag + from ems.planning_run pr + where pr.site_id = $site_id + and pr.status = 'active' + order by pr.created_at desc + limit 1; +``` + +Poslední běhy (včetně comparison) za 48 h: + +```sql +select pr.id, pr.status, pr.run_type, pr.created_at, + pr.solver_params->'inputs'->>'relaxed_neg_prep_window' as r3, + pr.solver_params->'inputs'->>'evening_push_hard_suppressed' as push_sup + from ems.planning_run pr + where pr.site_id = $site_id + and pr.created_at >= now() - interval '48 hours' + order by pr.created_at desc + limit 20; +``` + +--- + +## 3) Site archetypy (orientace) + +| | **home-01** | **BA81** | **KV1** | +|---|-------------|----------|---------| +| Buy | spot | fixed ~2,55 NT | fixed 5,25 | +| `block_export_on_negative_sell` | false | false | **true** | +| Neg sell fáze | 80 % prep | default | **100 % (off)** | +| `deye_gen_microinverter_cutoff` | false | **true** | false | +| `planner_terminal_soc_value_factor` | **0,9** | 0,2 | 0,2 | +| Typický večerní bug | A + B (relaxed + drží SoC) | C (no evening_push) | občas A, jinak OK | + +Konfigurace z DB: + +```sql +select s.code, + smc.purchase_pricing_mode, + sgc.block_export_on_negative_sell, + ai.deye_gen_microinverter_cutoff_enabled, + ab.reserve_soc_percent, + ab.min_soc_percent, + ab.planner_neg_sell_prep_soc_percent, + ab.planner_terminal_soc_value_factor + from ems.site s + left join ems.site_market_config smc + on smc.site_id = s.id and smc.valid_to is null + left join ems.site_grid_connection sgc on sgc.site_id = s.id + left join ems.asset_inverter ai + on ai.site_id = s.id and ai.code = 'deye-main' + left join ems.asset_battery ab + on ab.site_id = s.id and ab.code = 'bat-main' + where s.code in ('home-01', 'BA81', 'KV1'); +``` + +--- + +## 4) Provozní režim a log + +```sql +select mode_code from ems.site_operating_mode where site_id = $site_id; + +select activated_at at time zone 'Europe/Prague' as ts_prague, + mode_code, + activated_by + from ems.site_operating_mode_log + where site_id = $site_id + order by activated_at desc + limit 8; +``` + +Rolling replan běží jen v **AUTO**. + +--- + +## 5) Večerní okno — planning_interval + +```sql +select pi.interval_start at time zone 'Europe/Prague' as slot_prague, + pi.battery_setpoint_w, + pi.grid_setpoint_w, + pi.battery_soc_target_pct, + pi.effective_buy_price, + pi.effective_sell_price + from ems.planning_interval pi + where pi.run_id = ( + select id from ems.planning_run + where site_id = $site_id and status = 'active' + order by created_at desc limit 1 + ) + and pi.interval_start >= (current_timestamp at time zone 'Europe/Prague')::date + + interval '17 hours' + and pi.interval_start < (current_timestamp at time zone 'Europe/Prague')::date + + interval '1 day' + interval '6 hours' + order by pi.interval_start; +``` + +**Červená vlajka:** `battery_setpoint_w = 0` a `grid_setpoint_w > 0` při sell ~3 Kč a SoC > reserve — import místo exportu baterie. + +--- + +## 6) Zítřejší neg ceny + +```sql +select interval_start at time zone 'Europe/Prague' as slot_prague, + effective_buy_price_czk_kwh as buy, + effective_sell_price_czk_kwh as sell + from ems.vw_site_effective_price + where site_id = $site_id + and interval_start >= (current_timestamp at time zone 'Europe/Prague')::date + + interval '1 day' + and interval_start < (current_timestamp at time zone 'Europe/Prague')::date + + interval '2 days' + and (effective_buy_price_czk_kwh < 0 or effective_sell_price_czk_kwh < 0) + order by interval_start; +``` + +--- + +## 7) Slovník solver_params.inputs (výběr) + +| Klíč | Význam | +|------|--------| +| `evening_push_hard_suppressed` | true = **bez** tvrdého `ge_bat` push (typicky retry krok 3) | +| `relaxed_neg_prep_window` | Vypnuty neg-evening kotvy, prep hold, neg evening push | +| `pre_neg_pv_export_forecast_ok` | Cushion FVE v sell<0 okně — false → nemá exportovat ranní PV před neg | +| `neg_evening_push_ts` | Sloty D−1 večer pro vývoj před neg oknem | +| `charge_acquisition_buy_czk_kwh` | Účinná cena energie v baterii pro arbitráž exportu | +| `neg_sell_day_pv_usable_wh` | Forecast Wh do baterie v sell<0 okně zítřka | +| `morning_pre_neg_export_hard` | Tvrdý ranní export před sell<0 | + +Plný snap: `select ems.fn_planning_run_debug();` + +--- + +## 8) Fix větve (implementační plán v repu) + +| Branch | Obsah | Priorita | +|--------|--------|----------| +| **1** | Failed-run journal, bisect Infeasible, granulární relaxace | P0 | +| **2** | home-01 neg-večer → `reserve_soc`, oddělit push od prep relax | P0 | +| **3** | charge-slot-budget v R__063, BA81/KV1 večerní export | P1 | +| **4** | BA81 GEN cutoff audit | P1 | +| **5** | Dynamický terminal SoC při future neg buy | P2 | + +Detail: plán v `.cursor/plans/` nebo `docs/planning-changelog.md` + `docs/04-modules/planning-charge-slot-budget.md`. + +--- + +## 9) Ověření po fixu + +```bash +pytest backend/tests/test_planning_dispatch_milp.py -k "evening or NegSell or Infeasible" +``` + +```bash +python scripts/diagnose_home01_infeasible.py +``` + +MCP po deployi — nový active run **bez** `relaxed_neg_prep_window` nebo s `evening_push_hard_suppressed: false` a večerní sloty s `grid_setpoint_w < 0`. diff --git a/CLAUDE.md b/CLAUDE.md index b6d5df5..84ce0d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -216,6 +216,8 @@ 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`**. | +| **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/07-mcp-postgres-ems.md b/docs/07-mcp-postgres-ems.md index 586ce43..a3452ce 100644 --- a/docs/07-mcp-postgres-ems.md +++ b/docs/07-mcp-postgres-ems.md @@ -74,6 +74,9 @@ Měnící funkce (**`ems.fn_delete_forecast_pv_prague_calendar_day`**, **`ems.fn - Stručná návěstí také v **[`../CLAUDE.md`](../CLAUDE.md)** (sekce MCP + tabulka „Kde hledat co“). - Trvalé pravidlo pro agenta: **[`../.cursor/rules/mcp-postgres-ems.mdc`](../.cursor/rules/mcp-postgres-ems.mdc)** (`alwaysApply: true`). +- **Agent skills (Cursor):** + - Vysvětlení plánu (sloty, proč nabíjí/exportuje): **[`.cursor/skills/ems-plan-explain/SKILL.md`](../.cursor/skills/ems-plan-explain/SKILL.md)** — `fn_plan_explain_bundle`. + - **Triáž bugů plánovače** (422 Infeasible, degradovaný relaxed solve, večerní export, BA81/KV1): **[`.cursor/skills/ems-planner-bug-triage/SKILL.md`](../.cursor/skills/ems-planner-bug-triage/SKILL.md)** — MCP dotazy na `solver_params.inputs`, klasifikace A–E, fix větve; SQL šablony v [`reference.md`](../.cursor/skills/ems-planner-bug-triage/reference.md). ---