package briefing import ( "fmt" "sort" "strings" "gitea.maximumdirect.net/eric/weatherreporter/internal/forecast" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" ) type ThreeDay struct { Days []OutlookDay `json:"days"` RelevantAlerts []forecast.AlertOverlap `json:"relevantAlerts,omitempty"` Discussion DiscussionContext `json:"discussion,omitempty"` WeatherStory *WeatherStoryContext `json:"weatherStory,omitempty"` } type OutlookDay struct { Date string `json:"date"` Period timeutil.Period `json:"period"` OverallCharacter string `json:"overallCharacter"` Temperature forecast.Range `json:"temperature,omitempty"` MaxPrecipitationProbability *forecast.TimedValue `json:"maxPrecipitationProbability,omitempty"` PeakWindGust *forecast.TimedValue `json:"peakWindGust,omitempty"` Risks []string `json:"risks,omitempty"` OutdoorWindows OutdoorWindows `json:"outdoorWindows"` RelevantAlerts []forecast.AlertOverlap `json:"relevantAlerts,omitempty"` Dayparts []forecast.DaypartSummary `json:"dayparts"` } func BuildThreeDay(ctx BuildContext, summaries []forecast.DailySummary) (Package, error) { if ctx.Resolved.Definition.ID != report.ThreeDay { return Package{}, fmt.Errorf("3-day briefing requires a 3-day report definition") } if len(summaries) == 0 { return Package{}, fmt.Errorf("3-day forecast summaries are required") } pkg := Package{ Metadata: BuildMetadata(ctx), ThreeDay: &ThreeDay{ Discussion: buildDiscussion(summaries[0].Discussion), WeatherStory: buildWeatherStory(ctx.Bundle), }, } for _, summary := range summaries { day := buildOutlookDay(summary) pkg.ThreeDay.Days = append(pkg.ThreeDay.Days, day) } pkg.ThreeDay.RelevantAlerts = collectOutlookAlerts(pkg.ThreeDay.Days) return pkg, nil } func collectOutlookAlerts(days []OutlookDay) []forecast.AlertOverlap { alerts := map[string]forecast.AlertOverlap{} for _, day := range days { for _, alert := range day.RelevantAlerts { key := alert.Event if key == "" { key = alert.Headline } if key != "" { alerts[key] = alert } } } keys := make([]string, 0, len(alerts)) for key := range alerts { keys = append(keys, key) } sort.Strings(keys) out := make([]forecast.AlertOverlap, 0, len(keys)) for _, key := range keys { out = append(out, alerts[key]) } return out } func buildOutlookDay(summary forecast.DailySummary) OutlookDay { day := OutlookDay{ Date: summary.Date, Period: summary.Period, OutdoorWindows: buildOutdoorWindows(summary.Dayparts), RelevantAlerts: summary.AlertOverlaps, Dayparts: summary.Dayparts, } conditions := map[string]struct{}{} risks := map[string]struct{}{} for _, daypart := range summary.Dayparts { addRange(&day.Temperature, daypart.Temperature) maxTimedValue(&day.MaxPrecipitationProbability, daypart.MaxPrecipitationProbability) maxTimedValue(&day.PeakWindGust, daypart.PeakWindGust) if daypart.DominantCondition != "" { conditions[daypart.DominantCondition] = struct{}{} } for _, risk := range hazardsForIndicators(daypart.Indicators) { risks[risk] = struct{}{} } if daypart.MaxPrecipitationProbability != nil && daypart.MaxPrecipitationProbability.Value >= 50 { risks["precipitation"] = struct{}{} } if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 { risks["wind"] = struct{}{} } } for _, alert := range summary.AlertOverlaps { if alert.Event != "" { risks[alert.Event] = struct{}{} } } day.Risks = sortedSet(risks) day.OverallCharacter = outlookCharacter(sortedSet(conditions), day.Risks) return day } func outlookCharacter(conditions []string, risks []string) string { if len(conditions) == 0 && len(risks) == 0 { return "Quiet weather is expected." } parts := []string{} if len(conditions) > 0 { parts = append(parts, strings.Join(conditions, "; ")) } if len(risks) > 0 { parts = append(parts, "risks: "+strings.Join(risks, "; ")) } return strings.Join(parts, ". ") + "." }