Files
ems/db/routines/R__068_fn_economics_daily_month.sql
Dusan Vojacek 8114ec5e63
Some checks failed
CI and deploy / migration-check (push) Failing after 10s
CI and deploy / deploy (push) Has been skipped
FIX RYCHLOST EKONOMIKA
2026-04-27 19:47:18 +02:00

209 lines
7.5 KiB
SQL
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.
-- 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).
create or replace function ems.fn_economics_daily_for_window(
p_site_id int,
p_ts_from timestamptz,
p_ts_to timestamptz
)
returns table (
site_id int,
day_local date,
interval_count int,
import_kwh numeric,
export_kwh numeric,
pv_kwh numeric,
load_kwh numeric,
pv_self_consumption_kwh numeric,
ev_kwh numeric,
hp_kwh numeric,
grid_import_cashflow_czk numeric,
grid_export_revenue_czk numeric,
import_cost_czk numeric,
export_revenue_czk numeric,
net_cost_czk numeric,
green_bonus_czk numeric,
total_balance_czk numeric,
planned_net_cost_czk numeric,
planned_balance_czk numeric,
deviation_cost_czk numeric
)
language sql
stable
as $fn$
with slots as (
select
ai.site_id,
(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.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
from ems.audit_interval ai
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
),
daily_agg as (
select
s.site_id,
s.day_local,
count(*)::int as interval_count,
round(sum(s.import_kwh), 3) as import_kwh,
round(sum(s.export_kwh), 3) as export_kwh,
round(sum(greatest(s.actual_pv_power_w, 0)::numeric / 4000), 3) as pv_kwh,
round(sum(greatest(s.actual_load_power_w, 0)::numeric / 4000), 3) as load_kwh,
round(sum(greatest(s.actual_ev_power_w, 0)::numeric / 4000), 3) as ev_kwh,
round(sum(greatest(s.actual_heat_pump_power_w, 0)::numeric / 4000), 3) as hp_kwh,
round(
sum(greatest(s.actual_pv_power_w, 0)::numeric / 4000) - sum(s.export_kwh),
3
) as pv_self_consumption_kwh,
round(sum(s.grid_import_cashflow_czk), 2) as grid_import_cashflow_czk,
round(sum(s.grid_export_revenue_czk), 2) as grid_export_revenue_czk,
round(sum(case when s.dynamic_cost_czk > 0 then s.dynamic_cost_czk else 0 end), 2) as import_cost_czk,
round(sum(case when s.dynamic_cost_czk < 0 then abs(s.dynamic_cost_czk) else 0 end), 2) as export_revenue_czk,
round(sum(s.dynamic_cost_czk), 2) as net_cost_czk,
round(coalesce(sum(s.green_bonus_czk), 0), 2) as green_bonus_czk,
round(-sum(s.dynamic_cost_czk) + coalesce(sum(s.green_bonus_czk), 0), 2) as total_balance_czk,
round(sum(s.planned_cost_czk), 2) as planned_net_cost_czk,
round(-coalesce(sum(s.planned_cost_czk), 0), 2) as planned_balance_czk,
round(sum(s.dynamic_cost_czk) - coalesce(sum(s.planned_cost_czk), 0), 2) as deviation_cost_czk
from slots s
group by s.site_id, s.day_local
)
select
d.site_id,
d.day_local,
d.interval_count,
d.import_kwh,
d.export_kwh,
d.pv_kwh,
d.load_kwh,
d.pv_self_consumption_kwh,
d.ev_kwh,
d.hp_kwh,
d.grid_import_cashflow_czk,
d.grid_export_revenue_czk,
d.import_cost_czk,
d.export_revenue_czk,
d.net_cost_czk,
d.green_bonus_czk,
d.total_balance_czk,
d.planned_net_cost_czk,
d.planned_balance_czk,
d.deviation_cost_czk
from daily_agg d
order by d.day_local;
$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.';
create or replace function ems.fn_economics_daily_month(
p_site_id int,
p_month_start date,
p_month_end date
)
returns jsonb
language sql
stable
as $fn$
select jsonb_build_object(
'has_green_bonus',
exists (
select 1
from ems.asset_pv_array apv
where apv.site_id = p_site_id
and apv.green_bonus_czk_kwh is not null
),
'days',
coalesce(
(
select jsonb_agg(sub.day_row order by sub.day_local)
from (
select
r.day_local,
jsonb_build_object(
'day', r.day_local,
'interval_count', r.interval_count,
'import_kwh', r.import_kwh,
'export_kwh', r.export_kwh,
'pv_kwh', r.pv_kwh,
'load_kwh', r.load_kwh,
'pv_self_consumption_kwh', r.pv_self_consumption_kwh,
'ev_kwh', r.ev_kwh,
'hp_kwh', r.hp_kwh,
'import_cost_czk',
case when l.site_id is not null then l.import_cost_czk else r.import_cost_czk end,
'export_revenue_czk',
case when l.site_id is not null then l.export_revenue_czk else r.export_revenue_czk end,
'grid_import_cashflow_czk',
coalesce(l.grid_import_cashflow_czk, r.grid_import_cashflow_czk),
'grid_export_revenue_czk',
coalesce(l.grid_export_revenue_czk, r.grid_export_revenue_czk),
'net_cost_czk',
case when l.site_id is not null then l.net_cost_czk else r.net_cost_czk end,
'green_bonus_czk',
case when l.site_id is not null then l.green_bonus_czk else r.green_bonus_czk end,
'total_balance_czk',
case when l.site_id is not null then l.total_balance_czk else r.total_balance_czk end,
'planned_balance_czk', r.planned_balance_czk,
'deviation_cost_czk', r.deviation_cost_czk,
'is_locked', (l.site_id is not null)
) as day_row
from ems.fn_economics_daily_for_window(
p_site_id,
(p_month_start::timestamp at time zone 'Europe/Prague'),
(p_month_end::timestamp at time zone 'Europe/Prague')
) r
left join ems.audit_day_lock l
on l.site_id = r.site_id
and l.day_local = r.day_local
order by r.day_local
) sub
),
'[]'::jsonb
)
);
$fn$;
comment on function ems.fn_economics_daily_month(int, date, date) is
'Měsíční denní ekonomika + lock merge jako JSON (GET /economics/daily).';