Files
ems/docs/04-modules/forecast.md
Dusan Vojacek dede8d604d
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped
fix cutoff a grid peak shaving register
2026-04-29 13:36:38 +02:00

199 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 716 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 (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.
---
## 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