Files
ems/docs/04-modules/planning-extended-horizon.md
Dusan Vojacek f48a7aad61
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped
zkraceni intervalu planneru na max 35h
2026-04-19 21:09:48 +02:00

7.2 KiB
Raw Permalink Blame History

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):

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ší:

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:

-- 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

# 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

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