-- 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, deye_physical_mode, 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(trim(r.value->>'deye_physical_mode'), ''), 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, deye_physical_mode, 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(trim(r.value->>'deye_physical_mode'), ''), 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$;