zasadni uprava LP planneru
This commit is contained in:
@@ -63,6 +63,12 @@ declare
|
||||
v_degrad_czk_kwh numeric;
|
||||
v_ref_buy_czk_kwh numeric;
|
||||
v_purchase_pricing_mode text;
|
||||
v_lookahead_slots int := 4;
|
||||
v_grid_charge_cap_am int := 6;
|
||||
v_grid_charge_cap_pm int := 6;
|
||||
v_buy_lookahead_eps numeric := 0.05;
|
||||
v_grid_slots_am int := 0;
|
||||
v_grid_slots_pm int := 0;
|
||||
begin
|
||||
drop table if exists _ems_plan_slot_wk;
|
||||
create temp table _ems_plan_slot_wk on commit drop as
|
||||
@@ -243,9 +249,56 @@ begin
|
||||
v_per_slot_discharge_wh := v_max_discharge_w * v_discharge_eff * 0.25;
|
||||
v_energy_to_fill := v_soc_max_wh - p_current_soc_wh;
|
||||
v_exportable := v_soc_max_wh - v_min_soc_wh;
|
||||
v_grid_target_wh := greatest(v_energy_to_fill, 0) * v_charge_buf;
|
||||
-- Rozpočet masek: buffer neinfluje počet slotů nad skutečný deficit; nad reserve jen deficit.
|
||||
if p_current_soc_wh >= v_reserve_wh then
|
||||
v_grid_target_wh := greatest(v_energy_to_fill, 0);
|
||||
else
|
||||
v_grid_target_wh := least(
|
||||
greatest(v_energy_to_fill, 0) * v_charge_buf,
|
||||
greatest(v_energy_to_fill, 0)
|
||||
);
|
||||
end if;
|
||||
v_discharge_target_wh := v_exportable * v_discharge_buf;
|
||||
|
||||
-- Referenční nákup pro arbitráž (celý horizont, ne jen allow_charge).
|
||||
select coalesce(min(wk.buy_price), 0)
|
||||
into v_ref_buy_czk_kwh
|
||||
from _ems_plan_slot_wk wk;
|
||||
|
||||
-- Lookahead min buy (VT→NT) a store_score pro vrstvu A.
|
||||
alter table _ems_plan_slot_wk
|
||||
add column if not exists future_sell_lookahead numeric,
|
||||
add column if not exists buy_min_next_n numeric,
|
||||
add column if not exists store_score numeric;
|
||||
|
||||
update _ems_plan_slot_wk wk
|
||||
set
|
||||
future_sell_lookahead = coalesce(
|
||||
(
|
||||
select max(w2.sell_price)
|
||||
from _ems_plan_slot_wk w2
|
||||
where w2.slot_ord > wk.slot_ord
|
||||
),
|
||||
wk.sell_price
|
||||
),
|
||||
buy_min_next_n = (
|
||||
select min(w2.buy_price)
|
||||
from _ems_plan_slot_wk w2
|
||||
where w2.slot_ord > wk.slot_ord
|
||||
and w2.slot_ord <= wk.slot_ord + v_lookahead_slots
|
||||
),
|
||||
store_score =
|
||||
coalesce(
|
||||
(
|
||||
select max(w2.sell_price)
|
||||
from _ems_plan_slot_wk w2
|
||||
where w2.slot_ord > wk.slot_ord
|
||||
),
|
||||
wk.sell_price
|
||||
)
|
||||
- wk.sell_price
|
||||
- greatest(0::numeric, wk.buy_price - wk.sell_price);
|
||||
|
||||
-- AM/PM rozpočet grid charging (Europe/Prague 00–12 vs 12–24).
|
||||
-- Chybějící segment dostane celý budget.
|
||||
select
|
||||
@@ -269,18 +322,14 @@ begin
|
||||
v_chg_pm_wh := v_grid_target_wh - v_chg_am_wh;
|
||||
end if;
|
||||
|
||||
-- charge mask: dvě nezávislé vrstvy
|
||||
-- charge mask: dvě nezávislé vrstvy (tenký anti-mikrocyklus, ekonomika z cen)
|
||||
--
|
||||
-- A) PV-surplus sloty (pv_surplus_w > 0): ranking dle sell_price ASC.
|
||||
-- Nejlevnější PV-surplus sloty vybereme, dokud kumulativní
|
||||
-- PV surplus nepokryje charge target (energy_to_fill × charge_buf).
|
||||
-- Zbylé PV-surplus sloty mají allow_charge = false → PV jde do sítě.
|
||||
-- Toto je hlavní mechanismus proti mikro-cyklování z PV:
|
||||
-- v drahých slotech se PV prodává přímo, nabíjení jen v levných.
|
||||
-- A) PV-surplus: ranking store_score DESC (future_sell − sell − max(0,buy−sell)).
|
||||
-- Sloty s nejvyšší hodnotou uložení vs export pokrývají charge target.
|
||||
-- Zbylé PV-surplus → allow_charge=false (PV jen do sítě / bc≤surplus v LP).
|
||||
--
|
||||
-- B) Non-PV sloty (pv_surplus_w <= 0): AM/PM budget, OTE-first (jen spot nákup).
|
||||
-- U purchase_pricing_mode = fixed se grid nabíjení neplánuje — buy je
|
||||
-- v každém slotu stejný, cyklus ze sítě by byl čistá ztráta; nabíjení jen z FVE.
|
||||
-- B) Non-PV grid: jen spot, buy ≤ ref_buy+degrad, buy ≤ min(next N)+ε,
|
||||
-- cap K slotů AM/PM; nikdy při sell < buy − degrad (ztrátový slot).
|
||||
if v_charge_buf <= 0 then
|
||||
update _ems_plan_slot_wk wk set allow_charge = true;
|
||||
elsif v_energy_to_fill <= 0 then
|
||||
@@ -288,13 +337,14 @@ begin
|
||||
else
|
||||
update _ems_plan_slot_wk wk set allow_charge = false;
|
||||
|
||||
-- A) PV-surplus: cheapest sell_price first
|
||||
-- A) PV-surplus: nejvyšší store_score (ukládat FVE vs exportovat)
|
||||
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
|
||||
order by wk.sell_price, wk.slot_ord
|
||||
and wk.sell_price >= wk.buy_price - v_degrad_czk_kwh
|
||||
order by wk.store_score desc nulls last, wk.slot_ord
|
||||
loop
|
||||
exit when v_cum >= v_grid_target_wh;
|
||||
update _ems_plan_slot_wk wk set allow_charge = true where wk.slot_ord = r_slot.slot_ord;
|
||||
@@ -302,49 +352,54 @@ begin
|
||||
end loop;
|
||||
|
||||
if v_purchase_pricing_mode <> 'fixed' then
|
||||
-- B) Non-PV AM: OTE-first, then predicted, ordered by buy_price
|
||||
-- B) Non-PV AM: OTE-first, levný buy + lookahead, cap slotů
|
||||
v_cum := 0;
|
||||
v_grid_slots_am := 0;
|
||||
for r_slot in
|
||||
select wk.slot_ord
|
||||
from _ems_plan_slot_wk wk
|
||||
where wk.pv_surplus_w <= 0
|
||||
and extract(hour from wk.interval_start at time zone 'Europe/Prague') < 12
|
||||
and wk.buy_price <= v_ref_buy_czk_kwh + v_degrad_czk_kwh
|
||||
and (
|
||||
wk.buy_min_next_n is null
|
||||
or wk.buy_price <= wk.buy_min_next_n + v_buy_lookahead_eps
|
||||
)
|
||||
order by wk.is_predicted_price::int, wk.buy_price, wk.slot_ord
|
||||
loop
|
||||
exit when v_cum >= v_chg_am_wh;
|
||||
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 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;
|
||||
end loop;
|
||||
|
||||
-- B) Non-PV PM: OTE-first, then predicted, ordered by buy_price
|
||||
-- B) Non-PV PM
|
||||
v_cum := 0;
|
||||
v_grid_slots_pm := 0;
|
||||
for r_slot in
|
||||
select wk.slot_ord
|
||||
from _ems_plan_slot_wk wk
|
||||
where wk.pv_surplus_w <= 0
|
||||
and extract(hour from wk.interval_start at time zone 'Europe/Prague') >= 12
|
||||
and wk.buy_price <= v_ref_buy_czk_kwh + v_degrad_czk_kwh
|
||||
and (
|
||||
wk.buy_min_next_n is null
|
||||
or wk.buy_price <= wk.buy_min_next_n + v_buy_lookahead_eps
|
||||
)
|
||||
order by wk.is_predicted_price::int, wk.buy_price, wk.slot_ord
|
||||
loop
|
||||
exit when v_cum >= v_chg_pm_wh;
|
||||
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 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;
|
||||
end loop;
|
||||
end if;
|
||||
end if;
|
||||
|
||||
-- Referenční nákup pro arbitráž exportu: nejlevnější buy mezi sloty, kde lze nabíjet
|
||||
-- (ne buy ve stejném slotu — střídač nekupuje a neprodává současně).
|
||||
select coalesce(
|
||||
min(wk.buy_price) filter (where wk.allow_charge),
|
||||
min(wk.buy_price)
|
||||
)
|
||||
into v_ref_buy_czk_kwh
|
||||
from _ems_plan_slot_wk wk;
|
||||
|
||||
v_ref_buy_czk_kwh := coalesce(v_ref_buy_czk_kwh, 0);
|
||||
|
||||
-- discharge-export mask
|
||||
if v_discharge_buf <= 0 then
|
||||
update _ems_plan_slot_wk wk set allow_discharge_export = true;
|
||||
@@ -464,9 +519,9 @@ $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: PV-surplus sloty rankované dle sell_price ASC – nejlevnější pokrývají charge target, zbytek → PV do sítě; '
|
||||
'non-PV sloty dle buy_price s AM/PM rozpočtem 50/50 a OTE-first prioritou (is_predicted_price::int ASC). '
|
||||
'Discharge-export mask: nejdražší sell_price sloty globálně. '
|
||||
'Charge mask A: PV-surplus dle store_score DESC (future_sell−sell−max(0,buy−sell)); zbytek → PV export. '
|
||||
'Charge mask B: non-PV jen spot, buy≤ref_buy+degrad, lookahead min buy v N slotech, cap 6 slotů AM/PM. '
|
||||
'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.';
|
||||
|
||||
Reference in New Issue
Block a user