update for 4 hour rain forecast

This commit is contained in:
2026-04-06 18:32:33 +10:00
parent fb50c8ed71
commit 3a7309b2cf
20 changed files with 716 additions and 132 deletions
+28
View File
@@ -112,6 +112,34 @@ CREATE INDEX IF NOT EXISTS idx_predictions_rain_1h_site_ts
CREATE INDEX IF NOT EXISTS idx_predictions_rain_1h_pending_eval
ON predictions_rain_1h(site, evaluated_at, ts DESC);
-- Rain model predictions (next 4h)
CREATE TABLE IF NOT EXISTS predictions_rain_4h (
ts TIMESTAMPTZ NOT NULL,
generated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
site TEXT NOT NULL,
model_name TEXT NOT NULL,
model_version TEXT NOT NULL,
threshold DOUBLE PRECISION NOT NULL,
probability DOUBLE PRECISION NOT NULL,
predict_rain BOOLEAN NOT NULL,
rain_next_4h_mm_actual DOUBLE PRECISION,
rain_next_4h_actual BOOLEAN,
evaluated_at TIMESTAMPTZ,
metadata JSONB,
PRIMARY KEY (site, model_name, model_version, ts)
);
SELECT create_hypertable('predictions_rain_4h', 'ts', if_not_exists => TRUE);
CREATE INDEX IF NOT EXISTS idx_predictions_rain_4h_site_ts
ON predictions_rain_4h(site, ts DESC);
CREATE INDEX IF NOT EXISTS idx_predictions_rain_4h_pending_eval
ON predictions_rain_4h(site, evaluated_at, ts DESC);
-- Raw retention: 90 days
DO $$
BEGIN
+79 -15
View File
@@ -100,44 +100,104 @@ FROM baseline;
CREATE OR REPLACE VIEW rain_prediction_drift_daily AS
WITH all_predictions AS (
SELECT
1::INT AS horizon_hours,
ts,
site,
model_name,
model_version,
threshold,
probability,
predict_rain
FROM predictions_rain_1h
UNION ALL
SELECT
4::INT AS horizon_hours,
ts,
site,
model_name,
model_version,
threshold,
probability,
predict_rain
FROM predictions_rain_4h
)
SELECT
time_bucket(INTERVAL '1 day', ts) AS day,
site,
model_name,
model_version,
horizon_hours,
count(*) AS prediction_rows,
avg(probability) AS probability_mean,
stddev_samp(probability) AS probability_stddev,
avg(CASE WHEN predict_rain THEN 1.0 ELSE 0.0 END) AS predicted_positive_rate,
avg(threshold) AS threshold_mean
FROM predictions_rain_1h
GROUP BY 1,2,3,4;
FROM all_predictions
GROUP BY 1,2,3,4,5;
CREATE OR REPLACE VIEW rain_calibration_drift_daily AS
WITH all_predictions AS (
SELECT
1::INT AS horizon_hours,
ts,
site,
model_name,
model_version,
probability,
predict_rain,
rain_next_1h_actual AS rain_actual
FROM predictions_rain_1h
UNION ALL
SELECT
4::INT AS horizon_hours,
ts,
site,
model_name,
model_version,
probability,
predict_rain,
rain_next_4h_actual AS rain_actual
FROM predictions_rain_4h
)
SELECT
time_bucket(INTERVAL '1 day', ts) AS day,
site,
model_name,
model_version,
count(*) FILTER (WHERE rain_next_1h_actual IS NOT NULL) AS evaluated_rows,
avg(probability) FILTER (WHERE rain_next_1h_actual IS NOT NULL) AS mean_probability,
avg(CASE WHEN rain_next_1h_actual THEN 1.0 ELSE 0.0 END) FILTER (WHERE rain_next_1h_actual IS NOT NULL) AS observed_positive_rate,
avg(power(probability - CASE WHEN rain_next_1h_actual THEN 1.0 ELSE 0.0 END, 2.0))
FILTER (WHERE rain_next_1h_actual IS NOT NULL) AS brier_score,
horizon_hours,
count(*) FILTER (WHERE rain_actual IS NOT NULL) AS evaluated_rows,
avg(probability) FILTER (WHERE rain_actual IS NOT NULL) AS mean_probability,
avg(CASE WHEN rain_actual THEN 1.0 ELSE 0.0 END) FILTER (WHERE rain_actual IS NOT NULL) AS observed_positive_rate,
avg(power(probability - CASE WHEN rain_actual THEN 1.0 ELSE 0.0 END, 2.0))
FILTER (WHERE rain_actual IS NOT NULL) AS brier_score,
avg(
CASE
WHEN rain_next_1h_actual IS NULL THEN NULL
WHEN predict_rain = rain_next_1h_actual THEN 1.0
WHEN rain_actual IS NULL THEN NULL
WHEN predict_rain = rain_actual THEN 1.0
ELSE 0.0
END
) AS decision_accuracy
FROM predictions_rain_1h
GROUP BY 1,2,3,4;
FROM all_predictions
GROUP BY 1,2,3,4,5;
CREATE OR REPLACE VIEW rain_pipeline_health AS
WITH sites AS (
WITH prediction_latest AS (
SELECT
site,
max(generated_at) AS prediction_generated_latest_ts,
max(evaluated_at) AS prediction_evaluated_latest_ts
FROM (
SELECT site, generated_at, evaluated_at FROM predictions_rain_1h
UNION ALL
SELECT site, generated_at, evaluated_at FROM predictions_rain_4h
) p
GROUP BY site
),
sites AS (
SELECT DISTINCT site FROM observations_ws90
UNION
SELECT DISTINCT site FROM observations_baro
@@ -145,12 +205,16 @@ WITH sites AS (
SELECT DISTINCT site FROM forecast_openmeteo_hourly
UNION
SELECT DISTINCT site FROM predictions_rain_1h
UNION
SELECT DISTINCT site FROM predictions_rain_4h
)
SELECT
s.site,
(SELECT max(ts) FROM observations_ws90 w WHERE w.site = s.site) AS ws90_latest_ts,
(SELECT max(ts) FROM observations_baro b WHERE b.site = s.site) AS baro_latest_ts,
(SELECT max(ts) FROM forecast_openmeteo_hourly f WHERE f.site = s.site) AS forecast_latest_ts,
(SELECT max(generated_at) FROM predictions_rain_1h p WHERE p.site = s.site) AS prediction_generated_latest_ts,
(SELECT max(evaluated_at) FROM predictions_rain_1h p WHERE p.site = s.site) AS prediction_evaluated_latest_ts
FROM sites s;
p.prediction_generated_latest_ts,
p.prediction_evaluated_latest_ts
FROM sites s
LEFT JOIN prediction_latest p
ON p.site = s.site;
+29
View File
@@ -0,0 +1,29 @@
-- Add 4-hour rain prediction storage table.
-- Safe to re-run.
CREATE TABLE IF NOT EXISTS predictions_rain_4h (
ts TIMESTAMPTZ NOT NULL,
generated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
site TEXT NOT NULL,
model_name TEXT NOT NULL,
model_version TEXT NOT NULL,
threshold DOUBLE PRECISION NOT NULL,
probability DOUBLE PRECISION NOT NULL,
predict_rain BOOLEAN NOT NULL,
rain_next_4h_mm_actual DOUBLE PRECISION,
rain_next_4h_actual BOOLEAN,
evaluated_at TIMESTAMPTZ,
metadata JSONB,
PRIMARY KEY (site, model_name, model_version, ts)
);
SELECT create_hypertable('predictions_rain_4h', 'ts', if_not_exists => TRUE);
CREATE INDEX IF NOT EXISTS idx_predictions_rain_4h_site_ts
ON predictions_rain_4h(site, ts DESC);
CREATE INDEX IF NOT EXISTS idx_predictions_rain_4h_pending_eval
ON predictions_rain_4h(site, evaluated_at, ts DESC);