584 lines
40 KiB
Markdown
584 lines
40 KiB
Markdown
# 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-28 — večerní export: plný site cap (v28)
|
||
|
||
**Problém (v27):** Push používal `ge_bat ≤ (max_discharge−load)/2` kvůli LP limitu `bd+ge_bat ≤ BMS` při bilanci `bd≈load+ge_bat` — plán ~8 kW místo až **13,5 kW** (home-01).
|
||
|
||
**Změna (tag `2026-05-28-evening-peak-full-export-v28`):** Push cap `min(export_cap, max_discharge−load)`; v `evening_push_ts` BMS **`load + ge_bat ≤ max_discharge`** místo `bd+ge_bat`. Deye realtime dál řídí load-first na zařízení.
|
||
|
||
**Ověření:** `pytest … -k evening_push_export_near_site_cap_home01` · `planner_build_tag` **v28** · `|grid_setpoint_w|` ≈ **13,5 kW** při typickém večerním load ~1,8 kW.
|
||
|
||
## 2026-05-28 — večerní export: oprava home-01 bez prodeje (v27)
|
||
|
||
**Problém (v26, home-01 run 17010):** Večer baterie vybíjela jen do domu (`export_mode` NONE, `grid_setpoint_w` 0). Dva důvody: (1) `evening_early` (`ge_bat=0`) platilo i **po** nejvyšším sell slotu, takže 19–21 h nemohly exportovat; (2) při **drahém importu** (`buy` ≫ ranní `ref_buy`) bilance s `gi≈0` dává `ge_bat≈0` při `bd≈load`, takže tvrdý push na `ge_bat` bez `bd≥load+ge_bat` byl neřešitelný / ignorovaný; **terminal SoC** dále tlumil `z_export`.
|
||
|
||
**Změna (tag `2026-05-28-evening-peak-full-export-v27`):** `evening_early` jen pro sloty **před** `min(evening_push_ts)`; push: `ge_bat` cap ≈ `(max_discharge−load)/2`, `bd+ge_bat≥load+ge_bat`, `z_export=1`; vyšší bonus `EVENING_PUSH_Z_EXPORT_BONUS_CZK`. Detail: [`planning.md`](04-modules/planning.md).
|
||
|
||
**Ověření:** `pytest … -k evening_peak_battery_export` · po deployi `planner_build_tag` **v27** · večerní špička: `BATTERY_SELL` a `|grid_setpoint_w|` řádově kW (ne jen vybíjení do load).
|
||
|
||
## 2026-05-28 — večerní export: plný výkon u top sell, bez předčasného vybití (v26)
|
||
|
||
**Problém:** Ve **stejném večeru** LP rozlévalo vývoz baterie do více slotů v širokém pásmu „denní večerní max − degrad“ (řádově 0,15 Kč/kWh), často jen na **~50 %** výkonu (např. ~3,1 kW místo 6,25 kW u BA81). Před **nejdražší** čtvrthodinou už nezůstala energie na plný výkon; Deye pak jede na hard cap, ale plán to neodrážel (`grid_setpoint_w ≈ −1` při `BATTERY_SELL` u home-01).
|
||
|
||
**Změna (tag `2026-05-28-evening-peak-full-export-v26`)** — doplňuje v24 (Wh rozpočet), **nemění** globální ekonomiku LP. Detail: [`docs/04-modules/planning.md`](04-modules/planning.md) sekce *Večerní export z baterie*.
|
||
|
||
| Mechanismus | Co dělá | Co **nedělá** |
|
||
|-------------|---------|----------------|
|
||
| Globální LP | Max. zisk v horizontu; export kde sedí marže a masky | Není „jen jeden večerní slot“ |
|
||
| `evening_early` (`ge_bat = 0`) | Od **17:00**: `sell < denní_večerní_max − 0,05` Kč/kWh — baterie nevybíjí *před* absolutní špičkou | **Neplatí ráno**; neblokuje `ge_pv` |
|
||
| `evening_push` | Top večerní sloty (≥ max−0,05): **plný** `ge_bat`; **počet slotů** = Wh rozpočet, řazení `sell` desc | Není jediný slot; není široké peak−degrad pro push |
|
||
| `_dispatch_grid_setpoint_w` | `grid_setpoint_w` z `ge` / `ge_bat` pro Deye reg 143 | — |
|
||
|
||
**Ověření:** `pytest … -k evening_peak_battery_export_at_site_cap` · `planner_build_tag` = **v26**.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — reg 340 cap z výkonu střídače, min dle firmware (V082)
|
||
|
||
**Změna:** `asset_inverter.deye_reg340_max_solar_w` / `deye_reg340_min_solar_w`; `fn_inverter_pv_a_max_w` bere strop z DB sloupce (home-01 **32 000 W**, ostatní Deye **65 000 W**), ne součet Wp polí — studené panely mohou překročit nominál. `compute_pv_a_reg340_max_solar_w(..., min_w=)` — spodní limit jen pro kladné hodnoty (home-01 min **400 W**).
|
||
|
||
**Ověření:** `select ems.fn_inverter_pv_a_max_w(<deye-main id>);` · `pytest backend/tests/test_control_exporter_reg340.py`.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — reg 340 jen když plán curtailuje / exportuje / nabíjí
|
||
|
||
**Změna:** `plan_skips_deye_reg340_write` v `setpoints.py` — bez FC 0x10 na reg **340**, pokud slot nemá export, nabíjení baterie ani `pv_a_curtailed_w` (Deye řídí PV A přes 108/109/142).
|
||
|
||
**Ověření:** `pytest backend/tests/test_control_exporter_reg340.py`.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — dvoufázová SoC před buy<0, PV A curtail jen v buy<0 (v25)
|
||
|
||
**Požadavek:** (1) **PV A omezení** jen při `buy<0` — raději import se ziskem než „zdarma“ ze střechy. (2) **Před `buy<0`** dostatečně **nízké SoC** (vejde import v okně + PV B + rezerva na odpolední `sell<0`). (3) **Nejpozději při posledním `sell≥0` před `buy<0`** baterie **~100 %** (bez exportu — PV do bat). (4) Ranní `sell<0` před `buy<0`: PV smí do baterie (ne tvrdé `bc_pv=0`).
|
||
|
||
**Oprava (tag `2026-05-28-pre-neg-buy-soc-phases-v25`):** `_pre_neg_buy_soc_ceiling_wh`, kotvy `soc` na `last_pos_sell` (max) a `first_neg_buy-1` (strop), `pre_neg_buy_empty_ts` výboj, `pos_sell_pre_neg_buy_ts` `ge=0`, `bc_pv=0` jen při `buy<0`, `NEG_SELL_CURTAIL` jen `buy<0`, ranní PV charge shortfall.
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py -k PreNegBuySocPhase`.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — dynamický večerní push (v24)
|
||
|
||
**Problém:** Tvrdý večerní push používal pevné **`max_slots_per_day = 3`** a aktivaci jen při **`len(evening_push_ts) ≥ 2`** — nesouvisí s `discharge_slot_buffer`, SoC ani počtem večerních peak slotů (changelog v17 mluvil o top-6/≥7, v kódu bylo 3/2).
|
||
|
||
**Oprava (tag `2026-05-28-evening-push-dynamic-budget-v24`):** `_evening_push_discharge_budget_wh` + `_evening_battery_export_push_indices` — kandidáti = večerní peak ∩ maržní export; řazení `sell desc`; přidávat sloty dokud `kumulované_Wh ≤ min(available_soc, exportable_full × discharge_slot_buffer)` (`per_slot` = max_discharge × účinnost × 0,25 h). Jedna i více slotů podle rozpočtu; žádný pevný top-3.
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py -k EveningPushBudget` a celý soubor MILP.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — noční/ranní výboj baterie před buy<0 (v23)
|
||
|
||
**Požadavek:** Před ranním oknem záporných cen **vybít baterii do sítě** (ne jen ~500 W do domu), aby zůstala kapacita na levný import v `buy<0`.
|
||
|
||
**Oprava (tag `2026-05-28-pre-neg-batt-discharge-v23`):** `_pre_neg_buy_discharge_indices` — sloty `t < first_neg_buy_idx`, `sell ≥ 1` Kč/kWh, marže exportu z baterie; **`ge_bat`** + shortfall + push na DB export cap, **bez** přidání do `discharge_export_slots` (v19b). Výjimka z `ge_bat=0` v pre-selection; exportní SoC podlaha `min_soc`.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — rozlišení buy<0 vs sell<0 (v22 / v22b)
|
||
|
||
**v22b — Infeasible:** Tvrdý `is_daytime_pv_surplus` + `ge_pv=0` z pv_store blokoval export před buy<0. Oprava: jen měkká `PRE_NEG_CHARGE_PENALTY`; u `buy<0` přeskočit sell<0 ventil; `neg_buy` shortfall jen na **posledním** buy<0 slotu; retry `relaxed_neg_buy_charge`. Tag `2026-05-28-buy-sell-split-v22b`.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — rozlišení buy<0 vs sell<0 (v22, superseded by v22b)
|
||
|
||
**Problém (MCP run 16706, v21b):** Znaménka v objective OK (`grid<0` = export, `bat>0` = nabíjení). Chování ale „opačně“:
|
||
- **Před buy<0** (05:30–07:00, buy≥0): nabíjení z PV/sítě místo přípravy kapacity.
|
||
- **Při buy<0** (12:15–12:45): export do sítě místo importu — ventil `w_pv_b_vent` u sell<0 platil i když buy<0.
|
||
|
||
**Oprava (tag `2026-05-28-buy-sell-split-v22`):**
|
||
- **Před `first_neg_buy_idx` a buy≥0:** tvrdé `bc_pv=bc_gi=0` jen v `is_daytime_pv_surplus_slot` (SQL); jinak měkká penalizace `PRE_NEG_CHARGE_PENALTY`.
|
||
- **sell<0 a buy≥0:** export pole B / curtail A (v21b), bez `neg_sell_soc` shortfallu v buy<0 slotech.
|
||
- **buy<0:** tvrdě `ge=ge_pv=ge_bat=0` + měkký **`neg_buy_charge_shortfall`** (tlak na `bc_gi+bc_pv`).
|
||
- **sell<0 + buy<0:** žádný větev ventilu plné baterie → jen nabíjení/curtail.
|
||
|
||
**Ověření:** replan home-01 → tag v22; 11:00–11:45 import+nabíjení, 12:15 bez exportu při buy<0.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — ranní sell<0: držet SoC před buy<0 (v21 / v21b)
|
||
|
||
**Problém (MCP run 16692, tag v20):** Od ~05:30 nabíjení z PV; v 09:15 už **98,3 %** SoC; od 09:15 masivní **export při sell<0** (−7 kW). V **11:00–12:45** `buy<0`, ale baterie plná → **žádný import**.
|
||
|
||
**v21:** `neg_sell_soc_underfill` / `pv_charge_shortfall` jen od `first_neg_buy_idx`; **`bc_pv=0`** před buy<0 v sell<0.
|
||
|
||
**v21b — Infeasible:** `bc_pv=0` + ventil **`w_pv_b_vent`** (export jen při plné baterii) → přebytek **pole B** (`pv_b` > load) nemá kam (bilance). **Oprava:** před `first_neg_buy_idx` povolit **`ge_pv ≤ pv_b`** bez ventilu; safety `soc_max` u sell<0 charge jen od `first_neg_buy_idx`.
|
||
|
||
**Tag:** `2026-05-28-morning-hold-soc-v21b`
|
||
|
||
**Ověření:** `scripts/diagnose_home01_infeasible.py`; replan home-01 → tag v `solver_params`.
|
||
|
||
---
|
||
|
||
## 2026-05-28 — revert tvrdých v19 constraintů (v20)
|
||
|
||
**Problém:** v19–v19c opakovaně **Solver: Infeasible** na home-01 (ověřeno proti MCP run 16674 — `buy<0` od 11:00, ne 13:00). Vrstvené Python patch bez reprodukce na živých slotech.
|
||
|
||
**Rozhodnutí:** **Revert** celé v19 Python vrstvy (pre-neg discharge, `bc_pv=0` před buy<0, neg-buy shortfall). Zůstává stabilní základ:
|
||
- **v17:** `bc_gi=0` při sell<0+PV+buy≥0; `ge_pv ≤ pv_b` při sell<0
|
||
- **v18:** večerní export push z DB `min(discharge, export)` W
|
||
|
||
Strategie před buy<0 / import v buy<0 patří do **SQL `R__063`** (masky `allow_*`), ne dalších tvrdých LP constraintů — až po feasibilitě na MCP datech.
|
||
|
||
**Tag:** `2026-05-28-revert-v19-hard-v20`
|
||
|
||
**Diagnostika:** `scripts/diagnose_home01_infeasible.py` + fixture z MCP `planning_interval` run 16674.
|
||
|
||
---
|
||
|
||
## 2026-05-27 (k) — Infeasible: soc na každém buy<0 slotu + sell<0 v pre-neg (v19c)
|
||
|
||
**Problém:** (1) **`neg_buy_soc_underfill`** na **každém** `buy<0` slotu vyžadoval `soc = soc_max` každých 15 min — při startu pod max fyzicky nemožné. (2) **`pre_neg_buy_discharge_ts`** mohlo zahrnout `sell<0` + `allow_discharge_export` → **`ge_bat=0`** (sell<0) vs **`z_export` → ge_bat≥1** → Infeasible.
|
||
|
||
**Oprava (tag `2026-05-27-pre-neg-buy-strategy-v19c`):**
|
||
- `neg_buy_soc_underfill` jen na **posledním** `buy<0` slotu horizontu.
|
||
- `pre_neg_buy_discharge_ts` jen při **`sell ≥ 1`** (ne SQL discharge maska se záporným sell).
|
||
- Třetí retry: **`relaxed_neg_buy_pressure`** (vypne měkké shortfall, ponechá `bc_pv=0` před buy<0).
|
||
|
||
---
|
||
|
||
## 2026-05-27 (j) — Infeasible: pre-neg export mimo discharge_export_slots (v19b)
|
||
|
||
**Problém:** v19 přidávalo noční sloty (`sell ≥ 1`) do **`discharge_export_slots`** → režim **`w_arb`**: `bd` jen při exportu, ne k loadu → v noci nešlo pokrýt baseload → **Solver: Infeasible** (i po `relaxed_expensive_import`).
|
||
|
||
**Oprava (tag `2026-05-27-pre-neg-buy-strategy-v19b`):** `pre_neg_buy_discharge_ts` pouze povolí **`ge_bat`** (+ shortfall), **bez** rozšíření `discharge_export_slots`. Baterie dál může vybíjet k domu (`bd`) a paralelně exportovat (`ge_bat`).
|
||
|
||
---
|
||
|
||
## 2026-05-27 (i) — strategie před buy<0: noční výboj, bez PV→bat, import v záporném nákupu (v19)
|
||
|
||
**Problém (home-01 run 16662, tag v18):** Večerní/ranní export OK. Zbývá: (1) noc jen ~500 W do domu, žádný `ge_bat` výboj před `buy<0`; (2) 08:45–11:30 nabíjení z PV A do ~98 % ještě před `buy<0` (13:00); (3) v `buy<0` baterie plná → žádný import; (4) `neg_sell_soc_underfill` tlačilo na soc_max už v ranním `sell<0` okně.
|
||
|
||
**Oprava (tag `2026-05-27-pre-neg-buy-strategy-v19`):**
|
||
|
||
1. **Noční výboj:** `pre_neg_buy_discharge_ts` — shortfall + push `ge_bat` na site cap, bonus `z_export`, export podlaha `min_soc` (ne safety ramp).
|
||
2. **`bc_pv[t]=0` pro všechny sloty před `first_neg_buy_idx`** (i když `t in charge_slots` z `sell<0+PV`).
|
||
3. **`neg_sell_soc_underfill` jen po `first_neg_buy_idx`** — před záporným nákupem nehonit soc_max.
|
||
4. **`neg_buy_soc_underfill` + `neg_buy_grid_shortfall`** v `buy<0` slotech — tlak na soc_max a max `bc_gi` ze sítě.
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py` — po deploy replan home-01: tag v19; noc `ge_bat` ~13,5 kW; před 13:00 SoC pod max; 13:00–14:45 import + nabíjení k 100 %.
|
||
|
||
---
|
||
|
||
## 2026-05-27 (h) — export push z DB limitů, bez hardcoded 8000 W (v18)
|
||
|
||
**Problém:** `EVENING_BATTERY_EXPORT_MIN_W` a `PRENEG_MORNING_EXPORT_MIN_W` = 8000 W v kódu brzdily home-01 na 8 kW místo `site_grid_connection.max_export_power_w` (13,5 kW); u KV1 náhodou sedělo. `EVENING_PEAK_FULL_POWER_TOP_K = 6` arbitrární.
|
||
|
||
**Oprava (tag `2026-05-27-site-export-cap-from-db-v18`):**
|
||
- Smazány konstanty `EVENING_BATTERY_EXPORT_MIN_W`, `PRENEG_MORNING_EXPORT_MIN_W`, `EVENING_PEAK_FULL_POWER_TOP_K`.
|
||
- Helper `_battery_export_cap_w(battery, grid)` = `min(max_discharge_power_w, max_export_power_w)` z DB.
|
||
- Ranní/večerní push `ge_bat >= export_push_w * z_export` používá výhradně site limit (KV1 ~8 kW, home-01 ~13,5 kW).
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py` — 87 passed (1 pre-existing).
|
||
|
||
---
|
||
|
||
## 2026-05-27 (g) — bc_gi=0 v sell<0+pv slotech, ge_pv≤pv_b při sell<0, evening top-K (v17)
|
||
|
||
**Problém v16 (run 16652):**
|
||
1. **Nákup ze sítě 18 kW v 09:15–09:45 za buy 1,1–1,2 Kč:** R__063 přidává `allow_charge=true` i pro `sell<0+pv_surplus>0` (= "povolit PV nabíjení aby pole A nešlo do mínusu"), ale `t in charge_slots` v Pythonu pak otevřelo i `bc_gi` (grid→bat) za pozitivní buy → ztráta ~25 Kč.
|
||
2. **Export pole A v sell<0 oknu (11:00–14:45):** `ge_pv` mohlo zahrnovat celý PV surplus, tj. pole A se mu vyhodil do mínusu za cenu až −1,08 Kč/kWh (~10 Kč ztráta na hodinu).
|
||
3. **Večerní prodej jen 8 kW místo 13,5 kW:** `EVENING_BATTERY_EXPORT_MIN_W = 8000` byl spodek tlaku — LP rozprostíral vybití do víc slotů místo zhuštění do peaků.
|
||
|
||
**Oprava (tag `2026-05-27-no-grid-charge-pos-buy-v17`):**
|
||
|
||
1. **bc_gi=0 v `sell<0+pv_surplus>0` slotech s buy≥0** (mimo `charge_slots` už zůstává). Důvod: `t in charge_slots` z PV důvodu **není** ekvivalentní "povolit nákup ze sítě". Arbitráž ze sítě (cheap buy → peak sell) zachována dokud `pv_surplus=0` (= test `test_vt_nt_cycle_evening_battery_sell`).
|
||
2. **ge_pv ≤ pv_b_forecast_w v `sell<0` slotech s pv_b > 0** (home-01: bez block_export). Pole A musí jít do baterie nebo curtail; pole B s green bonus 7,135 Kč → net 6+ Kč i při sell=−1.
|
||
3. **Evening top-K full power push:** Top-6 nejvýnosnějších evening slotů má `ge_bat ≥ min(max_discharge, max_export)` (= 13,5 kW pro home-01). Aktivní jen pokud `len(evening_push_ts) ≥ 7` (= multi-slot peak okno, ne 1-slot regresní testy).
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py` — 87 passed (1 pre-existing fail). Po deploy + replan home-01:
|
||
- 09:15–09:45 **bez** import 18 kW (bc_gi=0).
|
||
- 11:00–14:45 `curtail_a ≈ pv_a − epsilon`, `ge_pv ≤ pv_b`.
|
||
- Večerní peak (20:30, 20:45, 21:00, 22:00) **ge_bat ≥ 13 500 W** → kratší okno, vyšší marže.
|
||
|
||
---
|
||
|
||
## 2026-05-27 (f) — zjednodušená strategie pro buy<0 okno (v16, revert v14+v15)
|
||
|
||
**Problém v14/v15 (run 16622, 16636, 16642):** Vrstvy soft penalty (cap+slack, PV charge suppressed penalty) LP **nedonutily** vybít baterii ani omezit PV pumping. LP přijímal sloupec slack 24 kWh × 50 Kč/kWh = 1190 Kč a baterii nabíjel z ranního PV (10:30 SoC=95 %), pak v `buy<0` okně (13:00–14:45) curtail pole A 5–9 kW + export pole A do mínusu.
|
||
|
||
**Strukturální root cause (3 vrstvy):**
|
||
1. R__063 `allow_charge=false` ze SQL Pythonský `solve_dispatch` ignoruje pro PV charging (`bc_pv ≤ pv_surplus` i pro `t not in charge_slots`).
|
||
2. `discharge_export_slots` v noci `false` (R__063) → LP nemá cestu jak baterii vybít přes ge_bat.
|
||
3. `acquisition` v LP je vstupní konstanta — LP nevidí, že buy<0 okno je „lepší cesta" než ranní PV pumping.
|
||
|
||
**Oprava (tag `2026-05-27-simple-buy-neg-window-v16`):** Reverted v14+v15, znovu postaveno **2 jednoduchá pravidla** podle business logiky:
|
||
|
||
1. **Tvrdé `bc_pv[t] = 0` pre-first_neg_buy_idx** (slots kde `t not in charge_slots`): PV poteče do gridu (sell≥0) nebo curtail, ne do baterie. R__063 už pro `sell<0+pv_surplus` přidává `allow_charge=true` (= `t in charge_slots`), takže pole A v `sell<0` slotech může nabíjet baterii (= nevyhodit do mínusu).
|
||
2. **Rozšíření `discharge_export_slots`** o pre-`buy<0` sloty se **dynamickým prahem** `sell ≥ max(avg(buy<0) + degradation_cost, 0.1) Kč/kWh`. Pro home-01 (avg buy<0 ≈ −0,22, degrad ≈ 0,15) to dělá práh ~0,1 Kč → prakticky všechny noční sloty se `sell > 0`. Ekonomická logika: marže `sell_t − acquisition_in_neg_buy_window − degradation`, a pokud `acquisition ≈ záporný` (buy<0 v okně), je výhodné vybít a znovu nabít i za sell ~1 Kč/kWh.
|
||
|
||
**Business logika (od uživatele):**
|
||
- Noc před `buy<0`: vybít baterii za sell ~3 Kč/kWh.
|
||
- Ráno: minimální SoC.
|
||
- `buy<0` okno: PV B necurtailovat (R__063 už řeší), nabíjet ze sítě (LP samo, buy záporný = `t in charge_slots`).
|
||
- Po `sell>0`: baterie plná, max prodej.
|
||
- Večer: prodat zbytek.
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py tests/test_planning_charge_slot_selection.py` — 87 passed (1 pre-existing fail nesouvisí). Po deploy MCP: `select pr.solver_params->'planner_build_tag'` = `…-v16`, plán home-01 25.5.: SoC v 12:45 < 50 %, 13:00–14:45 SoC roste z capu k ~95 %, `pv_a_curtailed_w` blízko 0 v okně.
|
||
|
||
---
|
||
|
||
## 2026-05-27 (c) — rezervace SoC pro `sell<0` okno + fallback acquisition ≥ 0 (v13)
|
||
|
||
**Problém (home-01 run 16614, tag v12):** Aktivní plán pro 2026-05-25:
|
||
- 10:30 SoC = 96,9 %, 10:45 SoC = 98,3 % (baterie plná z PV ráno) → odpoledne v `sell<0` slotech (13:00–14:45, sell až −1,08 Kč) **ge_pv export** + curtail pole A 5 kW. Ztráta 6+ Kč.
|
||
- `acquisition_pass1 = −0,035` (`R__063` fallback path: `(ref_buy_am + ref_buy_pm)/2`, ref_buy_pm < 0 protože PM zahrnuje 13:30–14:00 s buy ≈ −0,36 Kč) → `two_pass_converged = false`.
|
||
|
||
**Oprava (tag `2026-05-27-neg-sell-soc-reservation-v13`):**
|
||
|
||
- **`R__063` PV vrstva A — rezervace pro `sell<0` okno:** před iterátorem vrstvy A spočítat `v_neg_window_pv_surplus_wh = sum(min(pv_surplus_w, max_charge_w) * eff * 0.25) FILTER (sell<0, pv_surplus>0)`. Snížit `v_pv_layer_cap_wh` o tuto hodnotu (lower bound 0). Důsledek: před `sell<0` oknem se nabíjí jen `deficit − neg_window_pv_wh`; do okna doráží baterie nenaplněná a `sell<0` PV slot ji dorovná místo exportu / curtailu pole A.
|
||
- **`R__063` fallback acquisition:** když `v_est_grid_wh = 0` a `min(buy) FILTER (allow_grid_charge AND buy>=0)` je NULL, místo avg `ref_buy_am/pm` (může být záporný) použít `coalesce(min(buy) FILTER (buy>=0), 0)`. Navíc `v_charge_acquisition := greatest(v_charge_acquisition, 0)` jako pojistka — arbitrážní akviziční cena nesmí být < 0.
|
||
|
||
**Ověření:**
|
||
- Replan home-01 (po redeploy R__063) → 10:45 SoC < 95 %, 13:00–14:45 SoC roste (PV charging), 13:30 `grid_setpoint` < 0 jen pole B (curtail pole A = 0), bilance: `cashflow_czk(13:00–15:00) > 0`.
|
||
- `acquisition_pass1_czk_kwh ≥ 0`, `two_pass_converged = true`.
|
||
|
||
---
|
||
|
||
## 2026-05-27 (b) — acquisition: vyloučit záporný OTE buy z váženého průměru
|
||
|
||
**Problém (home-01 run 16588):** `two_pass_converged=false`, `acquisition_pass1≈−0.035` (pass1 nabíjení v `buy<0` slotech), `pass2≈0.88`. Noční grid 4,8 Kč už v plánu není (maska B OK), ale two-pass a arbitrážní marže exportu baterie byly křivé.
|
||
|
||
**Oprava:** `R__063` — vážená acquisition ve filtru B a v `charge_acquisition_buy_czk_kwh` jen z `allow_grid_charge` s `buy_price >= 0`. `planning_engine._recompute_charge_acquisition_from_results` přeskočí `buy<0`.
|
||
|
||
**Ověření:** po redeploy replan home-01 → `two_pass_converged=true`, `|acq1−acq2| < 0.05`.
|
||
|
||
---
|
||
|
||
## 2026-05-27 — self-konzistentní grid maska B + ekonomický rozpad plánu (v12)
|
||
|
||
**Problém (home-01, run 16522, tag v11):** Noční grid nabíjení (23:30–23:45, buy ~4,8 Kč) při `acquisition_pass1≈4,81` / `pass2≈0,84`, `two_pass_converged=false`; 26 slotů export při `sell<0`.
|
||
|
||
**Oprava (tag `2026-05-27-self-consistent-grid-mask-v12`):**
|
||
|
||
- **`R__063`:** iterativní filtr vrstvy B (spot) + sloupce `pv_charge_wh_ahead`, `neg_buy_wh_ahead`, `grid_charge_suppressed_reason`, `min_buy_before_cutoff_czk_kwh`; failsafe unlock.
|
||
- **`V081`:** `planning_interval.cashflow_czk`, `battery_arbitrage_czk`, `penalty_czk`, `green_bonus_czk`; commit přes `fn_planning_run_commit`.
|
||
- **`planning_engine.py`:** post-processing ekonomiky, `solver_params.objective_terms` rozšíření; `fn_plan_explain_bundle` → `economics_summary`.
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_economics_columns.py`, `DynamicGridFilterTests`, `Home01RegressionTests::test_home01_no_night_charge_before_pv_day`, `test_two_pass_converged_after_filter`; po deploy MCP: `grid_charge_suppressed_reason` ve `fn_load_planning_slots_full`, `two_pass_converged=true` na novém run.
|
||
|
||
---
|
||
|
||
## 2026-05-26 (o) — home-01: neg. výkup bez placeného exportu FVE + dump baterie před extrémním buy
|
||
|
||
**Problém (run 16480, tag v10):** Po ranním nabití na `soc_max` solver při `sell<0` exportoval **celý PV přebytek** (~9 kW, `PV_SURPLUS`) — binárka `w_pv_full_neg` povolila `ge_pv ≤ pv_surplus` místo jen ventilu pole B. Zároveň `ge_bat=0` blokoval výboj baterie před oknem `buy ≤ −2` (round-trip arbitráž).
|
||
|
||
**Oprava (tag `2026-05-26-neg-sell-bat-dump-extreme-buy-v11`):**
|
||
|
||
- Spot `sell<0`: `ge_pv=0` dokud není plná baterie; při plné jen `ge_pv ≤ pv_b` (`w_pv_b_vent_neg`) + penalizace `NEG_SELL_PV_B_VENT_PENALTY` (4 Kč/kWh).
|
||
- Před extrémním buy (`buy ≤ planner_extreme_buy_threshold`, default −2): v okně **12 slotů** smí `ge_bat>0` při `sell<0`, pokud `min_buy_future < sell − degrad`.
|
||
- Odstraněn `w_pv_full_neg` (export celého surplusu).
|
||
|
||
**Ověření:** `test_neg_sell_full_battery_exports_at_most_pv_b_not_full_surplus`, `test_neg_sell_bat_dump_before_extreme_buy`, `test_neg_sell_pv_to_battery_not_grid_when_soc_has_room`; po deploy replan home-01 — neg sell bez ~9 kW exportu.
|
||
|
||
---
|
||
|
||
## 2026-05-25 (n) — home-01 AUTO: záporný výkup bez exportu, večerní špička
|
||
|
||
**Problém (run 16412, AUTO):** Dnes večer téměř bez exportu (terminal SoC drží energii na zítřek); zítra 07:30+ masivní **PV_SURPLUS** při `sell<0` místo nabíjení; zítra večer export OK.
|
||
|
||
**Příčiny:**
|
||
1. Spot při `sell<0`: `skip_pv_store_block` kvůli `pv_b` povoloval export i s prázdnou baterií.
|
||
2. R__063 večerní maska spot: `sell > ref_buy` — ve slotu často `sell < buy`, večerní export dnes vypnutý.
|
||
3. Večerní `ge_bat` push jen 50 % výkonu vs. terminal SoC shadow.
|
||
|
||
**Oprava (tag `2026-05-25-home01-neg-sell-evening-v10`):**
|
||
- Spot `sell<0`: `ge_pv`/`ge` jen pokud `soc[t-1] ≥ soc_max − headroom` (binárka `w_pv_full_neg`).
|
||
- R__063: večerní peak u spotu jako u fixního tarifu (denní max výkupu).
|
||
- `PEAK_EXPORT_SHORTFALL` 80 Kč/kWh; večerní push na plný `EVENING_BATTERY_EXPORT_MIN_W`.
|
||
|
||
**Ověření:** `Home01RegressionTests::test_neg_sell_pv_to_battery_not_grid_when_soc_has_room`; po deploy replan home-01 — neg sell bez exportu při SoC < max.
|
||
|
||
---
|
||
|
||
## 2026-05-25 (m) — BA81: záporný výkup bez exportu podle DB `purchase_pricing_mode`
|
||
|
||
**Problém (tag v8 v produkci):** KV1 OK; **BA81** pořád export při `sell < 0` (dnes i zítra). v8 používalo `_horizon_fixed_tariff_like` (rozptyl **buy** < 0,25 Kč/kWh). U BA81 buy skáče **NT/VT** (3,09 ↔ 4,09) → heuristika **false** → zákaz exportu se neaplikoval.
|
||
|
||
**Oprava (tag `2026-05-25-purchase-fixed-neg-sell-v9`):**
|
||
|
||
- `ems.fn_planning_site_context` vrací **`market.purchase_pricing_mode`** / **`sale_pricing_mode`** z `site_market_config`.
|
||
- Při **`sell < 0`** a **`purchase_pricing_mode = fixed`**: `ge = 0` (nezávisle na rozptylu buy). **home-01** (spot nákup) výjimku nemá — může ventovat PV B.
|
||
- `_horizon_fixed_tariff_like` zůstává jen pro **drahý import** / `charge_acquisition` (heuristika + DB `fixed`).
|
||
|
||
**Ověření:** `pytest …::NegativeSellPvChargeTests::test_ba81_fixed_purchase_nt_vt_buy_spread_neg_sell_no_export`; po deploy + replan BA81: žádný `grid < 0` při `sell < 0` v MCP.
|
||
|
||
---
|
||
|
||
## 2026-05-25 (l) — Plán 25. 5.: BA81 neg. výkup bez exportu, KV1 ranní curtail
|
||
|
||
**Problém (MCP plán run 16346–16350, tag v7):** KV1 06–08 h masivní **curtail** FVE (plná baterie, `ge_pv=0` z pv_store). BA81 při `sell<0` **export ~10 kW** místo nabíjení. Večer slabý export u KV1/home-01 (spot: `sell < buy`).
|
||
|
||
**Oprava (tag `2026-05-25-neg-sell-no-export-fixed-v8`):**
|
||
|
||
- Fixní tarif (BA81): při **`sell < 0`** tvrdě **`ge = 0`** (jako KV1 s block_export) — přebytek jen baterie/curtail.
|
||
- **`fixed_pv_b_export_cap`** jen při **`sell ≥ 0`** (po neg. okně export B).
|
||
- KV1: **`skip_pv_store_block`** při kladném `sell` + PV přebytek — méně curtailu před neg. oknem.
|
||
|
||
**Deploy:** služba v compose je **`backend`**, ne `ems-api`. Ověření:
|
||
`docker compose -f /opt/ems-deploy/docker-compose.yml exec backend grep PLANNER_BUILD_TAG /app/services/planning_engine.py`
|
||
|
||
---
|
||
|
||
## 2026-05-24 (k) — BA81: Infeasible při SoC = 100 % (telemetrie = soc_max)
|
||
|
||
**Problém:** Po v6 stále `Solver: Infeasible` při replanu, když `fn_planning_site_context` vrátí `soc_wh = soc_max_wh` (12 500).
|
||
|
||
**Příčina:** Při dlouhém `sell < 0` a vysoké FVE MILP potřebuje alespoň ~**650 Wh** rezervy pod `soc_max` pro modelování PV→baterie / export B. Na přesně 100 % SoC je model neřešitelný (reprodukce na datech runu 16184).
|
||
|
||
**Oprava:** tag **`2026-05-24-ba81-soc-headroom-v7`** — `_planner_soc_for_solver()` sníží vstupní SoC na `soc_max − max(650 Wh, 0,382×slot_nabíjení)`; v `solver_params.inputs.soc_headroom_applied_wh` je audit.
|
||
|
||
**Ověření:** `pytest …::NegativeSellPvChargeTests`; replan BA81 s telemetrií 100 % → tag v7, bez Infeasible.
|
||
|
||
---
|
||
|
||
## 2026-05-24 (j) — BA81: Solver Infeasible (plná baterie + pole B + GEN cut-off)
|
||
|
||
**Problém:** Po deployi večerních oprav u BA81 plánování padá na **`Solver: Infeasible`** (KV1 OK), typicky při **SoC ≈ 100 %** během dlouhého okna `sell < 0` (dnešní OTE).
|
||
|
||
**Příčiny (dvě vrstvy):**
|
||
|
||
1. **v5:** `ge_pv = 0` z pv_store při `pv_b > 0` → oprava `ge_pv ≤ pv_b`.
|
||
2. **v6 (skutečný blocker u BA81):** `deye_gen_microinverter_cutoff_enabled` společně s `sell < 0` vynucovalo **`ge == 0`** (podmínka `z_gen_cutoff is not None`). Při plné baterii nelze nabít ani exportovat přebytek pole B → Infeasible. BA81 má v kontextu `soc_wh = soc_max_wh = 12 500`.
|
||
|
||
**Oprava:** tag **`2026-05-24-ba81-gen-cutoff-v6`** — `ge == 0` jen při `block_export_on_negative_sell`; `ge_pv ≤ pv_b × (1 − z_gen_cutoff)`; v5 večerní push + pv_b cap zůstávají.
|
||
|
||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py::NegativeSellPvChargeTests`; MCP po deployi: `planner_build_tag = 2026-05-24-ba81-gen-cutoff-v6`.
|
||
|
||
---
|
||
|
||
## 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í **00–04** 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 5–11 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 00–04 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:45–05: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 (00–06) 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.
|
||
|
||
---
|
||
|
||
## 2026-05-24 (e) — BA81: FVE 13 kW → nabíjení jen ~3 kW (curtailment)
|
||
|
||
**Problém:** Run **15826** — `pv≈13 kW`, `battery_setpoint≈3,3 kW`, **`pv_a_curtailed≈9 kW`** (08:00–08:45). `allow_charge=true`, ale solver škrtí FVE místo plného nabíjení.
|
||
|
||
**Příčina:**
|
||
|
||
1. **`CURTAILMENT_PENALTY = 0,001 Kč/Wh`** vs degradace nabíjení → LP raději `ca` než `bc_pv`.
|
||
2. **`pv_charge_shortfall`** jen při `block_export_on_negative_sell` (KV1) — **BA81 má false** → žádný tlak na `bc_pv`.
|
||
3. SoC v plánu stagnuje ~52 % při záporném výkupu, zbytek jde do curtailment.
|
||
|
||
**Oprava (`planning_engine.py`):**
|
||
|
||
- `pv_charge_shortfall` pro **všechny** sloty `sell<0` + `allow_charge` + PV přebytek >500 W.
|
||
- Penalizace **50 Kč/kWh**.
|
||
- Tvrdé **`ca ≤ pv_a_forecast − bc_pv`** v okně záporného výkupu (nejdřív nabít, pak škrtit).
|
||
|
||
**Deploy:** restart **backend** (SQL beze změny) + replan.
|
||
|
||
---
|
||
|
||
## 2026-05-24 (f) — BA81: jen první slot sell<0 nabíjí 6 kW, další 1–2 kW
|
||
|
||
**Problém:** Run **15838** — 06:15 Prague ~6,1 kW, 06:30–07:30 ~1,4–2,2 kW, 07:45–08:45 **0 kW** + curtail ~9 kW, 09:00+ znovu ~3 kW. Uživatel: „jen u prvního slotu se zápornou cenou“.
|
||
|
||
**Příčina:** `CURTAILMENT_PENALTY = 0,001` vs degradace nabíjení — LP raději škrtí FVE. Oprava (e) pomohla jen prvnímu slotu (shortfall). Omezení `ca ≤ pv_a − bc_pv` bylo **špatně** (load-first: `pv_a_net` už závisí na `ca`). SoC v plánu stála ~51 % uprostřed okna, zbytek do curtailment.
|
||
|
||
**Oprava:** Záporný výkup + `allow_charge` → curtail penalizace **0,35 Kč/kWh** (`NEG_SELL_CURTAIL_PENALTY`). Shortfall nabíjení **80 Kč/kWh**. Odstraněno `ca ≤ pv_a − bc_pv`.
|
||
|
||
**Deploy:** jen **backend** restart + replan.
|
||
|
||
---
|
||
|
||
## 2026-05-24 (g) — BA81: plateau ~51 % SoC + curtail (run 15848/15849)
|
||
|
||
**Problém:** Po replanu stále 06:15 ~6 kW, 06:30–07:30 ~1–2 kW, **07:45–08:45 0 kW + curtail ~9 kW**, SoC plán ~51 %, pak znovu ~3 kW. `solver_params` bez `planner_build_tag` → nasazený backend pravděpodobně **bez** oprav (e)/(f).
|
||
|
||
**Příčiny (MCP + kód):**
|
||
|
||
1. **`charge_slots` v Pythonu** doplňoval `sell<0` jen při `block_export_on_negative_sell` (KV1). U BA81 (`false`) platily jen masky z DB → bez shortfall penalizace, i když R__063 nastaví `allow_charge` později.
|
||
2. **`safety_soc_target_wh`** z SQL roste jen k ~reserve + noční baseload (~50 % SoC v poledne). Jakmile `soc ≥ safety`, solver nemá motivaci dobít k `soc_max` v okně záporného výkupu (raději curtail / večerní export).
|
||
3. **`skip_pv_store_block`** u `pv_b` + fixní tarif: LP smí exportovat FVE při `sell<0` místo nabíjení (home-01 logika nepatří na BA81).
|
||
|
||
**Oprava (`planning_engine.py`):**
|
||
|
||
- `charge_slots` |= všechny sloty `sell<0` + PV přebytek > 500 W (jako R__063 ř. 787–791).
|
||
- V okně `sell<0` + `charge_slots`: safety deficit cílí na `max(safety_sql, 92 % soc_max)`.
|
||
- Fixní tarif: `ge_pv ≤ pv_b_forecast_w` při `sell<0`; `skip_pv_store` jen pro spot, ne fixed.
|
||
- Objective: odměna `bc_pv` při `sell<0` (`NEG_SELL_PV_CHARGE_REWARD`).
|
||
- `solver_params.planner_build_tag` = `2026-05-24-neg-sell-v2` (ověření deploye).
|
||
|
||
**Deploy:** `docker compose build ems-api && docker compose up -d ems-api` + rolling replan BA81.
|
||
|
||
**Ověření MCP:**
|
||
|
||
```sql
|
||
select pr.id, pr.solver_params->>'planner_build_tag' as tag,
|
||
max(pi.battery_setpoint_w) filter (where pi.effective_sell_price < 0) as max_neg_chg
|
||
from ems.planning_run pr
|
||
join ems.planning_interval pi on pi.run_id = pr.id
|
||
where pr.site_id = (select id from ems.site where code = 'BA81')
|
||
order by pr.id desc limit 1;
|
||
```
|
||
|
||
Očekáváno: `tag = 2026-05-24-neg-sell-v2`, v ranním okně `sell<0` více slotů s `battery_setpoint_w` ≥ 5000, SoC plán přes ~70 % směrem k 95 %.
|
||
|
||
---
|
||
|
||
## 2026-05-24 (i) — Večerní export BA81/KV1 + BA81 dobít na 100 %
|
||
|
||
**Problém:** Po v3 KV1 nabíjení OK, BA81 stále plateau ~94 % v neg. okně. **Večer žádný prodej** z baterie ani při sell ~3,7 Kč (BA81 i KV1).
|
||
|
||
**Příčiny:**
|
||
|
||
1. **`_slot_profitable_battery_export`:** u fixního tarifu porovnával `sell > acquisition + degrad` (BA81 acq ~3,61 → potřeba sell > ~3,91). Správně **`sell > buy + degrad`** jako v R__063.
|
||
2. **KV1 večer:** SQL večerní maska vyžadovala `sell > buy` (6,35 vs 3,7) → **`allow_discharge_export = false`**.
|
||
3. **LP:** `ge_bat >= export_push * z_export` — solver nechal **`z_export = 0`** (export „zdarma“ bez nutnosti).
|
||
|
||
**Oprava:** `planning_engine.py` tag **`2026-05-24-evening-export-v4`**; `R__063` večerní peak u fixed tarifu bez podmínky sell>buy. Měkký push `ge_bat`, odměna `z_export`, `neg_sell_soc_underfill`, večerní export floor = min_soc.
|
||
|
||
**Deploy:** `flyway migrate` (R__063) + rebuild `ems-api` + replan. MCP: `planner_build_tag = 2026-05-24-evening-export-v4`, večer `export_mode = BATTERY_SELL` nebo `grid_setpoint_w < -1000` v špičce.
|
||
|
||
---
|
||
|
||
## 2026-05-24 (h) — BA81: neg okno na plné soc_max (ne 92 %)
|
||
|
||
**Problém:** Po (g) plán lépe nabíjí v okně `sell<0`, ale SoC plán končí ~**92 %** a drží se do přechodu na kladný výkup; až pak dobíjí na 100 %.
|
||
|
||
**Příčina:** `NEG_SELL_CHARGE_SOC_FRAC_OF_MAX = 0.92` — umělý strop safety cíle v neg. okně.
|
||
|
||
**Oprava (`planning_engine.py`, tag **`2026-05-24-neg-sell-v3`**):
|
||
|
||
- Záporný výkup + PV: safety/shortfall cílí **`soc_max_wh`** (u BA81 100 %), ne 92 %.
|
||
- Po posledním `sell<0` tentýž den: **`post_neg_pv_topup`** — dobití z FVE na `soc_max` před exportem při kladném sell (ne ve high-sell špičce).
|
||
|
||
**Deploy:** rebuild `ems-api` + replan. MCP: `planner_build_tag = 2026-05-24-neg-sell-v3`, SoC v neg. okně ~100 % (resp. `planner_soc_max_wh`).
|
||
|
||
---
|
||
|
||
## Šablona pro další záznamy
|
||
|
||
```markdown
|
||
## YYYY-MM-DD — Krátký titul
|
||
|
||
**Problém:** …
|
||
|
||
**Změny:** …
|
||
|
||
**Soubory:** …
|
||
|
||
**Ověření:** …
|
||
```
|