skill pro debug
Some checks failed
CI and deploy / migration-check (push) Failing after 15s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-06-06 22:41:56 +02:00
parent 36cb06b9d0
commit 3161421d5c
5 changed files with 317 additions and 0 deletions

View File

@@ -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 AE (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, 35 klíčových flags, 23 večerní sloty (W, SoC %, buy/sell).
2. **Root cause** — jedna věta: degradovaný retry / konfigurace site / režim / exekuce.
3. **Bug typ** — AE z [reference.md](reference.md).
4. **Doporučená větev opravy** — Branch 15 + 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 v55v57 | `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).

View File

@@ -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 ~6080 % 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&lt;0 | BA81 + `deye_gen_microinverter_cutoff`, audit export v sell&lt;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 &gt; 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&lt;0 okně — false → nemá exportovat ranní PV před neg |
| `neg_evening_push_ts` | Sloty D1 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&lt;0 okně zítřka |
| `morning_pre_neg_export_hard` | Tvrdý ranní export před sell&lt;0 |
Plný snap: `select ems.fn_planning_run_debug(<run_id>);`
---
## 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`.