Made forecast-period conditionCode optional
This commit is contained in:
@@ -334,7 +334,7 @@ func TestForecastUSUnitsWithXMLFormatUppercaseQuery(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Now().UTC(),
|
||||
EndTime: time.Now().UTC().Add(time.Hour),
|
||||
ConditionCode: 1,
|
||||
ConditionCode: wmoCodePtr(1),
|
||||
TemperatureC: float64Ptr(10),
|
||||
}},
|
||||
},
|
||||
@@ -367,7 +367,7 @@ func TestForecastPrecisionTwo(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Now().UTC(),
|
||||
EndTime: time.Now().UTC().Add(time.Hour),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
TemperatureC: float64Ptr(12.345),
|
||||
}},
|
||||
},
|
||||
@@ -412,7 +412,7 @@ func TestForecastJSONOmitsLegacyDescriptionFields(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Now().UTC(),
|
||||
EndTime: time.Now().UTC().Add(time.Hour),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
TextDescription: "Cloudy",
|
||||
}},
|
||||
},
|
||||
@@ -463,7 +463,7 @@ func TestForecastTimezoneOffsetUppercaseTZConvertsAllTimes(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: periodStart,
|
||||
EndTime: periodEnd,
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
}},
|
||||
},
|
||||
}, "/forecast/hourly")
|
||||
@@ -498,7 +498,7 @@ func TestForecastTimezoneAbbreviationCDT(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: issuedAt,
|
||||
EndTime: issuedAt.Add(time.Hour),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
}},
|
||||
},
|
||||
}, "/forecast/hourly")
|
||||
@@ -525,7 +525,7 @@ func TestForecastTimezoneCityAliasChicago(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: issuedAt,
|
||||
EndTime: issuedAt.Add(time.Hour),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
}},
|
||||
},
|
||||
}, "/forecast/hourly")
|
||||
@@ -587,9 +587,9 @@ func TestForecastHourlyTodayFiltersByStartDateUTCDefault(t *testing.T) {
|
||||
Product: model.ForecastProductHourly,
|
||||
IssuedAt: time.Date(2026, 7, 10, 11, 0, 0, 0, time.UTC),
|
||||
Periods: []model.WeatherForecastPeriod{
|
||||
{StartTime: time.Date(2026, 7, 10, 0, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 10, 1, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 10, 23, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 2, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 3, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 10, 0, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 10, 1, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 10, 23, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 2, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 3, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
},
|
||||
},
|
||||
}, "/forecast/hourly/today")
|
||||
@@ -622,9 +622,9 @@ func TestForecastHourlyTomorrowFiltersByStartDateUTCDefault(t *testing.T) {
|
||||
Product: model.ForecastProductHourly,
|
||||
IssuedAt: time.Date(2026, 7, 10, 11, 0, 0, 0, time.UTC),
|
||||
Periods: []model.WeatherForecastPeriod{
|
||||
{StartTime: time.Date(2026, 7, 10, 23, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 1, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 15, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 16, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 10, 23, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 1, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 15, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 16, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
},
|
||||
},
|
||||
}, "/forecast/hourly/tomorrow")
|
||||
@@ -657,9 +657,9 @@ func TestForecastHourlyTodayTimezoneAffectsDaySlice(t *testing.T) {
|
||||
Product: model.ForecastProductHourly,
|
||||
IssuedAt: time.Date(2026, 7, 10, 11, 0, 0, 0, time.UTC),
|
||||
Periods: []model.WeatherForecastPeriod{
|
||||
{StartTime: time.Date(2026, 7, 10, 4, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 10, 5, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 3, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 4, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 5, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 6, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 10, 4, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 10, 5, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 3, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 4, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 5, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 6, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
},
|
||||
},
|
||||
}, "/forecast/hourly/today")
|
||||
@@ -690,8 +690,8 @@ func TestForecastHourlyTomorrowTimezoneAffectsDaySlice(t *testing.T) {
|
||||
Product: model.ForecastProductHourly,
|
||||
IssuedAt: time.Date(2026, 7, 10, 11, 0, 0, 0, time.UTC),
|
||||
Periods: []model.WeatherForecastPeriod{
|
||||
{StartTime: time.Date(2026, 7, 11, 3, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 4, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 5, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 6, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 3, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 4, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 5, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 6, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
},
|
||||
},
|
||||
}, "/forecast/hourly/tomorrow")
|
||||
@@ -724,7 +724,7 @@ func TestForecastHourlyTodaySupportsSameFlags(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Date(2026, 7, 10, 13, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2026, 7, 10, 14, 0, 0, 0, time.UTC),
|
||||
ConditionCode: 1,
|
||||
ConditionCode: wmoCodePtr(1),
|
||||
TemperatureC: float64Ptr(10.123),
|
||||
}},
|
||||
},
|
||||
@@ -755,7 +755,7 @@ func TestForecastHourlyTomorrowSupportsSameFlags(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Date(2026, 7, 11, 13, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2026, 7, 11, 14, 0, 0, 0, time.UTC),
|
||||
ConditionCode: 1,
|
||||
ConditionCode: wmoCodePtr(1),
|
||||
TemperatureC: float64Ptr(10.123),
|
||||
}},
|
||||
},
|
||||
@@ -835,7 +835,7 @@ func TestForecastHourlyTodayNoMatchingPeriodsReturnsDataWithEmptyPeriods(t *test
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Date(2026, 7, 11, 13, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2026, 7, 11, 14, 0, 0, 0, time.UTC),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
}},
|
||||
},
|
||||
}, "/forecast/hourly/today")
|
||||
@@ -888,7 +888,7 @@ func TestForecastNarrativePopulatedJSONEnvelope(t *testing.T) {
|
||||
StartTime: time.Now().UTC(),
|
||||
EndTime: time.Now().UTC().Add(12 * time.Hour),
|
||||
Name: "Tonight",
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
TextDescription: "Mostly clear overnight.",
|
||||
}},
|
||||
},
|
||||
@@ -952,7 +952,7 @@ func TestForecastNarrativeSupportsSameFlags(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Date(2026, 7, 10, 13, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2026, 7, 11, 1, 0, 0, 0, time.UTC),
|
||||
ConditionCode: 1,
|
||||
ConditionCode: wmoCodePtr(1),
|
||||
TemperatureC: float64Ptr(20.123),
|
||||
}},
|
||||
},
|
||||
@@ -1012,9 +1012,9 @@ func TestForecastNarrativeTodayTimezoneAffectsDaySlice(t *testing.T) {
|
||||
Product: model.ForecastProductNarrative,
|
||||
IssuedAt: time.Date(2026, 7, 10, 11, 0, 0, 0, time.UTC),
|
||||
Periods: []model.WeatherForecastPeriod{
|
||||
{StartTime: time.Date(2026, 7, 10, 4, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 10, 5, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 3, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 15, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 5, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 17, 30, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 10, 4, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 10, 5, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 3, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 15, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 5, 30, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 17, 30, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
},
|
||||
},
|
||||
}, "/forecast/narrative/today")
|
||||
@@ -1045,9 +1045,9 @@ func TestForecastNarrativeTomorrowFiltersByStartDateUTCDefault(t *testing.T) {
|
||||
Product: model.ForecastProductNarrative,
|
||||
IssuedAt: time.Date(2026, 7, 10, 11, 0, 0, 0, time.UTC),
|
||||
Periods: []model.WeatherForecastPeriod{
|
||||
{StartTime: time.Date(2026, 7, 10, 23, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 11, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC), ConditionCode: model.WMOUnknown},
|
||||
{StartTime: time.Date(2026, 7, 10, 23, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 11, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 0, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
{StartTime: time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC), ConditionCode: wmoCodePtr(model.WMOUnknown)},
|
||||
},
|
||||
},
|
||||
}, "/forecast/narrative/tomorrow")
|
||||
@@ -1806,6 +1806,11 @@ func float64Ptr(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func wmoCodePtr(v model.WMOCode) *model.WMOCode {
|
||||
out := v
|
||||
return &out
|
||||
}
|
||||
|
||||
type forecastTimePayload struct {
|
||||
Data struct {
|
||||
IssuedAt time.Time `json:"issuedAt"`
|
||||
|
||||
@@ -23,28 +23,28 @@ type WeatherForecastRunUS struct {
|
||||
|
||||
// WeatherForecastPeriodUS is the US-customary response shape for forecast periods.
|
||||
type WeatherForecastPeriodUS struct {
|
||||
StartTime time.Time `json:"startTime" xml:"startTime"`
|
||||
EndTime time.Time `json:"endTime" xml:"endTime"`
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty"`
|
||||
IsDay *bool `json:"isDay,omitempty" xml:"isDay,omitempty"`
|
||||
ConditionCode model.WMOCode `json:"conditionCode" xml:"conditionCode"`
|
||||
TextDescription string `json:"textDescription,omitempty" xml:"textDescription,omitempty"`
|
||||
TemperatureF *float64 `json:"temperatureF,omitempty" xml:"temperatureF,omitempty"`
|
||||
TemperatureFMin *float64 `json:"temperatureFMin,omitempty" xml:"temperatureFMin,omitempty"`
|
||||
TemperatureFMax *float64 `json:"temperatureFMax,omitempty" xml:"temperatureFMax,omitempty"`
|
||||
DewpointF *float64 `json:"dewpointF,omitempty" xml:"dewpointF,omitempty"`
|
||||
RelativeHumidityPercent *float64 `json:"relativeHumidityPercent,omitempty" xml:"relativeHumidityPercent,omitempty"`
|
||||
WindDirectionDegrees *float64 `json:"windDirectionDegrees,omitempty" xml:"windDirectionDegrees,omitempty"`
|
||||
WindSpeedMph *float64 `json:"windSpeedMph,omitempty" xml:"windSpeedMph,omitempty"`
|
||||
WindGustMph *float64 `json:"windGustMph,omitempty" xml:"windGustMph,omitempty"`
|
||||
BarometricPressureInHg *float64 `json:"barometricPressureInHg,omitempty" xml:"barometricPressureInHg,omitempty"`
|
||||
VisibilityMiles *float64 `json:"visibilityMiles,omitempty" xml:"visibilityMiles,omitempty"`
|
||||
ApparentTemperatureF *float64 `json:"apparentTemperatureF,omitempty" xml:"apparentTemperatureF,omitempty"`
|
||||
CloudCoverPercent *float64 `json:"cloudCoverPercent,omitempty" xml:"cloudCoverPercent,omitempty"`
|
||||
ProbabilityOfPrecipitationPercent *float64 `json:"probabilityOfPrecipitationPercent,omitempty" xml:"probabilityOfPrecipitationPercent,omitempty"`
|
||||
PrecipitationAmountIn *float64 `json:"precipitationAmountIn,omitempty" xml:"precipitationAmountIn,omitempty"`
|
||||
SnowfallDepthIn *float64 `json:"snowfallDepthIn,omitempty" xml:"snowfallDepthIn,omitempty"`
|
||||
UVIndex *float64 `json:"uvIndex,omitempty" xml:"uvIndex,omitempty"`
|
||||
StartTime time.Time `json:"startTime" xml:"startTime"`
|
||||
EndTime time.Time `json:"endTime" xml:"endTime"`
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty"`
|
||||
IsDay *bool `json:"isDay,omitempty" xml:"isDay,omitempty"`
|
||||
ConditionCode *model.WMOCode `json:"conditionCode,omitempty" xml:"conditionCode,omitempty"`
|
||||
TextDescription string `json:"textDescription,omitempty" xml:"textDescription,omitempty"`
|
||||
TemperatureF *float64 `json:"temperatureF,omitempty" xml:"temperatureF,omitempty"`
|
||||
TemperatureFMin *float64 `json:"temperatureFMin,omitempty" xml:"temperatureFMin,omitempty"`
|
||||
TemperatureFMax *float64 `json:"temperatureFMax,omitempty" xml:"temperatureFMax,omitempty"`
|
||||
DewpointF *float64 `json:"dewpointF,omitempty" xml:"dewpointF,omitempty"`
|
||||
RelativeHumidityPercent *float64 `json:"relativeHumidityPercent,omitempty" xml:"relativeHumidityPercent,omitempty"`
|
||||
WindDirectionDegrees *float64 `json:"windDirectionDegrees,omitempty" xml:"windDirectionDegrees,omitempty"`
|
||||
WindSpeedMph *float64 `json:"windSpeedMph,omitempty" xml:"windSpeedMph,omitempty"`
|
||||
WindGustMph *float64 `json:"windGustMph,omitempty" xml:"windGustMph,omitempty"`
|
||||
BarometricPressureInHg *float64 `json:"barometricPressureInHg,omitempty" xml:"barometricPressureInHg,omitempty"`
|
||||
VisibilityMiles *float64 `json:"visibilityMiles,omitempty" xml:"visibilityMiles,omitempty"`
|
||||
ApparentTemperatureF *float64 `json:"apparentTemperatureF,omitempty" xml:"apparentTemperatureF,omitempty"`
|
||||
CloudCoverPercent *float64 `json:"cloudCoverPercent,omitempty" xml:"cloudCoverPercent,omitempty"`
|
||||
ProbabilityOfPrecipitationPercent *float64 `json:"probabilityOfPrecipitationPercent,omitempty" xml:"probabilityOfPrecipitationPercent,omitempty"`
|
||||
PrecipitationAmountIn *float64 `json:"precipitationAmountIn,omitempty" xml:"precipitationAmountIn,omitempty"`
|
||||
SnowfallDepthIn *float64 `json:"snowfallDepthIn,omitempty" xml:"snowfallDepthIn,omitempty"`
|
||||
UVIndex *float64 `json:"uvIndex,omitempty" xml:"uvIndex,omitempty"`
|
||||
}
|
||||
|
||||
func ForecastPayload(run *model.WeatherForecastRun, units Units, precision int, tz *time.Location) any {
|
||||
@@ -69,7 +69,7 @@ func ForecastPayload(run *model.WeatherForecastRun, units Units, precision int,
|
||||
EndTime: inLocationTime(p.EndTime, tz),
|
||||
Name: p.Name,
|
||||
IsDay: copyBoolPtr(p.IsDay),
|
||||
ConditionCode: p.ConditionCode,
|
||||
ConditionCode: copyWMOCodePtr(p.ConditionCode),
|
||||
TextDescription: p.TextDescription,
|
||||
TemperatureF: roundedPtr(celsiusToFahrenheitPtr(p.TemperatureC), precision),
|
||||
TemperatureFMin: roundedPtr(celsiusToFahrenheitPtr(p.TemperatureCMin), precision),
|
||||
@@ -110,7 +110,7 @@ func ForecastPayload(run *model.WeatherForecastRun, units Units, precision int,
|
||||
EndTime: inLocationTime(p.EndTime, tz),
|
||||
Name: p.Name,
|
||||
IsDay: copyBoolPtr(p.IsDay),
|
||||
ConditionCode: p.ConditionCode,
|
||||
ConditionCode: copyWMOCodePtr(p.ConditionCode),
|
||||
TextDescription: p.TextDescription,
|
||||
TemperatureC: roundedPtr(copyFloat64Ptr(p.TemperatureC), precision),
|
||||
TemperatureCMin: roundedPtr(copyFloat64Ptr(p.TemperatureCMin), precision),
|
||||
|
||||
@@ -5,6 +5,8 @@ package presenter
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/model"
|
||||
)
|
||||
|
||||
func celsiusToFahrenheitPtr(v *float64) *float64 {
|
||||
@@ -47,6 +49,14 @@ func copyTimePtr(v *time.Time) *time.Time {
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyWMOCodePtr(v *model.WMOCode) *model.WMOCode {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
out := *v
|
||||
return &out
|
||||
}
|
||||
|
||||
func inLocationTime(v time.Time, loc *time.Location) time.Time {
|
||||
if loc == nil {
|
||||
return v
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestForecastPayloadUS(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: issuedAt,
|
||||
EndTime: issuedAt.Add(1 * time.Hour),
|
||||
ConditionCode: 63,
|
||||
ConditionCode: wmoCodePtr(63),
|
||||
TemperatureC: float64Ptr(0),
|
||||
TemperatureCMin: float64Ptr(-5),
|
||||
TemperatureCMax: float64Ptr(5),
|
||||
@@ -91,7 +91,7 @@ func TestForecastPayloadOmitsLegacyDescriptionFields(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: time.Date(2026, 3, 20, 12, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2026, 3, 20, 13, 0, 0, 0, time.UTC),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
TextDescription: "Cloudy",
|
||||
}},
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func TestForecastPayloadTimezoneConversionMetricAndUS(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: issuedAt.Add(1 * time.Hour),
|
||||
EndTime: issuedAt.Add(2 * time.Hour),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func TestForecastPayloadNoTimezonePreservesUTCAndCopySemantics(t *testing.T) {
|
||||
Periods: []model.WeatherForecastPeriod{{
|
||||
StartTime: issuedAt,
|
||||
EndTime: issuedAt.Add(time.Hour),
|
||||
ConditionCode: model.WMOUnknown,
|
||||
ConditionCode: wmoCodePtr(model.WMOUnknown),
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -438,6 +438,11 @@ func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func wmoCodePtr(v model.WMOCode) *model.WMOCode {
|
||||
out := v
|
||||
return &out
|
||||
}
|
||||
|
||||
func assertApprox(t *testing.T, got *float64, want, eps float64) {
|
||||
t.Helper()
|
||||
if got == nil {
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
// Layer: adapters/outbound/postgres forecast feature.
|
||||
package postgres
|
||||
|
||||
import "gitea.maximumdirect.net/ejr/weatherfeeder/model"
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/model"
|
||||
)
|
||||
|
||||
func mapForecastParentRow(row forecastParentRow) model.WeatherForecastRun {
|
||||
return model.WeatherForecastRun{
|
||||
@@ -23,7 +27,7 @@ func mapForecastPeriodRow(row forecastPeriodRow) model.WeatherForecastPeriod {
|
||||
EndTime: row.EndTime.UTC(),
|
||||
Name: stringValue(row.Name),
|
||||
IsDay: boolPtr(row.IsDay),
|
||||
ConditionCode: model.WMOCode(row.ConditionCode),
|
||||
ConditionCode: wmoCodePtr(row.ConditionCode),
|
||||
TextDescription: stringValue(row.TextDescription),
|
||||
TemperatureC: float64Ptr(row.TemperatureC),
|
||||
TemperatureCMin: float64Ptr(row.TemperatureCMin),
|
||||
@@ -43,3 +47,11 @@ func mapForecastPeriodRow(row forecastPeriodRow) model.WeatherForecastPeriod {
|
||||
UVIndex: float64Ptr(row.UVIndex),
|
||||
}
|
||||
}
|
||||
|
||||
func wmoCodePtr(v sql.NullInt64) *model.WMOCode {
|
||||
if !v.Valid {
|
||||
return nil
|
||||
}
|
||||
out := model.WMOCode(v.Int64)
|
||||
return &out
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type forecastPeriodRow struct {
|
||||
EndTime time.Time
|
||||
Name sql.NullString
|
||||
IsDay sql.NullBool
|
||||
ConditionCode int
|
||||
ConditionCode sql.NullInt64
|
||||
TextDescription sql.NullString
|
||||
TemperatureC sql.NullFloat64
|
||||
TemperatureCMin sql.NullFloat64
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestMapForecastPeriodRowNullables(t *testing.T) {
|
||||
period := mapForecastPeriodRow(forecastPeriodRow{
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
ConditionCode: 80,
|
||||
ConditionCode: sql.NullInt64{Int64: 80, Valid: true},
|
||||
Name: sql.NullString{String: "Midnight", Valid: true},
|
||||
TemperatureC: sql.NullFloat64{Float64: 12.5, Valid: true},
|
||||
TemperatureCMin: sql.NullFloat64{Valid: false},
|
||||
@@ -84,6 +84,21 @@ func TestMapForecastPeriodRowNullables(t *testing.T) {
|
||||
if !period.StartTime.Equal(start) || !period.EndTime.Equal(end) {
|
||||
t.Fatalf("unexpected time range: %s - %s", period.StartTime, period.EndTime)
|
||||
}
|
||||
if period.ConditionCode == nil || *period.ConditionCode != 80 {
|
||||
t.Fatalf("expected condition code pointer 80, got %v", period.ConditionCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapForecastPeriodRowConditionCodeNullable(t *testing.T) {
|
||||
period := mapForecastPeriodRow(forecastPeriodRow{
|
||||
StartTime: time.Date(2026, 3, 20, 0, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2026, 3, 20, 1, 0, 0, 0, time.UTC),
|
||||
ConditionCode: sql.NullInt64{Valid: false},
|
||||
})
|
||||
|
||||
if period.ConditionCode != nil {
|
||||
t.Fatalf("expected nil condition code, got %v", period.ConditionCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapDiscussionParentRowNullables(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,9 @@ Periods: {{len .Data.Periods}}
|
||||
{{- range $i, $period := .Data.Periods}}
|
||||
|
||||
[{{$i}}] {{$period.StartTime}} -> {{$period.EndTime}}
|
||||
{{- if $period.ConditionCode}}
|
||||
Condition Code: {{$period.ConditionCode}}
|
||||
{{- end}}
|
||||
{{- if $period.TextDescription}}
|
||||
Summary: {{$period.TextDescription}}
|
||||
{{- end}}
|
||||
|
||||
@@ -10,7 +10,9 @@ Periods: {{len .Data.Periods}}
|
||||
{{- if $period.Name}}
|
||||
Name: {{$period.Name}}
|
||||
{{- end}}
|
||||
{{- if $period.ConditionCode}}
|
||||
Condition Code: {{$period.ConditionCode}}
|
||||
{{- end}}
|
||||
{{- if $period.TextDescription}}
|
||||
Summary: {{$period.TextDescription}}
|
||||
{{- end}}
|
||||
|
||||
Reference in New Issue
Block a user