154 lines
5.1 KiB
PL/PgSQL
154 lines
5.1 KiB
PL/PgSQL
-- 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).';
|