Files
ems/db/routines/R__011_fn_effective_price.sql
Dusan Vojacek ed88ef8910
Some checks failed
CI and deploy / migration-check (push) Failing after 11s
CI and deploy / deploy (push) Has been skipped
oprava import/export kwh
2026-05-01 14:58:29 +02:00

309 lines
9.9 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.
-- =============================================================
-- R__011_fn_effective_price.sql
-- EMS Platform funkce pro výpočet efektivní ceny per site
-- Repeatable migration nasazuje se při každé změně
-- =============================================================
-- Pomocné (audit/ekonomika): sjednocení import/export Wh — musí běžet před R__012/R__056.
create or replace function ems.fn_audit_grid_import_wh_for_economics(
p_import_wh numeric,
p_export_wh numeric,
p_grid_power_w int
)
returns numeric
language sql
stable
as $$
select case
when coalesce(p_import_wh, 0) > 0 and coalesce(p_export_wh, 0) > 0 then
coalesce(
p_import_wh,
greatest(coalesce(p_grid_power_w, 0), 0)::numeric / 4
)
when coalesce(p_export_wh, 0) = 0 then
greatest(
coalesce(
p_import_wh,
greatest(coalesce(p_grid_power_w, 0), 0)::numeric / 4
),
greatest(coalesce(p_grid_power_w, 0), 0)::numeric / 4
)
else
coalesce(
p_import_wh,
greatest(coalesce(p_grid_power_w, 0), 0)::numeric / 4
)
end;
$$;
create or replace function ems.fn_audit_grid_export_wh_for_economics(
p_import_wh numeric,
p_export_wh numeric,
p_grid_power_w int
)
returns numeric
language sql
stable
as $$
select case
when coalesce(p_import_wh, 0) > 0 and coalesce(p_export_wh, 0) > 0 then
coalesce(
p_export_wh,
abs(least(coalesce(p_grid_power_w, 0), 0))::numeric / 4
)
when coalesce(p_import_wh, 0) = 0 then
greatest(
coalesce(
p_export_wh,
abs(least(coalesce(p_grid_power_w, 0), 0))::numeric / 4
),
abs(least(coalesce(p_grid_power_w, 0), 0))::numeric / 4
)
else
coalesce(
p_export_wh,
abs(least(coalesce(p_grid_power_w, 0), 0))::numeric / 4
)
end;
$$;
comment on function ems.fn_audit_grid_import_wh_for_economics(numeric, numeric, int) is
'Import Wh pro audit/ekonomiku: u čistého importu max(uložený čítač, max(0,P_grid)×¼ h).';
comment on function ems.fn_audit_grid_export_wh_for_economics(numeric, numeric, int) is
'Export Wh pro audit/ekonomiku: u čistého exportu max(uložený čítač, |min(0,P_grid)|×¼ h).';
CREATE OR REPLACE FUNCTION ems.fn_effective_buy_price(
p_site_id INT,
p_interval_start TIMESTAMPTZ
)
RETURNS NUMERIC(10,6)
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
v_spot_price NUMERIC;
v_energy_czk NUMERIC;
v_dist_rate NUMERIC;
v_system_services NUMERIC;
v_ote_fee NUMERIC;
v_vat_rate NUMERIC;
v_buy_margin_fixed NUMERIC;
v_buy_margin_pct NUMERIC;
v_buy_margin NUMERIC;
v_is_vt BOOLEAN;
v_local_time TIME;
v_dow INT;
v_hdo_code_id INT;
v_tariff_id INT;
v_rate_type TEXT;
v_purchase_mode TEXT;
v_fixed_nt NUMERIC;
v_fixed_vt_sur NUMERIC;
BEGIN
SELECT
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.system_services_czk_kwh,
smc.ote_fee_czk_kwh,
smc.hdo_code_id,
smc.tariff_id,
dt.vat_rate
INTO
v_purchase_mode,
v_fixed_nt,
v_fixed_vt_sur,
v_buy_margin_fixed,
v_buy_margin_pct,
v_system_services,
v_ote_fee,
v_hdo_code_id,
v_tariff_id,
v_vat_rate
FROM ems.site_market_config smc
LEFT JOIN ems.distribution_tariff dt ON dt.id = smc.tariff_id
WHERE smc.site_id = p_site_id
AND smc.valid_from <= p_interval_start
AND (smc.valid_to IS NULL OR smc.valid_to > p_interval_start)
ORDER BY smc.valid_from DESC
LIMIT 1;
IF NOT FOUND THEN
RETURN NULL;
END IF;
SELECT buy_raw_price_czk_kwh INTO v_spot_price
FROM ems.market_interval_price
WHERE market_source IN ('OTE_CZ', 'OTE_CZ_DAM')
AND interval_start = p_interval_start
LIMIT 1;
v_local_time := (p_interval_start AT TIME ZONE 'Europe/Prague')::TIME;
v_dow := EXTRACT(DOW FROM p_interval_start AT TIME ZONE 'Europe/Prague');
-- 0=neděle, 6=sobota
IF v_hdo_code_id IS NOT NULL THEN
SELECT EXISTS (
SELECT 1
FROM ems.hdo_code_window w
WHERE w.hdo_code_id = v_hdo_code_id
AND (
w.day_type = 'all'
OR (w.day_type = 'workday' AND v_dow BETWEEN 1 AND 5)
OR (w.day_type = 'weekend' AND v_dow IN (0, 6))
)
AND w.rate_type = 'VT'
AND v_local_time >= w.window_from
AND v_local_time < w.window_to
) INTO v_is_vt;
ELSE
v_is_vt := false;
END IF;
v_rate_type := CASE WHEN v_is_vt THEN 'VT' ELSE 'NT' END;
IF v_tariff_id IS NOT NULL THEN
SELECT price_czk_kwh INTO v_dist_rate
FROM ems.distribution_tariff_rate
WHERE tariff_id = v_tariff_id
AND rate_type = v_rate_type
AND valid_from <= p_interval_start::DATE
AND (valid_to IS NULL OR valid_to > p_interval_start::DATE)
ORDER BY valid_from DESC
LIMIT 1;
END IF;
v_dist_rate := COALESCE(v_dist_rate, 0);
v_system_services := COALESCE(v_system_services, 0);
v_ote_fee := COALESCE(v_ote_fee, 0);
v_buy_margin_fixed := COALESCE(v_buy_margin_fixed, 0);
v_buy_margin_pct := COALESCE(v_buy_margin_pct, 0);
v_vat_rate := COALESCE(v_vat_rate, 0.21);
v_fixed_vt_sur := COALESCE(v_fixed_vt_sur, 0);
IF upper(trim(COALESCE(v_purchase_mode, ''))) = 'FIXED'
AND v_fixed_nt IS NOT NULL THEN
v_energy_czk := v_fixed_nt
+ CASE WHEN v_is_vt THEN v_fixed_vt_sur ELSE 0 END;
v_buy_margin := v_buy_margin_fixed + (v_energy_czk * v_buy_margin_pct / 100.0);
ELSIF v_spot_price IS NULL THEN
RETURN NULL;
ELSE
-- Spot: asymetrický faktor na raw OTE (stejné p jako u kladného ×(1+p/100)):
-- kladná raw → ×(1+p/100), záporná raw → ×(1p/100); fixní marže jen přičíst.
v_energy_czk := CASE
WHEN v_spot_price >= 0 THEN
v_spot_price * (1 + v_buy_margin_pct / 100.0)
ELSE
v_spot_price * (1 - v_buy_margin_pct / 100.0)
END;
v_buy_margin := v_buy_margin_fixed;
END IF;
RETURN ROUND(
(v_energy_czk + v_dist_rate + v_system_services + v_ote_fee + v_buy_margin)
* (1 + v_vat_rate),
6
);
END;
$$;
COMMENT ON FUNCTION ems.fn_effective_buy_price(INT, TIMESTAMPTZ) IS
'Efektivní nákupní cena elektřiny Kč/kWh včetně DPH.
Režim spot: složka OTE buy_raw jako kladná → ×(1+buy_margin_percent/100), záporná → ×(1buy_margin_percent/100); + buy_margin_fixed_czk + distribuce NT/VT (HDO) + systémové služby + OTE poplatek; pak DPH na celek.
Režim fixed: energie = buy_fixed_energy_nt_czk_kwh (+ příplatek VT dle HDO), marže = fix + procento z této uzavřené energické složky (symetricky jako dříve); + příplatky a DPH stejně.
DPH aplikováno na celou částku.';
-- ------------------------------------------------------------
CREATE OR REPLACE FUNCTION ems.fn_effective_sell_price(
p_site_id INT,
p_interval_start TIMESTAMPTZ
)
RETURNS NUMERIC(10,6)
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
v_spot_price NUMERIC;
v_sell_margin_fixed NUMERIC;
v_sell_margin_pct NUMERIC;
BEGIN
SELECT sell_margin_fixed_czk, sell_margin_percent
INTO v_sell_margin_fixed, v_sell_margin_pct
FROM ems.site_market_config
WHERE site_id = p_site_id
AND valid_from <= p_interval_start
AND (valid_to IS NULL OR valid_to > p_interval_start)
ORDER BY valid_from DESC
LIMIT 1;
IF NOT FOUND THEN
RETURN NULL;
END IF;
SELECT sell_raw_price_czk_kwh INTO v_spot_price
FROM ems.market_interval_price
WHERE market_source IN ('OTE_CZ', 'OTE_CZ_DAM')
AND interval_start = p_interval_start
LIMIT 1;
IF v_spot_price IS NULL THEN
RETURN NULL;
END IF;
RETURN ROUND(
v_spot_price
+ COALESCE(v_sell_margin_fixed, 0)
+ (v_spot_price * COALESCE(v_sell_margin_pct, 0) / 100.0),
6
);
END;
$$;
COMMENT ON FUNCTION ems.fn_effective_sell_price(INT, TIMESTAMPTZ) IS
'Efektivní prodejní cena elektřiny Kč/kWh bez DPH (neplátce DPH).
Složky: spot OTE + fixní/procentní prodejní marže (záporná = srážka).
Zelený bonus není součástí ceny počítá se z výroby přes fn_green_bonus_revenue().
Záporná hodnota = platíme za export (záporné spotové ceny).';
-- ------------------------------------------------------------
CREATE OR REPLACE FUNCTION ems.fn_green_bonus_revenue(
p_pv_array_id INT,
p_interval_start TIMESTAMPTZ,
p_production_wh NUMERIC
)
RETURNS NUMERIC
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
v_bonus_rate NUMERIC;
BEGIN
SELECT green_bonus_czk_kwh INTO v_bonus_rate
FROM ems.asset_pv_array
WHERE id = p_pv_array_id
AND green_bonus_czk_kwh IS NOT NULL
AND green_bonus_valid_from <= p_interval_start::DATE
AND (green_bonus_valid_to IS NULL
OR green_bonus_valid_to > p_interval_start::DATE);
IF v_bonus_rate IS NULL OR p_production_wh IS NULL OR p_production_wh <= 0 THEN
RETURN 0;
END IF;
RETURN ROUND((p_production_wh / 1000.0) * v_bonus_rate, 6);
END;
$$;
COMMENT ON FUNCTION ems.fn_green_bonus_revenue(INT, TIMESTAMPTZ, NUMERIC) IS
'Příjem ze zeleného bonusu za výrobu FVE pole v daném intervalu.
Bonus plyne z celkové výroby bez ohledu na to kam energie šla
(interní spotřeba, baterie, EV, TČ i export do sítě).
Sazba se načítá dle platnosti (valid_from/valid_to) ročně aktualizovatelné.
Vrátí 0 pokud pole nemá zelený bonus nebo výroba je nulová.
Použití: SELECT ems.fn_green_bonus_revenue(pv_array_id, interval_start, production_wh);';