Add a /conditions/current endpoint
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful

This commit is contained in:
2026-03-19 23:16:26 -05:00
parent 26a52f8c44
commit 8deb4fd12e
10 changed files with 576 additions and 3 deletions

View File

@@ -36,6 +36,51 @@ FROM observations
ORDER BY observed_at DESC, event_emitted_at DESC
LIMIT 1`
queryCurrentConditions = `
WITH windowed AS (
SELECT
temperature_c,
apparent_temperature_c,
dewpoint_c,
relative_humidity_percent,
wind_speed_kmh,
wind_direction_degrees,
condition_code,
is_day,
observed_at
FROM observations
WHERE observed_at > CURRENT_TIMESTAMP - make_interval(mins => $1)
)
SELECT
COUNT(*) AS sample_count,
AVG(temperature_c) AS temperature_c,
AVG(apparent_temperature_c) AS apparent_temperature_c,
AVG(dewpoint_c) AS dewpoint_c,
AVG(relative_humidity_percent) AS relative_humidity_percent,
AVG(wind_speed_kmh) AS wind_speed_kmh,
CASE
WHEN atan2d(
AVG(sind(wind_direction_degrees)),
AVG(cosd(wind_direction_degrees))
) < 0
THEN atan2d(
AVG(sind(wind_direction_degrees)),
AVG(cosd(wind_direction_degrees))
) + 360.0
ELSE atan2d(
AVG(sind(wind_direction_degrees)),
AVG(cosd(wind_direction_degrees))
)
END AS wind_direction_degrees,
MAX(condition_code) AS condition_code,
(
SELECT is_day
FROM windowed
ORDER BY observed_at DESC
LIMIT 1
) AS is_day
FROM windowed`
queryObservationPresentWeather = `
SELECT weather_index, raw_text
FROM observation_present_weather
@@ -193,6 +238,33 @@ func (r *Repository) LatestObservation(ctx context.Context) (*model.WeatherObser
return &obs, nil
}
func (r *Repository) CurrentConditions(ctx context.Context, observationWindowMinutes int) (*core.CurrentConditions, error) {
if r == nil || r.db == nil {
return nil, fmt.Errorf("postgres repository is not configured")
}
var row currentConditionsRow
err := r.db.QueryRowContext(ctx, queryCurrentConditions, observationWindowMinutes).Scan(
&row.SampleCount,
&row.TemperatureC,
&row.ApparentTemperatureC,
&row.DewpointC,
&row.RelativeHumidityPercent,
&row.WindSpeedKmh,
&row.WindDirectionDegrees,
&row.ConditionCode,
&row.IsDay,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("query current conditions: %w", err)
}
return mapCurrentConditionsRow(row), nil
}
func (r *Repository) LatestHourlyForecast(ctx context.Context) (*model.WeatherForecastRun, error) {
if r == nil || r.db == nil {
return nil, fmt.Errorf("postgres repository is not configured")
@@ -419,6 +491,40 @@ type observationParentRow struct {
ApparentTemperatureC sql.NullFloat64
}
type currentConditionsRow struct {
SampleCount int64
TemperatureC sql.NullFloat64
ApparentTemperatureC sql.NullFloat64
DewpointC sql.NullFloat64
RelativeHumidityPercent sql.NullFloat64
WindSpeedKmh sql.NullFloat64
WindDirectionDegrees sql.NullFloat64
ConditionCode sql.NullInt64
IsDay sql.NullBool
}
func mapCurrentConditionsRow(row currentConditionsRow) *core.CurrentConditions {
if row.SampleCount == 0 {
return nil
}
conditionCode := model.WMOUnknown
if row.ConditionCode.Valid {
conditionCode = model.WMOCode(row.ConditionCode.Int64)
}
return &core.CurrentConditions{
TemperatureC: float64Ptr(row.TemperatureC),
ApparentTemperatureC: float64Ptr(row.ApparentTemperatureC),
DewpointC: float64Ptr(row.DewpointC),
RelativeHumidityPercent: float64Ptr(row.RelativeHumidityPercent),
WindSpeedKmh: float64Ptr(row.WindSpeedKmh),
WindDirectionDegrees: float64Ptr(row.WindDirectionDegrees),
ConditionCode: conditionCode,
IsDay: boolPtr(row.IsDay),
}
}
func mapObservationParentRow(row observationParentRow) model.WeatherObservation {
return model.WeatherObservation{
StationID: stringValue(row.StationID),