second version
This commit is contained in:
153
db/routines/R__fn_extended_planning.sql
Normal file
153
db/routines/R__fn_extended_planning.sql
Normal file
@@ -0,0 +1,153 @@
|
||||
-- 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).';
|
||||
Reference in New Issue
Block a user