fix repeatable migrations
This commit is contained in:
118
db/routines/R__003_fn_baseline_consumption.sql
Normal file
118
db/routines/R__003_fn_baseline_consumption.sql
Normal 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().';
|
||||
Reference in New Issue
Block a user