uprava vypoctu slotu
This commit is contained in:
150
docs/04-modules/planning-arbitrage-accounting.md
Normal file
150
docs/04-modules/planning-arbitrage-accounting.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# 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.md), [`planning_engine.py`](../../backend/services/planning_engine.py) (`solve_dispatch`), [`R__063_fn_load_planning_slots_full.sql`](../../db/routines/R__063_fn_load_planning_slots_full.sql).
|
||||
|
||||
---
|
||||
|
||||
## 1. Co uživatel / provoz očekává (správný model)
|
||||
|
||||
Arbitráž baterie je **časový posun**:
|
||||
|
||||
1. 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í.
|
||||
2. 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ě:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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]` jsou správné pro **FVE export v tomže slotu** (fyzicky exportovat za haléř při drahém VT).
|
||||
|
||||
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_charge` a 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):
|
||||
|
||||
1. **Průměr přes `allow_charge` sloty** (vážený 0/1 — všechny povolené sloty stejně).
|
||||
2. **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))`.
|
||||
3. **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`/`sell` s **mezi-slotovou** arbitráží — uživatel to explicitně považuje za nesmysl.
|
||||
- Očekávat, že zvýšení `allow_discharge_export` samo spustí večerní **SELL**, když objective pořád trestá export při `buy[t] > sell[t]` ve stejném slotu.
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementace (2026-05) a backlog
|
||||
|
||||
### Hotovo (jednoduchá varianta před solve)
|
||||
|
||||
1. **`ems.fn_load_planning_slots_full`** (`R__063`): sloupce
|
||||
`charge_acquisition_buy_czk_kwh`, `charge_acquisition_cutoff_at`.
|
||||
2. **Vážený průměr** jen pro sloty **před** prvním `allow_discharge_export` (`cutoff = min(interval_start)` exportních slotů):
|
||||
- **grid:** `buy × per_slot_charge_wh` pro `allow_charge`;
|
||||
- **FVE:** `future_sell_lookahead` (fallback `sell`) × odhad Wh z `pv_surplus` (`least(surplus, max_charge)×η×0,25 h`).
|
||||
3. **`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 v `solver_params.inputs`.
|
||||
4. **FVE opportunity:** varianta **B** — lookahead `future_sell_opportunity`, ne jen `sell[t]` v odhadu PV Wh.
|
||||
|
||||
### Zbývá / vylepšit
|
||||
|
||||
1. **Iterace po solve:** přepočítat acquisition z plánovaných `bc`+`gi` / `pv` místo odhadu z masek — viz [`docs/05-todo.md`](../05-todo.md).
|
||||
2. **Objective:** explicitní `ge_bat × (sell − acquisition − degrad)` vs současné `−ge×sell` + `+ge_bat×acquisition` (ekvivalentní jen pokud `ge_pv` v exportním slotu ≈ 0).
|
||||
3. **Masky:** více charge slotů ∝ `energy_to_fill / per_slot_wh` — viz [`planning.md`](planning.md).
|
||||
4. **Bilance / guardy:** zpřesnit, aby večerní export nebyl vázaný na falešný `gi×buy` v tomže slotu.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ověření po změně (home-01)
|
||||
|
||||
```sql
|
||||
-- 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.*
|
||||
Reference in New Issue
Block a user