122 lines
4.9 KiB
Go
122 lines
4.9 KiB
Go
package briefing
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
|
)
|
|
|
|
type Weekend struct {
|
|
Days []OutlookDay `json:"days"`
|
|
Planning WeekendPlanning `json:"planning"`
|
|
RelevantAlerts []forecast.AlertOverlap `json:"relevantAlerts,omitempty"`
|
|
Discussion DiscussionContext `json:"discussion,omitempty"`
|
|
WeatherStory *WeatherStoryContext `json:"weatherStory,omitempty"`
|
|
}
|
|
|
|
type WeekendPlanning struct {
|
|
BestOutdoorWindows []OutdoorWindow `json:"bestOutdoorWindows,omitempty"`
|
|
WorstWeatherWindows []OutdoorWindow `json:"worstWeatherWindows,omitempty"`
|
|
RainStormTiming []string `json:"rainStormTiming,omitempty"`
|
|
ComfortConcerns []string `json:"comfortConcerns,omitempty"`
|
|
UncertaintyInputs []string `json:"uncertaintyInputs,omitempty"`
|
|
}
|
|
|
|
func BuildWeekend(ctx BuildContext, summaries []forecast.DailySummary) (Package, error) {
|
|
if ctx.Resolved.Definition.ID != report.Weekend {
|
|
return Package{}, fmt.Errorf("weekend briefing requires a weekend report definition")
|
|
}
|
|
if len(summaries) == 0 {
|
|
return Package{}, fmt.Errorf("weekend forecast summaries are required")
|
|
}
|
|
pkg := buildPackage(ctx)
|
|
pkg.Weekend = &Weekend{
|
|
Discussion: buildDiscussion(summaries[0].Discussion),
|
|
WeatherStory: buildWeatherStory(ctx.Bundle),
|
|
}
|
|
for _, summary := range summaries {
|
|
pkg.Weekend.Days = append(pkg.Weekend.Days, buildOutlookDay(summary))
|
|
}
|
|
pkg.Weekend.RelevantAlerts = collectOutlookAlerts(pkg.Weekend.Days)
|
|
setRelevantAlertCount(&pkg.Metadata, len(pkg.Weekend.RelevantAlerts))
|
|
pkg.Weekend.Planning = buildWeekendPlanning(pkg.Weekend.Days, pkg.Weekend.Discussion, ctx.Bundle)
|
|
return pkg, nil
|
|
}
|
|
|
|
func buildWeekendPlanning(days []OutlookDay, discussion DiscussionContext, bundle *forecast.Bundle) WeekendPlanning {
|
|
planning := WeekendPlanning{}
|
|
for _, day := range days {
|
|
if day.OutdoorWindows.Best != nil {
|
|
window := *day.OutdoorWindows.Best
|
|
window.Daypart = day.Date + " " + window.Daypart
|
|
planning.BestOutdoorWindows = append(planning.BestOutdoorWindows, window)
|
|
}
|
|
if day.OutdoorWindows.Worst != nil {
|
|
window := *day.OutdoorWindows.Worst
|
|
window.Daypart = day.Date + " " + window.Daypart
|
|
planning.WorstWeatherWindows = append(planning.WorstWeatherWindows, window)
|
|
}
|
|
for _, daypart := range day.Dayparts {
|
|
planning.RainStormTiming = appendUnique(planning.RainStormTiming, weekendRainStormNotes(day.Date, daypart)...)
|
|
planning.ComfortConcerns = appendUnique(planning.ComfortConcerns, weekendComfortNotes(day.Date, daypart)...)
|
|
}
|
|
}
|
|
planning.UncertaintyInputs = appendUnique(planning.UncertaintyInputs, discussion.KeyMessages...)
|
|
if discussion.ShortTerm != "" {
|
|
planning.UncertaintyInputs = appendUnique(planning.UncertaintyInputs, "Short-term discussion available for confidence context.")
|
|
}
|
|
if discussion.LongTerm != "" {
|
|
planning.UncertaintyInputs = appendUnique(planning.UncertaintyInputs, "Long-term discussion available for uncertainty context.")
|
|
}
|
|
if bundle != nil {
|
|
for _, warning := range bundle.Warnings {
|
|
if warning.Code != "" {
|
|
planning.UncertaintyInputs = appendUnique(planning.UncertaintyInputs, "Source warning: "+warning.Code+".")
|
|
}
|
|
}
|
|
}
|
|
if len(planning.RainStormTiming) == 0 {
|
|
planning.RainStormTiming = append(planning.RainStormTiming, "No focused rain or storm timing stands out in the available weekend forecast.")
|
|
}
|
|
if len(planning.ComfortConcerns) == 0 {
|
|
planning.ComfortConcerns = append(planning.ComfortConcerns, "No major heat, cold, or wind comfort concern stands out in the available weekend forecast.")
|
|
}
|
|
if len(planning.UncertaintyInputs) == 0 {
|
|
planning.UncertaintyInputs = append(planning.UncertaintyInputs, "No explicit confidence or uncertainty signal was available from the selected source context.")
|
|
}
|
|
return planning
|
|
}
|
|
|
|
func weekendRainStormNotes(date string, daypart forecast.DaypartSummary) []string {
|
|
notes := []string{}
|
|
label := weekendWindowLabel(date, daypart.Name)
|
|
if daypart.MaxPrecipitationProbability != nil && daypart.MaxPrecipitationProbability.Value >= 30 {
|
|
notes = append(notes, fmt.Sprintf("%s precipitation chance peaks near %.0f%%.", label, daypart.MaxPrecipitationProbability.Value))
|
|
}
|
|
return notes
|
|
}
|
|
|
|
func weekendComfortNotes(date string, daypart forecast.DaypartSummary) []string {
|
|
notes := []string{}
|
|
label := weekendWindowLabel(date, daypart.Name)
|
|
if daypart.Indicators.Heat {
|
|
notes = append(notes, label+" heat may affect outdoor comfort.")
|
|
}
|
|
if daypart.Indicators.Cold {
|
|
notes = append(notes, label+" cold may affect outdoor comfort.")
|
|
}
|
|
if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 25 {
|
|
notes = append(notes, fmt.Sprintf("%s gusts may reach %.0f mph.", label, daypart.PeakWindGust.Value))
|
|
}
|
|
return notes
|
|
}
|
|
|
|
func weekendWindowLabel(date string, daypart string) string {
|
|
if daypart == "" {
|
|
return date
|
|
}
|
|
return strings.TrimSpace(date + " " + daypart)
|
|
}
|