sync reference days
This commit is contained in:
@@ -98,7 +98,7 @@ Projekt je **SQL-first**: doménová logika, agregace, joiny mezi tabulkami a st
|
|||||||
|
|
||||||
13. **Endpoint `GET …/forecast/pv`** vrací `DISTINCT ON (interval_start, pv_array_id)` seřazené podle nejnovějšího `forecast_pv_run.created_at`, aby UI nemělo duplikáty slotů; plná historie běhů zůstává v tabulkách.
|
13. **Endpoint `GET …/forecast/pv`** vrací `DISTINCT ON (interval_start, pv_array_id)` seřazené podle nejnovějšího `forecast_pv_run.created_at`, aby UI nemělo duplikáty slotů; plná historie běhů zůstává v tabulkách.
|
||||||
|
|
||||||
13a. **PV delta kalibrace:** `GET …/forecast/pv-delta-profile` vrací JSON z `fn_pv_forecast_delta_profile`; `GET …/configuration` obsahuje `pv_forecast_calibration` z `ems.site_pv_forecast_calibration`; `PATCH …/configuration/pv-forecast-calibration` mění cutoff / policy / přepsání parametrů delty. Telemetrie `telemetry_inverter.is_export_limited` / `pv_derating_flags` (V058) řídí vyloučení slotu z učení v `fn_fill_forecast_accuracy` (`telemetry_derating`); `telemetry_collector` je plní čtením Deye reg **145** a **179** při poll střídače.
|
13a. **PV delta kalibrace:** `GET …/forecast/pv-delta-profile` vrací JSON z `fn_pv_forecast_delta_profile`; `GET …/configuration` obsahuje `pv_forecast_calibration` z `ems.site_pv_forecast_calibration`; `PATCH …/configuration/pv-forecast-calibration` mění cutoff / policy / přepsání parametrů delty. **Referenční dny** špičkové produkce zpětně: tabulka **`ems.site_pv_forecast_reference_day`** (V076) + volitelně sloupec **`reference_day_weight_mult`** v kalibraci — v `fn_pv_forecast_delta_profile` zvednou váhu řádků `forecast_accuracy` těchto kalendářních dní (datum ve `site.timezone` jako u slotů); doplňovat lze **`ems.fn_pv_forecast_sync_reference_days`**. Provozní mazání uložené predikce za den (hranice **Europe/Prague**, ne TZ site): **`ems.fn_delete_forecast_pv_prague_calendar_day`**. Telemetrie `telemetry_inverter.is_export_limited` / `pv_derating_flags` (V058) řídí vyloučení slotu z učení v `fn_fill_forecast_accuracy` (`telemetry_derating`); `telemetry_collector` je plní čtením Deye reg **145** a **179** při poll střídače.
|
||||||
|
|
||||||
14. **Příchod a odjezd EV** detekuje `telemetry_collector` z telemetrie nabíječky: přechod `available` → `preparing` / `charging` (resp. jakýkoli stav ≠ `available`) znamená příjezd; přechod na `available` uzavře `ev_session`. Tabulka `ev_arrival_stats` se při příjezdu doplňuje přes `fn_update_ev_arrival_stats` a **nemá se mazat** (dlouhodobá historická statistika).
|
14. **Příchod a odjezd EV** detekuje `telemetry_collector` z telemetrie nabíječky: přechod `available` → `preparing` / `charging` (resp. jakýkoli stav ≠ `available`) znamená příjezd; přechod na `available` uzavře `ev_session`. Tabulka `ev_arrival_stats` se při příjezdu doplňuje přes `fn_update_ev_arrival_stats` a **nemá se mazat** (dlouhodobá historická statistika).
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ Projekt je **SQL-first**: doménová logika, agregace, joiny mezi tabulkami a st
|
|||||||
| `signal_state` | Poslední požadovaná / odeslaná / ověřená hodnota na cíli (idempotence). |
|
| `signal_state` | Poslední požadovaná / odeslaná / ověřená hodnota na cíli (idempotence). |
|
||||||
| `cutoff_switch_log` | Log přepnutí cut-off přepínačů (mikroinvertory); edge trigger, důvod a cena. |
|
| `cutoff_switch_log` | Log přepnutí cut-off přepínačů (mikroinvertory); edge trigger, důvod a cena. |
|
||||||
|
|
||||||
**View / funkce (nejsou tabulky):** `vw_site_effective_price`, `vw_site_directory`, `vw_modbus_last_verified`, `vw_asset_inverter_modbus_poll`, `vw_asset_ev_charger_modbus_poll`, `vw_asset_heat_pump_modbus_poll`, `vw_latest_telemetry`, `vw_telemetry_hourly_7d`, `vw_telemetry_15m_7d` (15min agregát pro dashboard sloty; repeatable `R__071_vw_telemetry_15m_7d.sql`), `vw_audit_summary`, `vw_operating_mode`, `vw_forecast_accuracy_by_lead_time`, `vw_forecast_accuracy_daily`; `fn_effective_price`, `fn_green_bonus_revenue`, `fn_cop_estimate`, `fn_fill_audit_interval`, `fn_fill_forecast_accuracy`, `fn_set_mode`, `fn_expire_modes` (vrací řádky přepnutí pro Discord), `fn_restore_previous_mode`, `fn_update_ev_arrival_stats`, `fn_ev_expected_arrival`, `fn_update_baseline_stats`, `fn_rebuild_consumption_baseline_stats`, `fn_get_baseline_forecast`, `fn_update_market_price_stats`, `fn_update_tuv_usage_stats`, `fn_get_predicted_price`, dále read-modely: `fn_site_configuration`, `fn_site_full_status`, `fn_site_notifications_context`, `fn_plan_current_bundle`, `fn_planning_run_horizon`, `fn_planning_future_price_days`, `fn_economics_daily_month`, `fn_economics_monthly_chart`, `fn_economics_lock_day`, `fn_economics_unlock_day`, `fn_energy_flows_daily_month`, `fn_energy_flows_intervals_day`, `fn_forecast_pv_split`, `fn_ev_sessions_active`, `fn_ev_session_apply_patch`, `fn_ev_arrival_prediction_bundle`, `fn_ev_session_transition`, `fn_negative_price_predictions`, `fn_latest_ote_day_stats`, `fn_ote_day_slot_stats_prague`, `fn_ote_list_missing_days`, `fn_site_effective_prices_day_prague`, `fn_modbus_journal_list`, `fn_modbus_written_command_ids`, `fn_modbus_commands_by_ids`, `fn_inverter_modbus_caps_patch`, `fn_set_mode_with_context`, `fn_fill_audit_for_site_window`, plánování: `fn_load_planning_slots_full`, `fn_last_effective_ote`, `fn_planning_horizon_end`, `fn_planning_site_context`, `fn_pv_forecast_correction_factor`, `fn_planning_run_commit`, `fn_planning_slot_boundary_prague`, `fn_planning_interval_at_offset`, `fn_telemetry_inverter_sample`, `fn_telemetry_ev_charger_sample`, `fn_telemetry_heat_pump_sample`, `fn_battery_cycle_audit`, Deye helpery: `fn_deye_pack_system_time`, `fn_deye_clock_drift_sec`, `fn_deye_time_point_regs`, `fn_deye_tou_inactive_signature`, `fn_modbus_last_verified_map`, `fn_inverter_pv_a_max_w`, `fn_site_has_active_green_bonus_pv`.
|
**View / funkce (nejsou tabulky):** `vw_site_effective_price`, `vw_site_directory`, `vw_modbus_last_verified`, `vw_asset_inverter_modbus_poll`, `vw_asset_ev_charger_modbus_poll`, `vw_asset_heat_pump_modbus_poll`, `vw_latest_telemetry`, `vw_telemetry_hourly_7d`, `vw_telemetry_15m_7d` (15min agregát pro dashboard sloty; repeatable `R__071_vw_telemetry_15m_7d.sql`), `vw_audit_summary`, `vw_operating_mode`, `vw_forecast_accuracy_by_lead_time`, `vw_forecast_accuracy_daily`; `fn_effective_price`, `fn_green_bonus_revenue`, `fn_cop_estimate`, `fn_fill_audit_interval`, `fn_fill_forecast_accuracy`, `fn_delete_forecast_pv_prague_calendar_day`, `fn_pv_forecast_sync_reference_days`, `fn_set_mode`, `fn_expire_modes` (vrací řádky přepnutí pro Discord), `fn_restore_previous_mode`, `fn_update_ev_arrival_stats`, `fn_ev_expected_arrival`, `fn_update_baseline_stats`, `fn_rebuild_consumption_baseline_stats`, `fn_get_baseline_forecast`, `fn_update_market_price_stats`, `fn_update_tuv_usage_stats`, `fn_get_predicted_price`, dále read-modely: `fn_site_configuration`, `fn_site_full_status`, `fn_site_notifications_context`, `fn_plan_current_bundle`, `fn_planning_run_horizon`, `fn_planning_future_price_days`, `fn_economics_daily_month`, `fn_economics_monthly_chart`, `fn_economics_lock_day`, `fn_economics_unlock_day`, `fn_energy_flows_daily_month`, `fn_energy_flows_intervals_day`, `fn_forecast_pv_split`, `fn_ev_sessions_active`, `fn_ev_session_apply_patch`, `fn_ev_arrival_prediction_bundle`, `fn_ev_session_transition`, `fn_negative_price_predictions`, `fn_latest_ote_day_stats`, `fn_ote_day_slot_stats_prague`, `fn_ote_list_missing_days`, `fn_site_effective_prices_day_prague`, `fn_modbus_journal_list`, `fn_modbus_written_command_ids`, `fn_modbus_commands_by_ids`, `fn_inverter_modbus_caps_patch`, `fn_set_mode_with_context`, `fn_fill_audit_for_site_window`, plánování: `fn_load_planning_slots_full`, `fn_last_effective_ote`, `fn_planning_horizon_end`, `fn_planning_site_context`, `fn_pv_forecast_correction_factor`, `fn_planning_run_commit`, `fn_planning_slot_boundary_prague`, `fn_planning_interval_at_offset`, `fn_telemetry_inverter_sample`, `fn_telemetry_ev_charger_sample`, `fn_telemetry_heat_pump_sample`, `fn_battery_cycle_audit`, Deye helpery: `fn_deye_pack_system_time`, `fn_deye_clock_drift_sec`, `fn_deye_time_point_regs`, `fn_deye_tou_inactive_signature`, `fn_modbus_last_verified_map`, `fn_inverter_pv_a_max_w`, `fn_site_has_active_green_bonus_pv`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
21
db/migration/V076__pv_forecast_reference_day.sql
Normal file
21
db/migration/V076__pv_forecast_reference_day.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-- Kalendářní dny lokality označené jako referenční pro učení delty PV forecastu (dobrá obloha).
|
||||||
|
|
||||||
|
create table ems.site_pv_forecast_reference_day (
|
||||||
|
site_id int not null references ems.site (id) on delete cascade,
|
||||||
|
day_local date not null,
|
||||||
|
notes text null,
|
||||||
|
created_at timestamptz not null default now(),
|
||||||
|
primary key (site_id, day_local)
|
||||||
|
);
|
||||||
|
|
||||||
|
comment on table ems.site_pv_forecast_reference_day is
|
||||||
|
'Dny v kalendáři lokality podle jejího site.timezone (typicky datum ve zdi Europe/Prague), kterým se v ems.fn_pv_forecast_delta_profile zvýší váha řádků forecast_accuracy při počítání delta profilu.';
|
||||||
|
|
||||||
|
comment on column ems.site_pv_forecast_reference_day.day_local is
|
||||||
|
'Kalendářní datum v časové zóně lokality; porovnává se na (interval_start AT TIME ZONE site.timezone)::date ze slotů.';
|
||||||
|
|
||||||
|
alter table ems.site_pv_forecast_calibration
|
||||||
|
add column if not exists reference_day_weight_mult numeric null;
|
||||||
|
|
||||||
|
comment on column ems.site_pv_forecast_calibration.reference_day_weight_mult is
|
||||||
|
'Násobitel váhy učícího vzorku pro všechny sloty jejichž den spadá do site_pv_forecast_reference_day; NULL použije default v fn_pv_forecast_delta_profile (aktuálně 3).';
|
||||||
@@ -19,14 +19,16 @@ LANGUAGE sql
|
|||||||
STABLE
|
STABLE
|
||||||
SET work_mem = '64MB'
|
SET work_mem = '64MB'
|
||||||
AS $fn$
|
AS $fn$
|
||||||
WITH eff AS (
|
WITH eff AS (
|
||||||
SELECT
|
SELECT
|
||||||
coalesce(cal.delta_learn_min_ts, timestamptz '2026-04-11T22:00:00Z') AS delta_learn_min_ts,
|
coalesce(cal.delta_learn_min_ts, timestamptz '2026-04-11T22:00:00Z') AS delta_learn_min_ts,
|
||||||
coalesce(cal.half_life_days, p_half_life_days) AS half_life_days,
|
coalesce(cal.half_life_days, p_half_life_days) AS half_life_days,
|
||||||
coalesce(cal.threshold_w, p_threshold_w) AS threshold_w,
|
coalesce(cal.threshold_w, p_threshold_w) AS threshold_w,
|
||||||
coalesce(cal.top_n_days, p_top_n_days) AS top_n_days,
|
coalesce(cal.top_n_days, p_top_n_days) AS top_n_days,
|
||||||
coalesce(cal.non_top_day_factor, p_non_top_day_factor) AS non_top_day_factor,
|
coalesce(cal.non_top_day_factor, p_non_top_day_factor) AS non_top_day_factor,
|
||||||
coalesce(cal.day_weight_gamma, p_day_weight_gamma) AS day_weight_gamma
|
coalesce(cal.day_weight_gamma, p_day_weight_gamma) AS day_weight_gamma,
|
||||||
|
greatest(1::numeric, coalesce(cal.reference_day_weight_mult, 3::numeric))
|
||||||
|
AS reference_day_w_mult
|
||||||
FROM ems.site s
|
FROM ems.site s
|
||||||
LEFT JOIN ems.site_pv_forecast_calibration cal ON cal.site_id = s.id
|
LEFT JOIN ems.site_pv_forecast_calibration cal ON cal.site_id = s.id
|
||||||
WHERE s.id = p_site_id
|
WHERE s.id = p_site_id
|
||||||
@@ -170,6 +172,11 @@ AS $fn$
|
|||||||
) AS rn
|
) AS rn
|
||||||
FROM day_stats ds
|
FROM day_stats ds
|
||||||
),
|
),
|
||||||
|
ref_wall AS (
|
||||||
|
SELECT d.day_local
|
||||||
|
FROM ems.site_pv_forecast_reference_day d
|
||||||
|
WHERE d.site_id = p_site_id
|
||||||
|
),
|
||||||
filtered AS (
|
filtered AS (
|
||||||
SELECT
|
SELECT
|
||||||
s.pv_array_id,
|
s.pv_array_id,
|
||||||
@@ -194,7 +201,12 @@ AS $fn$
|
|||||||
),
|
),
|
||||||
greatest(0.25, least(coalesce(e.day_weight_gamma, 1.0), 8.0))
|
greatest(0.25, least(coalesce(e.day_weight_gamma, 1.0), 8.0))
|
||||||
)
|
)
|
||||||
) AS w
|
)
|
||||||
|
* CASE
|
||||||
|
WHEN EXISTS (SELECT 1 FROM ref_wall rw WHERE rw.day_local = s.day_local)
|
||||||
|
THEN e.reference_day_w_mult
|
||||||
|
ELSE 1::numeric
|
||||||
|
END AS w
|
||||||
FROM slots s
|
FROM slots s
|
||||||
CROSS JOIN bounds b
|
CROSS JOIN bounds b
|
||||||
CROSS JOIN eff e
|
CROSS JOIN eff e
|
||||||
@@ -281,4 +293,6 @@ AS $fn$
|
|||||||
$fn$;
|
$fn$;
|
||||||
|
|
||||||
COMMENT ON FUNCTION ems.fn_pv_forecast_delta_profile IS
|
COMMENT ON FUNCTION ems.fn_pv_forecast_delta_profile IS
|
||||||
'Aditivní delta profil PV forecastu po 15min slotu dne (96 slotů) per pv_array_id v `deltas_by_array`; `deltas` je součet delt přes pole (kompatibilita). Zdroj: forecast_accuracy s learning_eligible, cutoff a numerické defaulty z ems.site_pv_forecast_calibration (NULL sloupce = parametry volání).';
|
'Aditivní delta profil PV forecastu po 15min slotu dne (96 slotů) per pv_array_id v deltas_by_array; deltas je součet delt přes pole.'
|
||||||
|
' Zdroj forecast_accuracy learning_eligible, cutoff kalibrace, váhy dnů.'
|
||||||
|
' Dny z ems.site_pv_forecast_reference_day (den = interval ve site.timezone) mají násobenou váhu (site_pv_forecast_calibration.reference_day_weight_mult nebo default 3).';
|
||||||
|
|||||||
120
db/routines/R__086_fn_forecast_pv_prague_day_ops.sql
Normal file
120
db/routines/R__086_fn_forecast_pv_prague_day_ops.sql
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
-- Operace nad PV forecastem v DB a správa referenčních dnů kalibrace (náhrada dřívějších .sh nástrojů).
|
||||||
|
|
||||||
|
create or replace function ems.fn_delete_forecast_pv_prague_calendar_day(
|
||||||
|
p_day date,
|
||||||
|
p_site_id int default null
|
||||||
|
)
|
||||||
|
returns table (
|
||||||
|
targets_interval_rows bigint,
|
||||||
|
deleted_forecast_accuracy_rows bigint,
|
||||||
|
deleted_forecast_pv_interval_rows bigint,
|
||||||
|
deleted_empty_forecast_pv_run_rows bigint
|
||||||
|
)
|
||||||
|
language plpgsql
|
||||||
|
volatile
|
||||||
|
as $fn$
|
||||||
|
declare
|
||||||
|
v_ts_start timestamptz;
|
||||||
|
v_ts_end timestamptz;
|
||||||
|
v_tgt interval_rows bigint;
|
||||||
|
v_acc bigint;
|
||||||
|
v_iv bigint;
|
||||||
|
v_run bigint;
|
||||||
|
begin
|
||||||
|
v_ts_start := (p_day::text || ' 00:00:00')::timestamp at time zone 'Europe/Prague';
|
||||||
|
v_ts_end := ((p_day + 1)::text || ' 00:00:00')::timestamp at time zone 'Europe/Prague';
|
||||||
|
|
||||||
|
drop table if exists _ems_wipe_pv_forecast_targets;
|
||||||
|
|
||||||
|
create temporary table _ems_wipe_pv_forecast_targets (
|
||||||
|
run_id int not null,
|
||||||
|
pv_array_id int not null,
|
||||||
|
interval_start timestamptz not null
|
||||||
|
) on commit drop;
|
||||||
|
|
||||||
|
insert into _ems_wipe_pv_forecast_targets (run_id, pv_array_id, interval_start)
|
||||||
|
select fi.run_id, fi.pv_array_id, fi.interval_start
|
||||||
|
from ems.forecast_pv_interval fi
|
||||||
|
inner join ems.forecast_pv_run r on r.id = fi.run_id
|
||||||
|
where fi.interval_start >= v_ts_start
|
||||||
|
and fi.interval_start < v_ts_end
|
||||||
|
and (p_site_id is null or r.site_id = p_site_id);
|
||||||
|
|
||||||
|
get diagnostics v_tgt = row_count;
|
||||||
|
|
||||||
|
delete from ems.forecast_accuracy fa
|
||||||
|
using (select distinct run_id, interval_start from _ems_wipe_pv_forecast_targets) t
|
||||||
|
where fa.run_id = t.run_id
|
||||||
|
and fa.interval_start = t.interval_start;
|
||||||
|
|
||||||
|
get diagnostics v_acc = row_count;
|
||||||
|
|
||||||
|
delete from ems.forecast_pv_interval fi
|
||||||
|
using _ems_wipe_pv_forecast_targets t
|
||||||
|
where fi.run_id = t.run_id
|
||||||
|
and fi.pv_array_id = t.pv_array_id
|
||||||
|
and fi.interval_start = t.interval_start;
|
||||||
|
|
||||||
|
get diagnostics v_iv = row_count;
|
||||||
|
|
||||||
|
delete from ems.forecast_pv_run fr
|
||||||
|
where fr.id in (select distinct run_id from _ems_wipe_pv_forecast_targets)
|
||||||
|
and not exists (
|
||||||
|
select 1 from ems.forecast_pv_interval x where x.run_id = fr.id
|
||||||
|
);
|
||||||
|
|
||||||
|
get diagnostics v_run = row_count;
|
||||||
|
|
||||||
|
targets_interval_rows := v_tgt;
|
||||||
|
deleted_forecast_accuracy_rows := v_acc;
|
||||||
|
deleted_forecast_pv_interval_rows := v_iv;
|
||||||
|
deleted_empty_forecast_pv_run_rows := v_run;
|
||||||
|
return next;
|
||||||
|
end;
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
comment on function ems.fn_delete_forecast_pv_prague_calendar_day is
|
||||||
|
'Maze forecast_pv_interval (a navázané forecast_accuracy) pro řádky podle začátku intervalu '
|
||||||
|
'na daný kalendářní den hranovaný půlnocí Europe/Prague — ne podle TZ lokality.'
|
||||||
|
' p_site_id NULL = všechny lokality. Prázdné forecast_pv_run v mazané množině smaže návazně.'
|
||||||
|
' Destruktivní vůči historii přesnosti; preferuj jen provozní re-import forecastu.';
|
||||||
|
|
||||||
|
|
||||||
|
create or replace function ems.fn_pv_forecast_sync_reference_days(
|
||||||
|
p_site_id int,
|
||||||
|
p_days_local date[],
|
||||||
|
p_replace_existing boolean default false
|
||||||
|
)
|
||||||
|
returns int
|
||||||
|
language plpgsql
|
||||||
|
volatile
|
||||||
|
as $fn$
|
||||||
|
declare
|
||||||
|
v_after int;
|
||||||
|
begin
|
||||||
|
if not exists (select 1 from ems.site s where s.id = p_site_id) then
|
||||||
|
raise exception using
|
||||||
|
message = format('site_id %s neexistuje v ems.site', p_site_id),
|
||||||
|
errcode = 'P0001';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if p_replace_existing then
|
||||||
|
delete from ems.site_pv_forecast_reference_day d where d.site_id = p_site_id;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
insert into ems.site_pv_forecast_reference_day (site_id, day_local)
|
||||||
|
select p_site_id, d::date
|
||||||
|
from unnest(p_days_local) as u(d)
|
||||||
|
where d is not null
|
||||||
|
on conflict (site_id, day_local) do nothing;
|
||||||
|
|
||||||
|
select count(*)::int into v_after from ems.site_pv_forecast_reference_day d where d.site_id = p_site_id;
|
||||||
|
return v_after;
|
||||||
|
end;
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
comment on function ems.fn_pv_forecast_sync_reference_days is
|
||||||
|
'Zapíše kalendářní dny (datum ve zdi site.timezone lokality při použití s fn_pv_forecast_delta_profile) jako referenční. '
|
||||||
|
'p_replace_existing true smaže předchozí záznamy dané lokality; false jen doplňuje unnest bez přepsání. '
|
||||||
|
'Vrací počet řádků v site_pv_forecast_reference_day po operaci.';
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ GRANT SELECT ON ems.vw_telemetry_hourly_7d TO ems_anon;
|
|||||||
GRANT SELECT ON ems.vw_telemetry_15m_7d TO ems_anon;
|
GRANT SELECT ON ems.vw_telemetry_15m_7d TO ems_anon;
|
||||||
GRANT SELECT ON ems.forecast_accuracy TO ems_anon;
|
GRANT SELECT ON ems.forecast_accuracy TO ems_anon;
|
||||||
GRANT SELECT ON ems.site_pv_forecast_calibration TO ems_anon;
|
GRANT SELECT ON ems.site_pv_forecast_calibration TO ems_anon;
|
||||||
|
GRANT SELECT ON ems.site_pv_forecast_reference_day TO ems_anon;
|
||||||
GRANT SELECT ON ems.vw_forecast_accuracy_by_lead_time TO ems_anon;
|
GRANT SELECT ON ems.vw_forecast_accuracy_by_lead_time TO ems_anon;
|
||||||
GRANT SELECT ON ems.vw_forecast_accuracy_daily TO ems_anon;
|
GRANT SELECT ON ems.vw_forecast_accuracy_daily TO ems_anon;
|
||||||
GRANT SELECT ON ems.consumption_baseline_stats TO ems_anon;
|
GRANT SELECT ON ems.consumption_baseline_stats TO ems_anon;
|
||||||
|
|||||||
@@ -323,6 +323,11 @@ CREATE TABLE forecast_pv_interval (
|
|||||||
-- SELECT create_hypertable('forecast_pv_interval', 'interval_start');
|
-- SELECT create_hypertable('forecast_pv_interval', 'interval_start');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Kalibrace delty PV (per site)
|
||||||
|
|
||||||
|
- **`site_pv_forecast_calibration`** (V057+, rozšířeno V076) – parametry učení aditivní korekce výkonu PV z `forecast_accuracy` při každém výpočtu `fn_pv_forecast_delta_profile` (např. `half_life_days`, `top_n_days`; **V076**: `reference_day_weight_mult`).
|
||||||
|
- **`site_pv_forecast_reference_day`** (**V076**) – kalendářní datum ve smyslu lokality `(interval_start AT TIME ZONE site.timezone)::date`; tyto dny dostanou násobek váhy vzorků v `fn_pv_forecast_delta_profile`, aby zpětné „hezky svítící“ reference silněji vtáhly δ profil bez mazání řádků `forecast_pv_interval`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Plánování
|
## Plánování
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ bazální_w = load_power_w - ev_power_w - heat_pump_power_w
|
|||||||
|
|
||||||
**Solver (`planning_engine._load_slots`):** pro každý 15min interval efektivní ceny bere **`avg_power_w` z `consumption_baseline_stats`** podle DOW+hodiny slotu, jinak **500 W** – nečte `consumption_baseline_interval`. Stejná hodnota se ukládá do **`planning_interval.load_baseline_w`** při každém běhu plánovače (přehled v UI / PostgREST). Odchylka vs. skutečnost: tabulka **`baseline_load_forecast_accuracy`**, plněno po auditu.
|
**Solver (`planning_engine._load_slots`):** pro každý 15min interval efektivní ceny bere **`avg_power_w` z `consumption_baseline_stats`** podle DOW+hodiny slotu, jinak **500 W** – nečte `consumption_baseline_interval`. Stejná hodnota se ukládá do **`planning_interval.load_baseline_w`** při každém běhu plánovače (přehled v UI / PostgREST). Odchylka vs. skutečnost: tabulka **`baseline_load_forecast_accuracy`**, plněno po auditu.
|
||||||
|
|
||||||
**Operace: přepočet bez EMA „ocasu“:** denní job volá `fn_update_baseline_stats`, které při updatu bucketu míchá **70 % starý + 30 % nový** průměr. Je-li profil zaseklý, smaž statistiky a znovu načti z telemetrie — kanonické API je **`ems.fn_rebuild_consumption_baseline_stats(p_site_id, p_lookback_days)` v `db/routines/R__085_fn_rebuild_consumption_baseline_stats.sql`**: při **`p_site_id IS NULL`** maže celou `consumption_baseline_stats` a přepíná všechny řádky z `ems.site`; při konkrétním `site_id` jen řádky dané lokality. **Příklad (psql / MCP):** `select * from ems.fn_rebuild_consumption_baseline_stats(2, 30);` jedna lokality; **`select * from ems.fn_rebuild_consumption_baseline_stats(null::int, 30);`** všechny lokality *(první argument je site_id — ne zaměnit s počtem dnů).* Tenký wrapper: **`scripts/rebuild_consumption_baseline_stats.sh`**. Špatná měření (EV/TČ) funkce sama neopraví.
|
**Operace: přepočet bez EMA „ocasu“:** denní job volá `fn_update_baseline_stats`, které při updatu bucketu míchá **70 % starý + 30 % nový** průměr. Je-li profil zaseklý, smaž statistiky a znovu načti z telemetrie — kanonické API je **`ems.fn_rebuild_consumption_baseline_stats(p_site_id, p_lookback_days)` v `db/routines/R__085_fn_rebuild_consumption_baseline_stats.sql`**: při **`p_site_id IS NULL`** maže celou `consumption_baseline_stats` a přepíná všechny řádky z `ems.site`; při konkrétním `site_id` jen řádky dané lokality. **Příklad (psql / MCP):** `select * from ems.fn_rebuild_consumption_baseline_stats(2, 30);` jedna lokality; **`select * from ems.fn_rebuild_consumption_baseline_stats(null::int, 30);`** všechny lokality *(první argument je site_id — ne zaměnit s počtem dnů).* Špatná měření (EV/TČ) funkce sama neopraví.
|
||||||
|
|
||||||
> **Poznámka:** TUV jako samostatný odečet zůstává otevřený bod, pokud není měřen zvlášť; aktuálně je TČ zahrnut v `heat_pump_power_w`.
|
> **Poznámka:** TUV jako samostatný odečet zůstává otevřený bod, pokud není měřen zvlášť; aktuálně je TČ zahrnut v `heat_pump_power_w`.
|
||||||
|
|
||||||
|
|||||||
@@ -171,15 +171,31 @@ Viz `03-data-model.md`:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Jednorázové smazání PV forecastu za den (provoz)
|
## Operace SQL: mazání řádků PV forecastu za den (provozní výjimka)
|
||||||
|
|
||||||
Projekt standardně **nemazá** `forecast_pv_interval` / `forecast_pv_run`, aby zůstala historie pro `forecast_accuracy` a učení delty. Pokud potřebuješ záměrně smazat řádky FVE predikce za **jeden kalendářní den v `Europe/Prague`** (a znovu naplnit službou forecastu), použij:
|
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.
|
||||||
|
|
||||||
`scripts/wipe_pv_forecast_prague_day.sh` (volitelně `SITE_ID`, `DRY_RUN=1`; vyžaduje `DATABASE_URL` nebo PG env).
|
Příklad: `select * from ems.fn_delete_forecast_pv_prague_calendar_day('2026-05-02'::date, 2);`
|
||||||
|
|
||||||
Skript maže v pořadí: `forecast_accuracy` (odpovídající páry `run_id`/`interval_start`), `forecast_pv_interval` (PK řádků ve zvoleném dni), pak `forecast_pv_run`, které po smazání už nemají žádné intervaly — **pouze pokud** jejich `id` bylo v mazaném výběru (`IN` z cílové množiny), aby se neodstraňovaly náhodné orphan běhy z jiných událostí.
|
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*).
|
||||||
|
|
||||||
**Není to** mazání statistiky bazální spotřeby (`consumption_baseline_*`); pokud tě trápí load v plánovači, řeš výpočet/`fn_update_baseline_stats`, ne tento 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í.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
- **nebo** **`ems.site_grid_connection.block_export_on_negative_sell`** (migrace **V074**, default **false**) — bez GEN registrů na Deye; vhodné např. pro **KV1** (fixní nákup, bez nutnosti vést výkon neriťitelného pole B do sítě). **home-01** nech **false**, jinak může být horizont při přebytku z pole B a plné/nedostupné baterii **infeasible** (solver export potřebuje jako fyzikální ventil tam, kde FVE B nelze štípnout ani odpojit modelem bez GEN řádku).
|
- **nebo** **`ems.site_grid_connection.block_export_on_negative_sell`** (migrace **V074**, default **false**) — bez GEN registrů na Deye; vhodné např. pro **KV1** (fixní nákup, bez nutnosti vést výkon neriťitelného pole B do sítě). **home-01** nech **false**, jinak může být horizont při přebytku z pole B a plné/nedostupné baterii **infeasible** (solver export potřebuje jako fyzikální ventil tam, kde FVE B nelze štípnout ani odpojit modelem bez GEN řádku).
|
||||||
- **Uložené vstupy plánu** (`planning_interval`): `load_baseline_w`, `pv_*_forecast_raw_w`, `pv_*_forecast_solver_w` pro UI a audit.
|
- **Uložené vstupy plánu** (`planning_interval`): `load_baseline_w`, `pv_*_forecast_raw_w`, `pv_*_forecast_solver_w` pro UI a audit.
|
||||||
- **Více FVE polí s různou orientací:** `planning_engine._load_slots` sčítá predikovaný výkon za 15min přes **všechna** `asset_pv_array` dané lokality — `pv_a_forecast_w` = součet řádků s `controllable = true`, `pv_b_forecast_w` = součet s `controllable = false`. Pro každé pole a slot se bere **nejnovější** `forecast_pv_run` (`ORDER BY created_at DESC`, `DISTINCT ON (pv_array_id)`). Curtailment v LP zůstává **jedno** agregované `pv_a` (součet řiditelných polí); per-string curtailment by vyžadovalo rozšíření modelu.
|
- **Více FVE polí s různou orientací:** `planning_engine._load_slots` sčítá predikovaný výkon za 15min přes **všechna** `asset_pv_array` dané lokality — `pv_a_forecast_w` = součet řádků s `controllable = true`, `pv_b_forecast_w` = součet s `controllable = false`. Pro každé pole a slot se bere **nejnovější** `forecast_pv_run` (`ORDER BY created_at DESC`, `DISTINCT ON (pv_array_id)`). Curtailment v LP zůstává **jedno** agregované `pv_a` (součet řiditelných polí); per-string curtailment by vyžadovalo rozšíření modelu.
|
||||||
- **Kalibrace PV forecastu (delta profil):** tabulka `ems.site_pv_forecast_calibration` drží per `site_id` mimo jiné `delta_learn_min_ts` (dolní mez řádků z `forecast_accuracy` pro učení delty), volitelně `pv_curtailment_policy_effective_from` a přepsání parametrů (`top_n_days`, `half_life_days`, …). `ems.fn_fill_forecast_accuracy` nastavuje `learning_eligible` / `learning_exclude_reason` (sloty před cutoffem, nebo se škrcením / gen cut-off / záznamem v `ems.cutoff_switch_log` po účinnosti policy se z učení vyřadí; u škrcení zůstává `actual_power_w` NULL). Telemetrie: `ems.telemetry_inverter.is_export_limited` nebo `pv_derating_flags <> 0` v okně 15min → stejné vyloučení (`telemetry_derating`). `ems.fn_pv_forecast_delta_profile` vrací `deltas_by_array` i součtové `deltas`; `ems.fn_load_planning_slots_full` aplikuje stejnou **per-pole** korekci jako UI (`fn_forecast_pv_slots_range_corrected`); pokud v JSON profilu chybí `deltas_by_array`, použije se souhrnné `deltas` rozpuštěné podle podílu výkonu pole na slotu (solver má tak stále použitou korekci i bez per-pole JSON).
|
- **Kalibrace PV forecastu (delta profil):** tabulka `ems.site_pv_forecast_calibration` drží per `site_id` mimo jiné `delta_learn_min_ts` (dolní mez řádků z `forecast_accuracy` pro učení delty), volitelně `pv_curtailment_policy_effective_from` a přepsání parametrů (`top_n_days`, `half_life_days`, …; **V076** navíc `reference_day_weight_mult` pro „připnuté“ dny níže). **`ems.site_pv_forecast_reference_day`** (**V076**) umožňuje zvýšit váhu konkrétních kalendářních dnů (datum ve `site.timezone` jako u časování slotů) při agregaci δ z `forecast_accuracy` (`fn_pv_forecast_delta_profile`); hromadný zápis **`ems.fn_pv_forecast_sync_reference_days`**, detail **`docs/04-modules/forecast.md`**. `ems.fn_fill_forecast_accuracy` nastavuje `learning_eligible` / `learning_exclude_reason` (sloty před cutoffem, nebo se škrcením / gen cut-off / záznamem v `ems.cutoff_switch_log` po účinnosti policy se z učení vyřadí; u škrcení zůstává `actual_power_w` NULL). Telemetrie: `ems.telemetry_inverter.is_export_limited` nebo `pv_derating_flags <> 0` v okně 15min → stejné vyloučení (`telemetry_derating`). `ems.fn_pv_forecast_delta_profile` vrací `deltas_by_array` i součtové `deltas`; `ems.fn_load_planning_slots_full` aplikuje stejnou **per-pole** korekci jako UI (`fn_forecast_pv_slots_range_corrected`); pokud v JSON profilu chybí `deltas_by_array`, použije se souhrnné `deltas` rozpuštěné podle podílu výkonu pole na slotu (solver má tak stále použitou korekci i bez per-pole JSON).
|
||||||
|
|
||||||
Solver optimalizuje celý horizont (typicky do konce známých OTE dat, strop z `fn_planning_horizon_end`) najednou, čímž přirozeně zvládá:
|
Solver optimalizuje celý horizont (typicky do konce známých OTE dat, strop z `fn_planning_horizon_end`) najednou, čímž přirozeně zvládá:
|
||||||
- pohled dopředu (ráno ví že přes poledne bude záporná cena → prodává z baterie)
|
- pohled dopředu (ráno ví že přes poledne bude záporná cena → prodává z baterie)
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ limit 10;
|
|||||||
select ems.fn_plan_explain_bundle(2, 6);
|
select ems.fn_plan_explain_bundle(2, 6);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Měnící funkce (**`ems.fn_delete_forecast_pv_prague_calendar_day`**, **`ems.fn_rebuild_consumption_baseline_stats`**, …) MCP přes **`query` neprovede**, pokud má server jen read-only práva na DB — použij psql aplikačním účtem.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Odkud to vychází v repozitáři
|
## 5. Odkud to vychází v repozitáři
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Tenký wrapper nad ems.fn_rebuild_consumption_baseline_stats (kanonické API je v PostgreSQL).
|
|
||||||
#
|
|
||||||
# Použití:
|
|
||||||
# export DATABASE_URL='postgres://…/ems'
|
|
||||||
# LOOKBACK_DAYS=14 ./scripts/rebuild_consumption_baseline_stats.sh 2 # jedna lokality (site.id)
|
|
||||||
#
|
|
||||||
# ./scripts/rebuild_consumption_baseline_stats.sh --all # celá tabulka stats + všichni sites
|
|
||||||
#
|
|
||||||
# MCP / psql přímo (doporučeno v SQL-first režimu):
|
|
||||||
# select * from ems.fn_rebuild_consumption_baseline_stats(2, 30);
|
|
||||||
# select * from ems.fn_rebuild_consumption_baseline_stats(null::int, 30);
|
|
||||||
#
|
|
||||||
# DRY_RUN=1 … — jen řádek count (žádné mazání).
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ -z "${DATABASE_URL:-}" ]] && [[ -z "${PGHOST:-}" ]]; then
|
|
||||||
echo "Nastav DATABASE_URL nebo PG proměnné." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
LOOKBACK="${LOOKBACK_DAYS:-30}"
|
|
||||||
if ! [[ "$LOOKBACK" =~ ^[0-9]+$ ]] || [[ "$LOOKBACK" -lt 1 ]]; then
|
|
||||||
echo "LOOKBACK_DAYS musí být kladné celé číslo." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PSQL=(psql -v ON_ERROR_STOP=1)
|
|
||||||
if [[ -n "${DATABASE_URL:-}" ]]; then
|
|
||||||
PSQL+=("$DATABASE_URL")
|
|
||||||
else
|
|
||||||
PSQL+=("${PGDATABASE:-ems}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
MODE="${1:?Chybí argument: site_id (číslo) nebo --all}"
|
|
||||||
DRY="${DRY_RUN:-0}"
|
|
||||||
|
|
||||||
if [[ "$MODE" == "--all" ]]; then
|
|
||||||
if [[ "$DRY" == "1" ]] || [[ "$DRY" == "true" ]]; then
|
|
||||||
echo "DRY_RUN: consumption_baseline_stats řádků celkem, sites:"
|
|
||||||
"${PSQL[@]}" -c "
|
|
||||||
select count(*) as stats_rows from ems.consumption_baseline_stats;
|
|
||||||
select count(*) as sites from ems.site;
|
|
||||||
"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Volám fn_rebuild_consumption_baseline_stats(null, ${LOOKBACK}) …"
|
|
||||||
"${PSQL[@]}" -c "select * from ems.fn_rebuild_consumption_baseline_stats(null::int, ${LOOKBACK}::int);"
|
|
||||||
else
|
|
||||||
SITE_ID="$MODE"
|
|
||||||
if ! [[ "$SITE_ID" =~ ^[0-9]+$ ]]; then
|
|
||||||
echo "První argument: site_id (číslo) nebo --all." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ "$DRY" == "1" ]] || [[ "$DRY" == "true" ]]; then
|
|
||||||
"${PSQL[@]}" -c "select count(*) from ems.consumption_baseline_stats where site_id = ${SITE_ID}::int;"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Volám fn_rebuild_consumption_baseline_stats(${SITE_ID}, ${LOOKBACK}) …"
|
|
||||||
"${PSQL[@]}" -c "select * from ems.fn_rebuild_consumption_baseline_stats(${SITE_ID}::int, ${LOOKBACK}::int);"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Hotovo. Spusť rolling/denní plán nebo počkej na scheduler."
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Odstraní predikované PV intervaly (a navázaný tracking přesnosti) pro jeden kalendářní den v Europe/Prague.
|
|
||||||
#
|
|
||||||
# VAROVÁNÍ (provozní / datová kontinuita)
|
|
||||||
# ────────────────────────────────────────
|
|
||||||
# - Řád v repozitáři je držet historické běhy FVE forecastu pro analýzu a učení delty (@see docs/04-modules/forecast.md).
|
|
||||||
# - Používej jen když vědomě potřebuješ „načisto“ vygenerovat nový forecast (forecast service).
|
|
||||||
# - Fyzický breaker řeší měnič/Deye — skript jen čistí databázi od uloženého PV forecastu.
|
|
||||||
#
|
|
||||||
# Nenahrazuje mazání/load baseline spotřeby (`consumption_baseline_stats` / její výpočet).
|
|
||||||
#
|
|
||||||
# Použití:
|
|
||||||
# export DATABASE_URL='postgres://…/ems'
|
|
||||||
# ./scripts/wipe_pv_forecast_prague_day.sh # dnešní den (Europe/Prague), všechny lokality
|
|
||||||
# ./scripts/wipe_pv_forecast_prague_day.sh 2026-05-02 # konkrétní datum YYYY-MM-DD
|
|
||||||
# ./scripts/wipe_pv_forecast_prague_day.sh 2026-05-02 2 # jen site.id = 2 (např. home-01)
|
|
||||||
#
|
|
||||||
# Šedý režim (jen počty, žádné mazání):
|
|
||||||
# DRY_RUN=1 ./scripts/wipe_pv_forecast_prague_day.sh 2026-05-02 2
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
DAY="${1:-$(TZ=Europe/Prague date +%Y-%m-%d)}"
|
|
||||||
SITE_ID="${2:-}"
|
|
||||||
|
|
||||||
if ! [[ "$DAY" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
|
||||||
echo "Datum musí být YYYY-MM-DD, dostal jsem: $DAY" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${DATABASE_URL:-}" ]] && [[ -z "${PGHOST:-}" ]]; then
|
|
||||||
echo "Nastav DATABASE_URL nebo standardní PG proměnné (PGHOST, PGUSER, PGDATABASE, …)." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PSQL=(psql -v ON_ERROR_STOP=1)
|
|
||||||
if [[ -n "${DATABASE_URL:-}" ]]; then
|
|
||||||
PSQL+=("$DATABASE_URL")
|
|
||||||
else
|
|
||||||
PSQL+=("${PGDATABASE:-ems}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
SITE_CLAUSE=""
|
|
||||||
if [[ -n "$SITE_ID" ]]; then
|
|
||||||
if ! [[ "$SITE_ID" =~ ^[0-9]+$ ]]; then
|
|
||||||
echo "Druhý argument musí být číslo site_id." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
SITE_CLAUSE="AND r.site_id = ${SITE_ID}::int"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DRY="${DRY_RUN:-0}"
|
|
||||||
|
|
||||||
if [[ "$DRY" == "1" ]] || [[ "$DRY" == "true" ]]; then
|
|
||||||
echo "DRY_RUN: den $DAY (Europe/Prague)${SITE_ID:+ site_id=$SITE_ID}"
|
|
||||||
"${PSQL[@]}" -c "
|
|
||||||
with bounds as (
|
|
||||||
select
|
|
||||||
('${DAY}'::date::text || ' 00:00:00')::timestamp at time zone 'Europe/Prague' as ts_start,
|
|
||||||
(('${DAY}'::date + 1)::text || ' 00:00:00')::timestamp at time zone 'Europe/Prague' as ts_end
|
|
||||||
),
|
|
||||||
targets as (
|
|
||||||
select fi.run_id, fi.pv_array_id, fi.interval_start
|
|
||||||
from ems.forecast_pv_interval fi
|
|
||||||
inner join ems.forecast_pv_run r on r.id = fi.run_id
|
|
||||||
cross join bounds b
|
|
||||||
where fi.interval_start >= b.ts_start
|
|
||||||
and fi.interval_start < b.ts_end
|
|
||||||
${SITE_CLAUSE}
|
|
||||||
)
|
|
||||||
select count(*) as interval_rows_to_delete, count(distinct run_id) as distinct_run_ids
|
|
||||||
from targets;
|
|
||||||
"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Mažu PV forecast intervaly pro den $DAY (Europe/Prague)${SITE_ID:+ site_id=$SITE_ID} …"
|
|
||||||
|
|
||||||
"${PSQL[@]}" -c "
|
|
||||||
begin;
|
|
||||||
|
|
||||||
create temporary table _ems_wipe_pv_forecast_targets (
|
|
||||||
run_id int not null,
|
|
||||||
pv_array_id int not null,
|
|
||||||
interval_start timestamptz not null
|
|
||||||
) on commit drop;
|
|
||||||
|
|
||||||
with bounds as (
|
|
||||||
select
|
|
||||||
('${DAY}'::date::text || ' 00:00:00')::timestamp at time zone 'Europe/Prague' as ts_start,
|
|
||||||
(('${DAY}'::date + 1)::text || ' 00:00:00')::timestamp at time zone 'Europe/Prague' as ts_end
|
|
||||||
)
|
|
||||||
insert into _ems_wipe_pv_forecast_targets (run_id, pv_array_id, interval_start)
|
|
||||||
select fi.run_id, fi.pv_array_id, fi.interval_start
|
|
||||||
from ems.forecast_pv_interval fi
|
|
||||||
inner join ems.forecast_pv_run r on r.id = fi.run_id
|
|
||||||
cross join bounds b
|
|
||||||
where fi.interval_start >= b.ts_start
|
|
||||||
and fi.interval_start < b.ts_end
|
|
||||||
${SITE_CLAUSE};
|
|
||||||
|
|
||||||
delete from ems.forecast_accuracy fa
|
|
||||||
using (select distinct run_id, interval_start from _ems_wipe_pv_forecast_targets) t
|
|
||||||
where fa.run_id = t.run_id
|
|
||||||
and fa.interval_start = t.interval_start;
|
|
||||||
|
|
||||||
delete from ems.forecast_pv_interval fi
|
|
||||||
using _ems_wipe_pv_forecast_targets t
|
|
||||||
where fi.run_id = t.run_id
|
|
||||||
and fi.pv_array_id = t.pv_array_id
|
|
||||||
and fi.interval_start = t.interval_start;
|
|
||||||
|
|
||||||
delete from ems.forecast_pv_run fr
|
|
||||||
where fr.id in (select distinct run_id from _ems_wipe_pv_forecast_targets)
|
|
||||||
and not exists (
|
|
||||||
select 1 from ems.forecast_pv_interval x where x.run_id = fr.id
|
|
||||||
);
|
|
||||||
|
|
||||||
select
|
|
||||||
(select count(*) from _ems_wipe_pv_forecast_targets) as targets_interval_rows,
|
|
||||||
(select count(distinct run_id) from _ems_wipe_pv_forecast_targets) as distinct_run_ids_touched;
|
|
||||||
|
|
||||||
commit;
|
|
||||||
"
|
|
||||||
|
|
||||||
echo "Hotovo. Spusť forecast job / službu v backendu (Open-Meteo běh), aby se řádky ${DAY} doplnily znovu."
|
|
||||||
Reference in New Issue
Block a user