second version

This commit is contained in:
Dusan Vojacek
2026-04-03 14:23:16 +02:00
parent 897b95f728
commit 9f4126946d
105 changed files with 9738 additions and 1470 deletions

View File

@@ -21,12 +21,15 @@ SELECT
ROUND(SUM(actual_cost_czk), 2) AS actual_cost_czk,
ROUND(SUM(deviation_cost_czk), 2) AS total_deviation_czk,
-- Počet intervalů s velkými odchylkami (>1kW)
COUNT(*) FILTER (WHERE ABS(deviation_grid_w) > 1000) AS high_deviation_count
COUNT(*) FILTER (WHERE ABS(deviation_grid_w) > 1000) AS high_deviation_count,
ROUND(SUM(green_bonus_czk), 4) AS green_bonus_czk,
ROUND(COALESCE(SUM(actual_cost_czk), 0) * -1 + COALESCE(SUM(green_bonus_czk), 0), 2) AS total_revenue_czk
FROM ems.audit_interval
GROUP BY site_id, date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague');
COMMENT ON VIEW ems.vw_audit_daily IS
'Denní souhrn auditu per lokalita. Energie v kWh, náklady v Kč.
total_revenue_czk = -actual_cost_czk + green_bonus_czk (export/síť vs. bonus).
Používat pro dashboard denního přehledu a reporty.';
-- ============================================================

View File

@@ -0,0 +1,78 @@
CREATE OR REPLACE VIEW ems.vw_forecast_accuracy_by_lead_time AS
SELECT *
FROM (
SELECT
site_id,
pv_array_id,
CASE
WHEN lead_time_hours <= 6 THEN '0-6h'
WHEN lead_time_hours <= 12 THEN '6-12h'
WHEN lead_time_hours <= 24 THEN '12-24h'
WHEN lead_time_hours <= 48 THEN '24-48h'
ELSE '48h+'
END AS lead_time_bucket,
COUNT(*) AS slot_count,
COUNT(*) FILTER (WHERE actual_power_w > 100) AS daylight_slots,
ROUND(AVG(error_pct)
FILTER (WHERE actual_power_w > 100), 2) AS avg_error_pct,
ROUND(AVG(ABS(error_pct))
FILTER (WHERE actual_power_w > 100), 2) AS avg_abs_error_pct,
ROUND(STDDEV(error_pct)
FILTER (WHERE actual_power_w > 100), 2) AS stddev_error_pct,
CASE
WHEN AVG(error_pct) FILTER (WHERE actual_power_w > 100) > 10
THEN 'nadhodnocuje'
WHEN AVG(error_pct) FILTER (WHERE actual_power_w > 100) < -10
THEN 'podhodnocuje'
ELSE 'ok'
END AS bias
FROM ems.forecast_accuracy
WHERE actual_power_w IS NOT NULL
GROUP BY
site_id,
pv_array_id,
CASE
WHEN lead_time_hours <= 6 THEN '0-6h'
WHEN lead_time_hours <= 12 THEN '6-12h'
WHEN lead_time_hours <= 24 THEN '12-24h'
WHEN lead_time_hours <= 48 THEN '24-48h'
ELSE '48h+'
END
) bucketed
ORDER BY
site_id,
pv_array_id,
CASE lead_time_bucket
WHEN '0-6h' THEN 1
WHEN '6-12h' THEN 2
WHEN '12-24h' THEN 3
WHEN '24-48h' THEN 4
WHEN '48h+' THEN 5
END;
COMMENT ON VIEW ems.vw_forecast_accuracy_by_lead_time IS
'Přesnost FVE forecastu dle lead time (jak daleko předem byl forecast vytvořen).
Ignoruje noční sloty (actual < 100W). avg_error_pct > 0 = forecast nadhodnocuje.
Po 4+ týdnech dat lze použít pro kalibraci safety_factor v solveru.';
CREATE OR REPLACE VIEW ems.vw_forecast_accuracy_daily AS
SELECT
site_id,
pv_array_id,
DATE(interval_start AT TIME ZONE 'Europe/Prague') AS day,
COUNT(*) FILTER (WHERE actual_power_w IS NOT NULL
AND actual_power_w > 100) AS daylight_slots,
ROUND(SUM(forecast_power_w)::NUMERIC / 4000, 2) AS forecast_kwh,
ROUND(SUM(actual_power_w)::NUMERIC / 4000, 2) AS actual_kwh,
ROUND((SUM(forecast_power_w) - SUM(COALESCE(actual_power_w,0)))
::NUMERIC / NULLIF(SUM(actual_power_w),0) * 100, 2) AS day_error_pct
FROM ems.forecast_accuracy
GROUP BY
site_id,
pv_array_id,
DATE(interval_start AT TIME ZONE 'Europe/Prague')
ORDER BY day DESC;
COMMENT ON VIEW ems.vw_forecast_accuracy_daily IS
'Denní souhrn přesnosti FVE forecastu v kWh. forecast_kwh vs actual_kwh.
day_error_pct > 0 = forecast nadhodnotil denní výrobu.';

View File

@@ -18,7 +18,13 @@ SELECT DISTINCT ON (t.inverter_id)
t.inverter_temp_c,
t.operating_mode,
t.fault_code,
now() - t.measured_at AS data_age
now() - t.measured_at AS data_age,
t.pv1_power_w,
t.pv2_power_w,
t.gen_port_power_w,
t.batt_charge_today_wh,
t.batt_discharge_today_wh,
t.run_state
FROM ems.telemetry_inverter t
JOIN ems.asset_inverter inv ON inv.id = t.inverter_id
ORDER BY t.inverter_id, t.measured_at DESC;

