fix repeatable migrations

This commit is contained in:
Dusan Vojacek
2026-04-19 20:15:46 +02:00
parent 0c93f493a4
commit 22bca9cd9e
73 changed files with 22 additions and 15 deletions

View File

@@ -0,0 +1,118 @@
CREATE OR REPLACE FUNCTION ems.fn_update_baseline_stats(
p_site_id INT,
p_lookback_days INT DEFAULT 30
)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
v_count INT;
BEGIN
WITH raw AS (
SELECT
EXTRACT(DOW FROM ti.measured_at AT TIME ZONE 'Europe/Prague')::INT AS dow,
EXTRACT(HOUR FROM ti.measured_at AT TIME ZONE 'Europe/Prague')::INT AS hour,
GREATEST(0,
ti.load_power_w
- COALESCE((
SELECT AVG(tev.power_w)
FROM ems.telemetry_ev_charger tev
WHERE tev.site_id = ti.site_id
AND tev.measured_at BETWEEN
ti.measured_at - INTERVAL '30 seconds'
AND ti.measured_at + INTERVAL '30 seconds'
), 0)::INT
- COALESCE((
SELECT AVG(thp.power_w)
FROM ems.telemetry_heat_pump thp
WHERE thp.site_id = ti.site_id
AND thp.measured_at BETWEEN
ti.measured_at - INTERVAL '30 seconds'
AND ti.measured_at + INTERVAL '30 seconds'
), 0)::INT
) AS baseline_w
FROM ems.telemetry_inverter ti
WHERE ti.site_id = p_site_id
AND ti.measured_at >= now() - make_interval(days => p_lookback_days)
AND ti.load_power_w IS NOT NULL
AND ti.load_power_w > 0
),
agg AS (
SELECT
dow,
hour,
AVG(baseline_w) AS avg_w,
STDDEV(baseline_w) AS stddev_w,
COUNT(*) AS samples
FROM raw
GROUP BY dow, hour
HAVING COUNT(*) >= 4
)
INSERT INTO ems.consumption_baseline_stats
(site_id, day_of_week, hour_of_day,
avg_power_w, stddev_power_w, sample_count, last_updated)
SELECT
p_site_id, dow, hour,
ROUND(avg_w::NUMERIC, 2),
ROUND(stddev_w::NUMERIC, 2),
samples,
now()
FROM agg
ON CONFLICT (site_id, day_of_week, hour_of_day) DO UPDATE SET
avg_power_w = ROUND(
0.7 * ems.consumption_baseline_stats.avg_power_w
+ 0.3 * EXCLUDED.avg_power_w, 2),
stddev_power_w = ROUND(
COALESCE(0.7 * ems.consumption_baseline_stats.stddev_power_w
+ 0.3 * EXCLUDED.stddev_power_w,
EXCLUDED.stddev_power_w), 2),
sample_count = ems.consumption_baseline_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_baseline_stats(INT, INT) IS
'Aktualizuje průměry bazální spotřeby z telemetrie posledních N dní.
Používá exponenciální klouzavý průměr (EMA 70/30) pro postupné zpřesňování.
Volat denně po půlnoci. Pro první naplnění: fn_update_baseline_stats(2, 90).';
CREATE OR REPLACE FUNCTION ems.fn_get_baseline_forecast(
p_site_id INT,
p_from TIMESTAMPTZ,
p_to TIMESTAMPTZ
)
RETURNS TABLE (
interval_start TIMESTAMPTZ,
forecast_w INT,
confidence_w INT
)
LANGUAGE sql
STABLE
AS $$
SELECT
gs.slot AS interval_start,
COALESCE(cbs.avg_power_w, 500)::INT AS forecast_w,
COALESCE(
cbs.avg_power_w + 0.5 * COALESCE(cbs.stddev_power_w, 100),
550
)::INT AS confidence_w
FROM generate_series(p_from, p_to - INTERVAL '15 minutes',
INTERVAL '15 minutes') AS gs(slot)
LEFT JOIN ems.consumption_baseline_stats cbs
ON cbs.site_id = p_site_id
AND cbs.day_of_week = EXTRACT(DOW FROM gs.slot AT TIME ZONE 'Europe/Prague')::INT
AND cbs.hour_of_day = EXTRACT(HOUR FROM gs.slot AT TIME ZONE 'Europe/Prague')::INT
ORDER BY gs.slot;
$$;
COMMENT ON FUNCTION ems.fn_get_baseline_forecast(INT, TIMESTAMPTZ, TIMESTAMPTZ) IS
'Vrátí předpověď bazální spotřeby pro zadaný horizont jako 15min sloty.
forecast_w = průměr dle DOW+hodina z historických dat.
confidence_w = konzervativní odhad (avg + 0.5*stddev).
Fallback 500W pokud nejsou historická data.
Použití v solveru: nahrazuje pevný fallback 500W v _load_slots().';