prepsani s opusem dle planu
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-24 22:44:21 +02:00
parent 2d021b15c3
commit 8bef1c6da6
11 changed files with 720 additions and 16 deletions

View File

@@ -32,7 +32,11 @@ returns table (
future_sell_opportunity_czk_kwh numeric,
is_daytime_pv_surplus_slot boolean,
charge_acquisition_buy_czk_kwh numeric,
charge_acquisition_cutoff_at timestamptz
charge_acquisition_cutoff_at timestamptz,
min_buy_before_cutoff_czk_kwh numeric,
pv_charge_wh_ahead numeric,
neg_buy_wh_ahead numeric,
grid_charge_suppressed_reason text
)
language plpgsql
volatile
@@ -92,6 +96,14 @@ declare
v_est_pv_cost numeric;
v_export_window_start timestamptz;
v_plan_day_prague date;
v_acq_v2 numeric;
v_acq_prev numeric := -999;
v_iter int;
v_affected int;
v_cum_allowed numeric;
v_pv_ahead_total numeric;
v_target_deficit numeric;
r_unlock record;
begin
v_plan_day_prague := (p_from at time zone 'Europe/Prague')::date;
drop table if exists _ems_plan_slot_wk;
@@ -310,7 +322,11 @@ begin
add column if not exists buy_min_next_n numeric,
add column if not exists store_score numeric,
add column if not exists allow_grid_charge boolean default false,
add column if not exists export_window_start_at timestamptz;
add column if not exists export_window_start_at timestamptz,
add column if not exists min_buy_before_cutoff numeric,
add column if not exists pv_charge_wh_ahead numeric,
add column if not exists neg_buy_wh_ahead numeric,
add column if not exists grid_charge_suppressed_reason text;
-- První výkupní okno **per kalendářní den** (Prague). Globální min přes dny by
-- zablokoval NT grid nabíjení (včerejší večerní peak → dnešní 0006 už „po okně“).
@@ -512,6 +528,127 @@ begin
update _ems_plan_slot_wk wk
set allow_charge = true, allow_grid_charge = true
where wk.buy_price < 0;
-- Self-konzistentni filtr vrstvy B (spot): vyloucit drahe grid sloty, pokud PV / buy<0
-- alternativa pokryje deficit SoC pred prvnim exportem.
update _ems_plan_slot_wk wk
set pv_charge_wh_ahead = sub.pv_wh_ahead,
neg_buy_wh_ahead = sub.neg_buy_wh_ahead,
min_buy_before_cutoff = sub.min_buy_ahead
from (
select
wk.slot_ord,
least(
coalesce(sum(
case
when w2.slot_ord >= wk.slot_ord
and (v_first_neg_sell_ord is null or w2.slot_ord < v_first_neg_sell_ord)
and w2.pv_surplus_w > 0
and (w2.sell_price < 0 or w2.buy_price < 0)
then least(w2.pv_surplus_w::numeric, v_max_charge_w)
* v_charge_eff * 0.25
else 0
end
), 0),
v_soc_max_wh - p_current_soc_wh
) as pv_wh_ahead,
coalesce(sum(
case
when w2.slot_ord >= wk.slot_ord
and (v_first_neg_sell_ord is null or w2.slot_ord < v_first_neg_sell_ord)
and w2.buy_price < 0
then v_per_slot_charge_wh
else 0
end
), 0) as neg_buy_wh_ahead,
min(
case
when w2.slot_ord > wk.slot_ord
and (v_first_neg_sell_ord is null or w2.slot_ord < v_first_neg_sell_ord)
then w2.buy_price
else null
end
) as min_buy_ahead
from _ems_plan_slot_wk wk
cross join _ems_plan_slot_wk w2
group by wk.slot_ord
) sub
where wk.slot_ord = sub.slot_ord;
v_iter := 0;
loop
v_iter := v_iter + 1;
exit when v_iter > 5;
select coalesce(
sum(wk.buy_price * v_per_slot_charge_wh)
filter (
where wk.allow_grid_charge
and (v_first_neg_sell_ord is null or wk.slot_ord < v_first_neg_sell_ord)
)
/ nullif(sum(v_per_slot_charge_wh)
filter (
where wk.allow_grid_charge
and (v_first_neg_sell_ord is null or wk.slot_ord < v_first_neg_sell_ord)
), 0),
v_ref_buy_czk_kwh
)
into v_acq_v2
from _ems_plan_slot_wk wk;
exit when abs(v_acq_v2 - v_acq_prev) < 0.05;
v_acq_prev := v_acq_v2;
update _ems_plan_slot_wk wk
set allow_charge = false,
allow_grid_charge = false,
grid_charge_suppressed_reason =
case
when wk.pv_charge_wh_ahead + wk.neg_buy_wh_ahead
>= greatest(0, v_soc_max_wh - p_current_soc_wh) * 0.6
then 'cheaper_pv_ahead'
else 'cheaper_neg_buy_ahead'
end
where wk.allow_grid_charge
and wk.buy_price > v_acq_v2 - v_degrad_czk_kwh
and wk.buy_price >= 0
and (
wk.pv_charge_wh_ahead + wk.neg_buy_wh_ahead
>= greatest(0, v_soc_max_wh - p_current_soc_wh) * 0.6
);
get diagnostics v_affected = row_count;
exit when v_affected = 0;
end loop;
select coalesce(sum(v_per_slot_charge_wh) filter (where wk.allow_grid_charge), 0)
into v_cum_allowed
from _ems_plan_slot_wk wk;
select coalesce(min(wk.pv_charge_wh_ahead), 0)
into v_pv_ahead_total
from _ems_plan_slot_wk wk
where wk.slot_ord = 0;
v_target_deficit := greatest(0, v_soc_max_wh - p_current_soc_wh) - v_pv_ahead_total;
if v_cum_allowed < v_target_deficit * 0.6 then
for r_unlock in
select wk.slot_ord
from _ems_plan_slot_wk wk
where wk.grid_charge_suppressed_reason is not null
and wk.buy_price < 2 * v_acq_v2
order by wk.buy_price, wk.slot_ord
loop
update _ems_plan_slot_wk wk
set allow_charge = true,
allow_grid_charge = true,
grid_charge_suppressed_reason = 'safety_failsafe_unlock'
where wk.slot_ord = r_unlock.slot_ord;
v_cum_allowed := v_cum_allowed + v_per_slot_charge_wh;
exit when v_cum_allowed >= v_target_deficit * 0.6;
end loop;
end if;
elsif exists (
select 1
from _ems_plan_slot_wk w2
@@ -925,7 +1062,11 @@ begin
(
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
) as is_daytime_pv_surplus_slot,
w.min_buy_before_cutoff as min_buy_before_cutoff_czk_kwh,
coalesce(w.pv_charge_wh_ahead, 0) as pv_charge_wh_ahead,
coalesce(w.neg_buy_wh_ahead, 0) as neg_buy_wh_ahead,
w.grid_charge_suppressed_reason
from _ems_plan_slot_wk w
cross join night_tot nt
)
@@ -949,7 +1090,11 @@ begin
e.future_sell_opportunity_czk_kwh,
e.is_daytime_pv_surplus_slot,
v_charge_acquisition as charge_acquisition_buy_czk_kwh,
v_acquisition_cutoff as charge_acquisition_cutoff_at
v_acquisition_cutoff as charge_acquisition_cutoff_at,
e.min_buy_before_cutoff_czk_kwh,
e.pv_charge_wh_ahead,
e.neg_buy_wh_ahead,
e.grid_charge_suppressed_reason
from enriched e
order by e.slot_ord;
end;