Branch 3: charge-slot-budget v R__063 + odstranit v58 pro BA81/KV1 + fixed evening push
This commit is contained in:
@@ -36,7 +36,14 @@ returns table (
|
||||
min_buy_before_cutoff_czk_kwh numeric,
|
||||
pv_charge_wh_ahead numeric,
|
||||
neg_buy_wh_ahead numeric,
|
||||
grid_charge_suppressed_reason text
|
||||
grid_charge_suppressed_reason text,
|
||||
charge_target_wh numeric,
|
||||
pre_window_wh numeric,
|
||||
in_window_wh numeric,
|
||||
charge_slot_wh numeric,
|
||||
charge_cum_wh numeric,
|
||||
charge_layer text,
|
||||
charge_slot_reason text
|
||||
)
|
||||
language plpgsql
|
||||
volatile
|
||||
@@ -104,6 +111,10 @@ declare
|
||||
v_cum_allowed numeric;
|
||||
v_pv_ahead_total numeric;
|
||||
v_target_deficit numeric;
|
||||
v_charge_target_wh numeric;
|
||||
v_pre_window_wh numeric := 0;
|
||||
v_in_window_wh numeric := 0;
|
||||
v_charge_reliability_factor numeric := 0.85;
|
||||
r_unlock record;
|
||||
begin
|
||||
v_plan_day_prague := (p_from at time zone 'Europe/Prague')::date;
|
||||
@@ -308,6 +319,7 @@ begin
|
||||
);
|
||||
end if;
|
||||
v_discharge_target_wh := v_exportable * v_discharge_buf;
|
||||
v_charge_target_wh := greatest(v_grid_target_wh, 0);
|
||||
|
||||
-- Referenční nákup: globální min (export brána) + per AM/PM pás (grid nabíjení).
|
||||
select coalesce(min(wk.buy_price), 0)
|
||||
@@ -334,7 +346,11 @@ begin
|
||||
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;
|
||||
add column if not exists grid_charge_suppressed_reason text,
|
||||
add column if not exists charge_slot_wh numeric default 0,
|
||||
add column if not exists charge_cum_wh numeric,
|
||||
add column if not exists charge_layer text,
|
||||
add column if not exists charge_slot_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í 00–06 už „po okně“).
|
||||
@@ -477,7 +493,12 @@ begin
|
||||
exit when v_per_slot_charge_wh <= 0;
|
||||
exit when v_grid_slots_am >= v_grid_charge_cap_am;
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true, allow_grid_charge = true
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = 'grid_am',
|
||||
charge_slot_reason = 'grid_layer_b',
|
||||
charge_slot_wh = v_per_slot_charge_wh,
|
||||
charge_cum_wh = v_cum + v_per_slot_charge_wh
|
||||
where wk.slot_ord = r_slot.slot_ord;
|
||||
v_cum := v_cum + v_per_slot_charge_wh;
|
||||
v_grid_slots_am := v_grid_slots_am + 1;
|
||||
@@ -525,7 +546,12 @@ begin
|
||||
exit when v_per_slot_charge_wh <= 0;
|
||||
exit when v_grid_slots_pm >= v_grid_charge_cap_pm;
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true, allow_grid_charge = true
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = 'grid_pm',
|
||||
charge_slot_reason = 'grid_layer_b',
|
||||
charge_slot_wh = v_per_slot_charge_wh,
|
||||
charge_cum_wh = v_cum + v_per_slot_charge_wh
|
||||
where wk.slot_ord = r_slot.slot_ord;
|
||||
v_cum := v_cum + v_per_slot_charge_wh;
|
||||
v_grid_slots_pm := v_grid_slots_pm + 1;
|
||||
@@ -534,7 +560,11 @@ begin
|
||||
|
||||
-- Spot: záporný buy → grid nabíjení ve všech slotech (maximální arbitráž), mimo AM/PM rozpočet.
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true, allow_grid_charge = true
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = coalesce(wk.charge_layer, 'buy_negative'),
|
||||
charge_slot_reason = coalesce(wk.charge_slot_reason, 'buy_negative'),
|
||||
charge_slot_wh = greatest(wk.charge_slot_wh, v_per_slot_charge_wh)
|
||||
where wk.buy_price < 0;
|
||||
|
||||
-- Self-konzistentni filtr vrstvy B (spot): vyloucit drahe grid sloty, pokud PV / buy<0
|
||||
@@ -697,7 +727,12 @@ begin
|
||||
exit when v_per_slot_charge_wh <= 0;
|
||||
exit when v_grid_slots_am >= v_grid_charge_cap_am;
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true, allow_grid_charge = true
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = 'grid_am',
|
||||
charge_slot_reason = 'grid_layer_b',
|
||||
charge_slot_wh = v_per_slot_charge_wh,
|
||||
charge_cum_wh = v_cum + v_per_slot_charge_wh
|
||||
where wk.slot_ord = r_slot.slot_ord;
|
||||
v_cum := v_cum + v_per_slot_charge_wh;
|
||||
v_grid_slots_am := v_grid_slots_am + 1;
|
||||
@@ -749,7 +784,12 @@ begin
|
||||
exit when v_per_slot_charge_wh <= 0;
|
||||
exit when v_grid_slots_pm >= v_grid_charge_cap_pm;
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true, allow_grid_charge = true
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = 'grid_pm',
|
||||
charge_slot_reason = 'grid_layer_b',
|
||||
charge_slot_wh = v_per_slot_charge_wh,
|
||||
charge_cum_wh = v_cum + v_per_slot_charge_wh
|
||||
where wk.slot_ord = r_slot.slot_ord;
|
||||
v_cum := v_cum + v_per_slot_charge_wh;
|
||||
v_grid_slots_pm := v_grid_slots_pm + 1;
|
||||
@@ -760,40 +800,53 @@ begin
|
||||
-- A) PV-surplus: jen zbytek kapacity po grid vrstvě B
|
||||
v_pv_layer_cap_wh := greatest(v_energy_to_fill - v_grid_filled_wh, 0);
|
||||
|
||||
-- Rezervace SoC pro sell<0 okno: pokud v zápor. výkup. slotech máme
|
||||
-- očekávaný PV přebytek X Wh (po efektivitě), snížíme PV vrstvu A o X.
|
||||
-- Důsledek: do okna nedorazíme „plní" (98 % SoC), zbude prostor přijmout PV
|
||||
-- z neg-sell slotů místo exportu do mínusu / curtail pole A.
|
||||
-- Sample neg-sell PV sloty (sell<0 a buy<0, kde sell<buy) jsou vyloučené
|
||||
-- z hlavního A-loopu (filtr sell >= buy − degrad), takže redukce je čistá.
|
||||
declare
|
||||
v_neg_window_pv_surplus_wh numeric := 0;
|
||||
begin
|
||||
select coalesce(sum(least(wk.pv_surplus_w::numeric, v_max_charge_w) * v_charge_eff * 0.25), 0)
|
||||
into v_neg_window_pv_surplus_wh
|
||||
from _ems_plan_slot_wk wk
|
||||
where wk.sell_price < 0
|
||||
and wk.pv_surplus_w > 0;
|
||||
if v_neg_window_pv_surplus_wh > 0 then
|
||||
v_pv_layer_cap_wh := greatest(v_pv_layer_cap_wh - v_neg_window_pv_surplus_wh, 0);
|
||||
end if;
|
||||
end;
|
||||
-- Dodávka z forecastu v sell<0 okně snižuje potřebu nabíjení před oknem.
|
||||
select coalesce(sum(least(wk.pv_surplus_w::numeric, v_max_charge_w) * v_charge_eff * 0.25), 0)
|
||||
into v_in_window_wh
|
||||
from _ems_plan_slot_wk wk
|
||||
where wk.sell_price < 0
|
||||
and wk.pv_surplus_w > 0;
|
||||
|
||||
if v_in_window_wh > 0 then
|
||||
v_pv_layer_cap_wh := greatest(v_pv_layer_cap_wh - v_in_window_wh, 0);
|
||||
v_pre_window_wh := greatest(
|
||||
0,
|
||||
v_charge_target_wh - v_in_window_wh * v_charge_reliability_factor
|
||||
);
|
||||
end if;
|
||||
|
||||
v_cum := 0;
|
||||
for r_slot in
|
||||
select wk.slot_ord, wk.pv_surplus_w
|
||||
from _ems_plan_slot_wk wk
|
||||
where wk.pv_surplus_w > 0
|
||||
and wk.sell_price >= wk.buy_price - v_degrad_czk_kwh
|
||||
-- Držet PV na večerní peak jen při kladném výkupu; při sell<0 (záporný výkup) vždy nabíjet z FVE.
|
||||
and (
|
||||
wk.sell_price < 0
|
||||
v_purchase_pricing_mode = 'fixed'
|
||||
or wk.sell_price >= wk.buy_price - v_degrad_czk_kwh
|
||||
)
|
||||
-- Spot: neukládat do bat při výrazně lepším sell později; fixed: řazení sell ASC (§ charge-slot-budget).
|
||||
and (
|
||||
v_purchase_pricing_mode = 'fixed'
|
||||
or wk.sell_price < 0
|
||||
or wk.sell_price >= wk.future_sell_lookahead - v_degrad_czk_kwh
|
||||
)
|
||||
order by wk.store_score desc nulls last, wk.slot_ord
|
||||
order by
|
||||
case when v_purchase_pricing_mode = 'fixed' then wk.sell_price end asc nulls last,
|
||||
wk.store_score desc nulls last,
|
||||
wk.slot_ord
|
||||
loop
|
||||
exit when v_cum >= v_pv_layer_cap_wh;
|
||||
update _ems_plan_slot_wk wk set allow_charge = true where wk.slot_ord = r_slot.slot_ord;
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true,
|
||||
charge_layer = coalesce(wk.charge_layer, 'pv_a'),
|
||||
charge_slot_reason = coalesce(wk.charge_slot_reason, 'pv_layer_a'),
|
||||
charge_slot_wh = greatest(
|
||||
wk.charge_slot_wh,
|
||||
least(r_slot.pv_surplus_w, v_max_charge_w) * v_charge_eff * 0.25
|
||||
),
|
||||
charge_cum_wh = v_cum
|
||||
+ least(r_slot.pv_surplus_w, v_max_charge_w) * v_charge_eff * 0.25
|
||||
where wk.slot_ord = r_slot.slot_ord;
|
||||
v_cum := v_cum + least(r_slot.pv_surplus_w, v_max_charge_w) * v_charge_eff * 0.25;
|
||||
end loop;
|
||||
end if;
|
||||
@@ -1007,12 +1060,22 @@ begin
|
||||
|
||||
-- Záporný buy: vždy grid nabíjení (mimo rozpočet 6 slotů / PV vrstvu A).
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true, allow_grid_charge = true
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = coalesce(wk.charge_layer, 'buy_negative'),
|
||||
charge_slot_reason = coalesce(wk.charge_slot_reason, 'buy_negative'),
|
||||
charge_slot_wh = greatest(wk.charge_slot_wh, v_per_slot_charge_wh)
|
||||
where wk.buy_price < 0;
|
||||
|
||||
-- Záporný výkup + PV přebytek: nabíjení z FVE (KV1/BA81 block_export), bez filtru future_sell.
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true
|
||||
set allow_charge = true,
|
||||
charge_layer = coalesce(wk.charge_layer, 'neg_window'),
|
||||
charge_slot_reason = coalesce(wk.charge_slot_reason, 'neg_window_pv'),
|
||||
charge_slot_wh = greatest(
|
||||
wk.charge_slot_wh,
|
||||
least(wk.pv_surplus_w::numeric, v_max_charge_w) * v_charge_eff * 0.25
|
||||
)
|
||||
where wk.sell_price < 0
|
||||
and wk.pv_surplus_w > 0;
|
||||
|
||||
@@ -1021,6 +1084,9 @@ begin
|
||||
update _ems_plan_slot_wk wk
|
||||
set allow_charge = true,
|
||||
allow_grid_charge = true,
|
||||
charge_layer = coalesce(wk.charge_layer, 'neg_window'),
|
||||
charge_slot_reason = coalesce(wk.charge_slot_reason, 'neg_window_grid_charge'),
|
||||
charge_slot_wh = greatest(wk.charge_slot_wh, v_per_slot_charge_wh),
|
||||
grid_charge_suppressed_reason = coalesce(
|
||||
wk.grid_charge_suppressed_reason,
|
||||
'neg_window_grid_charge'
|
||||
@@ -1176,7 +1242,14 @@ begin
|
||||
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
|
||||
w.grid_charge_suppressed_reason,
|
||||
v_charge_target_wh as charge_target_wh,
|
||||
v_pre_window_wh as pre_window_wh,
|
||||
v_in_window_wh as in_window_wh,
|
||||
coalesce(w.charge_slot_wh, 0) as charge_slot_wh,
|
||||
w.charge_cum_wh,
|
||||
w.charge_layer,
|
||||
w.charge_slot_reason
|
||||
from _ems_plan_slot_wk w
|
||||
cross join night_tot nt
|
||||
)
|
||||
@@ -1204,7 +1277,14 @@ begin
|
||||
e.min_buy_before_cutoff_czk_kwh,
|
||||
e.pv_charge_wh_ahead,
|
||||
e.neg_buy_wh_ahead,
|
||||
e.grid_charge_suppressed_reason
|
||||
e.grid_charge_suppressed_reason,
|
||||
e.charge_target_wh,
|
||||
e.pre_window_wh,
|
||||
e.in_window_wh,
|
||||
e.charge_slot_wh,
|
||||
e.charge_cum_wh,
|
||||
e.charge_layer,
|
||||
e.charge_slot_reason
|
||||
from enriched e
|
||||
order by e.slot_ord;
|
||||
end;
|
||||
@@ -1212,11 +1292,13 @@ $fn$;
|
||||
|
||||
comment on function ems.fn_load_planning_slots_full is
|
||||
'15min sloty s cenami, forecastem, baseline a maskami proti mikro-cyklu (charge/discharge-export). '
|
||||
'Charge mask A: PV-surplus dle store_score DESC (future_sell−sell−max(0,buy−sell)); zbytek → PV export. '
|
||||
'Charge mask B: spot, nejlevnější buy v AM/PM do Wh rozpočtu (priorita den plánu, před exportním oknem). '
|
||||
'Charge-slot budget: charge_target_wh, pre_window_wh (deficit − forecast v sell<0), in_window_wh; '
|
||||
'debug charge_layer / charge_slot_reason / charge_cum_wh. '
|
||||
'Charge mask A: spot = store_score DESC; fixed = sell ASC + Wh kumulace pv_surplus. '
|
||||
'Charge mask B: spot nejlevnější buy v AM/PM; fixed nejnižší sell v AM/PM do Wh rozpočtu. '
|
||||
'ref_buy = min(buy) horizontu. Discharge-export: nejdražší sell kde sell>ref_buy+degrad (spot). '
|
||||
'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. '
|
||||
'charge_acquisition_buy_czk_kwh: vážený buy v allow_charge slotech před charge_acquisition_cutoff_at. '
|
||||
'charge_acquisition_buy_czk_kwh: vážený buy v allow_grid_charge slotech před charge_acquisition_cutoff_at. '
|
||||
'Grid maska B běží před PV vrstvou A; AM/PM rozpočet Wh 50/50; cap slotů z rozpočtu / per_slot_charge_wh.';
|
||||
|
||||
Reference in New Issue
Block a user