Files
ems/docs/04-modules/forecast.md
Dusan Vojacek d984716f69
Some checks failed
CI and deploy / migration-check (push) Failing after 12s
CI and deploy / deploy (push) Has been skipped
speedup zalozka planning
2026-05-21 10:37:32 +02:00

11 KiB
Raw Permalink Blame History

Modul: Forecast (Predikce výroby FVE)

Co modul dělá

  • Stahuje meteorologická data (irradiance, teplota) pro každé FVE pole zvlášť
  • Vypočítává predikovaný výkon v 15min intervalech
  • Ukládá výsledek per pv_array_id + run_id
  • Predikce se spouští každé 2 hodiny v :05 a ručně přes API. Plánovač používá poslední dostupné uložené forecasty; forecast nespouští implicitně před každým plánovacím během.

Kanonický forecast pro plánování (single source of truth)

Pro plánování (solver) a UI tabulky slotů je kanonický výkon FVE počítaný v DB funkcí:

  • ems.fn_forecast_pv_slots_range_canonical_ab(...)

Ta kombinuje dvě korekce do jedné řady:

  • delta-korekci per pv_array_id (z ems.fn_pv_forecast_delta_profile)
  • rolling multiplikativní faktor vs telemetrie (z ems.fn_pv_forecast_correction_factor) s lineárním decay do 1.0

Výstup je rozdělený na PVA (controllable=true, curtailment v LP) a PVB (controllable=false).


FVE pole na první instalaci (home-01)

Pole Výkon Azimut Sklon Střídač Řízení
A 10 kWp 184° 22° Deye 20kW řídíme
B 10 kWp 184° 35° Ongridový autonomní, predikujeme jako samostatné pole

Aktuální implementace: Forecast služba počítá všechna FVE pole lokality, která mají vyplněný azimuth_deg a tilt_deg; plánovač pracuje odděleně s pv_a_forecast_w i pv_b_forecast_w.

Azimut je uložen v kompasové / pvlib konvenci: 0=N, 90=E, 180=S, 270=W.


Zdroj meteorologických dat

Primární: Open-Meteo (open-meteo.com)

  • Zdarma pro nekomerční použití, API bez registrace
  • Poskytuje GHI (Global Horizontal Irradiance), DNI, teplotu, oblačnost
  • Historická data + forecast na 716 dní dopředu
  • 15min granularita nativně ✓

Endpoint:

GET https://api.open-meteo.com/v1/forecast
  ?latitude={lat}
  &longitude={lon}
  &minutely_15=direct_normal_irradiance,diffuse_radiation,shortwave_radiation,temperature_2m
  &timezone=auto
  &forecast_days=7

Záložní / budoucí: Solcast

  • Přesnější pro FVE, ale placený
  • Podporuje per-array predikci s azimutem a sklonem přímo
  • Zatím neimplementujeme, architektura to umožňuje přes forecast_source

Výpočet výkonu z irradiance

Implementace používá pvlib a model POA irradiance haydavies:

poa_global = pvlib.irradiance.get_total_irradiance(
    surface_tilt=tilt_deg,
    surface_azimuth=azimuth_deg,  # 0=N, 90=E, 180=S, 270=W
    solar_zenith=solar_pos["apparent_zenith"],
    solar_azimuth=solar_pos["azimuth"],
    dni=dni,
    ghi=ghi,
    dhi=dhi,
    dni_extra=dni_extra,
    model="haydavies",
)["poa_global"].fillna(0).clip(lower=0)

area_m2 = nominal_power_wp / (1000.0 * 0.20)
power_w = (poa_global * area_m2 * 0.20 * shading_factor).clip(
    lower=0,
    upper=nominal_power_wp * 1.1,
)

Kdo spouští predikci

Python service: forecast_service

Kdy se spouští

Trigger Čas Popis
Scheduled (cron) každé 2 hodiny v :05 Průběžný refresh forecastu pro všechny aktivní site
Manual trigger na vyžádání POST /api/v1/sites/{site_id}/forecast/run

