Files
ems/db/routines/R__fn_extended_planning.sql
Dusan Vojacek 9f4126946d second version
2026-04-03 14:23:16 +02:00

154 lines
5.1 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.
-- fn_update_market_price_stats, fn_update_tuv_usage_stats, fn_get_predicted_price
-- (rozšířený horizont plánování predikce cen a TUV)
CREATE OR REPLACE FUNCTION ems.fn_update_market_price_stats(
p_site_id INT,
p_lookback_days INT DEFAULT 90
)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE v_count INT;
BEGIN
INSERT INTO ems.market_price_stats
(site_id, day_of_week, hour_of_day,
avg_price, stddev_price, p25_price, p75_price,
sample_count, last_updated)
SELECT
p_site_id,
EXTRACT(DOW FROM interval_start AT TIME ZONE 'Europe/Prague')::INT,
EXTRACT(HOUR FROM interval_start AT TIME ZONE 'Europe/Prague')::INT,
AVG(buy_raw_price_czk_kwh),
STDDEV(buy_raw_price_czk_kwh),
PERCENTILE_CONT(0.25) WITHIN GROUP (
ORDER BY buy_raw_price_czk_kwh),
PERCENTILE_CONT(0.75) WITHIN GROUP (
ORDER BY buy_raw_price_czk_kwh),
COUNT(*)::INT,
now()
FROM ems.market_interval_price
WHERE market_source IN ('OTE_CZ', 'OTE_CZ_DAM')
AND interval_start >= now() - make_interval(days => p_lookback_days)
GROUP BY
EXTRACT(DOW FROM interval_start AT TIME ZONE 'Europe/Prague'),
EXTRACT(HOUR FROM interval_start AT TIME ZONE 'Europe/Prague')
HAVING COUNT(*) >= 4
ON CONFLICT (site_id, day_of_week, hour_of_day) DO UPDATE SET
avg_price = 0.7 * ems.market_price_stats.avg_price
+ 0.3 * EXCLUDED.avg_price,
stddev_price = COALESCE(
0.7 * ems.market_price_stats.stddev_price
+ 0.3 * EXCLUDED.stddev_price,
EXCLUDED.stddev_price),
p25_price = EXCLUDED.p25_price,
p75_price = EXCLUDED.p75_price,
sample_count = ems.market_price_stats.sample_count
+ EXCLUDED.sample_count,
last_updated = now();
GET DIAGNOSTICS v_count = ROW_COUNT;
RETURN v_count;
END;
$$;
COMMENT ON FUNCTION ems.fn_update_market_price_stats IS
'Aktualizuje historické průměry spotové ceny per DOW+hodina.
EMA 70/30 pro postupné zpřesňování. Volat denně po importu OTE.
Pro první naplnění: SELECT ems.fn_update_market_price_stats(2, 180);';
CREATE OR REPLACE FUNCTION ems.fn_update_tuv_usage_stats(
p_site_id INT,
p_lookback_days INT DEFAULT 30
)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE v_count INT;
BEGIN
INSERT INTO ems.tuv_usage_stats
(site_id, day_of_week, hour_of_day,
avg_temp_delta_c, stddev_temp_delta,
sample_count, last_updated)
WITH deltas AS (
SELECT
measured_at,
EXTRACT(DOW FROM measured_at AT TIME ZONE 'Europe/Prague')::INT AS dow,
EXTRACT(HOUR FROM measured_at AT TIME ZONE 'Europe/Prague')::INT AS hour,
tuv_tank_temp_c - LAG(tuv_tank_temp_c) OVER (
PARTITION BY site_id ORDER BY measured_at
) AS temp_delta_c
FROM ems.telemetry_heat_pump
WHERE site_id = p_site_id
AND measured_at >= now() - make_interval(days => p_lookback_days)
AND tuv_tank_temp_c IS NOT NULL
)
SELECT
p_site_id, dow, hour,
AVG(temp_delta_c),
STDDEV(temp_delta_c),
COUNT(*)::INT,
now()
FROM deltas
WHERE temp_delta_c IS NOT NULL
AND ABS(temp_delta_c) < 5
GROUP BY dow, hour
HAVING COUNT(*) >= 4
ON CONFLICT (site_id, day_of_week, hour_of_day) DO UPDATE SET
avg_temp_delta_c = 0.7 * ems.tuv_usage_stats.avg_temp_delta_c
+ 0.3 * EXCLUDED.avg_temp_delta_c,
stddev_temp_delta = COALESCE(
0.7 * ems.tuv_usage_stats.stddev_temp_delta
+ 0.3 * EXCLUDED.stddev_temp_delta,
EXCLUDED.stddev_temp_delta),
sample_count = ems.tuv_usage_stats.sample_count
+ EXCLUDED.sample_count,
last_updated = now();
GET DIAGNOSTICS v_count = ROW_COUNT;
RETURN v_count;
END;
$$;
COMMENT ON FUNCTION ems.fn_update_tuv_usage_stats IS
'Aktualizuje statistiku poklesu teploty TUV zásobníku per DOW+hodina.
Záporné avg_temp_delta_c = zásobník se ochlazuje (spotřeba teplé vody).
Potřeba min. 1 měsíc telemetrie TČ. Volat denně.';
CREATE OR REPLACE FUNCTION ems.fn_get_predicted_price(
p_site_id INT,
p_interval_start TIMESTAMPTZ
)
RETURNS NUMERIC
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
v_dow INT;
v_hour INT;
v_price NUMERIC;
v_corr NUMERIC := 1.0;
BEGIN
v_dow := EXTRACT(DOW FROM p_interval_start AT TIME ZONE 'Europe/Prague')::INT;
v_hour := EXTRACT(HOUR FROM p_interval_start AT TIME ZONE 'Europe/Prague')::INT;
SELECT avg_price INTO v_price
FROM ems.market_price_stats
WHERE site_id = p_site_id
AND day_of_week = v_dow
AND hour_of_day = v_hour;
IF v_price IS NULL THEN
RETURN 2.50;
END IF;
RETURN ROUND(v_price * v_corr, 6);
END;
$$;
COMMENT ON FUNCTION ems.fn_get_predicted_price IS
'Vrátí predikovanou cenu pro slot za horizontem OTE.
Zatím používá jen sezónní průměr. Fáze 3d přidá korekci počasím.
Fallback 2.50 Kč/kWh pokud nejsou historická data (min. 3 měsíce).';