sjednoceni forecastu
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-05 10:42:49 +02:00
parent 459f33d55c
commit 5b383e9028
9 changed files with 461 additions and 253 deletions

View File

@@ -0,0 +1,97 @@
---
name: Unify PV correction source
status: draft
owner: cursor-agent
---
## Cíl
Uděláme **single source of truth** pro PV forecast používaný v plánování tak, aby:
- **solver** i **UI** četly *stejnou* PV řadu (žádné „dvě korekce dvěma cestami“),
- kanonický výpočet byl v **PostgreSQL**,
- výsledky byly auditovatelné (raw vs delta vs rolling-factor + decay).
## Zjištěný problém (dnes)
- Solver používá PV z DB + **multiplikativní rolling faktor** v Pythonu (`compute_correction_factor` + `apply_forecast_correction` v `backend/services/planning_engine.py`).
- UI (Planning tabulka) zobrazuje PV přes endpoint **delta-korekce** (`/forecast/pv-slots-corrected``ems.fn_forecast_pv_slots_range_corrected`), což může být jiné číslo než PV, se kterým solver počítal.
- Důsledek: v tabulce slotů nesedí výkonová bilance (UI ukáže např. 5.9 kW, ale plán implicitně pracuje s ~10.4 kW).
## Cílové chování (nová kanonická DB řada)
Kanonické PV pro plánování definujeme jako kombinaci obou korekcí:
1. **Delta-korekce (aditivní)** per PV array (odečíst `delta_profile[slot_of_day]`, clamp na 0)
2. Agregace do **PV-A / PV-B** podle `ems.asset_pv_array.controllable`
3. **Rolling faktor (multiplikativní)** z `ems.fn_pv_forecast_correction_factor(...)` aplikovaný na PV-A i PV-B
4. **Decay (lineární útlum)** faktoru podle offsetu slotu od `now` (stejná logika jako dnes v `apply_forecast_correction`)
Výstup této kanonické řady se musí propsat do:
- `ems.fn_load_planning_slots_full` (vstup pro solver),
- `ems.fn_plan_current_bundle` (výstup pro UI),
- a do uložených sloupců `planning_interval.pv_*_forecast_solver_w` (audit).
## Návrh DB API (kanonická funkce)
Přidat novou repeatable rutinu, např.:
- `db/routines/R__0xx_fn_forecast_pv_slots_range_canonical_ab.sql`
Funkce vrátí JSON pole slotů pro `[from, to)` s minimálně:
- `interval_start`
- `pv_a_forecast_raw_w`, `pv_b_forecast_raw_w`
- `pv_a_forecast_delta_w`, `pv_b_forecast_delta_w` (po delta-korekci)
- `rolling_factor` (globální faktor) + `rolling_effective_factor` (po decay pro slot)
- `pv_a_forecast_canonical_w`, `pv_b_forecast_canonical_w` (delta × rolling_effective_factor)
Poznámky:
- Delta profil už dnes existuje (`ems.fn_pv_forecast_delta_profile`) a `fn_forecast_pv_slots_range_corrected` už umí per-array delty; tu logiku zrecyklujeme.
- Rolling faktor už dnes existuje v DB (`ems.fn_pv_forecast_correction_factor`), jen se dnes aplikuje v Pythonu.
- Decay parametrizovat (např. `p_decay_slots int default 16`, `p_min_clamp numeric`, `p_max_clamp numeric`, `p_window_h numeric`).
## Změny solveru (Python)
V `backend/services/planning_engine.py`:
- Přepnout loader PV slotů na kanonickou DB řadu (A/B corrected).
- **Odstranit** aplikaci PV korekce v Pythonu (nebo ji dočasně nechat za feature flagem jen jako fallback při chybě DB funkce).
- Uložit do `planning_run` diagnostiku (např. `forecast_correction_factor` nahradit/rozšířit o `pv_forecast_method = 'canonical_db_delta+rolling'` + `rolling_factor`).
## Změny DB pro plánovací sloty a current bundle
- `db/routines/R__063_fn_load_planning_slots_full.sql`:
- zdroj PV A/B musí být `pv_*_forecast_canonical_w` z nové funkce.
- zachovat raw/solver sloupce pro audit a UI.
- `ems.fn_plan_current_bundle` (repeatable rutina ve `db/routines/`, dohledat a upravit):
- pro intervaly z `planning_interval` vracet explicitně:
- `pv_a_forecast_solver_w`, `pv_b_forecast_solver_w`, a `pv_forecast_total_w = pva+pvb` (aby UI nemuselo „domýšlet“ přes jiné endpointy),
- pro sloty za horizontem (forecast extension) vracet `pv_forecast_total_w` jako **kanonický součet** (canonical A+B) z nové funkce.
## Změny UI
V `frontend/src/pages/Planning.tsx`:
- Pro tabulku slotů a graf použít **jen** PV z `/sites/{id}/plan/current`:
- pro plánované sloty: `pv_a_forecast_solver_w + pv_b_forecast_solver_w` (ne `pv-slots-corrected`),
- pro forecast-only sloty: `pv_forecast_total_w` (které už bude kanonické z DB).
- Endpoint `/forecast/pv-slots-corrected` ponechat pro stránku forecastu a diagnostiku, ale **ne** jako zdroj pro Planning tabulku.
## Ověření
- Pro konkrétní slot (např. `home-01`, `10:15` Prague) musí sedět:
- UI PV (z `/plan/current`) == PV v solver vstupu == uložené `planning_interval.pv_*_forecast_solver_w`.
- Výkonová bilance v tabulce slotů: `PV - load - EV - HP = battery + export(+/- import)` bez „magické energie“.
- Doplnit regresní test: UI zobrazuje stejné PV jako `planning_interval` (alespoň na DTO úrovni / snapshot).
## Dokumentace
Aktualizovat:
- `docs/04-modules/forecast.md` (kde vzniká kanonické PV: delta + rolling factor + decay),
- `docs/04-modules/planning.md` (solver čte kanonický PV z DB; UI používá stejné sloupce z `/plan/current`),
- případně krátká poznámka do `docs/02-architecture.md` k „read-model = single point of truth“ pro plán.