738 lines
26 KiB
Go
738 lines
26 KiB
Go
package generatedtext
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/facts"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
|
"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
|
|
}
|
|
|
|
type TomorrowRenderContext struct {
|
|
Report TomorrowReportContext
|
|
GeneratedText Tomorrow
|
|
Modules TomorrowTemplateModules
|
|
Collected facts.CollectedFacts
|
|
Derived facts.DerivedFacts
|
|
}
|
|
|
|
type DailyRenderContext struct {
|
|
Report DailyReportContext
|
|
GeneratedText Daily
|
|
Modules DailyTemplateModules
|
|
Collected facts.CollectedFacts
|
|
Derived facts.DerivedFacts
|
|
}
|
|
|
|
type TodayRenderContext struct {
|
|
Report TodayReportContext
|
|
GeneratedText Today
|
|
Modules TodayTemplateModules
|
|
Collected facts.CollectedFacts
|
|
Derived facts.DerivedFacts
|
|
}
|
|
|
|
type TodayReportContext struct {
|
|
Title string
|
|
ForecastDate time.Time
|
|
ForecastDateLabel string
|
|
ForecastDayName string
|
|
GeneratedAt time.Time
|
|
GeneratedAtLabel string
|
|
ValidPeriod timeutil.Period
|
|
Timezone string
|
|
}
|
|
|
|
type TodayTemplateModules struct {
|
|
Metadata *briefing.MetadataModule
|
|
CurrentConditions *briefing.CurrentConditionsModule
|
|
HourlyForecast *briefing.HourlyForecastModule
|
|
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
|
DerivedDaypartSummaries *map[string]briefing.DerivedDaypartSummaryModule
|
|
Dayparts []TodayDaypartContext
|
|
PrecipTiming *briefing.PrecipTimingModule
|
|
AlertDigest *briefing.AlertDigestModule
|
|
SPCConvectiveOutlooks *briefing.SPCConvectiveOutlooksModule
|
|
AreaForecastDiscussion *briefing.AreaForecastDiscussionModule
|
|
SPCConvectiveDiscussion *briefing.SPCConvectiveDiscussionModule
|
|
WeatherStory *briefing.WeatherStoryModule
|
|
TodayPlanning *briefing.TodayPlanningModule
|
|
}
|
|
|
|
type TodayDaypartContext struct {
|
|
Key string
|
|
Summary briefing.DerivedDaypartSummaryModule
|
|
}
|
|
|
|
type TomorrowReportContext struct {
|
|
Title string
|
|
ForecastDate time.Time
|
|
ForecastDateLabel string
|
|
ForecastDayName string
|
|
GeneratedAt time.Time
|
|
GeneratedAtLabel string
|
|
ValidPeriod timeutil.Period
|
|
Timezone string
|
|
}
|
|
|
|
type TomorrowTemplateModules struct {
|
|
Metadata *briefing.MetadataModule
|
|
CurrentConditions *briefing.CurrentConditionsModule
|
|
HourlyForecast *briefing.HourlyForecastModule
|
|
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
|
DerivedDaypartSummaries *map[string]briefing.DerivedDaypartSummaryModule
|
|
Dayparts []TomorrowDaypartContext
|
|
PrecipTiming *briefing.PrecipTimingModule
|
|
AlertDigest *briefing.AlertDigestModule
|
|
SPCConvectiveOutlooks *briefing.SPCConvectiveOutlooksModule
|
|
AreaForecastDiscussion *briefing.AreaForecastDiscussionModule
|
|
SPCConvectiveDiscussion *briefing.SPCConvectiveDiscussionModule
|
|
WeatherStory *briefing.WeatherStoryModule
|
|
TomorrowPlanning *briefing.TomorrowPlanningModule
|
|
}
|
|
|
|
type TomorrowDaypartContext struct {
|
|
Key string
|
|
Summary briefing.DerivedDaypartSummaryModule
|
|
}
|
|
|
|
type DailyReportContext struct {
|
|
Title string
|
|
ForecastDate time.Time
|
|
ForecastDateLabel string
|
|
ForecastDayName string
|
|
GeneratedAt time.Time
|
|
GeneratedAtLabel string
|
|
ValidPeriod timeutil.Period
|
|
Timezone string
|
|
}
|
|
|
|
type DailyTemplateModules struct {
|
|
Metadata *briefing.MetadataModule
|
|
CurrentConditions *briefing.CurrentConditionsModule
|
|
HourlyForecast *briefing.HourlyForecastModule
|
|
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
|
DerivedDaypartSummaries *map[string]briefing.DerivedDaypartSummaryModule
|
|
Dayparts []DailyDaypartContext
|
|
PrecipTiming *briefing.PrecipTimingModule
|
|
AlertDigest *briefing.AlertDigestModule
|
|
SPCConvectiveOutlooks *briefing.SPCConvectiveOutlooksModule
|
|
AreaForecastDiscussion *briefing.AreaForecastDiscussionModule
|
|
SPCConvectiveDiscussion *briefing.SPCConvectiveDiscussionModule
|
|
WeatherStory *briefing.WeatherStoryModule
|
|
OutdoorWindows *briefing.OutdoorWindowsModule
|
|
DailyPlanning *briefing.DailyPlanningModule
|
|
}
|
|
|
|
type DailyDaypartContext struct {
|
|
Key string
|
|
Summary briefing.DerivedDaypartSummaryModule
|
|
}
|
|
|
|
type dayStyleReportContext struct {
|
|
Title string
|
|
ForecastDate time.Time
|
|
ForecastDateLabel string
|
|
ForecastDayName string
|
|
GeneratedAt time.Time
|
|
GeneratedAtLabel string
|
|
ValidPeriod timeutil.Period
|
|
Timezone string
|
|
}
|
|
|
|
type dayStyleTemplateModules struct {
|
|
Metadata *briefing.MetadataModule
|
|
CurrentConditions *briefing.CurrentConditionsModule
|
|
HourlyForecast *briefing.HourlyForecastModule
|
|
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
|
DerivedDaypartSummaries *map[string]briefing.DerivedDaypartSummaryModule
|
|
PrecipTiming *briefing.PrecipTimingModule
|
|
AlertDigest *briefing.AlertDigestModule
|
|
SPCConvectiveOutlooks *briefing.SPCConvectiveOutlooksModule
|
|
AreaForecastDiscussion *briefing.AreaForecastDiscussionModule
|
|
SPCConvectiveDiscussion *briefing.SPCConvectiveDiscussionModule
|
|
WeatherStory *briefing.WeatherStoryModule
|
|
}
|
|
|
|
func BuildHourlyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Hourly, collected facts.CollectedFacts, derived facts.DerivedFacts) (HourlyRenderContext, error) {
|
|
location, err := timeutil.LoadLocation(identity.Timezone)
|
|
if err != nil {
|
|
return HourlyRenderContext{}, fmt.Errorf("build hourly render context: %w", err)
|
|
}
|
|
if identity.GeneratedAt.IsZero() {
|
|
return HourlyRenderContext{}, fmt.Errorf("build hourly render context: generatedAt is required")
|
|
}
|
|
if !identity.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(identity),
|
|
GeneratedAt: identity.GeneratedAt,
|
|
GeneratedAtLabel: generatedAtLabel(identity.GeneratedAt, location),
|
|
ValidPeriod: identity.ValidPeriod,
|
|
ValidPeriodLabel: periodLabel(identity.ValidPeriod, location),
|
|
Timezone: identity.Timezone,
|
|
},
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func BuildDailyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Daily, collected facts.CollectedFacts, derived facts.DerivedFacts) (DailyRenderContext, error) {
|
|
reportContext, err := buildDayStyleReportContext(identity, "daily", func(dayName string) string {
|
|
return dayName + "'s Weather"
|
|
})
|
|
if err != nil {
|
|
return DailyRenderContext{}, err
|
|
}
|
|
modules, err := dailyTemplateModules(snapshot, derived)
|
|
if err != nil {
|
|
return DailyRenderContext{}, err
|
|
}
|
|
return DailyRenderContext{
|
|
Report: reportContext.dailyReportContext(),
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func BuildTodayRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Today, collected facts.CollectedFacts, derived facts.DerivedFacts) (TodayRenderContext, error) {
|
|
reportContext, err := buildDayStyleReportContext(identity, "today", func(string) string {
|
|
return "Today's Weather"
|
|
})
|
|
if err != nil {
|
|
return TodayRenderContext{}, err
|
|
}
|
|
modules, err := todayTemplateModules(snapshot, derived)
|
|
if err != nil {
|
|
return TodayRenderContext{}, err
|
|
}
|
|
return TodayRenderContext{
|
|
Report: reportContext.todayReportContext(),
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func BuildTomorrowRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Tomorrow, collected facts.CollectedFacts, derived facts.DerivedFacts) (TomorrowRenderContext, error) {
|
|
reportContext, err := buildDayStyleReportContext(identity, "tomorrow", func(dayName string) string {
|
|
return dayName + "'s Weather"
|
|
})
|
|
if err != nil {
|
|
return TomorrowRenderContext{}, err
|
|
}
|
|
modules, err := tomorrowTemplateModules(snapshot, derived)
|
|
if err != nil {
|
|
return TomorrowRenderContext{}, err
|
|
}
|
|
return TomorrowRenderContext{
|
|
Report: reportContext.tomorrowReportContext(),
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func buildDayStyleReportContext(identity briefing.PreparedIdentity, name string, title func(string) string) (dayStyleReportContext, error) {
|
|
location, err := timeutil.LoadLocation(identity.Timezone)
|
|
if err != nil {
|
|
return dayStyleReportContext{}, fmt.Errorf("build %s render context: %w", name, err)
|
|
}
|
|
if identity.GeneratedAt.IsZero() {
|
|
return dayStyleReportContext{}, fmt.Errorf("build %s render context: generatedAt is required", name)
|
|
}
|
|
if !identity.ValidPeriod.IsValid() {
|
|
return dayStyleReportContext{}, fmt.Errorf("build %s render context: valid period is required", name)
|
|
}
|
|
|
|
forecastDate := identity.ValidPeriod.Start.In(location)
|
|
forecastDayName := forecastDate.Format("Monday")
|
|
return dayStyleReportContext{
|
|
Title: title(forecastDayName),
|
|
ForecastDate: forecastDate,
|
|
ForecastDateLabel: forecastDate.Format("Monday, January 2, 2006"),
|
|
ForecastDayName: forecastDayName,
|
|
GeneratedAt: identity.GeneratedAt,
|
|
GeneratedAtLabel: generatedAtLabel(identity.GeneratedAt, location),
|
|
ValidPeriod: identity.ValidPeriod,
|
|
Timezone: identity.Timezone,
|
|
}, nil
|
|
}
|
|
|
|
func (ctx dayStyleReportContext) dailyReportContext() DailyReportContext {
|
|
return DailyReportContext{
|
|
Title: ctx.Title,
|
|
ForecastDate: ctx.ForecastDate,
|
|
ForecastDateLabel: ctx.ForecastDateLabel,
|
|
ForecastDayName: ctx.ForecastDayName,
|
|
GeneratedAt: ctx.GeneratedAt,
|
|
GeneratedAtLabel: ctx.GeneratedAtLabel,
|
|
ValidPeriod: ctx.ValidPeriod,
|
|
Timezone: ctx.Timezone,
|
|
}
|
|
}
|
|
|
|
func (ctx dayStyleReportContext) todayReportContext() TodayReportContext {
|
|
return TodayReportContext{
|
|
Title: ctx.Title,
|
|
ForecastDate: ctx.ForecastDate,
|
|
ForecastDateLabel: ctx.ForecastDateLabel,
|
|
ForecastDayName: ctx.ForecastDayName,
|
|
GeneratedAt: ctx.GeneratedAt,
|
|
GeneratedAtLabel: ctx.GeneratedAtLabel,
|
|
ValidPeriod: ctx.ValidPeriod,
|
|
Timezone: ctx.Timezone,
|
|
}
|
|
}
|
|
|
|
func (ctx dayStyleReportContext) tomorrowReportContext() TomorrowReportContext {
|
|
return TomorrowReportContext{
|
|
Title: ctx.Title,
|
|
ForecastDate: ctx.ForecastDate,
|
|
ForecastDateLabel: ctx.ForecastDateLabel,
|
|
ForecastDayName: ctx.ForecastDayName,
|
|
GeneratedAt: ctx.GeneratedAt,
|
|
GeneratedAtLabel: ctx.GeneratedAtLabel,
|
|
ValidPeriod: ctx.ValidPeriod,
|
|
Timezone: ctx.Timezone,
|
|
}
|
|
}
|
|
|
|
func dailyTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts) (DailyTemplateModules, error) {
|
|
common, lookup, err := dayStyleTemplateModuleSnapshot(snapshot)
|
|
if err != nil {
|
|
return DailyTemplateModules{}, err
|
|
}
|
|
outdoor, err := lookup.outdoorWindows()
|
|
if err != nil {
|
|
return DailyTemplateModules{}, err
|
|
}
|
|
planning, err := lookup.dailyPlanning()
|
|
if err != nil {
|
|
return DailyTemplateModules{}, err
|
|
}
|
|
return DailyTemplateModules{
|
|
Metadata: common.Metadata,
|
|
CurrentConditions: common.CurrentConditions,
|
|
HourlyForecast: common.HourlyForecast,
|
|
DerivedDailySummary: common.DerivedDailySummary,
|
|
DerivedDaypartSummaries: common.DerivedDaypartSummaries,
|
|
Dayparts: orderedDailyDayparts(common.DerivedDaypartSummaries, derived.DaypartSummaries),
|
|
PrecipTiming: common.PrecipTiming,
|
|
AlertDigest: common.AlertDigest,
|
|
SPCConvectiveOutlooks: common.SPCConvectiveOutlooks,
|
|
AreaForecastDiscussion: common.AreaForecastDiscussion,
|
|
SPCConvectiveDiscussion: common.SPCConvectiveDiscussion,
|
|
WeatherStory: common.WeatherStory,
|
|
OutdoorWindows: outdoor,
|
|
DailyPlanning: planning,
|
|
}, nil
|
|
}
|
|
|
|
func todayTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts) (TodayTemplateModules, error) {
|
|
common, lookup, err := dayStyleTemplateModuleSnapshot(snapshot)
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
planning, err := lookup.todayPlanning()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
return TodayTemplateModules{
|
|
Metadata: common.Metadata,
|
|
CurrentConditions: common.CurrentConditions,
|
|
HourlyForecast: common.HourlyForecast,
|
|
DerivedDailySummary: common.DerivedDailySummary,
|
|
DerivedDaypartSummaries: common.DerivedDaypartSummaries,
|
|
Dayparts: orderedTodayDayparts(common.DerivedDaypartSummaries, derived.DaypartSummaries),
|
|
PrecipTiming: common.PrecipTiming,
|
|
AlertDigest: common.AlertDigest,
|
|
SPCConvectiveOutlooks: common.SPCConvectiveOutlooks,
|
|
AreaForecastDiscussion: common.AreaForecastDiscussion,
|
|
SPCConvectiveDiscussion: common.SPCConvectiveDiscussion,
|
|
WeatherStory: common.WeatherStory,
|
|
TodayPlanning: planning,
|
|
}, nil
|
|
}
|
|
|
|
func hourlyTemplateModules(snapshot module.Snapshot) (HourlyTemplateModules, error) {
|
|
lookup := newModuleSnapshotLookup(snapshot)
|
|
metadata, err := lookup.metadata()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
current, err := lookup.currentConditions()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
hourly, err := lookup.hourlyForecast()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
precip, err := lookup.precipTiming()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
alerts, err := lookup.alertDigest()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
outlooks, err := lookup.spcConvectiveOutlooks()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
discussion, err := lookup.areaForecastDiscussion()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
spcDiscussion, err := lookup.spcConvectiveDiscussion()
|
|
if err != nil {
|
|
return HourlyTemplateModules{}, err
|
|
}
|
|
story, err := lookup.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 tomorrowTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts) (TomorrowTemplateModules, error) {
|
|
common, lookup, err := dayStyleTemplateModuleSnapshot(snapshot)
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
planning, err := lookup.tomorrowPlanning()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
return TomorrowTemplateModules{
|
|
Metadata: common.Metadata,
|
|
CurrentConditions: common.CurrentConditions,
|
|
HourlyForecast: common.HourlyForecast,
|
|
DerivedDailySummary: common.DerivedDailySummary,
|
|
DerivedDaypartSummaries: common.DerivedDaypartSummaries,
|
|
Dayparts: orderedTomorrowDayparts(common.DerivedDaypartSummaries, derived.DaypartSummaries),
|
|
PrecipTiming: common.PrecipTiming,
|
|
AlertDigest: common.AlertDigest,
|
|
SPCConvectiveOutlooks: common.SPCConvectiveOutlooks,
|
|
AreaForecastDiscussion: common.AreaForecastDiscussion,
|
|
SPCConvectiveDiscussion: common.SPCConvectiveDiscussion,
|
|
WeatherStory: common.WeatherStory,
|
|
TomorrowPlanning: planning,
|
|
}, nil
|
|
}
|
|
|
|
func dayStyleTemplateModuleSnapshot(snapshot module.Snapshot) (dayStyleTemplateModules, moduleSnapshotLookup, error) {
|
|
lookup := newModuleSnapshotLookup(snapshot)
|
|
metadata, err := lookup.metadata()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
current, err := lookup.currentConditions()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
hourly, err := lookup.hourlyForecast()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
daily, err := lookup.derivedDailySummary()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
dayparts, err := lookup.derivedDaypartSummaries()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
precip, err := lookup.precipTiming()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
alerts, err := lookup.alertDigest()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
outlooks, err := lookup.spcConvectiveOutlooks()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
discussion, err := lookup.areaForecastDiscussion()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
spcDiscussion, err := lookup.spcConvectiveDiscussion()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
story, err := lookup.weatherStory()
|
|
if err != nil {
|
|
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
|
}
|
|
return dayStyleTemplateModules{
|
|
Metadata: metadata,
|
|
CurrentConditions: current,
|
|
HourlyForecast: hourly,
|
|
DerivedDailySummary: daily,
|
|
DerivedDaypartSummaries: dayparts,
|
|
PrecipTiming: precip,
|
|
AlertDigest: alerts,
|
|
SPCConvectiveOutlooks: outlooks,
|
|
AreaForecastDiscussion: discussion,
|
|
SPCConvectiveDiscussion: spcDiscussion,
|
|
WeatherStory: story,
|
|
}, lookup, nil
|
|
}
|
|
|
|
type moduleSnapshotLookup struct {
|
|
snapshot module.Snapshot
|
|
stanzas map[string]module.Output
|
|
}
|
|
|
|
func newModuleSnapshotLookup(snapshot module.Snapshot) moduleSnapshotLookup {
|
|
stanzas := make(map[string]module.Output, len(snapshot.Outputs))
|
|
for _, output := range snapshot.Outputs {
|
|
stanzas[output.StanzaName] = output
|
|
}
|
|
return moduleSnapshotLookup{
|
|
snapshot: snapshot,
|
|
stanzas: stanzas,
|
|
}
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) metadata() (*briefing.MetadataModule, error) {
|
|
return optionalStanza[briefing.MetadataModule](lookup, module.Metadata)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) currentConditions() (*briefing.CurrentConditionsModule, error) {
|
|
return optionalStanza[briefing.CurrentConditionsModule](lookup, module.CurrentConditions)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) hourlyForecast() (*briefing.HourlyForecastModule, error) {
|
|
return optionalStanza[briefing.HourlyForecastModule](lookup, module.HourlyForecast)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) derivedDailySummary() (*briefing.DerivedDailySummaryModule, error) {
|
|
return optionalStanza[briefing.DerivedDailySummaryModule](lookup, module.DerivedDailySummary)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) derivedDaypartSummaries() (*map[string]briefing.DerivedDaypartSummaryModule, error) {
|
|
return optionalStanza[map[string]briefing.DerivedDaypartSummaryModule](lookup, module.DerivedDaypartSummaries)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) precipTiming() (*briefing.PrecipTimingModule, error) {
|
|
return optionalStanza[briefing.PrecipTimingModule](lookup, module.PrecipTiming)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) alertDigest() (*briefing.AlertDigestModule, error) {
|
|
return optionalStanza[briefing.AlertDigestModule](lookup, module.AlertDigest)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) spcConvectiveOutlooks() (*briefing.SPCConvectiveOutlooksModule, error) {
|
|
return optionalStanza[briefing.SPCConvectiveOutlooksModule](lookup, module.SPCConvectiveOutlooks)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) areaForecastDiscussion() (*briefing.AreaForecastDiscussionModule, error) {
|
|
return optionalStanza[briefing.AreaForecastDiscussionModule](lookup, module.AreaForecastDiscussion)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) spcConvectiveDiscussion() (*briefing.SPCConvectiveDiscussionModule, error) {
|
|
return optionalStanza[briefing.SPCConvectiveDiscussionModule](lookup, module.SPCConvectiveDiscussion)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) weatherStory() (*briefing.WeatherStoryModule, error) {
|
|
return optionalStanza[briefing.WeatherStoryModule](lookup, module.WeatherStory)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) outdoorWindows() (*briefing.OutdoorWindowsModule, error) {
|
|
return optionalStanza[briefing.OutdoorWindowsModule](lookup, module.OutdoorWindows)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) dailyPlanning() (*briefing.DailyPlanningModule, error) {
|
|
return optionalStanza[briefing.DailyPlanningModule](lookup, module.DailyPlanning)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) todayPlanning() (*briefing.TodayPlanningModule, error) {
|
|
return optionalStanza[briefing.TodayPlanningModule](lookup, module.TodayPlanning)
|
|
}
|
|
|
|
func (lookup moduleSnapshotLookup) tomorrowPlanning() (*briefing.TomorrowPlanningModule, error) {
|
|
return optionalStanza[briefing.TomorrowPlanningModule](lookup, module.TomorrowPlanning)
|
|
}
|
|
|
|
func optionalStanza[T any](lookup moduleSnapshotLookup, id module.ID) (*T, error) {
|
|
name := string(id)
|
|
output, ok := lookup.stanzas[name]
|
|
if !ok || output.Value == nil {
|
|
return nil, nil
|
|
}
|
|
value, _, err := module.StanzaValue[T](lookup.snapshot, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build render context module %q: %w", id, err)
|
|
}
|
|
return &value, nil
|
|
}
|
|
|
|
func orderedTodayDayparts(dayparts *map[string]briefing.DerivedDaypartSummaryModule, ordered []forecast.DaypartSummary) []TodayDaypartContext {
|
|
orderedRows := orderedDaypartRows(dayparts, ordered)
|
|
out := make([]TodayDaypartContext, 0, len(orderedRows))
|
|
for _, row := range orderedRows {
|
|
out = append(out, TodayDaypartContext{Key: row.Key, Summary: row.Summary})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func orderedDailyDayparts(dayparts *map[string]briefing.DerivedDaypartSummaryModule, ordered []forecast.DaypartSummary) []DailyDaypartContext {
|
|
orderedRows := orderedDaypartRows(dayparts, ordered)
|
|
out := make([]DailyDaypartContext, 0, len(orderedRows))
|
|
for _, row := range orderedRows {
|
|
out = append(out, DailyDaypartContext{Key: row.Key, Summary: row.Summary})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func orderedTomorrowDayparts(dayparts *map[string]briefing.DerivedDaypartSummaryModule, ordered []forecast.DaypartSummary) []TomorrowDaypartContext {
|
|
orderedRows := orderedDaypartRows(dayparts, ordered)
|
|
out := make([]TomorrowDaypartContext, 0, len(orderedRows))
|
|
for _, row := range orderedRows {
|
|
out = append(out, TomorrowDaypartContext{Key: row.Key, Summary: row.Summary})
|
|
}
|
|
return out
|
|
}
|
|
|
|
type orderedDaypartRow struct {
|
|
Key string
|
|
Summary briefing.DerivedDaypartSummaryModule
|
|
}
|
|
|
|
func orderedDaypartRows(dayparts *map[string]briefing.DerivedDaypartSummaryModule, ordered []forecast.DaypartSummary) []orderedDaypartRow {
|
|
if dayparts == nil || len(*dayparts) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]orderedDaypartRow, 0, len(*dayparts))
|
|
seen := map[string]struct{}{}
|
|
for _, daypart := range ordered {
|
|
for _, key := range daypartModuleKeyCandidates(daypart) {
|
|
value, ok := (*dayparts)[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, exists := seen[key]; exists {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
out = append(out, orderedDaypartRow{Key: key, Summary: value})
|
|
break
|
|
}
|
|
}
|
|
|
|
var remaining []string
|
|
for key := range *dayparts {
|
|
if _, ok := seen[key]; !ok {
|
|
remaining = append(remaining, key)
|
|
}
|
|
}
|
|
sort.Strings(remaining)
|
|
for _, key := range remaining {
|
|
out = append(out, orderedDaypartRow{Key: key, Summary: (*dayparts)[key]})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func daypartModuleKeyCandidates(daypart forecast.DaypartSummary) []string {
|
|
key := forecast.CanonicalDaypartKey(daypart.Name)
|
|
if key == "" {
|
|
key = "unnamed"
|
|
}
|
|
if daypart.Period.Start.IsZero() {
|
|
return []string{key}
|
|
}
|
|
return []string{key, daypart.Period.Start.Format(timeutil.DateLayout) + "_" + key}
|
|
}
|
|
|
|
func locationName(identity briefing.PreparedIdentity) string {
|
|
if identity.Location != nil {
|
|
if identity.Location.Name != "" && identity.Location.Region != "" {
|
|
return identity.Location.Name + ", " + identity.Location.Region
|
|
}
|
|
if identity.Location.Name != "" {
|
|
return identity.Location.Name
|
|
}
|
|
}
|
|
if identity.SourceLocation != "" {
|
|
return identity.SourceLocation
|
|
}
|
|
if identity.SourceLocationID != "" {
|
|
return identity.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")
|
|
}
|
|
|
|
func generatedAtLabel(value time.Time, location *time.Location) string {
|
|
return value.In(location).Format("Monday, January 2, 2006 at 3:04 PM")
|
|
}
|