Updated model and normalizers to map provider “feels like” fields into a single ApparentTemperatureC field.

This commit is contained in:
2026-01-17 10:36:19 -06:00
parent c12cf91115
commit b4a67e208c
8 changed files with 33 additions and 14 deletions

View File

@@ -103,8 +103,6 @@ type WeatherForecastPeriod struct {
BarometricPressurePa *float64 `json:"barometricPressurePa,omitempty"`
VisibilityMeters *float64 `json:"visibilityMeters,omitempty"`
ApparentTemperatureC *float64 `json:"apparentTemperatureC,omitempty"`
WindChillC *float64 `json:"windChillC,omitempty"`
HeatIndexC *float64 `json:"heatIndexC,omitempty"`
CloudCoverPercent *float64 `json:"cloudCoverPercent,omitempty"`
// Precipitation (forecast-specific). Keep these generic and provider-independent.

View File

@@ -36,8 +36,7 @@ type WeatherObservation struct {
VisibilityMeters *float64 `json:"visibilityMeters,omitempty"`
RelativeHumidityPercent *float64 `json:"relativeHumidityPercent,omitempty"`
WindChillC *float64 `json:"windChillC,omitempty"`
HeatIndexC *float64 `json:"heatIndexC,omitempty"`
ApparentTemperatureC *float64 `json:"apparentTemperatureC,omitempty"`
ElevationMeters *float64 `json:"elevationMeters,omitempty"`
RawMessage string `json:"rawMessage,omitempty"`

View File

@@ -80,6 +80,14 @@ func buildObservation(parsed nwsObservationResponse) (model.WeatherObservation,
// NWS observation responses typically do not include a day/night flag -> nil.
canonicalText := standards.WMOText(wmo, nil)
// Apparent temperature: prefer wind chill when both are supplied.
var apparentC *float64
if parsed.Properties.WindChill.Value != nil {
apparentC = parsed.Properties.WindChill.Value
} else if parsed.Properties.HeatIndex.Value != nil {
apparentC = parsed.Properties.HeatIndex.Value
}
obs := model.WeatherObservation{
StationID: parsed.Properties.StationID,
StationName: parsed.Properties.StationName,
@@ -108,8 +116,7 @@ func buildObservation(parsed nwsObservationResponse) (model.WeatherObservation,
VisibilityMeters: parsed.Properties.Visibility.Value,
RelativeHumidityPercent: parsed.Properties.RelativeHumidity.Value,
WindChillC: parsed.Properties.WindChill.Value,
HeatIndexC: parsed.Properties.HeatIndex.Value,
ApparentTemperatureC: apparentC,
ElevationMeters: parsed.Properties.Elevation.Value,
RawMessage: parsed.Properties.RawMessage,

View File

@@ -25,7 +25,7 @@ import (
// Event.EmittedAt when present, otherwise the first hourly time.
// - Hourly payloads are array-oriented; missing fields are treated as nil per-period.
// - Snowfall is provided in centimeters and is converted to millimeters.
// - apparent_temperature is ignored (no canonical "feels like" field).
// - apparent_temperature is mapped to ApparentTemperatureC when present.
type ForecastNormalizer struct{}
func (ForecastNormalizer) Match(e event.Event) bool {
@@ -121,6 +121,9 @@ func buildForecast(parsed omForecastResponse, fallbackIssued time.Time) (model.W
if v := floatAt(parsed.Hourly.Temperature2m, i); v != nil {
period.TemperatureC = v
}
if v := floatAt(parsed.Hourly.ApparentTemp, i); v != nil {
period.ApparentTemperatureC = v
}
if v := floatAt(parsed.Hourly.DewPoint2m, i); v != nil {
period.DewpointC = v
}

View File

@@ -107,6 +107,10 @@ func buildObservation(parsed omResponse) (model.WeatherObservation, time.Time, e
v := *parsed.Current.Temperature2m
obs.TemperatureC = &v
}
if parsed.Current.ApparentTemperature != nil {
v := *parsed.Current.ApparentTemperature
obs.ApparentTemperatureC = &v
}
if parsed.Current.RelativeHumidity2m != nil {
v := *parsed.Current.RelativeHumidity2m

View File

@@ -19,6 +19,7 @@ type omCurrent struct {
Time string `json:"time"` // e.g. "2026-01-10T12:30" (often no timezone suffix)
Temperature2m *float64 `json:"temperature_2m"`
ApparentTemperature *float64 `json:"apparent_temperature"`
RelativeHumidity2m *float64 `json:"relative_humidity_2m"`
WeatherCode *int `json:"weather_code"`

View File

@@ -68,6 +68,11 @@ func buildObservation(parsed owmResponse) (model.WeatherObservation, time.Time,
// - wind speed is m/s -> km/h conversion
tempC := parsed.Main.Temp
rh := parsed.Main.Humidity
var apparentC *float64
if parsed.Main.FeelsLike != nil {
v := *parsed.Main.FeelsLike
apparentC = &v
}
surfacePa := normcommon.PressurePaFromHPa(parsed.Main.Pressure)
var seaLevelPa *float64
@@ -117,6 +122,7 @@ func buildObservation(parsed owmResponse) (model.WeatherObservation, time.Time,
IconURL: iconURL,
TemperatureC: &tempC,
ApparentTemperatureC: apparentC,
WindDirectionDegrees: parsed.Wind.Deg,
WindSpeedKmh: &wsKmh,

View File

@@ -16,6 +16,7 @@ type owmResponse struct {
Main struct {
Temp float64 `json:"temp"` // °C when units=metric (enforced by source)
FeelsLike *float64 `json:"feels_like"` // °C when units=metric (enforced by source)
Pressure float64 `json:"pressure"` // hPa
Humidity float64 `json:"humidity"` // %
SeaLevel *float64 `json:"sea_level"`