2026-05-29 18:16:10 +00:00
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" )
}
2026-05-29 20:04:27 -05:00
pkg := buildPackage ( ctx )
pkg . Weekend = & Weekend {
Discussion : buildDiscussion ( summaries [ 0 ] . Discussion ) ,
WeatherStory : buildWeatherStory ( ctx . Bundle ) ,
2026-05-29 18:16:10 +00:00
}
for _ , summary := range summaries {
pkg . Weekend . Days = append ( pkg . Weekend . Days , buildOutlookDay ( summary ) )
}
pkg . Weekend . RelevantAlerts = collectOutlookAlerts ( pkg . Weekend . Days )
2026-05-29 19:34:23 -05:00
setRelevantAlertCount ( & pkg . Metadata , len ( pkg . Weekend . RelevantAlerts ) )
2026-05-29 18:16:10 +00:00
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 )
}