Implementované provozní změny (2026-03)

  • Forecast horizont je konfigurovatelný přes open_meteo_forecast_days.
  • Runtime guard: hodnota se clampuje do rozmezí 2..16.
  • Default je 7 dní.
  • Endpoint GET /api/v1/sites/{site_id}/forecast/pv?date=YYYY-MM-DD vrací vždy poslední ok run per (interval_start, pv_array_id) (DISTINCT ON), takže UI nevidí duplikáty z historických běhů.
  • Kalibrace delty: GET /api/v1/sites/{site_id}/forecast/pv-delta-profile?from=…&to=… vrací JSON z ems.fn_pv_forecast_delta_profile (deltas, deltas_by_array, delta_learn_min_ts z ems.site_pv_forecast_calibration). Volitelné query parametry: half_life_days, threshold_w, top_n_days, non_top_day_factor, day_weight_gamma (NULL u numerických přepsání = hodnota z kalibrační tabulky / default funkce).
  • Cache delty (V079): sloupce delta_profile_cache / delta_profile_cached_at v site_pv_forecast_calibration; refresh ems.fn_refresh_site_pv_delta_profile_cache(site_id) po fn_fill_forecast_accuracy a po PATCH kalibrace; čtení pro plánování/UI přes fn_pv_forecast_delta_profile_cached (TTL 30 min, pak fallback na plný přepočet).
  • Úprava kalibrace z API: PATCH /api/v1/sites/{site_id}/configuration/pv-forecast-calibration s JSON tělem (částečný update); odpověď je aktuální řádek kalibrace. Souhrn konfigurace v GET …/configuration obsahuje klíč pv_forecast_calibration.
  • Telemetrie pro učení delty: telemetry_collector při Modbus poll čte reg. 145 a 178; fn_telemetry_inverter_sample ukládá is_export_limited / pv_derating_flags (bity 1 = solar sell off, 2 = GEN/MI cut-off aktivní dle masky (reg178 & 3) == 3). fn_fill_forecast_accuracy sloty s těmito signály označí telemetry_derating.

Logika běhu predikce

def run_forecast(site_id: int):
    site = db.get_site(site_id)
    arrays = db.get_pv_arrays_with_azimuth_and_tilt(site_id)

    for array in arrays:
        # 1. Stáhnout meteorologická data
        weather = open_meteo_client.fetch(
            lat=site.lat, lon=site.lon,
            forecast_days=clamp(OPEN_METEO_FORECAST_DAYS, 2, 16)
        )

        # 2. Vytvořit forecast_pv_run
        run = db.create_forecast_run(
            site_id=site_id,
            pv_array_id=array.id,
            forecast_source="open_meteo",
            horizon_start=today_00,
            horizon_end=today_end + horizon_days
        )

        # 3. Vypočítat a uložit intervaly (15min)
        intervals = []
        for slot in weather.slots_15min:
            power = calculate_pv_power(
                irradiance_wm2=slot.shortwave_radiation,
                temp_c=slot.temperature_2m,
                nominal_power_wp=array.nominal_power_wp,
                azimuth_deg=array.azimuth_deg,
                tilt_deg=array.tilt_deg,
                shading_factor=array.shading_factor
            )
            intervals.append(ForecastInterval(
                run_id=run.id,
                pv_array_id=array.id,
                interval_start=slot.time,
                power_w=power,
                irradiance_wm2=slot.shortwave_radiation,
                temp_c=slot.temperature_2m
            ))

        db.insert_forecast_intervals(intervals)

DB struktura

Viz 03-data-model.md:

  • forecast_pv_run každý běh predikce
  • forecast_pv_interval 15min výsledky per pole a běh

