second version
This commit is contained in:
190
docs/04-modules/planning-extended-horizon.md
Normal file
190
docs/04-modules/planning-extended-horizon.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Rozšířený horizont plánování (96h)
|
||||
|
||||
## Motivace
|
||||
|
||||
OTE publikuje ceny max 36h dopředu. FVE forecast je dostupný na 7 dní.
|
||||
Rozšířením horizontu solver vidí vzdálené příležitosti (záporné ceny, levná okna)
|
||||
a může optimálně připravit baterii, TUV zásobník a EV nabíjení.
|
||||
|
||||
Klíčový princip: solver nepotřebuje explicitní "šetři baterii před zápornou cenou"
|
||||
constraint. Pokud dostane správné (odhadované) ceny pro celých 96h, sám pozná
|
||||
že je výhodnější počkat na zápornou cenu než vybíjet dnes za průměrnou.
|
||||
|
||||
## Datové zdroje pro predikci cen za horizont OTE
|
||||
|
||||
### Vrstva 1 – Sezónní průměr z historických OTE dat
|
||||
|
||||
Tabulka `ems.market_price_stats` (analogie `consumption_baseline_stats`):
|
||||
```sql
|
||||
SELECT
|
||||
EXTRACT(DOW FROM interval_start AT TIME ZONE 'Europe/Prague') AS dow,
|
||||
EXTRACT(HOUR FROM interval_start AT TIME ZONE 'Europe/Prague') AS hour,
|
||||
AVG(buy_raw_price_czk_kwh) AS avg_price,
|
||||
STDDEV(buy_raw_price_czk_kwh) AS stddev_price,
|
||||
PERCENTILE_CONT(0.25) WITHIN GROUP (
|
||||
ORDER BY buy_raw_price_czk_kwh) AS p25,
|
||||
PERCENTILE_CONT(0.75) WITHIN GROUP (
|
||||
ORDER BY buy_raw_price_czk_kwh) AS p75
|
||||
FROM ems.market_interval_price
|
||||
WHERE market_source IN ('OTE_CZ', 'OTE_CZ_DAM')
|
||||
AND interval_start >= now() - INTERVAL '6 months'
|
||||
GROUP BY dow, hour
|
||||
```
|
||||
|
||||
Plnit denně po importu OTE. Min. 3 měsíce dat pro smysluplné průměry.
|
||||
|
||||
### Vrstva 2 – Korekce počasím (proxy)
|
||||
|
||||
Záporné/nízké ceny korelují s vysokou FVE výrobou v celé síti CZ.
|
||||
```
|
||||
predicted_irradiance > historical_avg * 1.3 → cena * 0.70 (30% sleva)
|
||||
predicted_irradiance < historical_avg * 0.5 → cena * 1.20 (20% přirážka)
|
||||
```
|
||||
|
||||
Korelaci ověřit po 3+ měsících dat. Zatím použít konzervativní korekci ±15%.
|
||||
|
||||
### Kombinovaná predikce s uncertainty margin
|
||||
```
|
||||
predicted_price[t] = seasonal_avg[dow, hour]
|
||||
× weather_correction_factor[t]
|
||||
× (1 ± uncertainty_margin[t])
|
||||
|
||||
uncertainty_margin roste s horizontem:
|
||||
0-36h: 0% (přesné OTE ceny, žádná predikce)
|
||||
36-72h: 20%
|
||||
72-96h: 35%
|
||||
```
|
||||
|
||||
## Uncertainty weighting v objective function
|
||||
|
||||
Vzdálenější sloty mají nižší váhu – solver je konzervativnější:
|
||||
```python
|
||||
def slot_weight(t: int, now_index: int) -> float:
|
||||
hours_ahead = (t - now_index) * 0.25
|
||||
if hours_ahead <= 36: return 1.0 # přesné OTE ceny
|
||||
if hours_ahead <= 72: return 0.7 # predikce, střední jistota
|
||||
return 0.4 # predikce, nízká jistota
|
||||
|
||||
# V objective function:
|
||||
prob += pulp.lpSum(
|
||||
slot_weight(t, now_index) * (
|
||||
gi[t] * slots[t].buy_price * INTERVAL_H / 1000
|
||||
- ge[t] * slots[t].sell_price * INTERVAL_H / 1000
|
||||
+ ...
|
||||
)
|
||||
for t in range(T)
|
||||
)
|
||||
```
|
||||
|
||||
## TUV predikce potřeby
|
||||
|
||||
### Princip
|
||||
|
||||
TUV zásobník drží teplo ~24h. Solver může ohřát vodu v levném okně
|
||||
před očekávanou spotřebou. Potřebuje vědět:
|
||||
- Aktuální teplotu zásobníku (z telemetrie)
|
||||
- Kdy typicky klesá teplota (statistika per DOW+hodina)
|
||||
- Minimální přijatelnou teplotu (tuv_min_temp_c)
|
||||
|
||||
### Tabulka `ems.tuv_usage_stats`
|
||||
|
||||
Analogie `consumption_baseline_stats` pro TUV zásobník:
|
||||
```sql
|
||||
-- Průměrný pokles teploty zásobníku per DOW+hodina
|
||||
-- (záporné = zásobník se ochladil, kladné = TČ ohřívalo)
|
||||
SELECT
|
||||
EXTRACT(DOW FROM measured_at AT TIME ZONE 'Europe/Prague') AS dow,
|
||||
EXTRACT(HOUR FROM measured_at AT TIME ZONE 'Europe/Prague') AS hour,
|
||||
AVG(temp_delta_c) AS avg_temp_delta, -- průměrná změna za hodinu
|
||||
STDDEV(temp_delta_c) AS stddev_temp_delta
|
||||
FROM (
|
||||
SELECT
|
||||
measured_at,
|
||||
tuv_tank_temp_c - LAG(tuv_tank_temp_c) OVER (
|
||||
PARTITION BY site_id ORDER BY measured_at
|
||||
) AS temp_delta_c
|
||||
FROM ems.telemetry_heat_pump
|
||||
WHERE site_id = $1
|
||||
AND measured_at >= now() - INTERVAL '30 days'
|
||||
) sub
|
||||
WHERE temp_delta_c IS NOT NULL
|
||||
AND ABS(temp_delta_c) < 5 -- filtruj extrémní skoky (start TČ)
|
||||
GROUP BY dow, hour
|
||||
```
|
||||
|
||||
### Použití v solveru
|
||||
```python
|
||||
# Pro každý slot t zjisti predikovanou teplotu zásobníku:
|
||||
tuv_predicted[t] = tuv_current + SUM(avg_temp_delta[dow, hour]
|
||||
for slots before t)
|
||||
|
||||
# Pokud tuv_predicted[t] < tuv_min_temp + safety_margin:
|
||||
# → solver musí naplánovat ohřev před tímto slotem
|
||||
# → heat_pump_enabled[t-N] = True (kde N = počet slotů potřebných pro ohřev)
|
||||
|
||||
# Potřebný čas ohřevu (orientační):
|
||||
# delta_temp = tuv_target - tuv_current
|
||||
# time_h = delta_temp × volume_l × 1.163 / (cop × hp_power_w / 1000)
|
||||
```
|
||||
|
||||
## EV v rozšířeném horizontu
|
||||
|
||||
### Tesla (s API – fáze 2)
|
||||
```
|
||||
Vstup: aktuální SoC z Tesla API, nastavený deadline uživatelem
|
||||
Solver: deadline constraint přes celých 96h
|
||||
nabij nejlevněji v rámci časového okna
|
||||
```
|
||||
|
||||
### Zoe (bez API)
|
||||
```
|
||||
Vstup: ev_arrival_stats (statistika příjezdů per DOW+hodina)
|
||||
energy_delivered_wh z aktuální session (odhad SoC)
|
||||
Solver: soft constraint – pravděpodobnost příjezdu jako váha
|
||||
pokud P(příjezd v slot t) > 60%: rezervuj nabíjecí kapacitu
|
||||
```
|
||||
|
||||
### Predikce příjezdu v solveru
|
||||
```python
|
||||
# Pro každý slot t kde P(příjezd) > 0.4:
|
||||
arrival_prob = ev_arrival_stats[dow, hour] / total_arrivals_this_dow
|
||||
|
||||
# Soft constraint (ne hard – auto nemusí přijet):
|
||||
# Přidej "expected EV consumption" jako součást load_baseline
|
||||
ev_expected_w[t] = arrival_prob * ev_charge_power_typical
|
||||
```
|
||||
|
||||
## Implementační plán
|
||||
|
||||
### Fáze 3a – Historické průměry cen (hotovo)
|
||||
|
||||
1. Tabulka `ems.market_price_stats` – migrace **V022__extended_planning.sql**
|
||||
2. `fn_update_market_price_stats()` – `db/routines/R__fn_extended_planning.sql`, APScheduler **14:45** (`main.py`)
|
||||
3. Solver: slotová páteř `generate_series` + `COALESCE(effective_*, fn_get_predicted_price(...))` v `_load_slots`
|
||||
|
||||
### Fáze 3b – TUV statistika potřeby (hotovo)
|
||||
|
||||
1. Tabulka `ems.tuv_usage_stats` – V022
|
||||
2. `fn_update_tuv_usage_stats()` – repeatable výše, job **00:45**
|
||||
3. Solver: look-ahead simulace teploty + součet `hp` v okně 9 slotů (`solve_dispatch`)
|
||||
|
||||
### Fáze 3c – Rozšíření solveru na 96h (hotovo)
|
||||
|
||||
1. `HORIZON_HOURS = 96`, `slot_weight()` – váhy **1,0 / 0,7 / 0,4** v účelové funkci
|
||||
2. Příznak `PlanningSlot.is_predicted_price` (z SQL `(ep.effective_buy IS NULL)`)
|
||||
|
||||
### Fáze 3d – EV v rozšířeném horizontu (závisí na Tesla API)
|
||||
|
||||
1. Pravděpodobnostní příjezd ze statistiky
|
||||
2. Deadline constraint přes celých 96h
|
||||
3. Tesla API integrace
|
||||
|
||||
### Fáze 3e – Korekce cen počasím
|
||||
|
||||
Po nasbírání 3+ měsíců korelačních dat rozšířit `fn_get_predicted_price` (viz vrstva 2 výše).
|
||||
|
||||
## Prerekvizity
|
||||
|
||||
- Min. 3 měsíce historických OTE dat pro smysluplné průměry
|
||||
- Min. 1 měsíc telemetrie TUV pro tuv_usage_stats
|
||||
- Stabilní základní provoz (Modbus zápis, telemetrie)
|
||||
Reference in New Issue
Block a user