package generatedtext import ( "fmt" "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/briefing" "gitea.maximumdirect.net/eric/weatherreporter/internal/facts" "gitea.maximumdirect.net/eric/weatherreporter/internal/module" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" ) type HourlyRenderContext struct { Report HourlyReportContext GeneratedText Hourly Modules HourlyTemplateModules Collected facts.CollectedFacts Derived facts.DerivedFacts } type HourlyReportContext struct { Title string LocationName string GeneratedAt time.Time GeneratedAtLabel string ValidPeriod timeutil.Period ValidPeriodLabel string Timezone string } type HourlyTemplateModules struct { Metadata *briefing.MetadataModule CurrentConditions *briefing.CurrentConditionsModule HourlyForecast *briefing.HourlyForecastModule PrecipTiming *briefing.PrecipTimingModule AlertDigest *briefing.AlertDigestModule SPCConvectiveOutlooks *briefing.SPCConvectiveOutlooksModule AreaForecastDiscussion *briefing.AreaForecastDiscussionModule SPCConvectiveDiscussion *briefing.SPCConvectiveDiscussionModule WeatherStory *briefing.WeatherStoryModule } func BuildHourlyRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Hourly, collected facts.CollectedFacts, derived facts.DerivedFacts) (HourlyRenderContext, error) { location, err := timeutil.LoadLocation(metadata.Timezone) if err != nil { return HourlyRenderContext{}, fmt.Errorf("build hourly render context: %w", err) } if metadata.GeneratedAt.IsZero() { return HourlyRenderContext{}, fmt.Errorf("build hourly render context: generatedAt is required") } if !metadata.ValidPeriod.IsValid() { return HourlyRenderContext{}, fmt.Errorf("build hourly render context: valid period is required") } modules, err := hourlyTemplateModules(snapshot) if err != nil { return HourlyRenderContext{}, err } return HourlyRenderContext{ Report: HourlyReportContext{ Title: "Hourly Report", LocationName: locationName(metadata), GeneratedAt: metadata.GeneratedAt, GeneratedAtLabel: timeLabel(metadata.GeneratedAt, location), ValidPeriod: metadata.ValidPeriod, ValidPeriodLabel: periodLabel(metadata.ValidPeriod, location), Timezone: metadata.Timezone, }, GeneratedText: generated, Modules: modules, Collected: collected, Derived: derived, }, nil } func hourlyTemplateModules(snapshot module.Snapshot) (HourlyTemplateModules, error) { metadata, err := optionalStanza[briefing.MetadataModule](snapshot, string(module.Metadata)) if err != nil { return HourlyTemplateModules{}, err } current, err := optionalStanza[briefing.CurrentConditionsModule](snapshot, string(module.CurrentConditions)) if err != nil { return HourlyTemplateModules{}, err } hourly, err := optionalStanza[briefing.HourlyForecastModule](snapshot, string(module.HourlyForecast)) if err != nil { return HourlyTemplateModules{}, err } precip, err := optionalStanza[briefing.PrecipTimingModule](snapshot, string(module.PrecipTiming)) if err != nil { return HourlyTemplateModules{}, err } alerts, err := optionalStanza[briefing.AlertDigestModule](snapshot, string(module.AlertDigest)) if err != nil { return HourlyTemplateModules{}, err } outlooks, err := optionalStanza[briefing.SPCConvectiveOutlooksModule](snapshot, string(module.SPCConvectiveOutlooks)) if err != nil { return HourlyTemplateModules{}, err } discussion, err := optionalStanza[briefing.AreaForecastDiscussionModule](snapshot, string(module.AreaForecastDiscussion)) if err != nil { return HourlyTemplateModules{}, err } spcDiscussion, err := optionalStanza[briefing.SPCConvectiveDiscussionModule](snapshot, string(module.SPCConvectiveDiscussion)) if err != nil { return HourlyTemplateModules{}, err } story, err := optionalStanza[briefing.WeatherStoryModule](snapshot, string(module.WeatherStory)) if err != nil { return HourlyTemplateModules{}, err } return HourlyTemplateModules{ Metadata: metadata, CurrentConditions: current, HourlyForecast: hourly, PrecipTiming: precip, AlertDigest: alerts, SPCConvectiveOutlooks: outlooks, AreaForecastDiscussion: discussion, SPCConvectiveDiscussion: spcDiscussion, WeatherStory: story, }, nil } func optionalStanza[T any](snapshot module.Snapshot, name string) (*T, error) { output, ok := snapshot.LookupStanza(name) if !ok || output.Value == nil { return nil, nil } value, _, err := module.StanzaValue[T](snapshot, name) if err != nil { return nil, fmt.Errorf("build hourly render context: %w", err) } return &value, nil } func locationName(metadata briefing.Metadata) string { if metadata.Location != nil { if metadata.Location.Name != "" && metadata.Location.Region != "" { return metadata.Location.Name + ", " + metadata.Location.Region } if metadata.Location.Name != "" { return metadata.Location.Name } } if metadata.SourceLocation != "" { return metadata.SourceLocation } if metadata.SourceLocationID != "" { return metadata.SourceLocationID } return "Unknown location" } func periodLabel(period timeutil.Period, location *time.Location) string { return fmt.Sprintf("%s to %s", timeLabel(period.Start, location), timeLabel(period.End, location)) } func timeLabel(value time.Time, location *time.Location) string { return value.In(location).Format("2006-01-02 at 3:04 PM") }