Refactored the derived_daily_summary module to utilize narrative forecast data where available
This commit is contained in:
@@ -5,22 +5,20 @@ import (
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
type DerivedDailySummaryModule struct {
|
||||
Date string `json:"date,omitempty"`
|
||||
HighTempF *int `json:"high_temp_f,omitempty"`
|
||||
LowTempF *int `json:"low_temp_f,omitempty"`
|
||||
MaxPopPercent *int `json:"max_pop_percent,omitempty"`
|
||||
MaxPopWindow string `json:"max_pop_window,omitempty"`
|
||||
FirstPrecipHour string `json:"first_precip_hour,omitempty"`
|
||||
LastPrecipHour string `json:"last_precip_hour,omitempty"`
|
||||
ThunderMentioned bool `json:"thunder_mentioned"`
|
||||
MaxWindGustMph *int `json:"max_wind_gust_mph,omitempty"`
|
||||
HeatIndexMaxF *int `json:"heat_index_max_f,omitempty"`
|
||||
DominantConditions []string `json:"dominant_conditions,omitempty"`
|
||||
Hazards []string `json:"hazards,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
HighTempF *int `json:"high_temp_f,omitempty"`
|
||||
LowTempF *int `json:"low_temp_f,omitempty"`
|
||||
DailyPrecipitationProbability *int `json:"daily_precipitation_probability,omitempty"`
|
||||
MostLikelyPrecipitationHour string `json:"most_likely_precipitation_hour,omitempty"`
|
||||
ThunderMentioned bool `json:"thunder_mentioned"`
|
||||
MaxWindGustMph *int `json:"max_wind_gust_mph,omitempty"`
|
||||
HeatIndexMaxF *int `json:"heat_index_max_f,omitempty"`
|
||||
DominantConditions []string `json:"dominant_conditions,omitempty"`
|
||||
Hazards []string `json:"hazards,omitempty"`
|
||||
}
|
||||
|
||||
func buildDerivedDailySummaryModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
||||
@@ -46,17 +44,10 @@ func derivedDailySummaryValue(summary forecast.DailySummary, timing forecast.Pre
|
||||
var apparent forecast.Range
|
||||
var maxPop *forecast.TimedValue
|
||||
var maxGust *forecast.TimedValue
|
||||
var maxPopWindow timeutil.Period
|
||||
for _, daypart := range summary.Dayparts {
|
||||
addRange(&temperature, daypart.Temperature)
|
||||
addRange(&apparent, daypart.ApparentTemperature)
|
||||
if daypart.MaxPrecipitationProbability != nil {
|
||||
if maxPop == nil || daypart.MaxPrecipitationProbability.Value > maxPop.Value {
|
||||
copied := *daypart.MaxPrecipitationProbability
|
||||
maxPop = &copied
|
||||
maxPopWindow = daypart.Period
|
||||
}
|
||||
}
|
||||
maxTimedValue(&maxPop, daypart.MaxPrecipitationProbability)
|
||||
maxTimedValue(&maxGust, daypart.PeakWindGust)
|
||||
if daypart.DominantCondition != "" {
|
||||
conditions[daypart.DominantCondition] = struct{}{}
|
||||
@@ -70,19 +61,112 @@ func derivedDailySummaryValue(summary forecast.DailySummary, timing forecast.Pre
|
||||
hazards[alert.Event] = struct{}{}
|
||||
}
|
||||
}
|
||||
value.HighTempF = roundedInt(temperature.Max)
|
||||
value.LowTempF = roundedInt(temperature.Min)
|
||||
narrativeTemperature := narrativeTemperatureRange(summary.NarrativePeriods)
|
||||
if narrativeTemperature.Max != nil {
|
||||
value.HighTempF = roundedInt(narrativeTemperature.Max)
|
||||
} else {
|
||||
value.HighTempF = roundedInt(temperature.Max)
|
||||
}
|
||||
if narrativeTemperature.Min != nil {
|
||||
value.LowTempF = roundedInt(narrativeTemperature.Min)
|
||||
} else {
|
||||
value.LowTempF = roundedInt(temperature.Min)
|
||||
}
|
||||
value.HeatIndexMaxF = roundedInt(apparent.Max)
|
||||
narrativePrecipitation := narrativeMaxPrecipitation(summary.NarrativePeriods)
|
||||
if narrativePrecipitation != nil {
|
||||
value.DailyPrecipitationProbability = roundedInt(&narrativePrecipitation.Value)
|
||||
} else if maxPop != nil {
|
||||
value.DailyPrecipitationProbability = roundedInt(&maxPop.Value)
|
||||
}
|
||||
if maxPop != nil {
|
||||
value.MaxPopPercent = roundedInt(&maxPop.Value)
|
||||
value.MaxPopWindow = periodClockLabel(maxPopWindow, timezone)
|
||||
value.MostLikelyPrecipitationHour = mostLikelyPrecipitationHour(maxPop, timezone)
|
||||
}
|
||||
if maxGust != nil {
|
||||
value.MaxWindGustMph = roundedInt(&maxGust.Value)
|
||||
}
|
||||
value.FirstPrecipHour = timedClockLabel(timing.FirstPrecipitation, timezone)
|
||||
value.LastPrecipHour = timedClockLabel(timing.LastPrecipitation, timezone)
|
||||
value.DominantConditions = sortedSet(conditions)
|
||||
value.DominantConditions = narrativeConditions(summary.NarrativePeriods)
|
||||
if len(value.DominantConditions) == 0 {
|
||||
value.DominantConditions = sortedSet(conditions)
|
||||
}
|
||||
value.Hazards = sortedSet(hazards)
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func narrativeTemperatureRange(periods []weatherdata.ForecastPeriod) forecast.Range {
|
||||
var out forecast.Range
|
||||
for _, period := range periods {
|
||||
addNarrativeHigh(&out, period.TemperatureFMax)
|
||||
addNarrativeLow(&out, period.TemperatureFMin)
|
||||
if period.TemperatureF != nil && period.IsDay != nil {
|
||||
if *period.IsDay {
|
||||
addNarrativeHigh(&out, period.TemperatureF)
|
||||
} else {
|
||||
addNarrativeLow(&out, period.TemperatureF)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func addNarrativeHigh(target *forecast.Range, value *float64) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
if target.Max == nil || *value > *target.Max {
|
||||
copied := *value
|
||||
target.Max = &copied
|
||||
}
|
||||
}
|
||||
|
||||
func addNarrativeLow(target *forecast.Range, value *float64) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
if target.Min == nil || *value < *target.Min {
|
||||
copied := *value
|
||||
target.Min = &copied
|
||||
}
|
||||
}
|
||||
|
||||
func narrativeMaxPrecipitation(periods []weatherdata.ForecastPeriod) *forecast.TimedValue {
|
||||
var maxPop *forecast.TimedValue
|
||||
for _, period := range periods {
|
||||
if period.ProbabilityOfPrecipitationPercent == nil {
|
||||
continue
|
||||
}
|
||||
value := forecast.TimedValue{
|
||||
Value: *period.ProbabilityOfPrecipitationPercent,
|
||||
Time: period.StartTime,
|
||||
}
|
||||
maxTimedValue(&maxPop, &value)
|
||||
}
|
||||
return maxPop
|
||||
}
|
||||
|
||||
func narrativeConditions(periods []weatherdata.ForecastPeriod) []string {
|
||||
seen := map[string]struct{}{}
|
||||
var out []string
|
||||
for _, period := range periods {
|
||||
if period.TextDescription == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[period.TextDescription]; ok {
|
||||
continue
|
||||
}
|
||||
seen[period.TextDescription] = struct{}{}
|
||||
out = append(out, period.TextDescription)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mostLikelyPrecipitationHour(maxPop *forecast.TimedValue, timezone string) string {
|
||||
if maxPop == nil || maxPop.Value <= 0 {
|
||||
return ""
|
||||
}
|
||||
percent := roundedInt(&maxPop.Value)
|
||||
if percent == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d%% at %s", *percent, clockLabel(maxPop.Time, timezone))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user