// forecast_read.go executes forecast and period queries. // Layer: adapters/outbound/postgres forecast feature. package postgres import ( "context" "database/sql" "errors" "fmt" "gitea.maximumdirect.net/ejr/weatherfeeder/model" ) func (r *Repository) LatestHourlyForecast(ctx context.Context) (*model.WeatherForecastRun, error) { return r.loadLatestForecastRun(ctx, queryLatestHourlyForecast, "hourly") } func (r *Repository) LatestNarrativeForecast(ctx context.Context) (*model.WeatherForecastRun, error) { return r.loadLatestForecastRun(ctx, queryLatestNarrativeForecast, "narrative") } func (r *Repository) loadLatestForecastRun( ctx context.Context, parentQuery string, productLabel string, ) (*model.WeatherForecastRun, error) { if r == nil || r.db == nil { return nil, fmt.Errorf("postgres repository is not configured") } var row forecastParentRow err := r.db.QueryRowContext(ctx, parentQuery).Scan( &row.EventID, &row.LocationID, &row.LocationName, &row.IssuedAt, &row.UpdatedAt, &row.Product, &row.Latitude, &row.Longitude, &row.ElevationMeters, ) if errors.Is(err, sql.ErrNoRows) { return nil, nil } if err != nil { return nil, fmt.Errorf("query latest %s forecast: %w", productLabel, err) } run := mapForecastParentRow(row) periods, err := r.loadForecastPeriods(ctx, row.EventID) if err != nil { return nil, err } run.Periods = periods return &run, nil } func (r *Repository) loadForecastPeriods(ctx context.Context, eventID string) ([]model.WeatherForecastPeriod, error) { rows, err := r.db.QueryContext(ctx, queryForecastPeriods, eventID) if err != nil { return nil, fmt.Errorf("query forecast periods: %w", err) } defer rows.Close() out := make([]model.WeatherForecastPeriod, 0) for rows.Next() { var row forecastPeriodRow if err := rows.Scan( &row.PeriodIndex, &row.StartTime, &row.EndTime, &row.Name, &row.IsDay, &row.ConditionCode, &row.TextDescription, &row.TemperatureC, &row.TemperatureCMin, &row.TemperatureCMax, &row.DewpointC, &row.RelativeHumidityPercent, &row.WindDirectionDegrees, &row.WindSpeedKmh, &row.WindGustKmh, &row.BarometricPressurePa, &row.VisibilityMeters, &row.ApparentTemperatureC, &row.CloudCoverPercent, &row.ProbabilityOfPrecipitationPercent, &row.PrecipitationAmountMM, &row.SnowfallDepthMM, &row.UVIndex, ); err != nil { return nil, fmt.Errorf("scan forecast period row: %w", err) } out = append(out, mapForecastPeriodRow(row)) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate forecast period rows: %w", err) } return out, nil }