# Plánování: historie 96h horizontu a budoucí rozšíření

> **Historické (do 2026-04):** produkční solver používal horizont až **96 h** s predikcí cen za hranicí OTE a váhami 1,0 / 0,7 / 0,4 v účelové funkci. Od **2026-04** je horizont **dynamický pouze z OTE** (`ems.fn_planning_horizon_end` / `fn_last_effective_ote`), bez predikovaných cen v LP a s **terminal SoC shadow price**; limity stropu a min. délky jsou v SQL, ne v env. Níže zůstává popis původního návrhu pro referenci a budoucí rozšíření (např. fáze 3e počasí).

## 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__018_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)