From 5a66cfa63f19b0e96fd64ae099a923f892113124 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Wed, 22 Apr 2026 21:05:14 +0200 Subject: [PATCH] ladime a ladime --- .cursor/rules/postgres-sql-drop-comment.mdc | 4 ++- .idea/data_source_mapping.xml | 1 + backend/app/routers/sites.py | 28 +------------------ .../R__078_fn_pv_forecast_delta_profile.sql | 12 ++++---- ...9_fn_forecast_pv_slots_range_corrected.sql | 12 ++------ docs/05-todo.md | 2 +- frontend/src/api/backend.ts | 6 ---- 7 files changed, 14 insertions(+), 51 deletions(-) diff --git a/.cursor/rules/postgres-sql-drop-comment.mdc b/.cursor/rules/postgres-sql-drop-comment.mdc index c24912d..1b50c2f 100644 --- a/.cursor/rules/postgres-sql-drop-comment.mdc +++ b/.cursor/rules/postgres-sql-drop-comment.mdc @@ -9,4 +9,6 @@ alwaysApply: false - U **`DROP FUNCTION`** (včetně schématu, např. `ems.fn_pv_forecast_delta_profile`) **nemusíme** uvádět signaturu argumentů, pokud platí předpoklad: **v DB existuje jen jedna funkce tohoto plného jména** (žádný jiný overload se stejným jménem). - Stejně u **`COMMENT ON FUNCTION`** používej **`COMMENT ON FUNCTION ems.nazev_funkce IS '...'`** bez seznamu typů argumentů — za stejného předpokladu jedné funkce na jméno. -**Důsledek:** při zavádění overloadů se stejným názvem je nutné signatury zase explicitně rozlišit, nebo přejmenovat / sloučit funkce tak, aby jméno bylo jednoznačné. +**Chyba při migraci je v pořádku:** pokud v DB existují **dvě (nebo víc) funkcí stejného jména** (overloady), `DROP FUNCTION` / `COMMENT ON FUNCTION` **bez** seznamu typů může Postgres **zamítnout jako nejednoznačné** — to je žádoucí: hned se detekuje **nechtěný stav**, který se má opravit **odstraněním jedné z funkcí** (nebo přejmenováním), ne obcházením přes dlouhou signaturu v migraci. + +**Když overload záměrně chceme:** jednoznačná jména nebo v daném skriptu dočasně uvést signaturu — v tomto projektu je default „jedna funkce na jméno“. diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml index 535b201..36fbe48 100644 --- a/.idea/data_source_mapping.xml +++ b/.idea/data_source_mapping.xml @@ -4,6 +4,7 @@ + diff --git a/backend/app/routers/sites.py b/backend/app/routers/sites.py index 84bf5e1..85c96b2 100644 --- a/backend/app/routers/sites.py +++ b/backend/app/routers/sites.py @@ -560,24 +560,6 @@ async def get_site_forecast_pv_slots_range_corrected( le=10_000, description="Ignorovat sloty s nízkou výrobou (W) při odhadu profilu", ), - top_n_days: int | None = Query( - None, - ge=1, - le=120, - description="Jen N nejlepších kalendářních dní (podle clear-ish skóre) pro delta profil; ostatní dny ztlumené", - ), - non_top_day_factor: float | None = Query( - None, - ge=0.0, - le=1.0, - description="Násobek váhy pro dny mimo top N (default 0.02)", - ), - day_weight_gamma: float | None = Query( - None, - ge=0.25, - le=8.0, - description="Exponent na denní váhu (>1 silněji preferuje jen velmi 'clear' dny)", - ), ) -> dict[str, list[dict[str, Any]]]: if to_ts <= from_ts: raise HTTPException(status_code=422, detail="'to' must be after 'from'") @@ -589,8 +571,6 @@ async def get_site_forecast_pv_slots_range_corrected( now = datetime.now(tz=timezone.utc) delta_to = delta_to_ts or now delta_from = delta_from_ts or (delta_to - timedelta(days=60)) - ntf = 0.02 if non_top_day_factor is None else float(non_top_day_factor) - dg = 1.0 if day_weight_gamma is None else float(day_weight_gamma) async with db.acquire() as conn: site_ok = await conn.fetchval( "SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id @@ -607,10 +587,7 @@ async def get_site_forecast_pv_slots_range_corrected( $4::timestamptz, $5::timestamptz, $6::numeric, - $7::int, - $8::int, - $9::numeric, - $10::numeric + $7::int ) """, site_id, @@ -620,9 +597,6 @@ async def get_site_forecast_pv_slots_range_corrected( delta_to, half_life_days, threshold_w, - top_n_days, - ntf, - dg, ) slots = raw if isinstance(raw, list) else [] if not isinstance(slots, list): diff --git a/db/routines/R__078_fn_pv_forecast_delta_profile.sql b/db/routines/R__078_fn_pv_forecast_delta_profile.sql index 6af4f77..feb1110 100644 --- a/db/routines/R__078_fn_pv_forecast_delta_profile.sql +++ b/db/routines/R__078_fn_pv_forecast_delta_profile.sql @@ -24,13 +24,11 @@ as $fn$ from ems.site s where s.id = p_site_id ), - -- Cutoff z analýzy DB (EMS Postgres): u site_id=2 (`home-01`) začíná být - -- `forecast_accuracy.actual_power_w` spolehlivě vyplněné pro celé kalendářní dny - -- od 2026-04-11 (Europe/Prague). Před tímto datem se v `forecast_accuracy` objevují - -- ustřelené hodnoty (abs(error_w) ~ 65k) způsobené historickým bugem v telemetrii - -- (signed/unsigned). Cutoff je fixní záměrně, aby se delta profil neučil z nekonzistentní historie. + -- Cutoff: učení delty jen od začátku kalendářního dne 2026-04-12 (Europe/Prague). + -- (UTC okamžik odpovídá DST v dubnu: půlnoc v Praze = předchozí den 22:00 UTC.) + -- Před tím mohou být v `forecast_accuracy` nekonzistentní historická data (telemetrie signed/unsigned). cutoff as ( - select timestamptz '2026-04-10T22:00:00Z' as min_ts + select timestamptz '2026-04-11T22:00:00Z' as min_ts ), bounds as ( select @@ -229,4 +227,4 @@ as $fn$ $fn$; comment on function ems.fn_pv_forecast_delta_profile is - 'Aditivní delta profil chyby PV forecastu po 15min slotu dne (96 slotů). Zdroj: forecast_accuracy, vážení exp(-age/half_life_days) * day_weight (clear-ish dny) * volitelně top_n_days (jen N nejlepších kalendářních dní podle w_energy*w_smooth, ostatní ztlumené) * power(day_weight, day_weight_gamma). Vrací JSON {deltas:[{slot_of_day, delta_w, sample_count}], ...}. Cutoff dat od 2026-04-11 Europe/Prague.'; + 'Aditivní delta profil chyby PV forecastu po 15min slotu dne (96 slotů). Zdroj: forecast_accuracy, vážení exp(-age/half_life_days) * day_weight (clear-ish dny) * volitelně top_n_days (jen N nejlepších kalendářních dní podle w_energy*w_smooth, ostatní ztlumené) * power(day_weight, day_weight_gamma). Vrací JSON {deltas:[{slot_of_day, delta_w, sample_count}], ...}. Cutoff dat od 2026-04-12 Europe/Prague.'; diff --git a/db/routines/R__079_fn_forecast_pv_slots_range_corrected.sql b/db/routines/R__079_fn_forecast_pv_slots_range_corrected.sql index 3975723..4c62743 100644 --- a/db/routines/R__079_fn_forecast_pv_slots_range_corrected.sql +++ b/db/routines/R__079_fn_forecast_pv_slots_range_corrected.sql @@ -12,10 +12,7 @@ create or replace function ems.fn_forecast_pv_slots_range_corrected( p_delta_data_from timestamptz, p_delta_data_to timestamptz default now(), p_half_life_days numeric default 14, - p_threshold_w int default 150, - p_top_n_days int default null, - p_non_top_day_factor numeric default 0.02, - p_day_weight_gamma numeric default 1.0 + p_threshold_w int default 150 ) returns jsonb language sql @@ -72,10 +69,7 @@ as $fn$ p_delta_data_from, p_delta_data_to, p_half_life_days, - p_threshold_w, - p_top_n_days, - p_non_top_day_factor, - p_day_weight_gamma + p_threshold_w ) as j ), deltas as ( @@ -128,4 +122,4 @@ as $fn$ $fn$; comment on function ems.fn_forecast_pv_slots_range_corrected is - 'JSON pole {interval_start, pv_forecast_total_w, pv_forecast_corrected_w, slot_of_day} po 15 min pro [p_from, p_to). Korekce je aditivní delta profil z fn_pv_forecast_delta_profile (top_n_days / non_top_day_factor / day_weight_gamma). Horizont je omezený na max. 60 dní.'; + 'JSON pole {interval_start, pv_forecast_total_w, pv_forecast_corrected_w, slot_of_day} po 15 min pro [p_from, p_to). Korekce je aditivní delta profil z fn_pv_forecast_delta_profile (parametry delty = defaulty v té funkci). Horizont je omezený na max. 60 dní.'; diff --git a/docs/05-todo.md b/docs/05-todo.md index 58b4f78..5e08516 100644 --- a/docs/05-todo.md +++ b/docs/05-todo.md @@ -18,7 +18,7 @@ Shrnutí otevřených bodů z `docs/06-open-questions.md`, checklistů v modulec | **Telemetry – výroba FVE:** Registry 672/673/667 jsou **signed** W; `pv_power_w` = max(0,pv1)+max(0,pv2)+max(0,gen) (dashboard); sloupce pv1/pv2/gen ukládají signed pro audit. | | **Ekonomika baterie:** snížení `reserve_soc_percent` na 10 % a `degradation_cost_czk_kwh` na 0.1500 (migrace `V026__battery_economics_tuning.sql`), úpravy objective pro ekonomicky konzistentnější nabíjení/vybíjení. | | **Planning UI operátor akce:** trvale viditelné akce import/forecast/init plan, volba data OTE (dnes/zítra), zobrazení `pv_scarcity_factor` ve stavu plánu. | -| **PV delta profil – cutoff historie:** po analýze `ems.forecast_accuracy` pro `home-01` je minimální spolehlivý začátek učení **2026-04-11 (Europe/Prague)** (UTC `2026-04-10T22:00:00Z`); cutoff je zafixovaný v `db/routines/R__078_fn_pv_forecast_delta_profile.sql` (ignoruje starší data i při širším `p_data_from`). | +| **PV delta profil – cutoff historie:** minimální začátek učení delty je **2026-04-12 (Europe/Prague)** (UTC `2026-04-11T22:00:00Z`); cutoff je zafixovaný v `db/routines/R__078_fn_pv_forecast_delta_profile.sql` (ignoruje starší data i při širším `p_data_from`). | --- diff --git a/frontend/src/api/backend.ts b/frontend/src/api/backend.ts index 85bbe81..0febe6f 100644 --- a/frontend/src/api/backend.ts +++ b/frontend/src/api/backend.ts @@ -129,12 +129,6 @@ export type ForecastPvSlotsCorrectedParams = { delta_to?: string half_life_days?: number threshold_w?: number - /** Jen N nejlepších kalendářních dní pro výpočet delta profilu (backend → fn_pv_forecast_delta_profile). */ - top_n_days?: number - /** Násobek váhy pro dny mimo top N (0–1, default na serveru 0.02). */ - non_top_day_factor?: number - /** Exponent na denní váhu (default 1 = beze změny oproti předchozímu chování bez top_n). */ - day_weight_gamma?: number } export async function getForecastPvSlotsRangeCorrected(