111 lines
4.1 KiB
Go
111 lines
4.1 KiB
Go
package briefing
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
|
)
|
|
|
|
type DerivedDaypartSummaryModule struct {
|
|
Date string `json:"date,omitempty"`
|
|
PeriodBegins string `json:"period_begins,omitempty"`
|
|
PeriodEnds string `json:"period_ends,omitempty"`
|
|
TempRangeF string `json:"temp_range_f,omitempty"`
|
|
ApparentTempRangeF string `json:"apparent_temp_range_f,omitempty"`
|
|
MaxPopPercent *int `json:"max_pop_percent,omitempty"`
|
|
MaxPopTime string `json:"max_pop_time,omitempty"`
|
|
MaxWindGustMph *int `json:"max_wind_gust_mph,omitempty"`
|
|
MaxWindGustTime string `json:"max_wind_gust_time,omitempty"`
|
|
DominantCondition string `json:"dominant_condition,omitempty"`
|
|
NotableConditions []string `json:"notable_conditions,omitempty"`
|
|
Snow bool `json:"snow,omitempty"`
|
|
Ice bool `json:"ice,omitempty"`
|
|
Fog bool `json:"fog,omitempty"`
|
|
Heat bool `json:"heat,omitempty"`
|
|
Cold bool `json:"cold,omitempty"`
|
|
Wind bool `json:"wind,omitempty"`
|
|
RelevantAlertCount int `json:"relevant_alert_count,omitempty"`
|
|
}
|
|
|
|
func buildDerivedDaypartSummariesModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
|
if len(ctx.Derived.DaypartSummaries) == 0 {
|
|
return nil, fmt.Errorf("daypart summary facts are required")
|
|
}
|
|
value := map[string]DerivedDaypartSummaryModule{}
|
|
prefixDates := multipleSummaryDates(ctx.Derived.DailySummaries)
|
|
for _, daypart := range ctx.Derived.DaypartSummaries {
|
|
key := daypartKey(daypart, prefixDates)
|
|
value[key] = derivedDaypartSummaryValue(daypart, ctx.Timezone)
|
|
}
|
|
return &module.Output{ID: module.DerivedDaypartSummaries, StanzaName: "derived_daypart_summaries", Value: value}, nil
|
|
}
|
|
|
|
func derivedDaypartSummaryValue(daypart forecast.DaypartSummary, timezone string) DerivedDaypartSummaryModule {
|
|
value := DerivedDaypartSummaryModule{
|
|
Date: localDateLabel(daypart.Period.Start, timezone),
|
|
PeriodBegins: friendlyPeriodBeginsLabel(daypart.Period, timezone),
|
|
PeriodEnds: friendlyPeriodEndsLabel(daypart.Period, timezone),
|
|
TempRangeF: rangeLabel(daypart.Temperature),
|
|
ApparentTempRangeF: daypartApparentRangeLabel(daypart.ApparentTemperature),
|
|
DominantCondition: daypart.DominantCondition,
|
|
NotableConditions: append([]string(nil), daypart.NotableConditions...),
|
|
Snow: daypart.Indicators.Snow,
|
|
Ice: daypart.Indicators.Ice,
|
|
Fog: daypart.Indicators.Fog,
|
|
Heat: daypart.Indicators.Heat,
|
|
Cold: daypart.Indicators.Cold,
|
|
Wind: daypart.Indicators.Wind,
|
|
RelevantAlertCount: len(daypart.AlertOverlaps),
|
|
}
|
|
if daypart.MaxPrecipitationProbability != nil {
|
|
value.MaxPopPercent = roundedInt(&daypart.MaxPrecipitationProbability.Value)
|
|
value.MaxPopTime = clockLabel(daypart.MaxPrecipitationProbability.Time, timezone)
|
|
}
|
|
if daypart.PeakWindGust != nil {
|
|
value.MaxWindGustMph = roundedInt(&daypart.PeakWindGust.Value)
|
|
value.MaxWindGustTime = clockLabel(daypart.PeakWindGust.Time, timezone)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func multipleSummaryDates(summaries []forecast.DailySummary) bool {
|
|
seen := map[string]struct{}{}
|
|
for _, summary := range summaries {
|
|
seen[summary.Date] = struct{}{}
|
|
}
|
|
return len(seen) > 1
|
|
}
|
|
|
|
func daypartKey(daypart forecast.DaypartSummary, prefixDate bool) string {
|
|
key := normalizedKey(daypart.Name)
|
|
if key == "" {
|
|
key = "unnamed"
|
|
}
|
|
if !prefixDate {
|
|
return key
|
|
}
|
|
return daypart.Period.Start.Format(timeutil.DateLayout) + "_" + key
|
|
}
|
|
|
|
func normalizedKey(value string) string {
|
|
lower := strings.ToLower(strings.TrimSpace(value))
|
|
var out strings.Builder
|
|
lastUnderscore := false
|
|
for _, r := range lower {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
|
out.WriteRune(r)
|
|
lastUnderscore = false
|
|
continue
|
|
}
|
|
if !lastUnderscore {
|
|
out.WriteByte('_')
|
|
lastUnderscore = true
|
|
}
|
|
}
|
|
return strings.Trim(out.String(), "_")
|
|
}
|