Files
ems/.cursor/skills/ems-plan-explain/SKILL.md
Dusan Vojacek 8960576ee8
Some checks failed
CI and deploy / migration-check (push) Failing after 22s
CI and deploy / deploy (push) Has been skipped
skill vysvetlovac
2026-04-26 23:05:12 +02:00

94 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: ems-plan-explain
description: >-
Explains EMS dispatch plans from live Postgres (MCP): why battery/grid/PV/curtailment
for a site and time window. If the user does not explicitly name a site (id, code, or
unambiguous name), query ems.site (with active plan hint), show a numbered list, and
ask which site to use — do not run plan analysis for multiple sites in one turn. Use when the
user asks why the plan looks a certain way, planning_interval rows, negative prices,
export zero, rolling replan, or says „vysvětli plán“, „proč nabíjí“, „proč škrtí FVE“.
---
# EMS — vysvětlení plánu (živá DB + kontext kódu)
## Kdy skill použít
- Otázky typu **proč** plán dělá X (nabíjení, export, curtailment, režim, ceny).
- Uživatel zmíní **kód lokality** (`BA81`, …) nebo „aktuální plán“.
- Porovnání **model vs realita** (záporná cena, nulový export, pole A/B).
## Tvrdá pravidla
1. **Nejdřív data z DB přes MCP** (`user-postgres-ems`, nástroj `query`, pouze `SELECT`). Nevysvětlovat konkrétní sloty „z hlavy“ bez dotazu.
2. Pokud MCP selže: uvést **přesnou chybu** a praktické kroky (VPN, MCP zapnutý, dostupnost DB).
3. **`site_id` jen po explicitní volbě uživatele** (kód, id, potvrzení jedné řádky), nebo když uživatel **lokalitu v dotazu sám pojmenoval**. Neuvedená lokalita → **nejprve jen dotaz na výběr** (viz Krok 1); **zakázáno** analyzovat plán pro více `site` v jedné odpovědi „preventivně“.
4. V odpovědi rozlišit: **co říká plán v DB** vs **co předpokládá LP model** vs **co omeží hardware** (např. taper nabíjení u vysokého SoC dnes v LP **není**).
## Postup (zkopíruj checklist)
```
- [ ] Zjistit site_id: uživatel ji v dotazu pojmenoval? → případně MCP lookup. Jinak MCP seznam + **zeptat se** (viz Krok 1); až po odpovědi → jedna `site_id`
- [ ] MCP: fn_plan_explain_bundle(site_id, hours) — default hours=6
- [ ] Z JSONu: operating_mode, grid limity, battery limity, intervals_next_window
- [ ] Potřebuji konkrétní čas? → doplnit SELECT na planning_interval (viz reference.md)
- [ ] Vysvětlit bilanci slotu + relevantní LP pravidla (solve_dispatch)
```
### Krok 1 — `site_id`
**Co znamená „lokalita explicitně zmíněná“:** v textu uživatele je **číselné `site_id`**, **kód lokality** (`BA81`, `home-01`, …), nebo **jednoznačný** název/fragment, ze kterého MCP vrátí **právě jednu** řádku `ems.site`.
- Pokud uživatel dal **`site_id` jako číslo**: ověřit MCP, že řádek v `ems.site` existuje → použít.
- Pokud dal **kód nebo část názvu** (`BA81`, …): MCP `select id, code, name from ems.site where code ilike … or name ilike …`.
- **0 řádků** → nabídnout seznam z [reference.md §0](reference.md) (všechny lokality) + **zeptat se**, kterou myslí.
- **1 řádek** → použít jeho `id`.
- **Více řádků** → číslovaný výpis + **zeptat se** na jednu (můžeš hintnout *kdo má aktivní plán*, ale **nepouštěj** analýzu dřív než výběr).
- Pokud **lokalita vůbec zmíněná není** („vysvětli plán“, „proč nabíjí“ bez kódu apod.):
1. MCP: SQL z **reference.md §0** (seřazený seznam `site` + `active_planning_run_id`).
2. V odpovědi uvést **číslovaný seznam** `id | code | name | má aktivní plán?`.
3. **Výslovně se zeptat uživatele**, kterou lokalitu myslí (číslo z výpisu, `code`, nebo `id`).
4. **`fn_plan_explain_bundle` ani rozšířený SELECT na `planning_interval` pro tuto otázku nespouštěj**, dokud uživatel **nevybere jednu** lokalitu (kód / číslo řádku / id / jednoznačné „tu s BA81“). **Nepředvybírej** „beru první řádek“ ani nespouštěj paralelně bundle pro všechny `site_id` — je to zbytečná zátěž a matoucí výstup.
5. Je v DB **jen jeden** záznam `ems.site`: stejně **nejdřív** napiš *která* lokalita to je a **zeptej se** na krátké potvrzení (např. *„Mám plán vysvětlit pro **CODE**?“* / stačí „ano“) — **bez** `fn_plan_explain_bundle` před odpovědí. Výjimku tvoří jen situace, kdy uživatel **v téže zprávě** současně explicitně odkáže na tuto jedinou lokalitu (pak není „neuvedená“).
### Krok 2 — balík pro vysvětlení
```sql
select ems.fn_plan_explain_bundle(<site_id>, <hours>);
```
- **`<hours>`**: default **6**. Jiná hodnota jen když uživatel explicitně chce delší/kratší okno.
- Výstup je **jeden JSONB** (`bundle`): viz `.cursor/rules/plan-explain-bundle.mdc` — které klíče číst.
### Krok 3 — interpretace (struktura odpovědi)
Krátce a v pořadí:
1. **Kontext**: `operating_mode.mode_code`, `active_planning_run` (`run_type`, `triggered_by`, `soc_at_replan_wh`, `forecast_correction_factor`).
2. **Slot(y)**: z `intervals_next_window` nebo z dodatečného SQL — pro každý relevantní interval:
- **Výkon**: `battery_setpoint_w` (+ nabíjení / vybíjení), `grid_setpoint_w` (+ import / export), `load_baseline_w`.
- **FVE**: `pv_a_forecast_solver_w`, `pv_b_forecast_solver_w`, `pv_a_curtailed_w` (useknuté W na **pole A**).
- **Ceny**: `effective_buy_price`, `effective_sell_price`, `is_predicted_price`.
- **Exekuce Deye** (pokud je ve sloupcích): `deye_physical_mode`, `deye_gen_cutoff_enabled`.
3. **Proč** (odkaz na logiku, ne dlouhá citace):
- Záporná **prodejní** cena → export do sítě v LP **neekonomický** / u části instalací **tvrdě 0**; přebytek → nabíjení / curtailment **A** / GEN cutoff (viz `solve_dispatch` v `backend/services/planning_engine.py`).
- **Pole B** je v modelu **nekontrolovatelné** — nelze ho `pv_a_curtailed` omezit.
- **Zelený bonus** není v účelové funkci LP; počítá se v auditu (`fn_green_bonus_revenue`) — viz `docs/04-modules/planning.md`.
4. **Mezery modelu** (upozornit jednou větu, když je to relevantní):
- LP používá horní strop **`max_charge_power_w`** bez závislosti na SoC → u vysokého SoC může reálný proud být nižší než plán.
### Kdy se zeptat uživatele
- **Lokalita neuvedená nebo nejednoznačná** — vždy **nejdřív** výběr / potvrzení (viz Krok 1); **nikdy** hned neanalyzovat všechny lokality najednou.
- **Čas bez časové zóny** („v 11:15“) — potvrdit **Europe/Prague** nebo explicitní offset.
- **Širší horizont** než pár hodin — domluvit `hours` nebo přesné `from`/`to` UTC pro doplnkový SELECT.
## Další SQL a šablony
→ [reference.md](reference.md)
## 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.
- Nevyhledávat plán přes desítky ad-hoc dotazů, když stačí **`fn_plan_explain_bundle`** a případně jeden doplnkový `SELECT` na časové okno.
- Nezaměňovat **`pv_a_curtailed_w`** (plán) s tím, **co je vždy zapsané na Modbus** — exekuce curtailmentu na Deye může být instalacně závislá; při pochybnostech říct „ověřit v `docs/05-todo.md` / modbus docs“.