Tracking přesnosti forecastu

  • ems.forecast_accuracy pro každý úspěšný forecast_pv_run a každý 15min slot ukládá predikovaný výkon, čas vzniku predikce, lead time (hodiny před začátkem slotu), později doplněnou skutečnost z telemetrie a odchylku (error_w, error_pct). Záznamy se uchovávají trvale (včetně všech historických běhů v forecast_pv_run / forecast_pv_interval ty se nemazají).
  • ems.fn_fill_forecast_accuracy(site_id, lookback_hours) inkrementálně vloží nebo aktualizuje řádky z forecast_pv_interval + run metadata a dopočte actual_power_w jako průměr 1min telemetrie ve slotu (pole B: gen_port_power_w, pole A: pv1_power_w + pv2_power_w). Volat každých 15 minut (např. spolu s audit fillerem); parametr lookback_hours omezuje okno zpětného zpracování (např. 48 h běžně, větší hodnota pro jednorázový backfill).
  • ems.vw_forecast_accuracy_by_lead_time agregace přesnosti podle bucketů lead time (06 h, …, 48 h+); noční sloty s nízkou výrobou (actual_power_w ≤ 100 W) se v metrikách typicky vynechávají.
  • ems.vw_forecast_accuracy_daily denní součty forecast vs actual v kWh (Praha kalendářní den) a relativní odchylka dne.
  • Po 4+ týdnech dat lze statistiky použít pro kalibraci safety_factor (nebo obdobných parametrů) v solveru viz plánovací modul.

Operace SQL: mazání řádků PV forecastu za den (provozní výjimka)

Projekt standardně nemá mazat forecast_pv_interval / forecast_pv_run, aby zůstala historie pro přesnost. Když záměrně promāžeš den (např. před regenerací výstupu předpovědi), použij ems.fn_delete_forecast_pv_prague_calendar_day(p_day date, p_site_id int DEFAULT NULL) (db/routines/R__086_fn_forecast_pv_prague_day_ops.sql). Hranice dne jsou Europe/Prague půlnoc (ne timezone lokality); p_site_id NULL = všechny lokality.

Příklad: select * from ems.fn_delete_forecast_pv_prague_calendar_day('2026-05-02'::date, 2);

Odstranění jde přes páry forecast_accuracy → řádek forecast_pv_interval→ prázdné forecast_pv_run, které měly jen interval v mazané množině (stejně jako dříve skript).

Na bazální spotřebu (consumption_baseline_stats) to nesahá → ems.fn_rebuild_consumption_baseline_stats v R__085.


Referenční dny při učení delty („hezky svítily“ zpětně)

Profil ems.fn_pv_forecast_delta_profile se nemerguje jako samostatný soubor — při každém načítání (fn_load_planning_slots_full / API) znovu agreguje chybu z forecast_accuracy v okně (lookback/exponenta half_life, rank top dnů odvozený od energie a hladkosti dnů).

„Zapošto“ k existující logice:

  1. Ověř, že máš forecast_accuracy pro ty dny (po skutečnosti slotů z actual_power_w z telemetrie) — obvykle díky fn_fill_forecast_accuracy.
  2. Založ řádek v ems.site_pv_forecast_reference_day(site_id, day_local, notes). day_local musí sedět na (interval_start AT TIME ZONE site.timezone)::date slovní hodiny lokality (typicky datum v Praze jako u home-01 Europe/Prague).
  3. (Volitelně) nastav site_pv_forecast_calibration.reference_day_weight_mult (NULL = výchozí násobitel 3, minimum v kódu 1). Ostatní dny berou jako dosud jejich váhy (top_n, non_top_day_factor, decay…) současně — „referenční den“ je multiplikátor navíc, nesamostatný paralelní model.

Hromadně: ems.fn_pv_forecast_sync_reference_days(site_id, p_days_local date[], p_replace_existing bool default false) — nahrazením true nejdřív vymaže dřívější řádky reference pro site, pak doplní unnest; vrací celkový počet pinů lokality po operaci.

Co to nedělá: nepřepisuje zpětně uložené forecast_pv_interval; mění jen to, jak moc vstupuje ten den do aktuálních δ slotů používaných v plánění.


Konfigurace (env proměnné)

OPEN_METEO_API_URL=https://api.open-meteo.com/v1/forecast
OPEN_METEO_FORECAST_DAYS=7

Monitoring

  • Zatím není samostatný /health/forecast endpoint.
  • Stav se kontroluje přes logy běhu scheduled_forecast_refresh, přes forecast API a přes obecné health endpointy.
  • Log každého běhu (délka horizontu, počet intervalů, trvání, zdroj)

Otevřené body

  • Ověřit přesný azimut a sklon obou FVE polí proti skutečné instalaci
  • Solcast jako alternativa v budoucnu forecast_source to umožňuje bez DB změn