Curate daypart prompt exports
This commit is contained in:
@@ -167,6 +167,11 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
|
|||||||
t.Fatalf("module snapshot missing rich helper field %q:\n%s", want, string(snapshotData))
|
t.Fatalf("module snapshot missing rich helper field %q:\n%s", want, string(snapshotData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, want := range []string{`"temperature_phrase_f"`, `"dominant_condition_lower"`, `"dominant_condition_display"`, `"max_pop_time_label"`} {
|
||||||
|
if !strings.Contains(string(snapshotData), want) {
|
||||||
|
t.Fatalf("module snapshot missing rich daypart helper field %q:\n%s", want, string(snapshotData))
|
||||||
|
}
|
||||||
|
}
|
||||||
data, err := os.ReadFile(result.DataPackagePath)
|
data, err := os.ReadFile(result.DataPackagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read data package: %v", err)
|
t.Fatalf("read data package: %v", err)
|
||||||
@@ -253,6 +258,22 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
|
|||||||
t.Fatalf("data package hourly period contains helper field %q: %#v", omitted, firstPeriod)
|
t.Fatalf("data package hourly period contains helper field %q: %#v", omitted, firstPeriod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dayparts, ok := savedDataPackage.Briefing.Values["derived_daypart_summaries"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("data package daypart summaries = %#v, want daypart map", savedDataPackage.Briefing.Values["derived_daypart_summaries"])
|
||||||
|
}
|
||||||
|
morning, ok := dayparts["morning"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("data package morning daypart = %#v, want daypart map", dayparts["morning"])
|
||||||
|
}
|
||||||
|
if morning["max_pop_time"] != "6:00 AM" {
|
||||||
|
t.Fatalf("data package morning max_pop_time = %#v, want friendly label", morning["max_pop_time"])
|
||||||
|
}
|
||||||
|
for _, omitted := range []string{"temperature_phrase_f", "dominant_condition_lower", "dominant_condition_display", "max_pop_time_label"} {
|
||||||
|
if _, ok := morning[omitted]; ok {
|
||||||
|
t.Fatalf("data package morning daypart contains helper field %q: %#v", omitted, morning)
|
||||||
|
}
|
||||||
|
}
|
||||||
story, ok := savedDataPackage.Briefing.Values["weather_story"].(map[string]any)
|
story, ok := savedDataPackage.Briefing.Values["weather_story"].(map[string]any)
|
||||||
if !ok || story["title"] != "Several Chances for Rain Through Monday" {
|
if !ok || story["title"] != "Several Chances for Rain Through Monday" {
|
||||||
t.Fatalf("data package weather story = %#v, want weather story title", savedDataPackage.Briefing.Values["weather_story"])
|
t.Fatalf("data package weather story = %#v, want weather story title", savedDataPackage.Briefing.Values["weather_story"])
|
||||||
|
|||||||
@@ -44,6 +44,34 @@ type DerivedDaypartSummaryModule struct {
|
|||||||
RelevantAlertCount int `json:"relevant_alert_count,omitempty"`
|
RelevantAlertCount int `json:"relevant_alert_count,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DerivedDaypartSummaryPromptExport struct {
|
||||||
|
Date string `json:"date,omitempty"`
|
||||||
|
DisplayName string `json:"display_name,omitempty"`
|
||||||
|
PeriodBegins string `json:"period_begins,omitempty"`
|
||||||
|
PeriodEnds string `json:"period_ends,omitempty"`
|
||||||
|
TempRangeF string `json:"temp_range_f,omitempty"`
|
||||||
|
ApparentTempRangeF string `json:"apparent_temp_range_f,omitempty"`
|
||||||
|
MaxPopPercent *int `json:"max_pop_percent,omitempty"`
|
||||||
|
MaxPopTime string `json:"max_pop_time,omitempty"`
|
||||||
|
MentionPrecipitation bool `json:"mention_precipitation,omitempty"`
|
||||||
|
MaxWindGustMph *int `json:"max_wind_gust_mph,omitempty"`
|
||||||
|
MaxWindGustTime string `json:"max_wind_gust_time,omitempty"`
|
||||||
|
DominantCondition string `json:"dominant_condition,omitempty"`
|
||||||
|
TemperatureTrend string `json:"temperature_trend,omitempty"`
|
||||||
|
TemperatureStartPhraseF string `json:"temperature_start_phrase_f,omitempty"`
|
||||||
|
TemperatureEndPhraseF string `json:"temperature_end_phrase_f,omitempty"`
|
||||||
|
TemperaturePeakPhraseF string `json:"temperature_peak_phrase_f,omitempty"`
|
||||||
|
TemperatureSteadyPhraseF string `json:"temperature_steady_phrase_f,omitempty"`
|
||||||
|
NotableConditions []string `json:"notable_conditions,omitempty"`
|
||||||
|
Snow bool `json:"snow,omitempty"`
|
||||||
|
Ice bool `json:"ice,omitempty"`
|
||||||
|
Fog bool `json:"fog,omitempty"`
|
||||||
|
Heat bool `json:"heat,omitempty"`
|
||||||
|
Cold bool `json:"cold,omitempty"`
|
||||||
|
Wind bool `json:"wind,omitempty"`
|
||||||
|
RelevantAlertCount int `json:"relevant_alert_count,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func buildDerivedDaypartSummariesModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
func buildDerivedDaypartSummariesModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
||||||
if len(ctx.Derived.DaypartSummaries) == 0 {
|
if len(ctx.Derived.DaypartSummaries) == 0 {
|
||||||
return nil, fmt.Errorf("daypart summary facts are required")
|
return nil, fmt.Errorf("daypart summary facts are required")
|
||||||
@@ -57,6 +85,52 @@ func buildDerivedDaypartSummariesModule(ctx ModuleContext, _ any) (*module.Outpu
|
|||||||
return &module.Output{ID: module.DerivedDaypartSummaries, StanzaName: "derived_daypart_summaries", Value: value}, nil
|
return &module.Output{ID: module.DerivedDaypartSummaries, StanzaName: "derived_daypart_summaries", Value: value}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exportDerivedDaypartSummariesPromptValue(value any) (any, error) {
|
||||||
|
rich, ok := value.(map[string]DerivedDaypartSummaryModule)
|
||||||
|
if !ok {
|
||||||
|
return nil, unexpectedPromptExportValue(value, map[string]DerivedDaypartSummaryModule{})
|
||||||
|
}
|
||||||
|
out := make(map[string]DerivedDaypartSummaryPromptExport, len(rich))
|
||||||
|
for key, daypart := range rich {
|
||||||
|
out[key] = derivedDaypartSummaryPromptValue(daypart)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func derivedDaypartSummaryPromptValue(rich DerivedDaypartSummaryModule) DerivedDaypartSummaryPromptExport {
|
||||||
|
maxPopTime := rich.MaxPopTime
|
||||||
|
if rich.MaxPopTimeLabel != "" {
|
||||||
|
maxPopTime = rich.MaxPopTimeLabel
|
||||||
|
}
|
||||||
|
return DerivedDaypartSummaryPromptExport{
|
||||||
|
Date: rich.Date,
|
||||||
|
DisplayName: rich.DisplayName,
|
||||||
|
PeriodBegins: rich.PeriodBegins,
|
||||||
|
PeriodEnds: rich.PeriodEnds,
|
||||||
|
TempRangeF: rich.TempRangeF,
|
||||||
|
ApparentTempRangeF: rich.ApparentTempRangeF,
|
||||||
|
MaxPopPercent: copyInt(rich.MaxPopPercent),
|
||||||
|
MaxPopTime: maxPopTime,
|
||||||
|
MentionPrecipitation: rich.MentionPrecipitation,
|
||||||
|
MaxWindGustMph: copyInt(rich.MaxWindGustMph),
|
||||||
|
MaxWindGustTime: rich.MaxWindGustTime,
|
||||||
|
DominantCondition: rich.DominantCondition,
|
||||||
|
TemperatureTrend: rich.TemperatureTrend,
|
||||||
|
TemperatureStartPhraseF: rich.TemperatureStartPhraseF,
|
||||||
|
TemperatureEndPhraseF: rich.TemperatureEndPhraseF,
|
||||||
|
TemperaturePeakPhraseF: rich.TemperaturePeakPhraseF,
|
||||||
|
TemperatureSteadyPhraseF: rich.TemperatureSteadyPhraseF,
|
||||||
|
NotableConditions: append([]string(nil), rich.NotableConditions...),
|
||||||
|
Snow: rich.Snow,
|
||||||
|
Ice: rich.Ice,
|
||||||
|
Fog: rich.Fog,
|
||||||
|
Heat: rich.Heat,
|
||||||
|
Cold: rich.Cold,
|
||||||
|
Wind: rich.Wind,
|
||||||
|
RelevantAlertCount: rich.RelevantAlertCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func derivedDaypartSummaryValue(daypart forecast.DaypartSummary, timezone string) DerivedDaypartSummaryModule {
|
func derivedDaypartSummaryValue(daypart forecast.DaypartSummary, timezone string) DerivedDaypartSummaryModule {
|
||||||
temperature := daypartTemperatureDisplay(daypart)
|
temperature := daypartTemperatureDisplay(daypart)
|
||||||
value := DerivedDaypartSummaryModule{
|
value := DerivedDaypartSummaryModule{
|
||||||
|
|||||||
@@ -201,6 +201,126 @@ func TestDerivedDaypartSummariesExposeConfiguredKeysAndHazards(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDerivedDaypartSummariesPromptExportOmitsTemplateHelpers(t *testing.T) {
|
||||||
|
registry := MustDefaultModuleRegistry()
|
||||||
|
ctx := derivedModuleContext(report.Daily)
|
||||||
|
|
||||||
|
output, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.DerivedDaypartSummaries})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildModule() error = %v", err)
|
||||||
|
}
|
||||||
|
richText := mustMarshalModuleJSON(t, output.Value)
|
||||||
|
for _, field := range []string{"temperature_phrase_f", "dominant_condition_lower", "dominant_condition_display", "max_pop_time_label"} {
|
||||||
|
if !strings.Contains(richText, field) {
|
||||||
|
t.Fatalf("rich daypart json = %s, want helper field %s", richText, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := moduleDataPackageValue[map[string]DerivedDaypartSummaryPromptExport](t, output)
|
||||||
|
morning, ok := prompt["morning"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("daypart prompt keys = %#v, want morning", prompt)
|
||||||
|
}
|
||||||
|
if morning.Date != "2026-05-29" || morning.DisplayName != "Morning" || morning.PeriodBegins != "2026-05-29 at 6:00 AM" || morning.PeriodEnds != "2026-05-29 at 12:00 PM" {
|
||||||
|
t.Fatalf("morning prompt period = %#v, want date/display/period labels", morning)
|
||||||
|
}
|
||||||
|
if morning.TempRangeF != "58" || morning.MaxPopPercent == nil || *morning.MaxPopPercent != 60 || morning.MaxPopTime != "6:00 AM" || !morning.MentionPrecipitation {
|
||||||
|
t.Fatalf("morning prompt precip/temp = %#v, want factual prompt fields with friendly max pop time", morning)
|
||||||
|
}
|
||||||
|
if morning.DominantCondition != "Showers" || morning.TemperatureTrend != "steady" || morning.TemperatureSteadyPhraseF != "upper 50s" {
|
||||||
|
t.Fatalf("morning prompt condition/trend = %#v, want condition and trend fields", morning)
|
||||||
|
}
|
||||||
|
if len(morning.NotableConditions) == 0 || morning.NotableConditions[0] != "Showers" {
|
||||||
|
t.Fatalf("morning prompt notable conditions = %#v, want copied conditions", morning.NotableConditions)
|
||||||
|
}
|
||||||
|
afternoon := prompt["afternoon"]
|
||||||
|
if !afternoon.Heat || !afternoon.Wind || afternoon.MaxWindGustMph == nil || *afternoon.MaxWindGustMph != 42 || afternoon.RelevantAlertCount != 1 {
|
||||||
|
t.Fatalf("afternoon prompt = %#v, want hazard, wind, and alert fields", afternoon)
|
||||||
|
}
|
||||||
|
|
||||||
|
promptText := mustMarshalModuleJSON(t, output.DataPackageValue())
|
||||||
|
for _, field := range []string{"date", "display_name", "period_begins", "period_ends", "temp_range_f", "max_pop_percent", "max_pop_time", "mention_precipitation", "max_wind_gust_mph", "dominant_condition", "temperature_trend", "temperature_steady_phrase_f", "notable_conditions", "relevant_alert_count"} {
|
||||||
|
if !strings.Contains(promptText, field) {
|
||||||
|
t.Fatalf("daypart prompt json = %s, want field %s", promptText, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, field := range []string{"temperature_phrase_f", "dominant_condition_lower", "dominant_condition_display", "max_pop_time_label"} {
|
||||||
|
if strings.Contains(promptText, field) {
|
||||||
|
t.Fatalf("daypart prompt json = %s, want omitted helper field %s", promptText, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDerivedDaypartPromptExportTemperatureTrends(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
temps []float64
|
||||||
|
wantTrend string
|
||||||
|
wantStart string
|
||||||
|
wantEnd string
|
||||||
|
wantPeak string
|
||||||
|
wantSteady string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "rising",
|
||||||
|
temps: []float64{58, 68},
|
||||||
|
wantTrend: "rising",
|
||||||
|
wantStart: "upper 50s",
|
||||||
|
wantEnd: "upper 60s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "falling",
|
||||||
|
temps: []float64{65, 58},
|
||||||
|
wantTrend: "falling",
|
||||||
|
wantStart: "mid 60s",
|
||||||
|
wantEnd: "upper 50s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "peaking",
|
||||||
|
temps: []float64{62, 78, 65},
|
||||||
|
wantTrend: "peaking",
|
||||||
|
wantPeak: "upper 70s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "steady",
|
||||||
|
temps: []float64{77, 78},
|
||||||
|
wantTrend: "steady",
|
||||||
|
wantSteady: "upper 70s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
rich := derivedDaypartSummaryValue(derivedDaypartWithTemperatures(test.name, "2026-05-29T12:00:00-05:00", "sunny", test.temps...), "America/Chicago")
|
||||||
|
prompt := derivedDaypartSummaryPromptValue(rich)
|
||||||
|
if prompt.TemperatureTrend != test.wantTrend ||
|
||||||
|
prompt.TemperatureStartPhraseF != test.wantStart ||
|
||||||
|
prompt.TemperatureEndPhraseF != test.wantEnd ||
|
||||||
|
prompt.TemperaturePeakPhraseF != test.wantPeak ||
|
||||||
|
prompt.TemperatureSteadyPhraseF != test.wantSteady {
|
||||||
|
t.Fatalf("prompt temperature presentation = %#v", prompt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDerivedDaypartPromptExportMaxPopTimeFallback(t *testing.T) {
|
||||||
|
withLabel := derivedDaypartSummaryPromptValue(DerivedDaypartSummaryModule{
|
||||||
|
MaxPopTime: "6 AM",
|
||||||
|
MaxPopTimeLabel: "6:00 AM",
|
||||||
|
})
|
||||||
|
if withLabel.MaxPopTime != "6:00 AM" {
|
||||||
|
t.Fatalf("MaxPopTime with label = %q, want friendly label", withLabel.MaxPopTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
withoutLabel := derivedDaypartSummaryPromptValue(DerivedDaypartSummaryModule{
|
||||||
|
MaxPopTime: "6 AM",
|
||||||
|
})
|
||||||
|
if withoutLabel.MaxPopTime != "6 AM" {
|
||||||
|
t.Fatalf("MaxPopTime without label = %q, want fallback time", withoutLabel.MaxPopTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDerivedDaypartTemperaturePresentationFields(t *testing.T) {
|
func TestDerivedDaypartTemperaturePresentationFields(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ func defaultModuleDefinitions() []ModuleDefinition {
|
|||||||
SupportedReports: daypartReports,
|
SupportedReports: daypartReports,
|
||||||
MissingData: module.MissingDataError,
|
MissingData: module.MissingDataError,
|
||||||
Builder: buildDerivedDaypartSummariesModule,
|
Builder: buildDerivedDaypartSummariesModule,
|
||||||
|
PromptExporter: exportDerivedDaypartSummariesPromptValue,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: module.PrecipTiming,
|
ID: module.PrecipTiming,
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
|||||||
if len(ctx.Modules.Dayparts) != 2 || ctx.Modules.Dayparts[0].Key != "morning" || ctx.Modules.Dayparts[1].Key != "afternoon" {
|
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)
|
t.Fatalf("Modules.Dayparts = %#v, want configured order", ctx.Modules.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)
|
||||||
|
}
|
||||||
|
if afternoon := (*ctx.Modules.DerivedDaypartSummaries)["afternoon"]; afternoon.MaxPopTimeLabel != "3:00 PM" {
|
||||||
|
t.Fatalf("today rich afternoon daypart = %#v, want max pop time label helper", afternoon)
|
||||||
|
}
|
||||||
if ctx.Modules.PrecipTiming == nil || len(ctx.Modules.PrecipTiming.PrecipitationWindows) != 1 {
|
if ctx.Modules.PrecipTiming == nil || len(ctx.Modules.PrecipTiming.PrecipitationWindows) != 1 {
|
||||||
t.Fatalf("Modules.PrecipTiming = %#v, want precipitation window", ctx.Modules.PrecipTiming)
|
t.Fatalf("Modules.PrecipTiming = %#v, want precipitation window", ctx.Modules.PrecipTiming)
|
||||||
}
|
}
|
||||||
@@ -311,6 +317,12 @@ func TestBuildDailyRenderContext(t *testing.T) {
|
|||||||
if len(ctx.Modules.Dayparts) != 3 || ctx.Modules.Dayparts[0].Key != "morning" || ctx.Modules.Dayparts[1].Key != "afternoon" || ctx.Modules.Dayparts[2].Key != "evening" {
|
if len(ctx.Modules.Dayparts) != 3 || ctx.Modules.Dayparts[0].Key != "morning" || ctx.Modules.Dayparts[1].Key != "afternoon" || ctx.Modules.Dayparts[2].Key != "evening" {
|
||||||
t.Fatalf("Modules.Dayparts = %#v, want derived order followed by remaining keys", ctx.Modules.Dayparts)
|
t.Fatalf("Modules.Dayparts = %#v, want derived order followed by remaining keys", ctx.Modules.Dayparts)
|
||||||
}
|
}
|
||||||
|
if morning := (*ctx.Modules.DerivedDaypartSummaries)["morning"]; morning.DominantConditionDisplay == "" || morning.DominantConditionLower == "" || morning.TemperatureSteadyPhraseF == "" {
|
||||||
|
t.Fatalf("daily rich morning daypart helpers = %#v, want render-context helper fields", morning)
|
||||||
|
}
|
||||||
|
if afternoon := (*ctx.Modules.DerivedDaypartSummaries)["afternoon"]; afternoon.MaxPopTimeLabel != "3:00 PM" {
|
||||||
|
t.Fatalf("daily rich afternoon daypart = %#v, want max pop time label helper", afternoon)
|
||||||
|
}
|
||||||
if ctx.Modules.PrecipTiming == nil || len(ctx.Modules.PrecipTiming.PrecipitationWindows) != 1 {
|
if ctx.Modules.PrecipTiming == nil || len(ctx.Modules.PrecipTiming.PrecipitationWindows) != 1 {
|
||||||
t.Fatalf("Modules.PrecipTiming = %#v, want precipitation window", ctx.Modules.PrecipTiming)
|
t.Fatalf("Modules.PrecipTiming = %#v, want precipitation window", ctx.Modules.PrecipTiming)
|
||||||
}
|
}
|
||||||
@@ -434,6 +446,12 @@ func TestBuildTomorrowRenderContext(t *testing.T) {
|
|||||||
if len(ctx.Modules.Dayparts) != 2 || ctx.Modules.Dayparts[0].Key != "morning" || ctx.Modules.Dayparts[1].Key != "afternoon" {
|
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)
|
t.Fatalf("Modules.Dayparts = %#v, want configured order", ctx.Modules.Dayparts)
|
||||||
}
|
}
|
||||||
|
if morning := (*ctx.Modules.DerivedDaypartSummaries)["morning"]; morning.DominantConditionDisplay == "" || morning.DominantConditionLower == "" || morning.TemperatureSteadyPhraseF == "" {
|
||||||
|
t.Fatalf("tomorrow rich morning daypart helpers = %#v, want render-context helper fields", morning)
|
||||||
|
}
|
||||||
|
if afternoon := (*ctx.Modules.DerivedDaypartSummaries)["afternoon"]; afternoon.MaxPopTimeLabel != "3:00 PM" {
|
||||||
|
t.Fatalf("tomorrow rich afternoon daypart = %#v, want max pop time label helper", afternoon)
|
||||||
|
}
|
||||||
if ctx.Modules.PrecipTiming == nil || len(ctx.Modules.PrecipTiming.PrecipitationWindows) != 1 {
|
if ctx.Modules.PrecipTiming == nil || len(ctx.Modules.PrecipTiming.PrecipitationWindows) != 1 {
|
||||||
t.Fatalf("Modules.PrecipTiming = %#v, want precipitation window", ctx.Modules.PrecipTiming)
|
t.Fatalf("Modules.PrecipTiming = %#v, want precipitation window", ctx.Modules.PrecipTiming)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -578,6 +578,84 @@ func TestRenderToday(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderDaypartTemplatesUseRichHelperFields(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
template string
|
||||||
|
context any
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "daily",
|
||||||
|
template: "daily",
|
||||||
|
context: testDailyRenderContext{
|
||||||
|
Report: testDailyReportContext{Title: "Daily", ForecastDateLabel: "Monday, June 15, 2026"},
|
||||||
|
GeneratedText: testDailyGeneratedText{Summary: "Daily summary."},
|
||||||
|
Modules: testDailyModules{Dayparts: []testDailyDaypart{
|
||||||
|
{Key: "morning", Summary: testDaypartSummary{
|
||||||
|
DisplayName: "Morning",
|
||||||
|
DominantConditionDisplay: "Showers",
|
||||||
|
TemperaturePhraseF: "upper 60s",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
want: []string{"- **Morning:** Showers, with temperatures in the upper 60s."},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "today",
|
||||||
|
template: "today",
|
||||||
|
context: testTodayRenderContext{
|
||||||
|
Report: testTodayReportContext{Title: "Today", ForecastDateLabel: "Monday, June 15, 2026"},
|
||||||
|
GeneratedText: testTomorrowGeneratedText{Summary: "Today summary."},
|
||||||
|
Modules: testTodayModules{Dayparts: []testTomorrowDaypart{
|
||||||
|
{Key: "afternoon", Summary: testDaypartSummary{
|
||||||
|
DisplayName: "Afternoon",
|
||||||
|
DominantConditionDisplay: "Storms",
|
||||||
|
TemperaturePeakPhraseF: "low 80s",
|
||||||
|
TemperatureTrend: "peaking",
|
||||||
|
MaxPopPercent: intPtr(70),
|
||||||
|
MentionPrecipitation: true,
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
want: []string{"- **Afternoon:** Storms, with temperatures peaking in the low 80s. Chance of precipitation is 70%."},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tomorrow",
|
||||||
|
template: "tomorrow",
|
||||||
|
context: testTomorrowRenderContext{
|
||||||
|
Report: testTomorrowReportContext{Title: "Tomorrow", ForecastDateLabel: "Tuesday, June 16, 2026"},
|
||||||
|
GeneratedText: testTomorrowGeneratedText{Summary: "Tomorrow summary."},
|
||||||
|
Modules: testTomorrowModules{Dayparts: []testTomorrowDaypart{
|
||||||
|
{Key: "morning", Summary: testDaypartSummary{
|
||||||
|
DisplayName: "Morning",
|
||||||
|
DominantConditionDisplay: "Clear",
|
||||||
|
TemperatureStartPhraseF: "upper 50s",
|
||||||
|
TemperatureEndPhraseF: "upper 60s",
|
||||||
|
TemperatureTrend: "rising",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
want: []string{"- **Morning:** Clear, with temperatures rising from the upper 50s to the upper 60s."},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rendered, err := Render(tt.template, tt.context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Render() error = %v", err)
|
||||||
|
}
|
||||||
|
text := string(rendered)
|
||||||
|
for _, want := range tt.want {
|
||||||
|
if !strings.Contains(text, want) {
|
||||||
|
t.Fatalf("rendered template missing %q:\n%s", want, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderTomorrowOmitsPrecipitationTimingWithoutWindows(t *testing.T) {
|
func TestRenderTomorrowOmitsPrecipitationTimingWithoutWindows(t *testing.T) {
|
||||||
rendered, err := Render("tomorrow", testTomorrowRenderContext{
|
rendered, err := Render("tomorrow", testTomorrowRenderContext{
|
||||||
Report: testTomorrowReportContext{
|
Report: testTomorrowReportContext{
|
||||||
|
|||||||
Reference in New Issue
Block a user