Files
ems/db/routines/R__063_fn_load_planning_slots_full.sql
Dusan Vojacek 66834ddfa6
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped
dalsi rozvolneni at vic jedeme arbitraz
2026-05-21 14:54:46 +02:00

681 lines
22 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- sloty pro LP: ceny, forecast, baseline, EV připojení + masky allow_charge / allow_discharge_export
-- DROP: změna RETURNS TABLE (nové sloupce) — CREATE OR REPLACE na rozdílný row type v PG neprojde.
-- Musí být plná signatura (pg_proc ukládá int jako integer); DROP bez () funkci se směrem nemaže.
drop function if exists ems.fn_load_planning_slots_full(
integer, timestamp with time zone, timestamp with time zone, numeric
);
create or replace function ems.fn_load_planning_slots_full(
p_site_id int,
p_from timestamptz,
p_to timestamptz,
p_current_soc_wh numeric
)
returns table (
slot_ord int,
interval_start timestamptz,
buy_price numeric,
sell_price numeric,
is_predicted_price boolean,
pv_a_forecast_w int,
pv_b_forecast_w int,
load_baseline_w int,
ev1_connected boolean,
ev2_connected boolean,
allow_charge 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,
charge_acquisition_buy_czk_kwh numeric,
charge_acquisition_cutoff_at timestamptz
)
language plpgsql
volatile
as $fn$
declare
v_charge_buf numeric;
v_discharge_buf numeric;
v_usable numeric;
v_min_soc_wh numeric;
v_soc_max_wh numeric;
v_energy_to_fill numeric;
v_exportable numeric;
v_charge_eff numeric;
v_discharge_eff numeric;
v_max_charge_w numeric;
v_max_discharge_w numeric;
v_per_slot_charge_wh numeric;
v_per_slot_discharge_wh numeric;
v_grid_target_wh numeric;
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_reserve_wh numeric;
v_daytime_en boolean;
v_night_buf_pct numeric;
v_degrad_czk_kwh numeric;
v_ref_buy_czk_kwh numeric;
v_ref_buy_am_czk_kwh numeric;
v_ref_buy_pm_czk_kwh numeric;
v_purchase_pricing_mode text;
v_lookahead_slots int := 4;
v_grid_charge_cap_am int;
v_grid_charge_cap_pm int;
v_buy_lookahead_eps numeric := 0.05;
v_buy_charge_band_czk_kwh numeric := 0.40;
v_grid_filled_wh numeric := 0;
v_pv_layer_cap_wh numeric;
v_grid_slots_am int := 0;
v_grid_slots_pm int := 0;
v_acquisition_cutoff timestamptz;
v_charge_acquisition numeric;
v_est_grid_wh numeric;
v_est_pv_wh numeric;
v_est_grid_cost numeric;
v_est_pv_cost numeric;
v_export_window_start timestamptz;
v_plan_day_prague date;
begin
v_plan_day_prague := (p_from at time zone 'Europe/Prague')::date;
drop table if exists _ems_plan_slot_wk;
create temp table _ems_plan_slot_wk on commit drop as
with
slot_spine as (
select gs as interval_start
from generate_series(
p_from,
(p_to - interval '15 minutes')::timestamptz,
interval '15 minutes'
) as gs
),
pv_canon as (
select
r.interval_start,
coalesce(r.pv_a_forecast_canonical_w, 0)::int as pv_a_forecast_w,
coalesce(r.pv_b_forecast_canonical_w, 0)::int as pv_b_forecast_w
from jsonb_to_recordset(
ems.fn_forecast_pv_slots_range_canonical_ab(
p_site_id,
p_from,
p_to,
now(),
greatest(p_from, now() - interval '120 days'),
now()
)
) as r(
interval_start timestamptz,
pv_a_forecast_canonical_w bigint,
pv_b_forecast_canonical_w bigint
)
)
select
(row_number() over (order by s.interval_start) - 1)::int as slot_ord,
s.interval_start,
coalesce(
ep.effective_buy_price_czk_kwh,
ems.fn_get_predicted_price(p_site_id, s.interval_start)
) as buy_price,
coalesce(
ep.effective_sell_price_czk_kwh,
ems.fn_get_predicted_price(p_site_id, s.interval_start) * 0.85
) as sell_price,
(ep.effective_buy_price_czk_kwh is null) as is_predicted_price,
coalesce(pv.pv_a_forecast_w, 0)::int as pv_a_forecast_w,
coalesce(pv.pv_b_forecast_w, 0)::int as pv_b_forecast_w,
coalesce(
(
select bs.avg_power_w
from ems.consumption_baseline_stats bs
where bs.site_id = p_site_id
and bs.day_of_week = extract(
dow from s.interval_start at time zone 'Europe/Prague'
)::int
and bs.hour_of_day = extract(
hour from s.interval_start at time zone 'Europe/Prague'
)::int
limit 1
),
500
)::int as load_baseline_w,
(coalesce(ev1.status, 'available') not in ('available', 'unavailable')) as ev1_connected,
(coalesce(ev2.status, 'available') not in ('available', 'unavailable')) as ev2_connected,
greatest(
0,
coalesce(pv.pv_a_forecast_w, 0) + coalesce(pv.pv_b_forecast_w, 0)
- coalesce(
(
select bs.avg_power_w
from ems.consumption_baseline_stats bs
where bs.site_id = p_site_id
and bs.day_of_week = extract(
dow from s.interval_start at time zone 'Europe/Prague'
)::int
and bs.hour_of_day = extract(
hour from s.interval_start at time zone 'Europe/Prague'
)::int
limit 1
),
500
)
)::int as pv_surplus_w,
false::boolean as allow_charge,
false::boolean as allow_discharge_export
from slot_spine s
left join pv_canon pv on pv.interval_start = s.interval_start
left join ems.vw_site_effective_price ep
on ep.site_id = p_site_id and ep.interval_start = s.interval_start
left join lateral (
select t.status
from ems.telemetry_ev_charger t
join ems.asset_ev_charger ch on ch.id = t.charger_id
where t.site_id = p_site_id and ch.code = 'ev-charger-1'
order by t.measured_at desc
limit 1
) ev1 on true
left join lateral (
select t.status
from ems.telemetry_ev_charger t
join ems.asset_ev_charger ch on ch.id = t.charger_id
where t.site_id = p_site_id and ch.code = 'ev-charger-2'
order by t.measured_at desc
limit 1
) ev2 on true;
if not exists (select 1 from _ems_plan_slot_wk) then
raise exception 'No planning slots available check market prices and horizon settings';
end if;
select
coalesce(ab.charge_slot_buffer, 0::numeric),
coalesce(ab.discharge_slot_buffer, 0::numeric),
ab.usable_capacity_wh::numeric,
(ab.min_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),
coalesce(
ab.bms_max_charge_w,
case when ab.max_charge_c_rate is not null
then (ab.max_charge_c_rate * ab.usable_capacity_wh)::bigint
end,
coalesce(ai.max_battery_charge_w, ai.max_charge_power_w)
)
)::numeric,
least(
coalesce(ai.max_battery_discharge_w, ai.max_discharge_power_w),
coalesce(
ab.bms_max_discharge_w,
case when ab.max_discharge_c_rate is not null
then (ab.max_discharge_c_rate * ab.usable_capacity_wh)::bigint
end,
coalesce(ai.max_battery_discharge_w, ai.max_discharge_power_w)
)
)::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),
coalesce(ab.degradation_cost_czk_kwh, 0.15::numeric)
into
v_charge_buf,
v_discharge_buf,
v_usable,
v_min_soc_wh,
v_soc_max_wh,
v_charge_eff,
v_max_charge_w,
v_max_discharge_w,
v_discharge_eff,
v_reserve_wh,
v_daytime_en,
v_night_buf_pct,
v_degrad_czk_kwh
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
order by ab.id
limit 1;
if v_usable is null then
raise exception 'No asset_battery for site_id=%', p_site_id;
end if;
select coalesce(smc.purchase_pricing_mode, 'spot')
into v_purchase_pricing_mode
from ems.site_market_config smc
where smc.site_id = p_site_id
and smc.valid_from <= p_from
and (smc.valid_to is null or smc.valid_to > p_from)
order by smc.valid_from desc
limit 1;
v_purchase_pricing_mode := coalesce(v_purchase_pricing_mode, 'spot');
v_per_slot_charge_wh := v_max_charge_w * v_charge_eff * 0.25;
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;
-- 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: globální min (export brána) + per AM/PM pás (grid nabíjení).
select coalesce(min(wk.buy_price), 0)
into v_ref_buy_czk_kwh
from _ems_plan_slot_wk wk;
select coalesce(min(wk.buy_price), v_ref_buy_czk_kwh)
into v_ref_buy_am_czk_kwh
from _ems_plan_slot_wk wk
where extract(hour from wk.interval_start at time zone 'Europe/Prague') < 12;
select coalesce(min(wk.buy_price), v_ref_buy_czk_kwh)
into v_ref_buy_pm_czk_kwh
from _ems_plan_slot_wk wk
where extract(hour from wk.interval_start at time zone 'Europe/Prague') >= 12;
-- První výkupní okno: sell nad min(buy) **téhož kalendářního dne** (Prague), ne globální
-- min(buy) zítra (NT) — jinak okno začne už ~15:30 a dnešní PM grid dostane 1 slot.
select min(wk.interval_start)
into v_export_window_start
from _ems_plan_slot_wk wk
where wk.sell_price > (
select coalesce(min(w2.buy_price), wk.buy_price) + v_degrad_czk_kwh
from _ems_plan_slot_wk w2
where (w2.interval_start at time zone 'Europe/Prague')::date
= (wk.interval_start at time zone 'Europe/Prague')::date
);
-- 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,
add column if not exists allow_grid_charge boolean default false;
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
and (
v_export_window_start is null
or w2.interval_start < v_export_window_start
)
),
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 0012 vs 1224).
-- 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;
elsif v_n_pm <= 0 then
v_chg_am_wh := v_grid_target_wh;
v_chg_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;
end if;
if v_per_slot_charge_wh > 0 then
v_grid_charge_cap_am := greatest(
1,
least(24, ceil(v_chg_am_wh / v_per_slot_charge_wh)::int)
);
v_grid_charge_cap_pm := greatest(
1,
least(24, ceil(v_chg_pm_wh / v_per_slot_charge_wh)::int)
);
else
v_grid_charge_cap_am := 6;
v_grid_charge_cap_pm := 6;
end if;
-- charge mask: grid arbitráž (B) před FVE (A); AM/PM rozpočet Wh zůstává 50/50.
--
-- B) Grid ze sítě: spot, buy v pásmu AM/PM ≤ min(buy v pásmu)+band, lookahead VT→NT;
-- i při pv_surplus>0; cap slotů ∝ rozpočet Wh / per_slot_charge_wh.
-- A) PV-surplus: store_score DESC, doplní jen zbytek do energy_to_fill po vrstvě B.
if v_charge_buf <= 0 then
update _ems_plan_slot_wk wk set allow_charge = true;
elsif v_energy_to_fill <= 0 then
update _ems_plan_slot_wk wk set allow_charge = true;
else
update _ems_plan_slot_wk wk set allow_charge = false;
v_grid_filled_wh := 0;
if v_purchase_pricing_mode <> 'fixed' then
-- B) Grid AM (dříve než PV, vlastní 50 % rozpočtu Wh)
v_cum := 0;
v_grid_slots_am := 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
and wk.buy_price <= v_ref_buy_am_czk_kwh + v_buy_charge_band_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
case
when (wk.interval_start at time zone 'Europe/Prague')::date = v_plan_day_prague
then 0
else 1
end,
case
when v_export_window_start is not null
and wk.interval_start < v_export_window_start
then 0
else 1
end,
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, allow_grid_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;
v_grid_filled_wh := v_grid_filled_wh + v_cum;
-- B) Grid PM
v_cum := 0;
v_grid_slots_pm := 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
and wk.buy_price <= v_ref_buy_pm_czk_kwh + v_buy_charge_band_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
case
when (wk.interval_start at time zone 'Europe/Prague')::date = v_plan_day_prague
then 0
else 1
end,
case
when v_export_window_start is not null
and wk.interval_start < v_export_window_start
then 0
else 1
end,
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, allow_grid_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;
v_grid_filled_wh := v_grid_filled_wh + v_cum;
end if;
-- 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);
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
order by 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;
v_cum := v_cum + least(r_slot.pv_surplus_w, v_max_charge_w) * v_charge_eff * 0.25;
end loop;
end if;
-- discharge-export mask
if v_discharge_buf <= 0 then
update _ems_plan_slot_wk wk set allow_discharge_export = true;
elsif v_exportable <= 0 then
update _ems_plan_slot_wk wk set allow_discharge_export = false;
else
update _ems_plan_slot_wk wk set allow_discharge_export = false;
v_cum := 0;
for r_slot in
select wk.slot_ord
from _ems_plan_slot_wk wk
where (
case
when v_purchase_pricing_mode = 'fixed' then
wk.sell_price > v_degrad_czk_kwh
else
wk.sell_price > v_ref_buy_czk_kwh + v_degrad_czk_kwh
end
)
order by wk.sell_price desc, wk.slot_ord desc
loop
exit when v_cum >= v_discharge_target_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;
end if;
-- Vážená acquisition cena zásoby (grid + FVE opportunity) jen pro sloty PŘED prvním
-- plánovaným exportem z baterie — nepočítá nákup po večerním/nočním vybití do sítě.
select min(wk.interval_start)
into v_acquisition_cutoff
from _ems_plan_slot_wk wk
where wk.allow_discharge_export;
-- Acquisition: jen grid vrstva B (ne odpolední FVE z vrstvy A) před 1. exportem.
select
coalesce(sum(
case
when wk.allow_grid_charge
and (
v_acquisition_cutoff is null
or wk.interval_start < v_acquisition_cutoff
)
then v_per_slot_charge_wh
else 0
end
), 0),
coalesce(sum(
case
when wk.allow_grid_charge
and (
v_acquisition_cutoff is null
or wk.interval_start < v_acquisition_cutoff
)
then wk.buy_price * v_per_slot_charge_wh
else 0
end
), 0),
min(
case
when wk.allow_grid_charge
and (
v_acquisition_cutoff is null
or wk.interval_start < v_acquisition_cutoff
)
then wk.buy_price
else null
end
)
into
v_est_grid_wh,
v_est_grid_cost,
v_charge_acquisition
from _ems_plan_slot_wk wk;
v_est_pv_wh := 0;
v_est_pv_cost := 0;
if v_est_grid_wh > 0 then
v_charge_acquisition := v_est_grid_cost / v_est_grid_wh;
elsif v_charge_acquisition is null then
v_charge_acquisition := coalesce(
(v_ref_buy_am_czk_kwh + v_ref_buy_pm_czk_kwh) / 2.0,
v_ref_buy_czk_kwh
);
end if;
-- v_charge_acquisition z min(grid) zůstane, pokud je jen jeden grid slot před exportem
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
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,
v_charge_acquisition as charge_acquisition_buy_czk_kwh,
v_acquisition_cutoff as charge_acquisition_cutoff_at
from enriched e
order by e.slot_ord;
end;
$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_sellsellmax(0,buysell)); 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:0006:00 Europe/Prague), safety_soc_target_wh (619), '
'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. '
'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.';