All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
// 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
|
|
}
|