---
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&lt;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).
