# 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í denně a před každým plánovacím během --- ## FVE pole na první instalaci (home-01) | Pole | Výkon | Azimut | Sklon | Střídač | Řízení | |---|---|---|---|---|---| | A | 10 kWp | TBD | TBD | Deye 20kW | řídíme | | B | 10 kWp | TBD | TBD | Ongridový | autonomní, **nepredikujeme odděleně** | > **Předpoklad:** Pole B (ongridový) je zapojeno do GEN portu Deye. Jeho výkon se projeví v `pv_power_w` telemetrie jako součást celkového výkonu. Pro plánování modelujeme jen pole A. Pole B bereme jako šum / bonus který se projeví v auditu. > Azimuty a sklony je nutné doplnit při konfiguraci lokality do `asset_pv_array`. --- ## 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 7–16 dní dopředu - 15min granularita nativně ✓ **Endpoint:** ``` GET https://api.open-meteo.com/v1/forecast ?latitude={lat} &longitude={lon} &hourly=shortwave_radiation,temperature_2m &minutely_15=shortwave_radiation,temperature_2m &timezone=Europe/Prague &forecast_days=3 ``` **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 Jednoduchý fyzikální model (dostatečný pro plánování): ```python def calculate_pv_power( irradiance_wm2: float, # GHI ze weather service temp_c: float, nominal_power_wp: int, azimuth_deg: float, tilt_deg: float, shading_factor: float = 1.0, temp_coeff: float = -0.004 # typicky -0.4%/°C pro křemík ) -> int: # 1. Korekce na teplotu panelu panel_temp = temp_c + 25 # zjednodušený NOCT model temp_correction = 1 + temp_coeff * (panel_temp - 25) # 2. Korekce na azimut a sklon (zjednodušená, bez přesného GHI→POA) # Přesnější model: pvlib knihovna (doporučeno pro produkci) orientation_factor = cos_angle_of_incidence(azimuth_deg, tilt_deg) # 3. Výsledný výkon power_w = (irradiance_wm2 / 1000) * nominal_power_wp * temp_correction * orientation_factor * shading_factor return max(0, int(power_w)) ``` > **Doporučení pro implementaci:** Použít knihovnu `pvlib` (Python) pro přesný POA irradiance výpočet z GHI + azimut + sklon. Je to standardní nástroj, dobře dokumentovaný. --- ## 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). - **Ú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 ```python def run_forecast(site_id: int, horizon_days: int = 2): site = db.get_site(site_id) arrays = db.get_pv_arrays(site_id, controllable=True) for array in arrays: # 1. Stáhnout meteorologická data weather = open_meteo_client.fetch( lat=site.lat, lon=site.lon, start=today, end=today + horizon_days ) # 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.upsert_forecast_intervals(intervals) db.update_forecast_run_status(run.id, "ok") ``` --- ## 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 (0–6 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. --- ## Konfigurace (env proměnné) ```env OPEN_METEO_API_URL=https://api.open-meteo.com/v1/forecast OPEN_METEO_FORECAST_DAYS=7 FORECAST_MAX_AGE_HOURS=2 # plánovač odmítne starší predikci FORECAST_RETRY_COUNT=3 ``` --- ## Monitoring - Alert pokud forecast pro dnešní den + zítřek není k dispozici do 15:00 - Endpoint `GET /health/forecast?site_id=1&date=YYYY-MM-DD` → čerstvost a počet intervalů - Log každého běhu (délka horizontu, počet intervalů, trvání, zdroj) --- ## Otevřené body - [ ] Doplnit přesný azimut a sklon obou FVE polí při instalaci - [ ] Rozhodnout: pvlib pro přesnější POA výpočet vs jednoduchý model – doporučujeme pvlib od začátku - [ ] Pole B (ongridový) – zda vůbec modelovat nebo ignorovat v plánu a jen sledovat v auditu - [ ] Solcast jako alternativa v budoucnu – `forecast_source` to umožňuje bez DB změn