puldenni sltovoani , zruseni omemzeni na zakaz exportu pri zapornem sellu, hlubsi vybijeni ped zaporbnym nakupem
Some checks failed
CI and deploy / migration-check (push) Failing after 11s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-26 00:27:36 +02:00
parent f6e239aa8d
commit 5d7d7e2823
5 changed files with 160 additions and 13 deletions

View File

@@ -0,0 +1,20 @@
-- Plánovač: vyšší strop SoC než provozní max, relaxované dno při extrémně záporném buy, práh z OTE horizontu.
ALTER TABLE ems.asset_battery
ADD COLUMN IF NOT EXISTS planner_max_soc_percent NUMERIC(5, 2),
ADD COLUMN IF NOT EXISTS planner_discharge_floor_percent NUMERIC(5, 2),
ADD COLUMN IF NOT EXISTS planner_extreme_buy_threshold_czk_kwh NUMERIC(10, 4) DEFAULT -5.0;
COMMENT ON COLUMN ems.asset_battery.planner_max_soc_percent IS
'Horní mez SoC (%) pro LP; NULL = použij max_soc_percent. Typicky 100 pro plné využití kapacity při silně záporném nákupu.';
COMMENT ON COLUMN ems.asset_battery.planner_discharge_floor_percent IS
'Dolní mez SoC (%) pro LP při aktivaci extrémně záporného nákupu v lookahead; NULL = použij min_soc_percent.';
COMMENT ON COLUMN ems.asset_battery.planner_extreme_buy_threshold_czk_kwh IS
'Prah effective buy (Kč/kWh): pokud min buy v lookahead <= prah, LP smí snížit SoC k planner_discharge_floor_percent.';
-- home-01: plánovat až na 100 % (provozní max_soc může zůstat 95 %)
UPDATE ems.asset_battery
SET planner_max_soc_percent = 100
WHERE site_id = 2 AND planner_max_soc_percent IS NULL;

View File

@@ -36,6 +36,11 @@ begin
'arb_floor_wh', (ab.reserve_soc_percent / 100.0 * ab.usable_capacity_wh)::numeric,
'reserve_soc_wh', (ab.reserve_soc_percent / 100.0 * ab.usable_capacity_wh)::numeric,
'soc_max_wh', (ab.max_soc_percent / 100.0 * ab.usable_capacity_wh)::numeric,
'planner_soc_max_wh', (
coalesce(ab.planner_max_soc_percent, ab.max_soc_percent) / 100.0 * ab.usable_capacity_wh
)::numeric,
'planner_extreme_buy_threshold_czk_kwh', coalesce(ab.planner_extreme_buy_threshold_czk_kwh, -5.0),
'planner_discharge_floor_percent', ab.planner_discharge_floor_percent,
'charge_efficiency', ab.charge_efficiency,
'discharge_efficiency', ab.discharge_efficiency,
'degradation_cost_czk_kwh', ab.degradation_cost_czk_kwh,

View File

