diff --git a/db/routines/R__068_fn_economics_daily_month.sql b/db/routines/R__068_fn_economics_daily_month.sql index cf9cfcf..0bb81cf 100644 --- a/db/routines/R__068_fn_economics_daily_month.sql +++ b/db/routines/R__068_fn_economics_daily_month.sql @@ -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,