Updated model and normalizers to map provider “feels like” fields into a single ApparentTemperatureC field.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user