149 lines
5.0 KiB
PL/PgSQL
149 lines
5.0 KiB
PL/PgSQL
-- uložení planning_run + planning_interval v jedné transakci
|
|
|
|
create or replace function ems.fn_planning_run_commit(
|
|
p_site_id int,
|
|
p_horizon_start timestamptz,
|
|
p_horizon_end timestamptz,
|
|
p_run_meta jsonb,
|
|
p_intervals jsonb
|
|
)
|
|
returns int
|
|
language plpgsql
|
|
as $fn$
|
|
declare
|
|
v_run_id int;
|
|
r record;
|
|
v_has_slot_inputs boolean;
|
|
begin
|
|
v_has_slot_inputs := coalesce(
|
|
(jsonb_typeof(p_intervals) = 'array' and jsonb_array_length(p_intervals) > 0
|
|
and (p_intervals->0) ? 'load_baseline_w'),
|
|
false
|
|
);
|
|
insert into ems.planning_run (
|
|
site_id, horizon_start, horizon_end, status,
|
|
run_type, triggered_by, replan_from,
|
|
soc_at_replan_wh, solver_duration_ms, forecast_correction_factor
|
|
) values (
|
|
p_site_id,
|
|
p_horizon_start,
|
|
p_horizon_end,
|
|
'draft',
|
|
nullif(trim(p_run_meta->>'run_type'), ''),
|
|
nullif(trim(p_run_meta->>'triggered_by'), ''),
|
|
case
|
|
when p_run_meta ? 'replan_from' and (p_run_meta->>'replan_from') is not null
|
|
and (p_run_meta->>'replan_from') <> 'null'
|
|
then (p_run_meta->>'replan_from')::timestamptz
|
|
else null::timestamptz
|
|
end,
|
|
(p_run_meta->>'soc_at_replan_wh')::numeric,
|
|
(p_run_meta->>'solver_duration_ms')::int,
|
|
(p_run_meta->>'forecast_correction_factor')::numeric
|
|
)
|
|
returning id into v_run_id;
|
|
|
|
for r in select * from jsonb_array_elements(p_intervals) as elem(value)
|
|
loop
|
|
if v_has_slot_inputs then
|
|
insert into ems.planning_interval (
|
|
run_id, interval_start,
|
|
battery_setpoint_w, battery_soc_target_pct,
|
|
grid_setpoint_w,
|
|
ev1_setpoint_w, ev2_setpoint_w, ev1_via_bat_w, ev2_via_bat_w,
|
|
heat_pump_enabled, heat_pump_setpoint_w,
|
|
pv_a_curtailed_w, expected_cost_czk,
|
|
effective_buy_price, effective_sell_price,
|
|
is_predicted_price,
|
|
load_baseline_w,
|
|
pv_a_forecast_raw_w, pv_b_forecast_raw_w,
|
|
pv_a_forecast_solver_w, pv_b_forecast_solver_w
|
|
) values (
|
|
v_run_id,
|
|
(r.value->>'interval_start')::timestamptz,
|
|
(r.value->>'battery_setpoint_w')::int,
|
|
(r.value->>'battery_soc_target_pct')::numeric,
|
|
(r.value->>'grid_setpoint_w')::int,
|
|
nullif(r.value->>'ev1_setpoint_w', '')::int,
|
|
nullif(r.value->>'ev2_setpoint_w', '')::int,
|
|
coalesce((r.value->>'ev1_via_bat_w')::int, 0),
|
|
coalesce((r.value->>'ev2_via_bat_w')::int, 0),
|
|
coalesce((r.value->>'heat_pump_enabled')::boolean, false),
|
|
(r.value->>'heat_pump_setpoint_w')::int,
|
|
(r.value->>'pv_a_curtailed_w')::int,
|
|
(r.value->>'expected_cost_czk')::numeric,
|
|
(r.value->>'effective_buy_price')::numeric,
|
|
(r.value->>'effective_sell_price')::numeric,
|
|
coalesce((r.value->>'is_predicted_price')::boolean, false),
|
|
(r.value->>'load_baseline_w')::int,
|
|
(r.value->>'pv_a_forecast_raw_w')::int,
|
|
(r.value->>'pv_b_forecast_raw_w')::int,
|
|
(r.value->>'pv_a_forecast_solver_w')::int,
|
|
(r.value->>'pv_b_forecast_solver_w')::int
|
|
);
|
|
else
|
|
insert into ems.planning_interval (
|
|
run_id, interval_start,
|
|
battery_setpoint_w, battery_soc_target_pct,
|
|
grid_setpoint_w,
|
|
ev1_setpoint_w, ev2_setpoint_w, ev1_via_bat_w, ev2_via_bat_w,
|
|
heat_pump_enabled, heat_pump_setpoint_w,
|
|
pv_a_curtailed_w, expected_cost_czk,
|
|
effective_buy_price, effective_sell_price,
|
|
is_predicted_price
|
|
) values (
|
|
v_run_id,
|
|
(r.value->>'interval_start')::timestamptz,
|
|
(r.value->>'battery_setpoint_w')::int,
|
|
(r.value->>'battery_soc_target_pct')::numeric,
|
|
(r.value->>'grid_setpoint_w')::int,
|
|
nullif(r.value->>'ev1_setpoint_w', '')::int,
|
|
nullif(r.value->>'ev2_setpoint_w', '')::int,
|
|
coalesce((r.value->>'ev1_via_bat_w')::int, 0),
|
|
coalesce((r.value->>'ev2_via_bat_w')::int, 0),
|
|
coalesce((r.value->>'heat_pump_enabled')::boolean, false),
|
|
(r.value->>'heat_pump_setpoint_w')::int,
|
|
(r.value->>'pv_a_curtailed_w')::int,
|
|
(r.value->>'expected_cost_czk')::numeric,
|
|
(r.value->>'effective_buy_price')::numeric,
|
|
(r.value->>'effective_sell_price')::numeric,
|
|
coalesce((r.value->>'is_predicted_price')::boolean, false)
|
|
);
|
|
end if;
|
|
end loop;
|
|
|
|
update ems.planning_run
|
|
set status = 'superseded'
|
|
where site_id = p_site_id
|
|
and status = 'active'
|
|
and id <> v_run_id;
|
|
|
|
update ems.planning_run
|
|
set status = 'active'
|
|
where id = v_run_id;
|
|
|
|
return v_run_id;
|
|
end;
|
|
$fn$;
|
|
|
|
create or replace function ems.fn_forecast_correction_log_insert(
|
|
p_site_id int,
|
|
p_window_start timestamptz,
|
|
p_window_end timestamptz,
|
|
p_actual_pv_wh numeric,
|
|
p_forecast_pv_wh numeric,
|
|
p_correction_factor numeric,
|
|
p_applied_to_run_id int
|
|
)
|
|
returns void
|
|
language sql
|
|
as $fn$
|
|
insert into ems.forecast_correction_log (
|
|
site_id, window_start, window_end,
|
|
actual_pv_wh, forecast_pv_wh, correction_factor, applied_to_run_id
|
|
) values (
|
|
p_site_id, p_window_start, p_window_end,
|
|
p_actual_pv_wh, p_forecast_pv_wh, p_correction_factor, p_applied_to_run_id
|
|
);
|
|
$fn$;
|