tuning palnneru
This commit is contained in:
@@ -23,7 +23,8 @@ begin
|
||||
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
|
||||
soc_at_replan_wh, solver_duration_ms, forecast_correction_factor,
|
||||
solver_params
|
||||
) values (
|
||||
p_site_id,
|
||||
p_horizon_start,
|
||||
@@ -39,7 +40,12 @@ begin
|
||||
end,
|
||||
(p_run_meta->>'soc_at_replan_wh')::numeric,
|
||||
(p_run_meta->>'solver_duration_ms')::int,
|
||||
(p_run_meta->>'forecast_correction_factor')::numeric
|
||||
(p_run_meta->>'forecast_correction_factor')::numeric,
|
||||
case
|
||||
when p_run_meta ? 'solver_params' and jsonb_typeof(p_run_meta->'solver_params') = 'object'
|
||||
then p_run_meta->'solver_params'
|
||||
else null::jsonb
|
||||
end
|
||||
)
|
||||
returning id into v_run_id;
|
||||
|
||||
|
||||
@@ -67,7 +67,11 @@ begin
|
||||
)::int,
|
||||
'charge_slot_buffer', ab.charge_slot_buffer,
|
||||
'discharge_slot_buffer', ab.discharge_slot_buffer,
|
||||
'planner_terminal_soc_value_factor', ab.planner_terminal_soc_value_factor
|
||||
'planner_terminal_soc_value_factor', ab.planner_terminal_soc_value_factor,
|
||||
'planner_daytime_charge_target_enabled', coalesce(ab.planner_daytime_charge_target_enabled, true),
|
||||
'planner_night_baseload_buffer_percent', coalesce(ab.planner_night_baseload_buffer_percent, 20::numeric),
|
||||
'planner_daytime_charge_price_quantile', coalesce(ab.planner_daytime_charge_price_quantile, 0.70::numeric),
|
||||
'planner_charge_commitment_penalty_czk_kwh', coalesce(ab.planner_charge_commitment_penalty_czk_kwh, 0.20::numeric)
|
||||
)
|
||||
into v_b
|
||||
from ems.asset_battery ab
|
||||
|
||||
@@ -18,7 +18,13 @@ returns table (
|
||||
ev1_connected boolean,
|
||||
ev2_connected boolean,
|
||||
allow_charge boolean,
|
||||
allow_discharge_export boolean
|
||||
allow_discharge_export boolean,
|
||||
night_baseload_target_wh numeric,
|
||||
night_baseload_buffer_wh numeric,
|
||||
safety_soc_target_wh numeric,
|
||||
future_avoided_buy_czk_kwh numeric,
|
||||
future_sell_opportunity_czk_kwh numeric,
|
||||
is_daytime_pv_surplus_slot boolean
|
||||
)
|
||||
language plpgsql
|
||||
volatile
|
||||
@@ -47,6 +53,9 @@ declare
|
||||
v_chg_pm_wh numeric;
|
||||
v_dis_am_wh numeric;
|
||||
v_dis_pm_wh numeric;
|
||||
v_reserve_wh numeric;
|
||||
v_daytime_en boolean;
|
||||
v_night_buf_pct numeric;
|
||||
begin
|
||||
drop table if exists _ems_plan_slot_wk;
|
||||
create temp table _ems_plan_slot_wk on commit drop as
|
||||
@@ -280,7 +289,10 @@ begin
|
||||
coalesce(ai.max_battery_discharge_w, ai.max_discharge_power_w)
|
||||
)
|
||||
)::numeric,
|
||||
greatest(coalesce(ab.discharge_efficiency, 1::numeric), 0.0001::numeric)
|
||||
greatest(coalesce(ab.discharge_efficiency, 1::numeric), 0.0001::numeric),
|
||||
(ab.reserve_soc_percent / 100.0 * ab.usable_capacity_wh)::numeric,
|
||||
coalesce(ab.planner_daytime_charge_target_enabled, true),
|
||||
coalesce(ab.planner_night_baseload_buffer_percent, 20::numeric)
|
||||
into
|
||||
v_charge_buf,
|
||||
v_discharge_buf,
|
||||
@@ -290,7 +302,10 @@ begin
|
||||
v_charge_eff,
|
||||
v_max_charge_w,
|
||||
v_max_discharge_w,
|
||||
v_discharge_eff
|
||||
v_discharge_eff,
|
||||
v_reserve_wh,
|
||||
v_daytime_en,
|
||||
v_night_buf_pct
|
||||
from ems.asset_battery ab
|
||||
join ems.asset_inverter ai on ai.id = ab.inverter_id and ai.site_id = ab.site_id
|
||||
where ab.site_id = p_site_id
|
||||
@@ -395,25 +410,97 @@ begin
|
||||
end if;
|
||||
|
||||
return query
|
||||
with night_tot as (
|
||||
select coalesce(sum(w2.load_baseline_w), 0) * 0.25 as night_wh
|
||||
from _ems_plan_slot_wk w2
|
||||
where extract(hour from w2.interval_start at time zone 'Europe/Prague') >= 20
|
||||
or extract(hour from w2.interval_start at time zone 'Europe/Prague') < 6
|
||||
),
|
||||
enriched as (
|
||||
select
|
||||
w.slot_ord,
|
||||
w.interval_start,
|
||||
w.buy_price,
|
||||
w.sell_price,
|
||||
w.is_predicted_price,
|
||||
w.pv_a_forecast_w,
|
||||
w.pv_b_forecast_w,
|
||||
w.load_baseline_w,
|
||||
w.ev1_connected,
|
||||
w.ev2_connected,
|
||||
w.allow_charge,
|
||||
w.allow_discharge_export,
|
||||
nt.night_wh as night_baseload_target_wh,
|
||||
nt.night_wh * (v_night_buf_pct / 100.0) as night_baseload_buffer_wh,
|
||||
case
|
||||
when not v_daytime_en then null::numeric
|
||||
when extract(hour from w.interval_start at time zone 'Europe/Prague') between 6 and 19 then
|
||||
least(
|
||||
v_soc_max_wh,
|
||||
v_reserve_wh + (nt.night_wh + nt.night_wh * (v_night_buf_pct / 100.0))
|
||||
* greatest(
|
||||
0::numeric,
|
||||
least(
|
||||
1::numeric,
|
||||
(
|
||||
extract(hour from w.interval_start at time zone 'Europe/Prague')::numeric
|
||||
+ (
|
||||
extract(minute from w.interval_start at time zone 'Europe/Prague')::numeric
|
||||
/ 60.0
|
||||
)
|
||||
- 6.0
|
||||
) / 14.0
|
||||
)
|
||||
)
|
||||
)
|
||||
else null::numeric
|
||||
end as safety_soc_target_wh,
|
||||
coalesce(
|
||||
max(w.buy_price) over (
|
||||
order by w.slot_ord rows between 1 following and unbounded following
|
||||
),
|
||||
w.buy_price
|
||||
) as future_avoided_buy_czk_kwh,
|
||||
coalesce(
|
||||
max(w.sell_price) over (
|
||||
order by w.slot_ord rows between 1 following and unbounded following
|
||||
),
|
||||
w.sell_price
|
||||
) as future_sell_opportunity_czk_kwh,
|
||||
(
|
||||
extract(hour from w.interval_start at time zone 'Europe/Prague') between 6 and 18
|
||||
and w.pv_surplus_w > 0
|
||||
) as is_daytime_pv_surplus_slot
|
||||
from _ems_plan_slot_wk w
|
||||
cross join night_tot nt
|
||||
)
|
||||
select
|
||||
w.slot_ord,
|
||||
w.interval_start,
|
||||
w.buy_price,
|
||||
w.sell_price,
|
||||
w.is_predicted_price,
|
||||
w.pv_a_forecast_w,
|
||||
w.pv_b_forecast_w,
|
||||
w.load_baseline_w,
|
||||
w.ev1_connected,
|
||||
w.ev2_connected,
|
||||
w.allow_charge,
|
||||
w.allow_discharge_export
|
||||
from _ems_plan_slot_wk w
|
||||
order by w.slot_ord;
|
||||
e.slot_ord,
|
||||
e.interval_start,
|
||||
e.buy_price,
|
||||
e.sell_price,
|
||||
e.is_predicted_price,
|
||||
e.pv_a_forecast_w,
|
||||
e.pv_b_forecast_w,
|
||||
e.load_baseline_w,
|
||||
e.ev1_connected,
|
||||
e.ev2_connected,
|
||||
e.allow_charge,
|
||||
e.allow_discharge_export,
|
||||
e.night_baseload_target_wh,
|
||||
e.night_baseload_buffer_wh,
|
||||
e.safety_soc_target_wh,
|
||||
e.future_avoided_buy_czk_kwh,
|
||||
e.future_sell_opportunity_czk_kwh,
|
||||
e.is_daytime_pv_surplus_slot
|
||||
from enriched e
|
||||
order by e.slot_ord;
|
||||
end;
|
||||
$fn$;
|
||||
|
||||
comment on function ems.fn_load_planning_slots_full(int, timestamptz, timestamptz, numeric) is
|
||||
'15min sloty s cenami, forecastem, baseline a maskami proti mikro-cyklu (charge/discharge-export). '
|
||||
'Masky charge/discharge-export se berou zvlášť pro 00–12 a 12–24 Europe/Prague (polovina budgetu na segment). '
|
||||
'Strop SoC pro výpočet energie k dobití: coalesce(planner_max_soc_percent, max_soc_percent).';
|
||||
'Strop SoC pro výpočet energie k dobití: coalesce(planner_max_soc_percent, max_soc_percent). '
|
||||
'Denní safety vstupy: night_baseload_* (20:00–06:00 Europe/Prague), safety_soc_target_wh (6–19), '
|
||||
'lookahead max buy/sell pro měkké LP penalizace.';
|
||||
|
||||
76
db/routines/R__087_fn_planning_run_debug.sql
Normal file
76
db/routines/R__087_fn_planning_run_debug.sql
Normal file
@@ -0,0 +1,76 @@
|
||||
-- Kompaktní JSON pro diagnostiku jednoho planning_run (MCP / UI).
|
||||
|
||||
create or replace function ems.fn_planning_run_debug(p_run_id int)
|
||||
returns jsonb
|
||||
language plpgsql
|
||||
stable
|
||||
as $fn$
|
||||
declare
|
||||
r_run ems.planning_run%rowtype;
|
||||
v_intervals jsonb;
|
||||
v_first_charge timestamptz;
|
||||
v_first_bat_export timestamptz;
|
||||
v_top_sell jsonb;
|
||||
begin
|
||||
select * into r_run from ems.planning_run where id = p_run_id;
|
||||
if not found then
|
||||
return null::jsonb;
|
||||
end if;
|
||||
|
||||
select coalesce(jsonb_agg(to_jsonb(pi.*) order by pi.interval_start), '[]'::jsonb)
|
||||
into v_intervals
|
||||
from ems.planning_interval pi
|
||||
where pi.run_id = p_run_id;
|
||||
|
||||
select pi.interval_start
|
||||
into v_first_charge
|
||||
from ems.planning_interval pi
|
||||
where pi.run_id = p_run_id
|
||||
and coalesce(pi.battery_setpoint_w, 0) > 500
|
||||
order by pi.interval_start
|
||||
limit 1;
|
||||
|
||||
select pi.interval_start
|
||||
into v_first_bat_export
|
||||
from ems.planning_interval pi
|
||||
where pi.run_id = p_run_id
|
||||
and coalesce(pi.battery_setpoint_w, 0) < -500
|
||||
and coalesce(pi.grid_setpoint_w, 0) < 0
|
||||
order by pi.interval_start
|
||||
limit 1;
|
||||
|
||||
select coalesce(
|
||||
jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'interval_start', x.interval_start,
|
||||
'effective_sell_price', x.effective_sell_price
|
||||
)
|
||||
order by x.effective_sell_price desc nulls last
|
||||
),
|
||||
'[]'::jsonb
|
||||
)
|
||||
into v_top_sell
|
||||
from (
|
||||
select pi.interval_start, pi.effective_sell_price
|
||||
from ems.planning_interval pi
|
||||
where pi.run_id = p_run_id
|
||||
order by pi.effective_sell_price desc nulls last
|
||||
limit 3
|
||||
) x;
|
||||
|
||||
return jsonb_build_object(
|
||||
'planning_run', to_jsonb(r_run),
|
||||
'solver_params', r_run.solver_params,
|
||||
'intervals', v_intervals,
|
||||
'summary', jsonb_build_object(
|
||||
'first_charge_slot', to_jsonb(v_first_charge),
|
||||
'first_battery_export_slot', to_jsonb(v_first_bat_export),
|
||||
'top_sell_slots', v_top_sell,
|
||||
'solver_params_version', r_run.solver_params->'version'
|
||||
)
|
||||
);
|
||||
end;
|
||||
$fn$;
|
||||
|
||||
comment on function ems.fn_planning_run_debug(int) is
|
||||
'Jeden jsonb: metadata planning_run, solver_params, všechny planning_interval řádky a krátký summary.';
|
||||
Reference in New Issue
Block a user