work on model training

This commit is contained in:
2026-03-05 11:03:20 +11:00
parent 96e72d7c43
commit c8e38cd597
10 changed files with 534 additions and 30 deletions

View File

@@ -46,6 +46,19 @@ type ForecastSeries struct {
Points []ForecastPoint `json:"points"`
}
type RainPredictionPoint struct {
TS time.Time `json:"ts"`
GeneratedAt time.Time `json:"generated_at"`
ModelName string `json:"model_name"`
ModelVersion string `json:"model_version"`
Threshold float64 `json:"threshold"`
Probability float64 `json:"probability"`
PredictRain bool `json:"predict_rain"`
RainNext1hMM *float64 `json:"rain_next_1h_mm_actual,omitempty"`
RainNext1hActual *bool `json:"rain_next_1h_actual,omitempty"`
EvaluatedAt *time.Time `json:"evaluated_at,omitempty"`
}
func (d *DB) ObservationSeries(ctx context.Context, site, bucket string, start, end time.Time) ([]ObservationPoint, error) {
if end.Before(start) || end.Equal(start) {
return nil, errors.New("invalid time range")
@@ -352,6 +365,139 @@ func (d *DB) ForecastSeriesRange(ctx context.Context, site, model string, start,
}, nil
}
func (d *DB) LatestRainPrediction(ctx context.Context, site, modelName string) (*RainPredictionPoint, error) {
query := `
SELECT
ts,
generated_at,
model_name,
model_version,
threshold,
probability,
predict_rain,
rain_next_1h_mm_actual,
rain_next_1h_actual,
evaluated_at
FROM predictions_rain_1h
WHERE site = $1
AND model_name = $2
ORDER BY ts DESC, generated_at DESC
LIMIT 1
`
var (
p RainPredictionPoint
rainMM, threshold, probability sql.NullFloat64
rainActual sql.NullBool
evaluatedAt sql.NullTime
predictRain sql.NullBool
)
err := d.Pool.QueryRow(ctx, query, site, modelName).Scan(
&p.TS,
&p.GeneratedAt,
&p.ModelName,
&p.ModelVersion,
&threshold,
&probability,
&predictRain,
&rainMM,
&rainActual,
&evaluatedAt,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
if threshold.Valid {
p.Threshold = threshold.Float64
}
if probability.Valid {
p.Probability = probability.Float64
}
if predictRain.Valid {
p.PredictRain = predictRain.Bool
}
p.RainNext1hMM = nullFloatPtr(rainMM)
p.RainNext1hActual = nullBoolPtr(rainActual)
p.EvaluatedAt = nullTimePtr(evaluatedAt)
return &p, nil
}
func (d *DB) RainPredictionSeriesRange(ctx context.Context, site, modelName string, start, end time.Time) ([]RainPredictionPoint, error) {
query := `
SELECT DISTINCT ON (ts)
ts,
generated_at,
model_name,
model_version,
threshold,
probability,
predict_rain,
rain_next_1h_mm_actual,
rain_next_1h_actual,
evaluated_at
FROM predictions_rain_1h
WHERE site = $1
AND model_name = $2
AND ts >= $3
AND ts <= $4
ORDER BY ts ASC, generated_at DESC
`
rows, err := d.Pool.Query(ctx, query, site, modelName, start, end)
if err != nil {
return nil, err
}
defer rows.Close()
points := make([]RainPredictionPoint, 0, 256)
for rows.Next() {
var (
p RainPredictionPoint
rainMM, threshold, probability sql.NullFloat64
rainActual sql.NullBool
evaluatedAt sql.NullTime
predictRain sql.NullBool
)
if err := rows.Scan(
&p.TS,
&p.GeneratedAt,
&p.ModelName,
&p.ModelVersion,
&threshold,
&probability,
&predictRain,
&rainMM,
&rainActual,
&evaluatedAt,
); err != nil {
return nil, err
}
if threshold.Valid {
p.Threshold = threshold.Float64
}
if probability.Valid {
p.Probability = probability.Float64
}
if predictRain.Valid {
p.PredictRain = predictRain.Bool
}
p.RainNext1hMM = nullFloatPtr(rainMM)
p.RainNext1hActual = nullBoolPtr(rainActual)
p.EvaluatedAt = nullTimePtr(evaluatedAt)
points = append(points, p)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return points, nil
}
func nullFloatPtr(v sql.NullFloat64) *float64 {
if !v.Valid {
return nil
@@ -367,3 +513,19 @@ func nullIntPtr(v sql.NullInt64) *int64 {
val := v.Int64
return &val
}
func nullBoolPtr(v sql.NullBool) *bool {
if !v.Valid {
return nil
}
val := v.Bool
return &val
}
func nullTimePtr(v sql.NullTime) *time.Time {
if !v.Valid {
return nil
}
val := v.Time
return &val
}