View File

@@ -5,38 +5,80 @@
-- =============================================================
CREATE OR REPLACE VIEW ems.vw_site_effective_price AS
WITH cfg_price AS (
SELECT
smc.site_id,
smc.tariff_id,
smc.hdo_code_id,
smc.system_services_czk_kwh,
smc.buy_margin_fixed_czk,
smc.buy_margin_percent,
smc.sell_margin_fixed_czk,
smc.sell_margin_percent,
mip.interval_start,
mip.interval_end,
mip.market_source,
mip.buy_raw_price_czk_kwh,
mip.sell_raw_price_czk_kwh,
(mip.interval_start AT TIME ZONE 'Europe/Prague')::time AS local_prague_time,
EXTRACT(DOW FROM mip.interval_start AT TIME ZONE 'Europe/Prague')::integer AS prague_dow
FROM ems.market_interval_price mip
CROSS JOIN ems.site_market_config smc
WHERE smc.valid_from <= mip.interval_start
AND (smc.valid_to IS NULL OR smc.valid_to > mip.interval_start)
),
rated AS (
SELECT
cp.*,
CASE
WHEN cp.hdo_code_id IS NOT NULL AND EXISTS (
SELECT 1
FROM ems.hdo_code_window w
WHERE w.hdo_code_id = cp.hdo_code_id
AND (
w.day_type = 'all'
OR (w.day_type = 'workday' AND cp.prague_dow BETWEEN 1 AND 5)
OR (w.day_type = 'weekend' AND cp.prague_dow IN (0, 6))
)
AND w.rate_type = 'VT'
AND cp.local_prague_time >= w.window_from
AND cp.local_prague_time < w.window_to
) THEN 'VT'::text
ELSE 'NT'::text
END AS rate_type
FROM cfg_price cp
)
SELECT
smc.site_id,
mip.interval_start,
mip.interval_end,
mip.market_source,
-- Raw ceny
mip.buy_raw_price_czk_kwh,
mip.sell_raw_price_czk_kwh,
-- Marže
smc.buy_margin_fixed_czk,
smc.buy_margin_percent,
smc.sell_margin_fixed_czk,
smc.sell_margin_percent,
-- Efektivní ceny
ROUND(
mip.buy_raw_price_czk_kwh
+ smc.buy_margin_fixed_czk
+ (mip.buy_raw_price_czk_kwh * smc.buy_margin_percent / 100.0),
6
) AS effective_buy_price_czk_kwh,
ROUND(
mip.sell_raw_price_czk_kwh
+ smc.sell_margin_fixed_czk
+ (mip.sell_raw_price_czk_kwh * smc.sell_margin_percent / 100.0),
6
) AS effective_sell_price_czk_kwh
FROM ems.market_interval_price mip
CROSS JOIN ems.site_market_config smc
WHERE smc.valid_from <= mip.interval_start
AND (smc.valid_to IS NULL OR smc.valid_to > mip.interval_start);
r.site_id,
r.interval_start,
r.interval_end,
r.market_source,
r.buy_raw_price_czk_kwh,
r.sell_raw_price_czk_kwh,
r.buy_margin_fixed_czk,
r.buy_margin_percent,
r.sell_margin_fixed_czk,
r.sell_margin_percent,
ems.fn_effective_buy_price(r.site_id, r.interval_start) AS effective_buy_price_czk_kwh,
ems.fn_effective_sell_price(r.site_id, r.interval_start) AS effective_sell_price_czk_kwh,
r.rate_type,
COALESCE(
(
SELECT dtr.price_czk_kwh
FROM ems.distribution_tariff_rate dtr
WHERE dtr.tariff_id = r.tariff_id
AND dtr.rate_type = r.rate_type
AND dtr.valid_from <= r.interval_start::date
AND (dtr.valid_to IS NULL OR dtr.valid_to > r.interval_start::date)
ORDER BY dtr.valid_from DESC
LIMIT 1
),
0::numeric
) AS dist_rate_czk_kwh,
COALESCE(r.system_services_czk_kwh, 0::numeric) AS system_services_czk_kwh
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.
Dopočítává marže z site_market_config na raw ceny z market_interval_price.
Nezahrnuje data bez platné market_config. Používat pro plánování a audit.';
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).';

View File

@@ -11,3 +11,10 @@ GRANT SELECT ON ems.vw_mode_log_recent TO ems_anon;
GRANT SELECT ON ems.vw_operating_mode TO ems_anon;
GRANT SELECT ON ems.telemetry_inverter_hourly TO ems_anon;
GRANT SELECT ON ems.vw_telemetry_hourly_7d TO ems_anon;
GRANT SELECT ON ems.telemetry_heat_pump TO ems_anon;
GRANT SELECT ON ems.forecast_accuracy TO ems_anon;
GRANT SELECT ON ems.vw_forecast_accuracy_by_lead_time TO ems_anon;
GRANT SELECT ON ems.vw_forecast_accuracy_daily TO ems_anon;
GRANT SELECT ON ems.consumption_baseline_stats TO ems_anon;
GRANT SELECT ON ems.market_price_stats TO ems_anon;
GRANT SELECT ON ems.tuv_usage_stats TO ems_anon;