package data import ( "context" "database/sql" "time" ) type PredictPoint struct { ForecastTime time.Time RainMMx1000 int32 } // ForecastRainAtIssued returns forecast hourly rows for a given station/provider at an exact issued_at time. func ForecastRainAtIssued(ctx context.Context, stationID, provider string, issuedAt time.Time) ([]PredictPoint, error) { const q = ` SELECT forecast_time, rain_mm_x1000 FROM forecast_hourly WHERE station_id=$1 AND provider=$2 AND issued_at=$3 ORDER BY forecast_time ASC` rows, err := DB().QueryContext(ctx, q, stationID, provider, issuedAt) if err != nil { return nil, err } defer rows.Close() var out []PredictPoint for rows.Next() { var p PredictPoint var rain sql.NullInt32 if err := rows.Scan(&p.ForecastTime, &rain); err != nil { continue } if rain.Valid { p.RainMMx1000 = rain.Int32 } out = append(out, p) } return out, nil } // ResolveIssuedAtInBucket finds the latest issued_at in [bucket, bucket+1h) for station/provider. func ResolveIssuedAtInBucket(ctx context.Context, stationID, provider string, bucketHour time.Time) (time.Time, bool, error) { const q = `SELECT issued_at FROM forecast_hourly WHERE station_id=$1 AND provider=$2 AND issued_at >= $3 AND issued_at < $3 + interval '1 hour' ORDER BY issued_at DESC LIMIT 1` var t time.Time err := DB().QueryRowContext(ctx, q, stationID, provider, bucketHour).Scan(&t) if err == sql.ErrNoRows { return time.Time{}, false, nil } if err != nil { return time.Time{}, false, err } return t, true, nil } // UpsertForecastRain writes rain-only rows for a provider at issued_at, upserting by key. // Only the rain_mm_x1000 column is set/updated; other columns remain NULL or unchanged. type UpsertRainItem struct { ForecastTime time.Time RainMMx1000 int32 } func UpsertForecastRain(ctx context.Context, stationID, provider string, issuedAt time.Time, items []UpsertRainItem) error { if len(items) == 0 { return nil } const q = ` INSERT INTO forecast_hourly ( station_id, provider, issued_at, forecast_time, rain_mm_x1000 ) VALUES ($1,$2,$3,$4,$5) ON CONFLICT (station_id, provider, issued_at, forecast_time) DO UPDATE SET rain_mm_x1000 = EXCLUDED.rain_mm_x1000` tx, err := DB().BeginTx(ctx, &sql.TxOptions{}) if err != nil { return err } defer func() { _ = tx.Rollback() }() for _, it := range items { if _, err := tx.ExecContext(ctx, q, stationID, provider, issuedAt, it.ForecastTime, it.RainMMx1000); err != nil { return err } } return tx.Commit() }