speedup ekonomics
Some checks failed
CI and deploy / migration-check (push) Failing after 9s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-27 19:57:35 +02:00
parent 8114ec5e63
commit 542cd9a73c

View File

@@ -1,6 +1,5 @@
-- Denní ekonomika za časové okno [p_ts_from, p_ts_to) bez joinu na vw_site_effective_price
-- (ten dělá cross join mip × site_market_config a je extrémně drahý při agregaci přes vw_economics_daily).
-- Ceny = fn_effective_buy_price / fn_effective_sell_price (stejně jako ve vw_site_effective_price).
-- Denní ekonomika za [p_ts_from, p_ts_to): jen audit_interval v okně + joiny (žádný vw_site_effective_price,
-- žádné volání fn_effective_* per řádek — musí zůstat v souladu s R__011_fn_effective_price.sql).
create or replace function ems.fn_economics_daily_for_window(
p_site_id int,
@@ -32,54 +31,200 @@ returns table (
language sql
stable
as $fn$
with slots as (
with slot0 as (
select
ai.site_id,
ai.interval_start,
(date_trunc('day', ai.interval_start at time zone 'Europe/Prague'))::date as day_local,
round(
coalesce(ai.actual_grid_import_wh, greatest(ai.actual_grid_power_w, 0)::numeric / 4) / 1000,
4
) as import_kwh,
round(
coalesce(ai.actual_grid_export_wh, abs(least(ai.actual_grid_power_w, 0))::numeric / 4) / 1000,
4
) as export_kwh,
round(
coalesce(ai.actual_grid_import_wh, greatest(ai.actual_grid_power_w, 0)::numeric / 4) / 1000.0
* coalesce(pr.buy_p, 0),
4
) as grid_import_cashflow_czk,
round(
coalesce(ai.actual_grid_export_wh, abs(least(ai.actual_grid_power_w, 0))::numeric / 4) / 1000.0
* coalesce(pr.sell_p, 0),
4
) as grid_export_revenue_czk,
round(
coalesce(ai.actual_grid_import_wh, greatest(ai.actual_grid_power_w, 0)::numeric / 4) / 1000.0
* coalesce(pr.buy_p, 0)
- coalesce(ai.actual_grid_export_wh, abs(least(ai.actual_grid_power_w, 0))::numeric / 4) / 1000.0
* coalesce(pr.sell_p, 0),
4
) as dynamic_cost_czk,
ai.actual_grid_import_wh,
ai.actual_grid_power_w,
ai.actual_grid_export_wh,
ai.green_bonus_czk,
pi.expected_cost_czk as planned_cost_czk,
ai.actual_pv_power_w,
ai.actual_load_power_w,
ai.actual_ev_power_w,
ai.actual_heat_pump_power_w
ai.actual_heat_pump_power_w,
smc.id as market_config_id,
smc.purchase_pricing_mode,
smc.buy_fixed_energy_nt_czk_kwh,
smc.buy_fixed_vt_surcharge_czk_kwh,
smc.buy_margin_fixed_czk,
smc.buy_margin_percent,
smc.sell_margin_fixed_czk,
smc.sell_margin_percent,
smc.system_services_czk_kwh,
smc.ote_fee_czk_kwh,
smc.hdo_code_id,
smc.tariff_id,
coalesce(dt.vat_rate, 0.21) as vat_rate,
mip.buy_raw_price_czk_kwh as buy_spot,
mip.sell_raw_price_czk_kwh as sell_spot,
pi.expected_cost_czk as planned_cost_czk
from ems.audit_interval ai
left join lateral (
select smc_inner.*
from ems.site_market_config smc_inner
where smc_inner.site_id = ai.site_id
and smc_inner.valid_from <= ai.interval_start
and (smc_inner.valid_to is null or smc_inner.valid_to > ai.interval_start)
order by smc_inner.valid_from desc
limit 1
) smc on true
left join ems.distribution_tariff dt on dt.id = smc.tariff_id
left join lateral (
select
mip_inner.buy_raw_price_czk_kwh,
mip_inner.sell_raw_price_czk_kwh
from ems.market_interval_price mip_inner
where mip_inner.interval_start = ai.interval_start
and mip_inner.market_source in ('OTE_CZ', 'OTE_CZ_DAM')
limit 1
) mip on true
left join ems.planning_interval pi
on pi.run_id = ai.planning_run_id
and pi.interval_start = ai.interval_start
cross join lateral (
select
ems.fn_effective_buy_price(ai.site_id, ai.interval_start) as buy_p,
ems.fn_effective_sell_price(ai.site_id, ai.interval_start) as sell_p
) pr
where ai.site_id = p_site_id
and ai.interval_start >= p_ts_from
and ai.interval_start < p_ts_to
),
slot1 as (
select
s0.*,
case
when s0.hdo_code_id is null then false
else exists (
select 1
from ems.hdo_code_window w
where w.hdo_code_id = s0.hdo_code_id
and (
w.day_type = 'all'
or (
w.day_type = 'workday'
and extract(dow from s0.interval_start at time zone 'Europe/Prague') between 1 and 5
)
or (
w.day_type = 'weekend'
and extract(dow from s0.interval_start at time zone 'Europe/Prague') in (0, 6)
)
)
and w.rate_type = 'VT'
and (s0.interval_start at time zone 'Europe/Prague')::time >= w.window_from
and (s0.interval_start at time zone 'Europe/Prague')::time < w.window_to
)
end as is_vt
from slot0 s0
),
slot2 as (
select
s1.*,
dr.dist_rate
from slot1 s1
left join lateral (
select dtr.price_czk_kwh as dist_rate
from ems.distribution_tariff_rate dtr
where dtr.tariff_id = s1.tariff_id
and dtr.rate_type = case when s1.is_vt then 'VT'::text else 'NT'::text end
and dtr.valid_from <= s1.interval_start::date
and (dtr.valid_to is null or dtr.valid_to > s1.interval_start::date)
order by dtr.valid_from desc
limit 1
) dr on s1.tariff_id is not null
),
slot3 as (
select
s2.*,
case
when upper(trim(coalesce(s2.purchase_pricing_mode, ''))) = 'FIXED'
and s2.buy_fixed_energy_nt_czk_kwh is not null
then
s2.buy_fixed_energy_nt_czk_kwh
+ case
when s2.is_vt then coalesce(s2.buy_fixed_vt_surcharge_czk_kwh, 0)
else 0::numeric
end
when s2.buy_spot is null then null::numeric
else s2.buy_spot
end as energy_czk
from slot2 s2
),
slot4 as (
select
s3.*,
case
when s3.market_config_id is null then null::numeric
when s3.energy_czk is null then null::numeric
else
coalesce(s3.buy_margin_fixed_czk, 0)
+ (s3.energy_czk * coalesce(s3.buy_margin_percent, 0) / 100.0)
end as buy_margin_part
from slot3 s3
),
slot5 as (
select
s4.*,
case
when s4.market_config_id is null then null::numeric
when s4.energy_czk is null then null::numeric
else round(
(
s4.energy_czk
+ coalesce(s4.dist_rate, 0)
+ coalesce(s4.system_services_czk_kwh, 0)
+ coalesce(s4.ote_fee_czk_kwh, 0)
+ s4.buy_margin_part
) * (1 + coalesce(s4.vat_rate, 0.21)),
6
)
end as buy_p,
case
when s4.market_config_id is null then null::numeric
when s4.sell_spot is null then null::numeric
else round(
s4.sell_spot
+ coalesce(s4.sell_margin_fixed_czk, 0)
+ (s4.sell_spot * coalesce(s4.sell_margin_percent, 0) / 100.0),
6
)
end as sell_p
from slot4 s4
),
slots as (
select
s5.site_id,
s5.day_local,
round(
coalesce(s5.actual_grid_import_wh, greatest(s5.actual_grid_power_w, 0)::numeric / 4) / 1000,
4
) as import_kwh,
round(
coalesce(s5.actual_grid_export_wh, abs(least(s5.actual_grid_power_w, 0))::numeric / 4) / 1000,
4
) as export_kwh,
round(
coalesce(s5.actual_grid_import_wh, greatest(s5.actual_grid_power_w, 0)::numeric / 4) / 1000.0
* coalesce(s5.buy_p, 0),
4
) as grid_import_cashflow_czk,
round(
coalesce(s5.actual_grid_export_wh, abs(least(s5.actual_grid_power_w, 0))::numeric / 4) / 1000.0
* coalesce(s5.sell_p, 0),
4
) as grid_export_revenue_czk,
round(
coalesce(s5.actual_grid_import_wh, greatest(s5.actual_grid_power_w, 0)::numeric / 4) / 1000.0
* coalesce(s5.buy_p, 0)
- coalesce(s5.actual_grid_export_wh, abs(least(s5.actual_grid_power_w, 0))::numeric / 4) / 1000.0
* coalesce(s5.sell_p, 0),
4
) as dynamic_cost_czk,
s5.green_bonus_czk,
s5.planned_cost_czk,
s5.actual_pv_power_w,
s5.actual_load_power_w,
s5.actual_ev_power_w,
s5.actual_heat_pump_power_w
from slot5 s5
),
daily_agg as (
select
s.site_id,
@@ -134,7 +279,7 @@ as $fn$
$fn$;
comment on function ems.fn_economics_daily_for_window is
'Denní souhrn ekonomiky pro site v polovičně otevřeném UTC okně [from, to); bez vw_site_effective_price.';
'Denní souhrn ekonomiky [from, to); ceny inline dle fn_effective_buy/sell (R__011), bez PL volání per slot.';
create or replace function ems.fn_economics_daily_month(
p_site_id int,