8.3 KiB
Plánování: arbitráž a účtování energie (mezi sloty vs. v jednom slotu)
Účel: Trvalá poznámka pro implementaci i pro agenty — aby se neopakovala chyba „řešit arbitráž přes buy a sell ve stejném 15min slotu“ nebo přes min(buy) celého horizontu jako nákupní cenu uložené energie.
Související: planning.md, planning_engine.py (solve_dispatch), R__063_fn_load_planning_slots_full.sql.
1. Co uživatel / provoz očekává (správný model)
Arbitráž baterie je časový posun:
- V levných hodinách (může jich být více za sebou) nabít z site — např. home-01: baterie 64 kWh, import z site typicky až 17 kW → za 15 min až ~4,25 kWh ze sítě na slot, ale klidně 8–16 slotů (2–4 h) dokud cena sedí.
- V drahých / výkupních hodinách (jiný čas) stejnou energii prodat do sítě nebo ušetřit drahý import domu.
Ekonomický přínos je přibližně:
zisk ≈ (efektivní sell ve výprodejním okně)
− (efektivní buy v nabíjecím okně)
− degradace cyklu / účinnost
Není to rozhodnutí „v tomto jednom 15min okně koupím za 7 Kč a prodám za 4,6 Kč“ — ve výprodejním slotu se nekupuje energie určená k exportu z baterie; ta byla nabitá dříve za jinou cenu.
2. Co dělá dnešní LP (a proč to arbitráž láme)
2.1 Účelová funkce je po slotech
V solve_dispatch je v každém slotu t zhruba:
náklad += gi[t] × buy[t]
výnos -= ge[t] × sell[t]
Energetická bilance je také per slot (15 min). Když solver v evening slotu zvýší ge_bat (export baterie), bilance často vyžaduje současně gi (síť krmí dům) a bd/ge_bat. Marginalně pak vypadá každá vyvezená kWh jako:
- „koupeno“ za
buy[t]večer (např. 7 Kč/kWh), - „prodáno“ za
sell[t]večer (např. 4,6 Kč/kWh),
→ v jednom okně ztráta, i když energie v baterii pochází z poledních 0,7 Kč/kWh.
Závěr: Samotné opravy typu „přidat ge_bat × (sell − ref_buy)“ nestačí, pokud ref_buy je jedna čísla z jednoho slotu — pořád myslíme příliš v rámci jednoho okna. Cíl je oddělit nákupní okno od výprodejního okna v ekonomice modelu.
2.2 Guardy sell < buy ve stejném slotu
Tvrdé zákazy typu ge_pv = 0 když sell[t] < buy[t] brání ztrátovému exportu FVE v tomže slotu (prodat za 3 Kč při VT nákupu 5 Kč).
Výjimka (AUTO, od 2026-05): pokud je v budoucnu slot s allow_charge (levné grid nabíjení) a sell[t] ≥ charge_acquisition + degrad, solver vyžaduje export PV přebytku (ge_pv, bc=0) — typicky ranní prodej FVE nad ~3 Kč/kWh a NT nabíjení odpoledne. Implementace: solve_dispatch() v planning_engine.py.
Pro baterii stejný test v exportním slotu nesmí být jediná logika arbitráže — večer téměř vždy sell[t] < buy[t] (VT/NT vs výkupní marže), přesto má smysl vybíjet do sítě energii nabitou v levném okně.
3. Proč min(buy) přes celý horizont není nákupní cena zásoby
min(buy_price) v horizontu je jeden 15min slot (nejlevnější čtvrthodina).
| home-01 (typicky) | Hodnota |
|---|---|
| Kapacita baterie | 64 kWh |
| Max import ze site | 17 kW |
| Max energie ze sítě / slot (15 min) | 17 kW × 0,25 h ≈ 4,25 kWh |
| Počet slotů na „plné“ grid nabíjení | až ~15 slotů (≈ 64/4,25), tedy hodiny |
Min buy tedy popisuje špičku v jednom čtvrthodině, ne průměrnou cenu energie, kterou skutečně natankujeme přes dlouhé nabíjecí okno.
Použití min(buy) jako „acquisition cost“ pro večerní export:
- podhodnotí skutečný náklad, pokud nabíjíme i v druhém/třetím levném slotu s vyšší cenou;
- neříká nic o tom, kolik energie v levném pásmu vůbec nabít — to řeší masky
allow_chargea rozpočet Wh, ne jedna čísla.
Kde je min(buy) dnes OK: hrubá brána („existuje v horizontu levný nákup?“), výběr slotů pro vrstvu B (buy ≤ min + ε), ne jako jediná proměnná pro výpočet zisku z baterie.
4. Co používat místo toho (směr návrhu)
| Pojem | Význam | Poznámka |
|---|---|---|
buy_charge_window |
Nákupní cena energie do baterie | Odvozená z množiny nabíjecích slotů (allow_charge / skutečný bc+gi), ne z jednoho minima |
sell_discharge_window |
Výkup při vybíjení do sítě | Např. průměr / percentil sell v allow_discharge_export slotech |
| Spread | sell_discharge − buy_charge − degradace |
Rozhoduje, zda má smysl večer ge_bat |
Příklady výpočtu buy_charge (zvolit jednu politiku v implementaci):
- Průměr přes
allow_chargesloty (vážený 0/1 — všechny povolené sloty stejně). - Průměr přes N nejlevnějších slotů, kde N = počet slotů potřebných na dobití:
ceil(energy_to_fill_wh / (max_charge_w × η × 0,25 h)). - Vážený průměr
sum(buy[t] × charge_wh[t]) / sum(charge_wh[t])z výsledku LP (až po solve — iterace nebo aproximace před solve z masky).
Pro home-01 při nabíjení 11:00–14:00 za ~0,7–0,9 Kč a výprodeji 19:00–22:00 za ~3,5–5,5 Kč je spread řádově 2–4 Kč/kWh — to LP dnes nevidí, pokud účtuje večerní buy[t].
5. Co nedělat v dalších iteracích
- Navrhovat „opravu arbitráže“ jen jako
sell[t] − min(buy horizontu)v objective — min buy je jeden slot, nabíjení je více hodin. - Zaměňovat stejnoslotové
buy/sells mezi-slotovou arbitráží — uživatel to explicitně považuje za nesmysl. - Očekávat, že zvýšení
allow_discharge_exportsamo spustí večerní SELL, když objective pořád trestá export přibuy[t] > sell[t]ve stejném slotu.
6. Implementace (2026-05) a backlog
Hotovo (jednoduchá varianta před solve)
ems.fn_load_planning_slots_full(R__063): sloupce
charge_acquisition_buy_czk_kwh,charge_acquisition_cutoff_at.- Vážený průměr
buyvallow_chargeslotech před prvnímallow_discharge_export(cutoff):
Σ(buy × per_slot_charge_wh) / Σ(per_slot_charge_wh)— bezfuture_sellz odpolední FVE (jinak acquisition nafukovala večerní export). solve_dispatch: v exportních slotech (allow_discharge_export) přičíst k objective
+ ge_bat[t] × charge_acquisition × INTERVAL_H/1000(náklad uložené energie), ponechat−ge×sell. Snapshot vsolver_params.inputs.- FVE opportunity: varianta B — lookahead
future_sell_opportunity, ne jensell[t]v odhadu PV Wh.
Zbývá / vylepšit
- Iterace po solve: přepočítat acquisition z plánovaných
bc+gi/pvmísto odhadu z masek — vizdocs/05-todo.md. - Objective: explicitní
ge_bat × (sell − acquisition − degrad)vs současné−ge×sell++ge_bat×acquisition(ekvivalentní jen pokudge_pvv exportním slotu ≈ 0). - Masky: více charge slotů ∝
energy_to_fill / per_slot_wh— vizplanning.md. - Bilance / guardy: zpřesnit, aby večerní export nebyl vázaný na falešný
gi×buyv tomže slotu.
7. Ověření po změně (home-01)
-- levné okno: víc allow_charge, rozumný buy_charge (~0.7–1.0)
select interval_start at time zone 'Europe/Prague' as t,
buy_price, allow_charge
from ems.fn_load_planning_slots_full(2, <from>, <to>, <soc_wh>)
where buy_price < 1.2
order by 1;
-- večer: BATTERY_SELL, záporný grid_setpoint
select interval_start at time zone 'Europe/Prague' as t,
effective_buy_price, effective_sell_price,
battery_setpoint_w, grid_setpoint_w, export_mode
from ems.planning_interval
where run_id = <active_run_id>
and extract(hour from interval_start at time zone 'Europe/Prague') between 19 and 22;
Očekávání: SoC před večerem 70–90 % po levném pásmu; večer export do sítě v špičce sell, ne jen ~2 kW do domu.
Poslední aktualizace: 2026-05 — charge_acquisition před 1. exportem + LP ge_bat náklad; iterace po solve v TODO.