Validate render identity and daypart fallbacks
This commit is contained in:
@@ -25,7 +25,7 @@ const (
|
||||
|
||||
type validator func([]byte) (any, []byte, error)
|
||||
|
||||
type renderContextBuilder func(report.ID, string, briefing.PreparedIdentity, module.Snapshot, facts.CollectedFacts, facts.DerivedFacts, any) (any, error)
|
||||
type renderContextBuilder func(report.ID, string, briefing.PreparedIdentity, module.Snapshot, facts.DerivedFacts, any) (any, error)
|
||||
|
||||
type catalogEntry struct {
|
||||
reportID report.ID
|
||||
@@ -140,11 +140,11 @@ func (h Handler) Validate(data []byte) (any, []byte, error) {
|
||||
return h.validate(data)
|
||||
}
|
||||
|
||||
func (h Handler) BuildRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
func (h Handler) BuildRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
if h.renderContextBuilder == nil {
|
||||
return nil, fmt.Errorf("render-context builder is not registered for template %q on report %q", h.templateID, h.reportID)
|
||||
}
|
||||
return h.renderContextBuilder(h.reportID, h.templateID, identity, snapshot, collected, derived, generated)
|
||||
return h.renderContextBuilder(h.reportID, h.templateID, identity, snapshot, derived, generated)
|
||||
}
|
||||
|
||||
func (h Handler) Render(data any) ([]byte, error) {
|
||||
@@ -171,34 +171,34 @@ func validateTomorrow(data []byte) (any, []byte, error) {
|
||||
return ValidateTomorrow(data)
|
||||
}
|
||||
|
||||
func buildHourlyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
func buildHourlyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, _ facts.DerivedFacts, generated any) (any, error) {
|
||||
hourly, ok := generated.(Hourly)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("report template %q requires hourly generated text for report %q", templateID, reportID)
|
||||
}
|
||||
return BuildHourlyRenderContext(identity, snapshot, hourly, collected, derived)
|
||||
return BuildHourlyRenderContext(identity, snapshot, hourly)
|
||||
}
|
||||
|
||||
func buildDailyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
func buildDailyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
daily, ok := generated.(Daily)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("report template %q requires daily generated text for report %q", templateID, reportID)
|
||||
}
|
||||
return BuildDailyRenderContext(identity, snapshot, daily, collected, derived)
|
||||
return BuildDailyRenderContext(identity, snapshot, daily, derived)
|
||||
}
|
||||
|
||||
func buildTodayContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
func buildTodayContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
today, ok := generated.(Today)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("report template %q requires today generated text for report %q", templateID, reportID)
|
||||
}
|
||||
return BuildTodayRenderContext(identity, snapshot, today, collected, derived)
|
||||
return BuildTodayRenderContext(identity, snapshot, today, derived)
|
||||
}
|
||||
|
||||
func buildTomorrowContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
func buildTomorrowContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, derived facts.DerivedFacts, generated any) (any, error) {
|
||||
tomorrow, ok := generated.(Tomorrow)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("report template %q requires tomorrow generated text for report %q", templateID, reportID)
|
||||
}
|
||||
return BuildTomorrowRenderContext(identity, snapshot, tomorrow, collected, derived)
|
||||
return BuildTomorrowRenderContext(identity, snapshot, tomorrow, derived)
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ func TestCatalogBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(hourly) error = %v", err)
|
||||
}
|
||||
_, err = hourlyHandler.BuildRenderContext(testMetadata(), testSnapshot(t), testCollected(), testDerived(), Tomorrow{
|
||||
_, err = hourlyHandler.BuildRenderContext(testMetadata(), testSnapshot(t), testDerived(), Tomorrow{
|
||||
Summary: "Storms become more likely tomorrow.",
|
||||
ForecastDiscussion: []string{"A front will keep showers in the forecast."},
|
||||
})
|
||||
@@ -290,7 +290,7 @@ func TestCatalogBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(tomorrow) error = %v", err)
|
||||
}
|
||||
_, err = tomorrowHandler.BuildRenderContext(testTomorrowMetadata(), testTomorrowSnapshot(t), testCollected(), testTomorrowDerived(), Hourly{
|
||||
_, err = tomorrowHandler.BuildRenderContext(testTomorrowMetadata(), testTomorrowSnapshot(t), testTomorrowDerived(), Hourly{
|
||||
Summary: "Storm chances increase.",
|
||||
ForecastDiscussion: "A front will keep showers in the forecast.",
|
||||
})
|
||||
@@ -309,7 +309,7 @@ func TestCatalogBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(today) error = %v", err)
|
||||
}
|
||||
_, err = todayHandler.BuildRenderContext(testTodayMetadata(), testTodaySnapshot(t), testCollected(), testTodayDerived(), Tomorrow{
|
||||
_, err = todayHandler.BuildRenderContext(testTodayMetadata(), testTodaySnapshot(t), testTodayDerived(), Tomorrow{
|
||||
Summary: "Storms become more likely tomorrow.",
|
||||
ForecastDiscussion: []string{"A front will keep showers in the forecast."},
|
||||
})
|
||||
@@ -328,7 +328,7 @@ func TestCatalogBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(daily) error = %v", err)
|
||||
}
|
||||
_, err = dailyHandler.BuildRenderContext(testDailyMetadata(), testDailySnapshot(t), testCollected(), testDailyDerived(), Tomorrow{
|
||||
_, err = dailyHandler.BuildRenderContext(testDailyMetadata(), testDailySnapshot(t), testDailyDerived(), Tomorrow{
|
||||
Summary: "Storms become more likely tomorrow.",
|
||||
ForecastDiscussion: []string{"A front will keep showers in the forecast."},
|
||||
})
|
||||
@@ -349,7 +349,7 @@ func TestCatalogBuildRenderContextSupportsDaily(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(daily) error = %v", err)
|
||||
}
|
||||
ctx, err := handler.BuildRenderContext(testDailyMetadata(), testDailySnapshot(t), testCollected(), testDailyDerived(), Daily{
|
||||
ctx, err := handler.BuildRenderContext(testDailyMetadata(), testDailySnapshot(t), testDailyDerived(), Daily{
|
||||
Summary: "Showers are possible during the selected day.",
|
||||
ForecastDiscussion: []string{"A front will keep rain chances in the forecast."},
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package generatedtext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"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/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
)
|
||||
|
||||
@@ -16,8 +18,6 @@ type HourlyRenderContext struct {
|
||||
Report HourlyReportContext
|
||||
GeneratedText Hourly
|
||||
Modules HourlyTemplateModules
|
||||
Collected facts.CollectedFacts
|
||||
Derived facts.DerivedFacts
|
||||
}
|
||||
|
||||
type HourlyReportContext struct {
|
||||
@@ -31,7 +31,6 @@ type HourlyReportContext struct {
|
||||
}
|
||||
|
||||
type HourlyTemplateModules struct {
|
||||
Metadata *briefing.MetadataModule
|
||||
CurrentConditions *briefing.CurrentConditionsModule
|
||||
HourlyForecast *briefing.HourlyForecastModule
|
||||
PrecipTiming *briefing.PrecipTimingModule
|
||||
@@ -46,24 +45,18 @@ 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 {
|
||||
@@ -78,12 +71,12 @@ type TodayReportContext struct {
|
||||
}
|
||||
|
||||
type TodayTemplateModules struct {
|
||||
Metadata *briefing.MetadataModule
|
||||
CurrentConditions *briefing.CurrentConditionsModule
|
||||
HourlyForecast *briefing.HourlyForecastModule
|
||||
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
||||
DerivedDaypartSummaries *map[string]briefing.DerivedDaypartSummaryModule
|
||||
Dayparts []TodayDaypartContext
|
||||
HasDaypartDetails bool
|
||||
PrecipTiming *briefing.PrecipTimingModule
|
||||
AlertDigest *briefing.AlertDigestModule
|
||||
SPCConvectiveOutlooks *briefing.SPCConvectiveOutlooksModule
|
||||
@@ -110,7 +103,6 @@ type TomorrowReportContext struct {
|
||||
}
|
||||
|
||||
type TomorrowTemplateModules struct {
|
||||
Metadata *briefing.MetadataModule
|
||||
CurrentConditions *briefing.CurrentConditionsModule
|
||||
HourlyForecast *briefing.HourlyForecastModule
|
||||
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
||||
@@ -142,7 +134,6 @@ type DailyReportContext struct {
|
||||
}
|
||||
|
||||
type DailyTemplateModules struct {
|
||||
Metadata *briefing.MetadataModule
|
||||
CurrentConditions *briefing.CurrentConditionsModule
|
||||
HourlyForecast *briefing.HourlyForecastModule
|
||||
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
||||
@@ -175,7 +166,6 @@ type dayStyleReportContext struct {
|
||||
}
|
||||
|
||||
type dayStyleTemplateModules struct {
|
||||
Metadata *briefing.MetadataModule
|
||||
CurrentConditions *briefing.CurrentConditionsModule
|
||||
HourlyForecast *briefing.HourlyForecastModule
|
||||
DerivedDailySummary *briefing.DerivedDailySummaryModule
|
||||
@@ -188,7 +178,10 @@ type dayStyleTemplateModules struct {
|
||||
WeatherStory *briefing.WeatherStoryModule
|
||||
}
|
||||
|
||||
func BuildHourlyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Hourly, collected facts.CollectedFacts, derived facts.DerivedFacts) (HourlyRenderContext, error) {
|
||||
func BuildHourlyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Hourly) (HourlyRenderContext, error) {
|
||||
if err := validateRenderIdentity(report.Hourly, identity, snapshot); err != nil {
|
||||
return HourlyRenderContext{}, err
|
||||
}
|
||||
location, err := timeutil.LoadLocation(identity.Timezone)
|
||||
if err != nil {
|
||||
return HourlyRenderContext{}, fmt.Errorf("build hourly render context: %w", err)
|
||||
@@ -215,12 +208,13 @@ func BuildHourlyRenderContext(identity briefing.PreparedIdentity, snapshot modul
|
||||
},
|
||||
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) {
|
||||
func BuildDailyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Daily, derived facts.DerivedFacts) (DailyRenderContext, error) {
|
||||
if err := validateRenderIdentity(report.Daily, identity, snapshot); err != nil {
|
||||
return DailyRenderContext{}, err
|
||||
}
|
||||
reportContext, err := buildDayStyleReportContext(identity, "daily", func(dayName string) string {
|
||||
return dayName + "'s Weather"
|
||||
})
|
||||
@@ -235,12 +229,13 @@ func BuildDailyRenderContext(identity briefing.PreparedIdentity, snapshot module
|
||||
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) {
|
||||
func BuildTodayRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Today, derived facts.DerivedFacts) (TodayRenderContext, error) {
|
||||
if err := validateRenderIdentity(report.Today, identity, snapshot); err != nil {
|
||||
return TodayRenderContext{}, err
|
||||
}
|
||||
reportContext, err := buildDayStyleReportContext(identity, "today", func(string) string {
|
||||
return "Today's Weather"
|
||||
})
|
||||
@@ -255,12 +250,13 @@ func BuildTodayRenderContext(identity briefing.PreparedIdentity, snapshot module
|
||||
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) {
|
||||
func BuildTomorrowRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Tomorrow, derived facts.DerivedFacts) (TomorrowRenderContext, error) {
|
||||
if err := validateRenderIdentity(report.Tomorrow, identity, snapshot); err != nil {
|
||||
return TomorrowRenderContext{}, err
|
||||
}
|
||||
reportContext, err := buildDayStyleReportContext(identity, "tomorrow", func(dayName string) string {
|
||||
return dayName + "'s Weather"
|
||||
})
|
||||
@@ -275,11 +271,36 @@ func BuildTomorrowRenderContext(identity briefing.PreparedIdentity, snapshot mod
|
||||
Report: reportContext.tomorrowReportContext(),
|
||||
GeneratedText: generated,
|
||||
Modules: modules,
|
||||
Collected: collected,
|
||||
Derived: derived,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateRenderIdentity(expected report.ID, identity briefing.PreparedIdentity, snapshot module.Snapshot) error {
|
||||
if identity.ReportID != expected {
|
||||
return fmt.Errorf("render identity report %q does not match expected report %q", identity.ReportID, expected)
|
||||
}
|
||||
metadata, found, err := module.StanzaValue[briefing.MetadataModule](snapshot, string(module.Metadata))
|
||||
if err != nil {
|
||||
return fmt.Errorf("read metadata module for report %q: %w", expected, err)
|
||||
}
|
||||
if !found || metadataMatchesIdentity(metadata, identity) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("metadata module does not match render identity for report %q", expected)
|
||||
}
|
||||
|
||||
func metadataMatchesIdentity(metadata briefing.MetadataModule, identity briefing.PreparedIdentity) bool {
|
||||
if metadata.RunID != identity.RunID || metadata.ReportID != identity.ReportID || metadata.Variant != identity.Variant || metadata.PromptID != identity.PromptID || !metadata.GeneratedAt.Equal(identity.GeneratedAt) || metadata.Units != identity.Units || metadata.Timezone != identity.Timezone || metadata.ValidPeriod != identity.ValidPeriod || !reflect.DeepEqual(metadata.Location, identity.Location) || len(metadata.SourceWarnings) != len(identity.SourceWarnings) {
|
||||
return false
|
||||
}
|
||||
for index, warning := range identity.SourceWarnings {
|
||||
summary := metadata.SourceWarnings[index]
|
||||
if summary.Source != warning.Source || summary.Code != warning.Code || summary.Severity != warning.Severity || summary.Message != warning.Message || summary.CompletenessImpact != warning.CompletenessImpact {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func buildDayStyleReportContext(identity briefing.PreparedIdentity, name string, title func(string) string) (dayStyleReportContext, error) {
|
||||
location, err := timeutil.LoadLocation(identity.Timezone)
|
||||
if err != nil {
|
||||
@@ -359,7 +380,6 @@ func dailyTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts)
|
||||
return DailyTemplateModules{}, err
|
||||
}
|
||||
return DailyTemplateModules{
|
||||
Metadata: common.Metadata,
|
||||
CurrentConditions: common.CurrentConditions,
|
||||
HourlyForecast: common.HourlyForecast,
|
||||
DerivedDailySummary: common.DerivedDailySummary,
|
||||
@@ -385,13 +405,14 @@ func todayTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts)
|
||||
if err != nil {
|
||||
return TodayTemplateModules{}, err
|
||||
}
|
||||
dayparts := orderedTodayDayparts(common.DerivedDaypartSummaries, derived.DaypartSummaries)
|
||||
return TodayTemplateModules{
|
||||
Metadata: common.Metadata,
|
||||
CurrentConditions: common.CurrentConditions,
|
||||
HourlyForecast: common.HourlyForecast,
|
||||
DerivedDailySummary: common.DerivedDailySummary,
|
||||
DerivedDaypartSummaries: common.DerivedDaypartSummaries,
|
||||
Dayparts: orderedTodayDayparts(common.DerivedDaypartSummaries, derived.DaypartSummaries),
|
||||
Dayparts: dayparts,
|
||||
HasDaypartDetails: hasTodayDaypartDetails(dayparts),
|
||||
PrecipTiming: common.PrecipTiming,
|
||||
AlertDigest: common.AlertDigest,
|
||||
SPCConvectiveOutlooks: common.SPCConvectiveOutlooks,
|
||||
@@ -404,10 +425,6 @@ func todayTemplateModules(snapshot module.Snapshot, derived facts.DerivedFacts)
|
||||
|
||||
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
|
||||
@@ -441,7 +458,6 @@ func hourlyTemplateModules(snapshot module.Snapshot) (HourlyTemplateModules, err
|
||||
return HourlyTemplateModules{}, err
|
||||
}
|
||||
return HourlyTemplateModules{
|
||||
Metadata: metadata,
|
||||
CurrentConditions: current,
|
||||
HourlyForecast: hourly,
|
||||
PrecipTiming: precip,
|
||||
@@ -463,7 +479,6 @@ func tomorrowTemplateModules(snapshot module.Snapshot, derived facts.DerivedFact
|
||||
return TomorrowTemplateModules{}, err
|
||||
}
|
||||
return TomorrowTemplateModules{
|
||||
Metadata: common.Metadata,
|
||||
CurrentConditions: common.CurrentConditions,
|
||||
HourlyForecast: common.HourlyForecast,
|
||||
DerivedDailySummary: common.DerivedDailySummary,
|
||||
@@ -481,10 +496,6 @@ func tomorrowTemplateModules(snapshot module.Snapshot, derived facts.DerivedFact
|
||||
|
||||
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
|
||||
@@ -526,7 +537,6 @@ func dayStyleTemplateModuleSnapshot(snapshot module.Snapshot) (dayStyleTemplateM
|
||||
return dayStyleTemplateModules{}, moduleSnapshotLookup{}, err
|
||||
}
|
||||
return dayStyleTemplateModules{
|
||||
Metadata: metadata,
|
||||
CurrentConditions: current,
|
||||
HourlyForecast: hourly,
|
||||
DerivedDailySummary: daily,
|
||||
@@ -556,10 +566,6 @@ func newModuleSnapshotLookup(snapshot module.Snapshot) moduleSnapshotLookup {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -638,6 +644,15 @@ func orderedTodayDayparts(dayparts *map[string]briefing.DerivedDaypartSummaryMod
|
||||
return out
|
||||
}
|
||||
|
||||
func hasTodayDaypartDetails(dayparts []TodayDaypartContext) bool {
|
||||
for _, daypart := range dayparts {
|
||||
if daypart.Summary.DominantConditionDisplay != "" || daypart.Summary.DominantCondition != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func orderedDailyDayparts(dayparts *map[string]briefing.DerivedDaypartSummaryModule, ordered []forecast.DaypartSummary) []DailyDaypartContext {
|
||||
orderedRows := orderedDaypartRows(dayparts, ordered)
|
||||
out := make([]DailyDaypartContext, 0, len(orderedRows))
|
||||
|
||||
@@ -22,9 +22,7 @@ func TestBuildHourlyRenderContext(t *testing.T) {
|
||||
ForecastDiscussion: "A front will keep the region unsettled.",
|
||||
PrecipitationTiming: "A cold front is moving into the region.",
|
||||
}
|
||||
collected := testCollected()
|
||||
derived := testDerived()
|
||||
ctx, err := BuildHourlyRenderContext(metadata, snapshot, generated, collected, derived)
|
||||
ctx, err := BuildHourlyRenderContext(metadata, snapshot, generated)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildHourlyRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -73,13 +71,6 @@ func TestBuildHourlyRenderContext(t *testing.T) {
|
||||
if ctx.Modules.WeatherStory == nil || ctx.Modules.WeatherStory.Title != "Morning storms" {
|
||||
t.Fatalf("Modules.WeatherStory = %#v, want story", ctx.Modules.WeatherStory)
|
||||
}
|
||||
if !ctx.Collected.FetchedAt.Equal(collected.FetchedAt) {
|
||||
t.Fatalf("Collected.FetchedAt = %s, want %s", ctx.Collected.FetchedAt, collected.FetchedAt)
|
||||
}
|
||||
if !ctx.Derived.PrecipTiming.ThunderMentioned {
|
||||
t.Fatalf("Derived.PrecipTiming.ThunderMentioned = false, want true")
|
||||
}
|
||||
|
||||
rendered, err := reporttemplate.Render("hourly", ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Render() error = %v", err)
|
||||
@@ -113,7 +104,7 @@ func TestBuildHourlyRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
||||
ctx, err := BuildHourlyRenderContext(testMetadata(), snapshot, Hourly{
|
||||
Summary: "Storm chances increase.",
|
||||
ForecastDiscussion: "A front will keep the region unsettled.",
|
||||
}, testCollected(), facts.DerivedFacts{})
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildHourlyRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -135,7 +126,7 @@ func TestBuildRenderContextReportsModuleExtractionError(t *testing.T) {
|
||||
_, err = BuildHourlyRenderContext(testMetadata(), snapshot, Hourly{
|
||||
Summary: "Storm chances increase.",
|
||||
ForecastDiscussion: "A front will keep the region unsettled.",
|
||||
}, testCollected(), facts.DerivedFacts{})
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("BuildHourlyRenderContext() error = nil, want extraction error")
|
||||
}
|
||||
@@ -144,6 +135,83 @@ func TestBuildRenderContextReportsModuleExtractionError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderContextsRejectConflictingIdentity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
build func() error
|
||||
}{
|
||||
{
|
||||
name: "hourly",
|
||||
build: func() error {
|
||||
identity := testMetadata()
|
||||
identity.ReportID = report.Daily
|
||||
_, err := BuildHourlyRenderContext(identity, testSnapshot(t), Hourly{})
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "daily",
|
||||
build: func() error {
|
||||
identity := testDailyMetadata()
|
||||
identity.ReportID = report.Today
|
||||
_, err := BuildDailyRenderContext(identity, testDailySnapshot(t), Daily{}, testDailyDerived())
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "today",
|
||||
build: func() error {
|
||||
identity := testTodayMetadata()
|
||||
identity.ReportID = report.Tomorrow
|
||||
_, err := BuildTodayRenderContext(identity, testTodaySnapshot(t), Today{}, testTodayDerived())
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tomorrow",
|
||||
build: func() error {
|
||||
identity := testTomorrowMetadata()
|
||||
identity.ReportID = report.Hourly
|
||||
_, err := BuildTomorrowRenderContext(identity, testTomorrowSnapshot(t), Tomorrow{}, testTomorrowDerived())
|
||||
return err
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.build()
|
||||
if err == nil {
|
||||
t.Fatal("render context error = nil, want conflicting identity rejection")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "render identity report") {
|
||||
t.Fatalf("render context error = %v, want identity mismatch", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderContextRejectsMetadataModuleMismatch(t *testing.T) {
|
||||
identity := testTodayMetadata()
|
||||
snapshot := testTodaySnapshot(t)
|
||||
for index := range snapshot.Outputs {
|
||||
if snapshot.Outputs[index].ID != module.Metadata {
|
||||
continue
|
||||
}
|
||||
metadata := snapshot.Outputs[index].Value.(briefing.MetadataModule)
|
||||
metadata.PromptID = "weather.daily_generated_text"
|
||||
snapshot.Outputs[index].Value = metadata
|
||||
}
|
||||
|
||||
_, err := BuildTodayRenderContext(identity, snapshot, Today{}, testTodayDerived())
|
||||
if err == nil {
|
||||
t.Fatal("BuildTodayRenderContext() error = nil, want metadata module mismatch")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "metadata module does not match render identity") {
|
||||
t.Fatalf("BuildTodayRenderContext() error = %v, want metadata mismatch", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildTodayRenderContext(t *testing.T) {
|
||||
metadata := testTodayMetadata()
|
||||
snapshot := testTodaySnapshot(t)
|
||||
@@ -155,10 +223,9 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
||||
},
|
||||
PrecipitationTiming: "The most likely rain window is from midafternoon into early evening.",
|
||||
}
|
||||
collected := testCollected()
|
||||
derived := testTodayDerived()
|
||||
|
||||
ctx, err := BuildTodayRenderContext(metadata, snapshot, generated, collected, derived)
|
||||
ctx, err := BuildTodayRenderContext(metadata, snapshot, generated, derived)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTodayRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -171,9 +238,6 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
||||
if ctx.Report.GeneratedAtLabel != "Monday, June 15, 2026 at 7:14 AM" {
|
||||
t.Fatalf("Report.GeneratedAtLabel = %q, want friendly generated-at label", ctx.Report.GeneratedAtLabel)
|
||||
}
|
||||
if ctx.Modules.Metadata == nil || ctx.Modules.Metadata.ReportID != report.Today {
|
||||
t.Fatalf("Modules.Metadata = %#v, want today metadata", ctx.Modules.Metadata)
|
||||
}
|
||||
if ctx.Modules.CurrentConditions == nil || ctx.Modules.HourlyForecast == nil {
|
||||
t.Fatalf("current/hourly modules = %#v/%#v, want available in render context", ctx.Modules.CurrentConditions, ctx.Modules.HourlyForecast)
|
||||
}
|
||||
@@ -186,6 +250,9 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
||||
if len(ctx.Modules.Dayparts) != 2 || ctx.Modules.Dayparts[0].Key != "morning" || ctx.Modules.Dayparts[1].Key != "afternoon" {
|
||||
t.Fatalf("Modules.Dayparts = %#v, want configured order", ctx.Modules.Dayparts)
|
||||
}
|
||||
if !ctx.Modules.HasDaypartDetails {
|
||||
t.Fatal("Modules.HasDaypartDetails = false, want displayable dayparts")
|
||||
}
|
||||
if morning := (*ctx.Modules.DerivedDaypartSummaries)["morning"]; morning.DominantConditionDisplay == "" || morning.DominantConditionLower == "" || morning.TemperatureSteadyPhraseF == "" {
|
||||
t.Fatalf("today rich morning daypart helpers = %#v, want render-context helper fields", morning)
|
||||
}
|
||||
@@ -201,13 +268,6 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
||||
if ctx.Modules.TodayPlanning.MorningReadiness[0] != "Take sunglasses early." {
|
||||
t.Fatalf("Modules.TodayPlanning = %#v, want today planning facts", ctx.Modules.TodayPlanning)
|
||||
}
|
||||
if !ctx.Collected.FetchedAt.Equal(collected.FetchedAt) {
|
||||
t.Fatalf("Collected.FetchedAt = %s, want %s", ctx.Collected.FetchedAt, collected.FetchedAt)
|
||||
}
|
||||
if len(ctx.Derived.DaypartSummaries) != 2 {
|
||||
t.Fatalf("Derived.DaypartSummaries = %#v, want passthrough facts", ctx.Derived.DaypartSummaries)
|
||||
}
|
||||
|
||||
rendered, err := reporttemplate.Render("today", ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Render() error = %v", err)
|
||||
@@ -260,7 +320,7 @@ func TestTodayRenderContextPreservesUnicodeDaypartDisplayNames(t *testing.T) {
|
||||
ctx, err := BuildTodayRenderContext(metadata, snapshot, Today{
|
||||
Summary: "Today summary.",
|
||||
ForecastDiscussion: []string{"Morning conditions stay quiet."},
|
||||
}, testCollected(), testTodayDerived())
|
||||
}, testTodayDerived())
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTodayRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -281,13 +341,16 @@ func TestBuildTodayRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
||||
ctx, err := BuildTodayRenderContext(testTodayMetadata(), snapshot, Today{
|
||||
Summary: "Dry weather is expected today.",
|
||||
ForecastDiscussion: []string{"High pressure keeps conditions quiet."},
|
||||
}, testCollected(), facts.DerivedFacts{})
|
||||
}, facts.DerivedFacts{})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTodayRenderContext() error = %v", err)
|
||||
}
|
||||
if ctx.Modules.Metadata != nil || ctx.Modules.CurrentConditions != nil || ctx.Modules.PrecipTiming != nil || len(ctx.Modules.Dayparts) != 0 || ctx.Modules.TodayPlanning != nil {
|
||||
if ctx.Modules.CurrentConditions != nil || ctx.Modules.PrecipTiming != nil || len(ctx.Modules.Dayparts) != 0 || ctx.Modules.TodayPlanning != nil {
|
||||
t.Fatalf("Modules = %#v, want omitted optional modules", ctx.Modules)
|
||||
}
|
||||
if ctx.Modules.HasDaypartDetails {
|
||||
t.Fatal("Modules.HasDaypartDetails = true, want no displayable dayparts")
|
||||
}
|
||||
rendered, err := reporttemplate.Render("today", ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Render() error = %v", err)
|
||||
@@ -310,7 +373,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
generatedAtLabel string
|
||||
timezone string
|
||||
validPeriod timeutil.Period
|
||||
metadata bool
|
||||
current bool
|
||||
hourly bool
|
||||
dailySummary bool
|
||||
@@ -323,8 +385,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
spcDiscussion bool
|
||||
story bool
|
||||
planning bool
|
||||
collected bool
|
||||
derived bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -340,12 +400,11 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
wantDayparts: 3,
|
||||
build: func(t *testing.T) result {
|
||||
t.Helper()
|
||||
collected := testCollected()
|
||||
derived := testDailyDerived()
|
||||
ctx, err := BuildDailyRenderContext(testDailyMetadata(), testDailySnapshot(t), Daily{
|
||||
Summary: "Daily summary.",
|
||||
ForecastDiscussion: []string{"Daily discussion."},
|
||||
}, collected, derived)
|
||||
}, derived)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildDailyRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -355,7 +414,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
generatedAtLabel: ctx.Report.GeneratedAtLabel,
|
||||
timezone: ctx.Report.Timezone,
|
||||
validPeriod: ctx.Report.ValidPeriod,
|
||||
metadata: ctx.Modules.Metadata != nil && ctx.Modules.Metadata.ReportID == report.Daily,
|
||||
current: ctx.Modules.CurrentConditions != nil,
|
||||
hourly: ctx.Modules.HourlyForecast != nil,
|
||||
dailySummary: ctx.Modules.DerivedDailySummary != nil,
|
||||
@@ -368,8 +426,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
spcDiscussion: ctx.Modules.SPCConvectiveDiscussion != nil,
|
||||
story: ctx.Modules.WeatherStory != nil,
|
||||
planning: ctx.Modules.DailyPlanning != nil,
|
||||
collected: ctx.Collected.FetchedAt.Equal(collected.FetchedAt),
|
||||
derived: len(ctx.Derived.DaypartSummaries) == len(derived.DaypartSummaries),
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -380,12 +436,11 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
wantDayparts: 2,
|
||||
build: func(t *testing.T) result {
|
||||
t.Helper()
|
||||
collected := testCollected()
|
||||
derived := testTodayDerived()
|
||||
ctx, err := BuildTodayRenderContext(testTodayMetadata(), testTodaySnapshot(t), Today{
|
||||
Summary: "Today summary.",
|
||||
ForecastDiscussion: []string{"Today discussion."},
|
||||
}, collected, derived)
|
||||
}, derived)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTodayRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -395,7 +450,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
generatedAtLabel: ctx.Report.GeneratedAtLabel,
|
||||
timezone: ctx.Report.Timezone,
|
||||
validPeriod: ctx.Report.ValidPeriod,
|
||||
metadata: ctx.Modules.Metadata != nil && ctx.Modules.Metadata.ReportID == report.Today,
|
||||
current: ctx.Modules.CurrentConditions != nil,
|
||||
hourly: ctx.Modules.HourlyForecast != nil,
|
||||
dailySummary: ctx.Modules.DerivedDailySummary != nil,
|
||||
@@ -408,8 +462,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
spcDiscussion: ctx.Modules.SPCConvectiveDiscussion != nil,
|
||||
story: ctx.Modules.WeatherStory != nil,
|
||||
planning: ctx.Modules.TodayPlanning != nil,
|
||||
collected: ctx.Collected.FetchedAt.Equal(collected.FetchedAt),
|
||||
derived: len(ctx.Derived.DaypartSummaries) == len(derived.DaypartSummaries),
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -420,12 +472,11 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
wantDayparts: 2,
|
||||
build: func(t *testing.T) result {
|
||||
t.Helper()
|
||||
collected := testCollected()
|
||||
derived := testTomorrowDerived()
|
||||
ctx, err := BuildTomorrowRenderContext(testTomorrowMetadata(), testTomorrowSnapshot(t), Tomorrow{
|
||||
Summary: "Tomorrow summary.",
|
||||
ForecastDiscussion: []string{"Tomorrow discussion."},
|
||||
}, collected, derived)
|
||||
}, derived)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTomorrowRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -435,7 +486,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
generatedAtLabel: ctx.Report.GeneratedAtLabel,
|
||||
timezone: ctx.Report.Timezone,
|
||||
validPeriod: ctx.Report.ValidPeriod,
|
||||
metadata: ctx.Modules.Metadata != nil && ctx.Modules.Metadata.ReportID == report.Tomorrow,
|
||||
current: ctx.Modules.CurrentConditions != nil,
|
||||
hourly: ctx.Modules.HourlyForecast != nil,
|
||||
dailySummary: ctx.Modules.DerivedDailySummary != nil,
|
||||
@@ -448,8 +498,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
spcDiscussion: ctx.Modules.SPCConvectiveDiscussion != nil,
|
||||
story: ctx.Modules.WeatherStory != nil,
|
||||
planning: ctx.Modules.TomorrowPlanning != nil,
|
||||
collected: ctx.Collected.FetchedAt.Equal(collected.FetchedAt),
|
||||
derived: len(ctx.Derived.DaypartSummaries) == len(derived.DaypartSummaries),
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -473,7 +521,7 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
if !result.validPeriod.IsValid() {
|
||||
t.Fatalf("validPeriod = %#v, want valid period", result.validPeriod)
|
||||
}
|
||||
if !result.metadata || !result.current || !result.hourly || !result.dailySummary || !result.daypartSummaries || !result.precip || !result.alerts || !result.outlooks || !result.discussion || !result.spcDiscussion || !result.story {
|
||||
if !result.current || !result.hourly || !result.dailySummary || !result.daypartSummaries || !result.precip || !result.alerts || !result.outlooks || !result.discussion || !result.spcDiscussion || !result.story {
|
||||
t.Fatalf("common modules = %#v, want all shared modules populated", result)
|
||||
}
|
||||
if result.dayparts != test.wantDayparts {
|
||||
@@ -482,9 +530,6 @@ func TestBuildDayStyleRenderContextsPopulateSharedFieldsAndPlanningModules(t *te
|
||||
if !result.planning {
|
||||
t.Fatalf("planning = false, want %s planning module populated", test.name)
|
||||
}
|
||||
if !result.collected || !result.derived {
|
||||
t.Fatalf("facts passthrough = collected:%t derived:%t, want both true", result.collected, result.derived)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -500,10 +545,9 @@ func TestBuildDailyRenderContext(t *testing.T) {
|
||||
},
|
||||
PrecipitationTiming: "The most likely rain window is from midafternoon into early evening.",
|
||||
}
|
||||
collected := testCollected()
|
||||
derived := testDailyDerived()
|
||||
|
||||
ctx, err := BuildDailyRenderContext(metadata, snapshot, generated, collected, derived)
|
||||
ctx, err := BuildDailyRenderContext(metadata, snapshot, generated, derived)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildDailyRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -519,9 +563,6 @@ func TestBuildDailyRenderContext(t *testing.T) {
|
||||
if !ctx.Report.ValidPeriod.Start.Equal(metadata.ValidPeriod.Start) || !ctx.Report.ValidPeriod.End.Equal(metadata.ValidPeriod.End) || ctx.Report.Timezone != "America/Chicago" {
|
||||
t.Fatalf("period/timezone = %#v/%q, want metadata passthrough", ctx.Report.ValidPeriod, ctx.Report.Timezone)
|
||||
}
|
||||
if ctx.Modules.Metadata == nil || ctx.Modules.Metadata.ReportID != report.Daily {
|
||||
t.Fatalf("Modules.Metadata = %#v, want daily metadata", ctx.Modules.Metadata)
|
||||
}
|
||||
if ctx.Modules.CurrentConditions == nil || ctx.Modules.HourlyForecast == nil {
|
||||
t.Fatalf("current/hourly modules = %#v/%#v, want available in render context", ctx.Modules.CurrentConditions, ctx.Modules.HourlyForecast)
|
||||
}
|
||||
@@ -552,13 +593,6 @@ func TestBuildDailyRenderContext(t *testing.T) {
|
||||
if ctx.Modules.DailyPlanning.MorningReadiness[0] != "Take sunglasses early." {
|
||||
t.Fatalf("Modules.DailyPlanning = %#v, want daily planning facts", ctx.Modules.DailyPlanning)
|
||||
}
|
||||
if !ctx.Collected.FetchedAt.Equal(collected.FetchedAt) {
|
||||
t.Fatalf("Collected.FetchedAt = %s, want %s", ctx.Collected.FetchedAt, collected.FetchedAt)
|
||||
}
|
||||
if len(ctx.Derived.DaypartSummaries) != 2 {
|
||||
t.Fatalf("Derived.DaypartSummaries = %#v, want passthrough facts", ctx.Derived.DaypartSummaries)
|
||||
}
|
||||
|
||||
rendered, err := reporttemplate.Render("daily", ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Render() error = %v", err)
|
||||
@@ -601,11 +635,11 @@ func TestBuildDailyRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
||||
ctx, err := BuildDailyRenderContext(testDailyMetadata(), snapshot, Daily{
|
||||
Summary: "Dry weather is expected for the selected day.",
|
||||
ForecastDiscussion: []string{"High pressure keeps conditions quiet."},
|
||||
}, testCollected(), facts.DerivedFacts{})
|
||||
}, facts.DerivedFacts{})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildDailyRenderContext() error = %v", err)
|
||||
}
|
||||
if ctx.Modules.Metadata != nil || ctx.Modules.CurrentConditions != nil || ctx.Modules.PrecipTiming != nil || ctx.Modules.OutdoorWindows != nil || ctx.Modules.DailyPlanning != nil || len(ctx.Modules.Dayparts) != 0 {
|
||||
if ctx.Modules.CurrentConditions != nil || ctx.Modules.PrecipTiming != nil || ctx.Modules.OutdoorWindows != nil || ctx.Modules.DailyPlanning != nil || len(ctx.Modules.Dayparts) != 0 {
|
||||
t.Fatalf("Modules = %#v, want omitted optional modules", ctx.Modules)
|
||||
}
|
||||
rendered, err := reporttemplate.Render("daily", ctx)
|
||||
@@ -632,10 +666,9 @@ func TestBuildTomorrowRenderContext(t *testing.T) {
|
||||
},
|
||||
PrecipitationTiming: "The most likely rain window is from midafternoon into early evening.",
|
||||
}
|
||||
collected := testCollected()
|
||||
derived := testTomorrowDerived()
|
||||
|
||||
ctx, err := BuildTomorrowRenderContext(metadata, snapshot, generated, collected, derived)
|
||||
ctx, err := BuildTomorrowRenderContext(metadata, snapshot, generated, derived)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTomorrowRenderContext() error = %v", err)
|
||||
}
|
||||
@@ -648,9 +681,6 @@ func TestBuildTomorrowRenderContext(t *testing.T) {
|
||||
if ctx.Report.GeneratedAtLabel != "Sunday, June 14, 2026 at 9:14 AM" {
|
||||
t.Fatalf("Report.GeneratedAtLabel = %q, want friendly generated-at label", ctx.Report.GeneratedAtLabel)
|
||||
}
|
||||
if ctx.Modules.Metadata == nil || ctx.Modules.Metadata.ReportID != report.Tomorrow {
|
||||
t.Fatalf("Modules.Metadata = %#v, want tomorrow metadata", ctx.Modules.Metadata)
|
||||
}
|
||||
if ctx.Modules.CurrentConditions == nil || ctx.Modules.HourlyForecast == nil {
|
||||
t.Fatalf("current/hourly modules = %#v/%#v, want available in render context", ctx.Modules.CurrentConditions, ctx.Modules.HourlyForecast)
|
||||
}
|
||||
@@ -675,13 +705,6 @@ func TestBuildTomorrowRenderContext(t *testing.T) {
|
||||
if ctx.Modules.AlertDigest == nil || ctx.Modules.SPCConvectiveOutlooks == nil || ctx.Modules.AreaForecastDiscussion == nil || ctx.Modules.SPCConvectiveDiscussion == nil || ctx.Modules.WeatherStory == nil || ctx.Modules.TomorrowPlanning == nil {
|
||||
t.Fatalf("optional modules missing from render context: %#v", ctx.Modules)
|
||||
}
|
||||
if !ctx.Collected.FetchedAt.Equal(collected.FetchedAt) {
|
||||
t.Fatalf("Collected.FetchedAt = %s, want %s", ctx.Collected.FetchedAt, collected.FetchedAt)
|
||||
}
|
||||
if len(ctx.Derived.DaypartSummaries) != 2 {
|
||||
t.Fatalf("Derived.DaypartSummaries = %#v, want passthrough facts", ctx.Derived.DaypartSummaries)
|
||||
}
|
||||
|
||||
rendered, err := reporttemplate.Render("tomorrow", ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Render() error = %v", err)
|
||||
@@ -721,11 +744,11 @@ func TestBuildTomorrowRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
||||
ctx, err := BuildTomorrowRenderContext(testTomorrowMetadata(), snapshot, Tomorrow{
|
||||
Summary: "Dry weather is expected tomorrow.",
|
||||
ForecastDiscussion: []string{"High pressure keeps conditions quiet."},
|
||||
}, testCollected(), facts.DerivedFacts{})
|
||||
}, facts.DerivedFacts{})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildTomorrowRenderContext() error = %v", err)
|
||||
}
|
||||
if ctx.Modules.Metadata != nil || ctx.Modules.CurrentConditions != nil || ctx.Modules.PrecipTiming != nil || len(ctx.Modules.Dayparts) != 0 {
|
||||
if ctx.Modules.CurrentConditions != nil || ctx.Modules.PrecipTiming != nil || len(ctx.Modules.Dayparts) != 0 {
|
||||
t.Fatalf("Modules = %#v, want omitted optional modules", ctx.Modules)
|
||||
}
|
||||
rendered, err := reporttemplate.Render("tomorrow", ctx)
|
||||
@@ -821,10 +844,6 @@ func testTodayMetadata() briefing.PreparedIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
func testCollected() facts.CollectedFacts {
|
||||
return facts.CollectedFacts{FetchedAt: time.Date(2026, 5, 29, 13, 31, 0, 0, time.UTC)}
|
||||
}
|
||||
|
||||
func testDerived() facts.DerivedFacts {
|
||||
return facts.DerivedFacts{PrecipTiming: forecast.PrecipTiming{ThunderMentioned: true}}
|
||||
}
|
||||
@@ -878,6 +897,7 @@ func testTodaySnapshot(t *testing.T) module.Snapshot {
|
||||
Units: "imperial",
|
||||
Timezone: "America/Chicago",
|
||||
ValidPeriod: testTodayMetadata().ValidPeriod,
|
||||
Location: testTodayMetadata().Location,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1010,6 +1030,7 @@ func testDailySnapshot(t *testing.T) module.Snapshot {
|
||||
Units: "imperial",
|
||||
Timezone: "America/Chicago",
|
||||
ValidPeriod: testDailyMetadata().ValidPeriod,
|
||||
Location: testDailyMetadata().Location,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1289,6 +1310,7 @@ func testTomorrowSnapshot(t *testing.T) module.Snapshot {
|
||||
Units: "imperial",
|
||||
Timezone: "America/Chicago",
|
||||
ValidPeriod: testTomorrowMetadata().ValidPeriod,
|
||||
Location: testTomorrowMetadata().Location,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user