@@ -41,6 +41,12 @@ declare
v_discharge_target_wh numeric;
v_cum numeric;
r_slot record;
v_n_am int;
v_n_pm int;
v_chg_am_wh numeric;
v_chg_pm_wh numeric;
v_dis_am_wh numeric;
v_dis_pm_wh numeric;
begin
drop table if exists _ems_plan_slot_wk;
create temp table _ems_plan_slot_wk on commit drop as
@@ -252,7 +258,7 @@ begin
coalesce(ab.discharge_slot_buffer, 0::numeric),
ab.usable_capacity_wh::numeric,
(ab.min_soc_percent / 100.0 * ab.usable_capacity_wh)::numeric,
(ab.max_soc_percent / 100.0 * ab.usable_capacity_wh)::numeric,
(coalesce(ab.planner_max_soc_percent, ab.max_soc_percent) / 100.0 * ab.usable_capacity_wh)::numeric,
greatest(coalesce(ab.charge_efficiency, 1::numeric), 0.0001::numeric),
least(
coalesce(ai.max_battery_charge_w, ai.max_charge_power_w),
@@ -302,6 +308,40 @@ begin
v_grid_target_wh := v_energy_to_fill * v_charge_buf;
v_discharge_target_wh := v_exportable * v_discharge_buf;
-- Rozpočet na půl dne (Europe/Prague): 00:0012:00 vs 12:0024:00; chybějící segment dostane celý budget.
select
coalesce(
count(*) filter (
where extract(hour from wk.interval_start at time zone 'Europe/Prague') < 12
),
0
)::int,
coalesce(
count(*) filter (
where extract(hour from wk.interval_start at time zone 'Europe/Prague') >= 12
),
0
)::int
into v_n_am, v_n_pm
from _ems_plan_slot_wk wk;
if v_n_am <= 0 then
v_chg_am_wh := 0;
v_chg_pm_wh := v_grid_target_wh;
v_dis_am_wh := 0;
v_dis_pm_wh := v_discharge_target_wh;
elsif v_n_pm <= 0 then
v_chg_am_wh := v_grid_target_wh;
v_chg_pm_wh := 0;
v_dis_am_wh := v_discharge_target_wh;
v_dis_pm_wh := 0;
else
v_chg_am_wh := v_grid_target_wh / 2.0;
v_chg_pm_wh := v_grid_target_wh - v_chg_am_wh;
v_dis_am_wh := v_discharge_target_wh / 2.0;
v_dis_pm_wh := v_discharge_target_wh - v_dis_am_wh;
end if;
-- charge mask (sloupce temp tabulky kvalifikujeme: RETURNS TABLE dělá PL proměnné stejných jmen)
if v_charge_buf <= 0 then
update _ems_plan_slot_wk wk set allow_charge = true;
@@ -314,9 +354,23 @@ begin
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
order by wk.buy_price, wk.slot_ord
loop
exit when v_cum >= v_grid_target_wh;
exit when v_cum >= v_chg_am_wh;
exit when v_per_slot_charge_wh <= 0;
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;
end loop;
v_cum := 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
order by wk.buy_price, wk.slot_ord
loop
exit when v_cum >= v_chg_pm_wh;
exit when v_per_slot_charge_wh <= 0;
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;
@@ -334,9 +388,22 @@ begin
for r_slot in
select wk.slot_ord
from _ems_plan_slot_wk wk
where extract(hour from wk.interval_start at time zone 'Europe/Prague') < 12
order by wk.sell_price desc, wk.slot_ord desc
loop
exit when v_cum >= v_discharge_target_wh;
exit when v_cum >= v_dis_am_wh;
exit when v_per_slot_discharge_wh <= 0;
update _ems_plan_slot_wk wk set allow_discharge_export = true where wk.slot_ord = r_slot.slot_ord;
v_cum := v_cum + v_per_slot_discharge_wh;
end loop;
v_cum := 0;
for r_slot in
select wk.slot_ord
from _ems_plan_slot_wk wk
where extract(hour from wk.interval_start at time zone 'Europe/Prague') >= 12
order by wk.sell_price desc, wk.slot_ord desc
loop
exit when v_cum >= v_dis_pm_wh;
exit when v_per_slot_discharge_wh <= 0;
update _ems_plan_slot_wk wk set allow_discharge_export = true where wk.slot_ord = r_slot.slot_ord;
v_cum := v_cum + v_per_slot_discharge_wh;
@@ -363,4 +430,6 @@ 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).';
'15min sloty s cenami, forecastem, baseline a maskami proti mikro-cyklu (charge/discharge-export). '
'Masky charge/discharge-export se berou zvlášť pro 0012 a 1224 Europe/Prague (polovina budgetu na segment). '
'Strop SoC pro výpočet energie k dobití: coalesce(planner_max_soc_percent, max_soc_percent).';