CREATE OR REPLACE FUNCTION ems.fn_fill_forecast_accuracy( p_site_id INT, p_lookback_hours INT DEFAULT 48 ) RETURNS INT LANGUAGE plpgsql AS $$ DECLARE v_count INT := 0; BEGIN INSERT INTO ems.forecast_accuracy ( site_id, pv_array_id, interval_start, run_id, forecast_power_w, forecast_created_at, lead_time_hours, actual_power_w, actual_filled_at, error_w, error_pct ) SELECT fpr.site_id, fpr.pv_array_id, fpi.interval_start, fpi.run_id, fpi.power_w AS forecast_power_w, fpr.created_at AS forecast_created_at, ROUND( EXTRACT(EPOCH FROM (fpi.interval_start - fpr.created_at)) / 3600.0, 2 ) AS lead_time_hours, slot.avg_actual_w::INT AS actual_power_w, now() AS actual_filled_at, fpi.power_w - COALESCE(slot.avg_actual_w::INT, 0) AS error_w, CASE WHEN slot.avg_actual_w IS NOT NULL AND slot.avg_actual_w > 0 THEN ROUND( (fpi.power_w::NUMERIC - slot.avg_actual_w::NUMERIC) / slot.avg_actual_w::NUMERIC * 100, 4 ) ELSE NULL END AS error_pct FROM ems.forecast_pv_interval fpi JOIN ems.forecast_pv_run fpr ON fpr.id = fpi.run_id JOIN ems.asset_pv_array pa ON pa.id = fpr.pv_array_id LEFT JOIN LATERAL ( SELECT AVG( CASE WHEN pa.controllable = false THEN ti.gen_port_power_w::NUMERIC ELSE (COALESCE(ti.pv1_power_w, 0) + COALESCE(ti.pv2_power_w, 0))::NUMERIC END ) AS avg_actual_w FROM ems.telemetry_inverter ti WHERE ti.site_id = fpr.site_id AND ti.measured_at >= fpi.interval_start AND ti.measured_at < fpi.interval_start + INTERVAL '15 minutes' ) slot ON true WHERE fpr.site_id = p_site_id AND fpr.status = 'ok' AND fpi.interval_start < now() - INTERVAL '15 minutes' AND fpi.interval_start >= now() - make_interval(hours => p_lookback_hours) ON CONFLICT (run_id, interval_start) DO UPDATE SET actual_power_w = EXCLUDED.actual_power_w, actual_filled_at = EXCLUDED.actual_filled_at, error_w = EXCLUDED.error_w, error_pct = EXCLUDED.error_pct; GET DIAGNOSTICS v_count = ROW_COUNT; RETURN v_count; END; $$; COMMENT ON FUNCTION ems.fn_fill_forecast_accuracy(INT, INT) IS 'Doplní skutečné hodnoty výroby do forecast_accuracy z telemetrie. Volat každých 15 minut (spolu s audit_filler) pro inkrementální plnění. p_lookback_hours: kolik hodin zpět zpracovat (default 48h pro catch-up). Pro první backfill: SELECT ems.fn_fill_forecast_accuracy(2, 8760) -- 1 rok';