From 91ee8a6adf927d356b9e99891670bbb0d0ddc6d3 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Fri, 1 May 2026 14:27:08 +0200 Subject: [PATCH] fix zaporne spot ceny v nakupu --- ...margin_percent_spot_asymmetric_comment.sql | 3 ++ db/routines/R__011_fn_effective_price.sql | 17 +++++++--- .../R__068_fn_economics_daily_month.sql | 20 +++++++++--- db/views/R__061_vw_site_effective_price.sql | 3 +- docs/03-data-model.md | 32 ++++++------------- docs/04-modules/market-prices.md | 11 ++++++- 6 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 db/migration/V075__buy_margin_percent_spot_asymmetric_comment.sql diff --git a/db/migration/V075__buy_margin_percent_spot_asymmetric_comment.sql b/db/migration/V075__buy_margin_percent_spot_asymmetric_comment.sql new file mode 100644 index 0000000..c4a7b25 --- /dev/null +++ b/db/migration/V075__buy_margin_percent_spot_asymmetric_comment.sql @@ -0,0 +1,3 @@ +-- buy_margin_percent: spot režim používá asymetrický faktor (R__011 fn_effective_buy_price). +comment on column ems.site_market_config.buy_margin_percent is + 'Procentní nákupní marže za režimu spot: při kladné buy_raw složka OTE ×(1+p/100); při záporné ×(1−p/100); buy_margin_fixed_czk se jen přičte. Za režimu FIXED stále fix + (uzavřená energická složka × p/100).'; diff --git a/db/routines/R__011_fn_effective_price.sql b/db/routines/R__011_fn_effective_price.sql index cfc248d..90058f9 100644 --- a/db/routines/R__011_fn_effective_price.sql +++ b/db/routines/R__011_fn_effective_price.sql @@ -119,14 +119,21 @@ BEGIN 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 - v_energy_czk := v_spot_price; + -- Spot: asymetrický faktor na raw OTE (stejné p jako u kladného ×(1+p/100)): + -- kladná raw → ×(1+p/100), záporná raw → ×(1−p/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; - v_buy_margin := v_buy_margin_fixed + (v_energy_czk * v_buy_margin_pct / 100.0); - RETURN ROUND( (v_energy_czk + v_dist_rate + v_system_services + v_ote_fee + v_buy_margin) * (1 + v_vat_rate), @@ -137,8 +144,8 @@ $$; 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: energie = OTE buy_raw + distribuce NT/VT (dle HDO) + systémové služby + OTE poplatek + marže (fix + % z energie). -Režim fixed: energie = buy_fixed_energy_nt_czk_kwh (+ buy_fixed_vt_surcharge_czk_kwh ve VT oknech dle HDO), pak stejné příplatky a DPH. +Režim spot: složka OTE buy_raw jako kladná → ×(1+buy_margin_percent/100), záporná → ×(1−buy_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.'; -- ------------------------------------------------------------ diff --git a/db/routines/R__068_fn_economics_daily_month.sql b/db/routines/R__068_fn_economics_daily_month.sql index 0bb81cf..e725b91 100644 --- a/db/routines/R__068_fn_economics_daily_month.sql +++ b/db/routines/R__068_fn_economics_daily_month.sql @@ -153,10 +153,21 @@ as $fn$ 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) + when upper(trim(coalesce(s3.purchase_pricing_mode, ''))) = 'FIXED' + and s3.buy_fixed_energy_nt_czk_kwh is not null + then + s3.energy_czk + + coalesce(s3.buy_margin_fixed_czk, 0) + (s3.energy_czk * coalesce(s3.buy_margin_percent, 0) / 100.0) - end as buy_margin_part + else + case + when s3.buy_spot >= 0 then + s3.buy_spot * (1 + coalesce(s3.buy_margin_percent, 0) / 100.0) + else + s3.buy_spot * (1 - coalesce(s3.buy_margin_percent, 0) / 100.0) + end + + coalesce(s3.buy_margin_fixed_czk, 0) + end as buy_pre_dist_margin_sum from slot3 s3 ), slot5 as ( @@ -167,11 +178,10 @@ as $fn$ when s4.energy_czk is null then null::numeric else round( ( - s4.energy_czk + s4.buy_pre_dist_margin_sum + 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 ) diff --git a/db/views/R__061_vw_site_effective_price.sql b/db/views/R__061_vw_site_effective_price.sql index 7703753..0e4c085 100644 --- a/db/views/R__061_vw_site_effective_price.sql +++ b/db/views/R__061_vw_site_effective_price.sql @@ -81,4 +81,5 @@ FROM rated r; COMMENT ON VIEW ems.vw_site_effective_price IS 'Efektivní nákupní a prodejní ceny elektřiny per lokalita a 15min interval. rate_type NT/VT dle HDO oken; dist_rate = variabilní distribuce bez DPH. -effective_* z fn_effective_buy_price / fn_effective_sell_price (marže, DPH u nákupu dle tarifu).'; +effective_buy z fn_effective_buy_price: u spot složky OTE asymetrická procentní marže (kladná raw ×(1+p/100), záporná ×(1−p/100)). +effective_sell z fn_effective_sell_price (marže bez DPH).'; diff --git a/docs/03-data-model.md b/docs/03-data-model.md index 635c1b1..42511af 100644 --- a/docs/03-data-model.md +++ b/docs/03-data-model.md @@ -211,31 +211,17 @@ CREATE TABLE market_interval_price ( -- SELECT create_hypertable('market_interval_price', 'interval_start'); ``` -### View: `market_vw_site_effective_price` -Efektivní ceny per site – dopočítané z raw + marže. Neukládá se, počítá se za běhu. +### View: `vw_site_effective_price` (`ems`) + +Efektivní ceny per site — neukládá se; nákupní cena přes **`ems.fn_effective_buy_price(site_id, interval_start)`** (zahrnuje HDO NT/VT, distribuci, systémové služby, OTE poplatek, marži a DPH podle tarifu). Pro **spot** je procentní marže na `buy_raw` asymetrická: kladná raw ×`(1+p/100)`, záporná ×`(1−p/100)`; viz `docs/04-modules/market-prices.md` a repeatable `db/routines/R__011_fn_effective_price.sql`. + +Prodejní strana nadále **`ems.fn_effective_sell_price`**: raw + prodejní marže (bez DPH). Zjednodušený vzorec bez distribuce jen pro ilustraci prodeje: ```sql -CREATE VIEW market_vw_site_effective_price AS -SELECT - smc.site_id, - mip.interval_start, - mip.interval_end, - mip.buy_raw_price_czk_kwh, - mip.sell_raw_price_czk_kwh, - -- efektivní nákupní cena - mip.buy_raw_price_czk_kwh - + smc.buy_margin_fixed_czk - + (mip.buy_raw_price_czk_kwh * smc.buy_margin_percent / 100) - AS effective_buy_price_czk_kwh, - -- efektivní prodejní cena - mip.sell_raw_price_czk_kwh - + smc.sell_margin_fixed_czk - + (mip.sell_raw_price_czk_kwh * smc.sell_margin_percent / 100) - AS effective_sell_price_czk_kwh -FROM market_interval_price mip -CROSS JOIN site_market_config smc -WHERE smc.valid_to IS NULL -- aktuálně platná konfigurace - OR now() BETWEEN smc.valid_from AND smc.valid_to; +-- pouze analogie k sell (nákup v produkci vždy přes funkci výše): +mip.sell_raw_price_czk_kwh + + smc.sell_margin_fixed_czk + + (mip.sell_raw_price_czk_kwh * smc.sell_margin_percent / 100) ``` --- diff --git a/docs/04-modules/market-prices.md b/docs/04-modules/market-prices.md index cbaf340..1044498 100644 --- a/docs/04-modules/market-prices.md +++ b/docs/04-modules/market-prices.md @@ -114,10 +114,19 @@ Marže se konfigurují v `site_market_config`: | Parametr | Typ | Příklad | |---|---|---| | `buy_margin_fixed_czk` | Kč/kWh | 0.05 (5 haléřů/kWh) | -| `buy_margin_percent` | % | 2.5 | +| `buy_margin_percent` | % | 2.5 (nebo 9 u obchodníka ×1.09 / ×0.91, viz níže) | | `sell_margin_fixed_czk` | Kč/kWh | -0.02 (srážka) | | `sell_margin_percent` | % | 0 | +**Nákup v režimu `spot`** (`purchase_pricing_mode = 'spot'`): skutečná logika je v `ems.fn_effective_buy_price` (volá ji `vw_site_effective_price`, plán, audit). Složka **OTE `buy_raw`** před distribucí / poplatky / DPH: + +- **`buy_raw_price_czk_kwh ≥ 0`:** energie ze spotu se násobí **`(1 + buy_margin_percent/100)`** (např. 9 % → ×1.09 na tuto složku), pak se přičtou `buy_margin_fixed_czk`, distribuce NT/VT, systémové služby, poplatek OTE a nakonec DPH přes celek. +- **`buy_raw_price_czk_kwh < 0`:** stejný procentní parametr se uplatní jako **`(1 − buy_margin_percent/100)`** (např. 9 % → ×0,91 na zápornou spotovou složku), aby obchodní marže záporné ceny „nezesilovala“ směrem k ještě nižší (dražší) hodnotě. + +**Režim `FIXED`** (uzavřená cena + příplatek VT): procentní marže zůstává **symetricky** jako `fix + uzavřená_energie × (buy_margin_percent/100)` jako dříve — asymmetric platí jen pro čistý spot ze `market_interval_price`. + +Denní ekonomika v DB (`ems.fn_economics_daily_for_window`, repeatable `R__068_fn_economics_daily_month.sql`) musí používat stejnou kombinaci jako `fn_effective_buy_price` (komentář ve funkci). + **Zelený bonus** není součástí `fn_effective_sell_price` ani view efektivní prodejní ceny – jde o samostatný příjem z výroby, viz níže. ---