Files
ems/docs/planning-changelog.md
Dusan Vojacek 645f48036d
Some checks failed
CI and deploy / migration-check (push) Failing after 11s
CI and deploy / deploy (push) Has been skipped
dalsi a dalsi oprava
2026-05-23 22:41:00 +02:00

136 lines
7.2 KiB
Markdown
Raw 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.
# Planning / LP — changelog
Změny v plánovači (`planning_engine.py`, `R__063_fn_load_planning_slots_full.sql`) a souvisejících testech.
Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověření.
---
## 2026-05-24 — Arbitráž: OTE místo hodin, export ve špičkách, FVE při sell<0
**Problém:** Plán ukazoval slabé nabíjení/vybíjení (KV1, BA81) přestože ekonomika (OTE) favorizovala opak. Ve špičkách MILP nevybíjel baterii naplno; noc BA81 držela SoC na rezervě bez exportu; záporný výkup neplnil FVE do baterie.
**Změny:**
| Oblast | Co | Proč |
|--------|-----|------|
| **R__063 — exportní maska** | Místo pevného vyloučení **0004** na den prvního `sell<0`: slot vynechat z rozpočtu Wh jen pokud **existuje pozdější slot tentýž den** (před prvním `sell<0`) s `sell > sell_slot + degradace`. | Řídit se **OTE cenami**, ne hodinami. BA81 noc může exportovat; home-01 půlnoc se vynechá, pokud je lepší sell ráno. |
| **R__063 — fixní tarif** | Discharge kandidáti: `sell > buy + degradace` (ne jen `sell > degradace`). | U BA81/KV1 export jen když je výkup nad fixním nákupem. |
| **R__063 — PV vrstva A** | `allow_charge` z FVE při `sell < 0` **bez** filtru `future_sell_lookahead`; filtr „drž na večerní peak“ jen pro `sell ≥ 0`. | V záporném výkupním okně nabít z FVE (KV1 `block_export`). |
| **LP — export shortfall** | Penalizace nevyužitého exportu na **`ge_bat`**, ne na `ge`; pro **všechny** `allow_discharge_export` sloty s kladnou marží (`sell > acquisition` resp. `sell > buy + degrad` u fixed). | Dříve jen `high_sell_slot` (globální max lookahead) → většina večerních slotů bez tlaku na vývoz. |
| **LP — ge_bat push** | Min. ~8 kW export z baterie ve **všech** ekonomicky výhodných discharge slotech (ne jen večer/ráno seznam). | Plán má odpovídat „vylije co dá síť“ ve špičkách. |
| **LP — záporný sell + block_export** | `charge_slots` rozšířeny o sloty `sell<0` s PV přebytkem; měkká penalizace `pv_charge_shortfall` (`bc_pv` vs přebytek FVE). | Postupné nabíjení / curtail místo plné FVE do baterie. |
**Soubory:** `db/routines/R__063_fn_load_planning_slots_full.sql`, `backend/services/planning_engine.py`, `backend/tests/test_planning_charge_slot_selection.py`, `docs/04-modules/planning.md`.
**Neměněno (záměrně):**
- `reserve_soc_percent` u BA81 (**30 %**) — podlaha pro **prodej do sítě**; pod ní jen dům. Noc držela 30 % kvůli **zakázanému exportu v masce**, ne kvůli špatné rezervě.
- Ranní export 511 před `sell<0`, večerní peak ≥17, kotva SoC — beze změny.
**Ověření po deployi:**
1. Flyway repeatable `R__063` + restart backendu.
2. Rolling replan BA81 / KV1 / home-01.
3. MCP: noc BA81 — `allow_discharge_export=true` kde není lepší sell později; večer `abs(battery_setpoint_w)` řádově kW u slotů s `export_mode=BATTERY_SELL`.
4. `pytest backend/tests/test_planning_dispatch_milp.py backend/tests/test_planning_charge_slot_selection.py`
---
## 2026-05-24 (b) — Po deployi: export stále slabý (oprava #2)
**Problém:** Po prvním deployi MCP stále `max_discharge ~300 W`, KV1 `allow_charge=false` při `sell<0`, 0× `BATTERY_SELL` u BA81/KV1. home-01 částečně OK (backend běží).
**Příčiny z MCP:**
1. **Flyway `R__063` neaplikovaný** na DB → masky bez `allow_charge` u záporného výkupu (`ch_true=0` na celém runu KV1).
2. **Fixed marže:** `_slot_profitable_battery_export` používal `buy` v slotu (predikce 4,08 Kč) místo **`charge_acquisition`** (~3,09) → večerní export vypnutý i při `sell` 3,7.
3. **`ge_bat ≤ max_export × z_export`:** solver volil `z_export=0``ge_bat=0` navzdory push.
4. **Safety SoC floor** (~91 %) na ne-high-sell večerních slotech → téměř žádný export.
**Opravy:**
| Změna | Soubor |
|--------|--------|
| Explicitní `allow_charge` pro `sell<0` + `pv_surplus>0` | `R__063` |
| Marže exportu: vždy `sell > acquisition + degrad` | `planning_engine._slot_profitable_battery_export` |
| `ge_bat` push bez násobení `z_export`; `z_export ≥ ge_bat/max_export` | `solve_dispatch` |
| Safety export floor ne na `profitable_export_ts` | `solve_dispatch` |
| Tvrdé `bc_pv ≥ 0.9×pv_surplus` v `charge_slots` + `sell<0` | `solve_dispatch` |
| Penalizace shortfall 40 / 25 Kč/kWh | konstanty |
**Deploy checklist (povinné obojí):**
```bash
# 1) SQL masky
flyway migrate # nebo deploy skript s R__063
# 2) Backend
docker compose build ems-api && docker compose up -d ems-api
# rolling replan nebo počkat :15
```
**Ověření v MCP:**
```sql
-- musí být > 0 po novém runu KV1:
select count(*) from ems.planning_run pr,
jsonb_array_elements(pr.solver_params->'masks') m
where pr.site_id=4 and pr.status='active'
and (m->>'allow_charge')::boolean
and (select effective_sell_price from ems.planning_interval pi
where pi.run_id=pr.id and pi.interval_start=(m->>'slot')::timestamptz) < 0;
```
---
## 2026-05-24 (c) — BA81: fixní tarif bez grid nabíjení
**Problém:** Po deployi run **15810**`max_chg ≈ 3275 W`, **`allow_grid_charge = 0`** na všech slotech. Noc 0004 jen import pro dům (~100 W), žádné NT nabíjení ze sítě. HW limit BA81 je **6250 W** (`bms_max_charge_w`), ne 18 kW.
**Příčina:** V `R__063` vrstva **B (grid)** běžela jen pro `purchase_pricing_mode <> 'fixed'`. BA81 má **`fixed`** → masky povolily jen **PV vrstvu A** (Wh rozpočet rozdělený přes denní FVE sloty → postupné ~3 kW).
**Oprava:** Pro `fixed` + existuje arbitráž (`sell > buy + degrad`) → stejná AM/PM logika grid slotů jako u spotu, řazení podle času slotu (`slot_ord`), před `export_window_start`.
**Ověření po `flyway migrate` + replan:**
```sql
select count(*) filter (where (m->>'allow_grid_charge')::boolean) as grid_slots
from ems.planning_run pr, jsonb_array_elements(pr.solver_params->'masks') m
where pr.site_id = (select id from ems.site where code='BA81') and pr.status='active';
-- očekáváno > 0
select max(pi.battery_setpoint_w), max(pi.grid_setpoint_w) filter (where pi.grid_setpoint_w > 1000)
from ems.planning_interval pi
join ems.planning_run pr on pr.id = pi.run_id
where pr.site_id = (select id from ems.site where code='BA81') and pr.status='active';
-- battery/grid nabíjení řádově k 6250 W v NT slotech
```
---
## 2026-05-24 (d) — BA81: grid jen 1 slot (globální export okno)
**Problém:** Run **15820** — mírné zlepšení (1× ~4,5 kW grid+bat o půlnoci), ale **00:4505:45 `allow_charge=false`**, max nabíjení pořád ~3,3 kW z FVE.
**Příčina:** `v_export_window_start` = **min přes celý horizont** (včerejší večerní sell 3,7 → čas ~22:15). Grid vrstva B řadí „před oknem“ vůči tomuto **jednomu** času → dnešní NT sloty (0006) už jsou „po okně“ a nedostanou `allow_grid_charge`.
**Oprava:** Sloupec **`export_window_start_at` per kalendářní den** (Prague); grid AM/PM i `buy_min_next_n` používají `wk.interval_start < wk.export_window_start_at`.
**Deploy:** `flyway migrate` (R__063) + replan.
---
## Šablona pro další záznamy
```markdown
## YYYY-MM-DD — Krátký titul
**Problém:**
**Změny:**
**Soubory:**
**Ověření:**
```