dalsi fix forecat tuningu
Some checks failed
CI and deploy / migration-check (push) Failing after 19s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-05 12:13:07 +02:00
parent 0d2839d6db
commit ab80d13ecb
3 changed files with 130 additions and 26 deletions

View File

@@ -1158,8 +1158,8 @@ async def run_rolling_replan(
)
slots = await _load_slots(site_id, replan_from, horizon_to, db, soc_wh=soc_wh)
# PV forecast korekce je kanonicky v DB (delta + rolling faktor + decay),
# viz ems.fn_forecast_pv_slots_range_canonical_ab a ems.fn_load_planning_slots_full.
# PV forecast korekce je kanonicky v DB (delta + rolling faktor + decay) a do LP vstupuje přes
# ems.fn_load_planning_slots_full. Pro audit/debug ale chceme ukládat i RAW (bez korekcí).
correction_factor, correction_log = 1.0, {
"window_start": None,
"window_end": None,
@@ -1169,6 +1169,31 @@ async def run_rolling_replan(
"reason": "canonical_db",
}
# RAW PV pro slot_inputs: přímý součet nejnovějších forecast_pv_interval per array/slot (bez delta/rolling).
raw_pv_rows = await db.fetchval(
"select ems.fn_forecast_pv_slots_range_raw_ab($1::int, $2::timestamptz, $3::timestamptz)",
site_id,
replan_from,
horizon_to,
)
raw_pv = raw_pv_rows if isinstance(raw_pv_rows, list) else json.loads(raw_pv_rows)
raw_by_ts: dict[str, tuple[int, int]] = {}
if isinstance(raw_pv, list):
for r in raw_pv:
if not isinstance(r, dict):
continue
ts = r.get("interval_start")
if isinstance(ts, str):
raw_by_ts[ts] = (
int(r.get("pv_a_forecast_raw_w") or 0),
int(r.get("pv_b_forecast_raw_w") or 0),
)
slots_raw_pv: list[PlanningSlot] = []
for s in slots:
key = s.interval_start.isoformat()
pva, pvb = raw_by_ts.get(key, (s.pv_a_forecast_w, s.pv_b_forecast_w))
slots_raw_pv.append(replace(s, pv_a_forecast_w=pva, pv_b_forecast_w=pvb))
commitment_prev = await _load_previous_plan_charge_commitment_prev_w(site_id, slots, db)
results, duration_ms, solver_snapshot = solve_dispatch(
@@ -1178,7 +1203,7 @@ async def run_rolling_replan(
charge_commitment_prev_w=commitment_prev,
)
slot_inputs = _build_slot_inputs(slots, slots)
slot_inputs = _build_slot_inputs(slots_raw_pv, slots)
run_id = await _save_planning_run(
site_id,
results,

View File

@@ -25,30 +25,26 @@ begin
and ti.measured_at >= p_window_start
and ti.measured_at < p_window_end;
with pv_arrays as (
select apa.id as pv_array_id
from ems.asset_pv_array apa
where apa.site_id = p_site_id
),
latest_run as (
select distinct on (fpr.pv_array_id)
fpr.pv_array_id,
fpr.id as run_id
from pv_arrays pa
join ems.forecast_pv_run fpr
on fpr.pv_array_id = pa.pv_array_id
and fpr.site_id = p_site_id
where fpr.status = 'ok'
and fpr.created_at <= p_window_start
order by fpr.pv_array_id, fpr.created_at desc
)
select coalesce(sum(fpi.power_w) * 0.25 / 1000.0, 0)
-- Forecast pro korekční faktor bereme stejně jako pro plánování/UI:
-- nejnovější `ok` run per (interval_start, pv_array_id) v daném okně.
select coalesce(sum(u.power_w) * 0.25 / 1000.0, 0)
into v_forecast
from ems.forecast_pv_interval fpi
join latest_run lr on lr.run_id = fpi.run_id
where fpi.interval_start >= p_window_start
and fpi.interval_start < p_window_end
and fpi.pv_array_id = lr.pv_array_id;
from (
select distinct on (fpi.interval_start, fpr.pv_array_id)
fpi.power_w
from ems.forecast_pv_interval fpi
join ems.forecast_pv_run fpr
on fpr.id = fpi.run_id
and fpr.site_id = p_site_id
and fpr.pv_array_id = fpi.pv_array_id
and fpr.status = 'ok'
where fpi.interval_start >= p_window_start
and fpi.interval_start < p_window_end
and fpi.pv_array_id in (
select apa.id from ems.asset_pv_array apa where apa.site_id = p_site_id
)
order by fpi.interval_start, fpr.pv_array_id, fpr.created_at desc
) u;
if v_forecast < 0.1 or coalesce(v_actual, 0) < 0.05 then
return jsonb_build_object(

View File

@@ -0,0 +1,83 @@
-- ============================================================
-- PV forecast sloty (15min) RAW (bez korekcí), rozdělené na PV-A/PV-B
--
-- Nejnovější `ok` forecast_pv_run per (interval_start, pv_array_id).
-- Slouží pro audit/debug v planning_interval.*_forecast_raw_w.
-- ============================================================
create or replace function ems.fn_forecast_pv_slots_range_raw_ab(
p_site_id int,
p_from timestamptz,
p_to timestamptz
)
returns jsonb
language sql
stable
set work_mem = '64MB'
as $fn$
with bounds as (
select
date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') as ts_from,
case
when p_to <= p_from then date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') + interval '15 minutes'
when p_to > p_from + interval '60 days' then date_bin(interval '15 minutes', p_from, timestamptz '1970-01-01T00:00:00Z') + interval '60 days'
else date_bin(interval '15 minutes', p_to, timestamptz '1970-01-01T00:00:00Z')
end as ts_to
),
slot_spine as (
select gs as interval_start
from bounds b,
generate_series(
b.ts_from,
(b.ts_to - interval '15 minutes')::timestamptz,
interval '15 minutes'
) as gs
),
fc_by_array as (
select distinct on (fpi.interval_start, fpr.pv_array_id)
fpi.interval_start,
apa.controllable,
fpi.power_w::bigint as power_w
from bounds b
join ems.forecast_pv_interval fpi
on fpi.interval_start >= b.ts_from
and fpi.interval_start < b.ts_to
and fpi.pv_array_id in (
select apa0.id from ems.asset_pv_array apa0 where apa0.site_id = p_site_id
)
join ems.forecast_pv_run fpr
on fpr.id = fpi.run_id
and fpr.site_id = p_site_id
and fpr.pv_array_id = fpi.pv_array_id
and fpr.status = 'ok'
join ems.asset_pv_array apa
on apa.id = fpr.pv_array_id
and apa.site_id = p_site_id
order by fpi.interval_start, fpr.pv_array_id, fpr.created_at desc
),
fc_ab as (
select
s.interval_start,
coalesce(sum(case when f.controllable then f.power_w else 0 end), 0)::bigint as pv_a_forecast_raw_w,
coalesce(sum(case when not f.controllable then f.power_w else 0 end), 0)::bigint as pv_b_forecast_raw_w
from slot_spine s
left join fc_by_array f on f.interval_start = s.interval_start
group by s.interval_start
)
select coalesce(
jsonb_agg(
jsonb_build_object(
'interval_start', r.interval_start,
'pv_a_forecast_raw_w', r.pv_a_forecast_raw_w,
'pv_b_forecast_raw_w', r.pv_b_forecast_raw_w
)
order by r.interval_start
),
'[]'::jsonb
)
from fc_ab r;
$fn$;
comment on function ems.fn_forecast_pv_slots_range_raw_ab is
'RAW PV forecast po 15 min (bez korekcí), rozdělený na PV-A/PV-B, jako nejnovější ok run per array a slot.';