609 lines
20 KiB
Go
609 lines
20 KiB
Go
package generatedtext
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"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 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
|
|
}
|
|
|
|
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: generatedAtLabel(metadata.GeneratedAt, location),
|
|
ValidPeriod: metadata.ValidPeriod,
|
|
ValidPeriodLabel: periodLabel(metadata.ValidPeriod, location),
|
|
Timezone: metadata.Timezone,
|
|
},
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func BuildTodayRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Today, collected facts.CollectedFacts, derived facts.DerivedFacts) (TodayRenderContext, error) {
|
|
location, err := timeutil.LoadLocation(metadata.Timezone)
|
|
if err != nil {
|
|
return TodayRenderContext{}, fmt.Errorf("build today render context: %w", err)
|
|
}
|
|
if metadata.GeneratedAt.IsZero() {
|
|
return TodayRenderContext{}, fmt.Errorf("build today render context: generatedAt is required")
|
|
}
|
|
if !metadata.ValidPeriod.IsValid() {
|
|
return TodayRenderContext{}, fmt.Errorf("build today render context: valid period is required")
|
|
}
|
|
modules, err := todayTemplateModules(snapshot, derived)
|
|
if err != nil {
|
|
return TodayRenderContext{}, err
|
|
}
|
|
forecastDate := metadata.ValidPeriod.Start.In(location)
|
|
return TodayRenderContext{
|
|
Report: TodayReportContext{
|
|
Title: "Today's Weather",
|
|
ForecastDate: forecastDate,
|
|
ForecastDateLabel: forecastDate.Format("Monday, January 2, 2006"),
|
|
ForecastDayName: forecastDate.Format("Monday"),
|
|
GeneratedAt: metadata.GeneratedAt,
|
|
GeneratedAtLabel: generatedAtLabel(metadata.GeneratedAt, location),
|
|
ValidPeriod: metadata.ValidPeriod,
|
|
Timezone: metadata.Timezone,
|
|
},
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func BuildTomorrowRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Tomorrow, collected facts.CollectedFacts, derived facts.DerivedFacts) (TomorrowRenderContext, error) {
|
|
location, err := timeutil.LoadLocation(metadata.Timezone)
|
|
if err != nil {
|
|
return TomorrowRenderContext{}, fmt.Errorf("build tomorrow render context: %w", err)
|
|
}
|
|
if metadata.GeneratedAt.IsZero() {
|
|
return TomorrowRenderContext{}, fmt.Errorf("build tomorrow render context: generatedAt is required")
|
|
}
|
|
if !metadata.ValidPeriod.IsValid() {
|
|
return TomorrowRenderContext{}, fmt.Errorf("build tomorrow render context: valid period is required")
|
|
}
|
|
modules, err := tomorrowTemplateModules(snapshot, derived)
|
|
if err != nil {
|
|
return TomorrowRenderContext{}, err
|
|
}
|
|
forecastDate := metadata.ValidPeriod.Start.In(location)
|
|
forecastDayName := forecastDate.Format("Monday")
|
|
return TomorrowRenderContext{
|
|
Report: TomorrowReportContext{
|
|
Title: forecastDayName + "'s Weather",
|
|
ForecastDate: forecastDate,
|
|
ForecastDateLabel: forecastDate.Format("Monday, January 2, 2006"),
|
|
ForecastDayName: forecastDayName,
|
|
GeneratedAt: metadata.GeneratedAt,
|
|
GeneratedAtLabel: generatedAtLabel(metadata.GeneratedAt, location),
|
|
ValidPeriod: metadata.ValidPeriod,
|
|
Timezone: metadata.Timezone,
|
|
},
|
|
GeneratedText: generated,
|
|
Modules: modules,
|
|
Collected: collected,
|
|
Derived: derived,
|
|
}, nil
|
|
}
|
|
|
|
func todayTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts) (TodayTemplateModules, error) {
|
|
lookup := newModuleSnapshotLookup(snapshot)
|
|
metadata, err := lookup.metadata()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
current, err := lookup.currentConditions()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
hourly, err := lookup.hourlyForecast()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
daily, err := lookup.derivedDailySummary()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
dayparts, err := lookup.derivedDaypartSummaries()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
precip, err := lookup.precipTiming()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
alerts, err := lookup.alertDigest()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
outlooks, err := lookup.spcConvectiveOutlooks()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
discussion, err := lookup.areaForecastDiscussion()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
spcDiscussion, err := lookup.spcConvectiveDiscussion()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
story, err := lookup.weatherStory()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
planning, err := lookup.todayPlanning()
|
|
if err != nil {
|
|
return TodayTemplateModules{}, err
|
|
}
|
|
return TodayTemplateModules{
|
|
Metadata: metadata,
|
|
CurrentConditions: current,
|
|
HourlyForecast: hourly,
|
|
DerivedDailySummary: daily,
|
|
DerivedDaypartSummaries: dayparts,
|
|
Dayparts: orderedTodayDayparts(dayparts, derived.DaypartSummaries),
|
|
PrecipTiming: precip,
|
|
AlertDigest: alerts,
|
|
SPCConvectiveOutlooks: outlooks,
|
|
AreaForecastDiscussion: discussion,
|
|
SPCConvectiveDiscussion: spcDiscussion,
|
|
WeatherStory: story,
|
|
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) {
|
|
lookup := newModuleSnapshotLookup(snapshot)
|
|
metadata, err := lookup.metadata()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
current, err := lookup.currentConditions()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
hourly, err := lookup.hourlyForecast()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
daily, err := lookup.derivedDailySummary()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
dayparts, err := lookup.derivedDaypartSummaries()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
precip, err := lookup.precipTiming()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
alerts, err := lookup.alertDigest()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
outlooks, err := lookup.spcConvectiveOutlooks()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
discussion, err := lookup.areaForecastDiscussion()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
spcDiscussion, err := lookup.spcConvectiveDiscussion()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
story, err := lookup.weatherStory()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
planning, err := lookup.tomorrowPlanning()
|
|
if err != nil {
|
|
return TomorrowTemplateModules{}, err
|
|
}
|
|
return TomorrowTemplateModules{
|
|
Metadata: metadata,
|
|
CurrentConditions: current,
|
|
HourlyForecast: hourly,
|
|
DerivedDailySummary: daily,
|
|
DerivedDaypartSummaries: dayparts,
|
|
Dayparts: orderedTomorrowDayparts(dayparts, derived.DaypartSummaries),
|
|
PrecipTiming: precip,
|
|
AlertDigest: alerts,
|
|
SPCConvectiveOutlooks: outlooks,
|
|
AreaForecastDiscussion: discussion,
|
|
SPCConvectiveDiscussion: spcDiscussion,
|
|
WeatherStory: story,
|
|
TomorrowPlanning: planning,
|
|
}, 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) 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 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 := normalizedDaypartKey(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 normalizedDaypartKey(value string) string {
|
|
lower := strings.ToLower(strings.TrimSpace(value))
|
|
var out strings.Builder
|
|
lastUnderscore := false
|
|
for _, r := range lower {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
|
out.WriteRune(r)
|
|
lastUnderscore = false
|
|
continue
|
|
}
|
|
if !lastUnderscore {
|
|
out.WriteByte('_')
|
|
lastUnderscore = true
|
|
}
|
|
}
|
|
return strings.Trim(out.String(), "_")
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
func generatedAtLabel(value time.Time, location *time.Location) string {
|
|
return value.In(location).Format("Monday, January 2, 2006 at 3:04 PM")
|
|
}
|