Updated precipitation timing language in the shared template

This commit is contained in:
2026-06-16 19:10:11 -05:00
parent d9ab1e47ec
commit f9d6d42b1b
8 changed files with 195 additions and 13 deletions

View File

@@ -105,9 +105,15 @@ func TestPrecipTimingModuleHandlesRainyAndDryForecasts(t *testing.T) {
if rainy.PrecipitationWindows[0].PeriodBegins != "2026-05-29 at 8:00 AM" || rainy.PrecipitationWindows[0].PeriodBeginsHourLabel != "8:00 AM" || rainy.PrecipitationWindows[0].PeriodEnds != "2026-05-29 at 9:00 AM" || rainy.PrecipitationWindows[0].PeriodEndsHourLabel != "9:00 AM" || rainy.PrecipitationWindows[0].MaxPopPercent == nil || *rainy.PrecipitationWindows[0].MaxPopPercent != 60 || rainy.PrecipitationWindows[0].MaxPopHourLabel != "8:00 AM" {
t.Fatalf("first precipitation window = %#v, want 8-9 AM at 60%%", rainy.PrecipitationWindows[0])
}
if rainy.PrecipitationWindows[0].PrecipitationType != "showers" || rainy.PrecipitationWindows[0].ExpectationPhrase != "Showers likely." {
t.Fatalf("first precipitation window phrase = %#v, want showers likely", rainy.PrecipitationWindows[0])
}
if rainy.PrecipitationWindows[1].PeriodBegins != "2026-05-29 at 12:00 PM" || rainy.PrecipitationWindows[1].PeriodBeginsHourLabel != "12:00 PM" || rainy.PrecipitationWindows[1].PeriodEnds != "2026-05-29 at 2:00 PM" || rainy.PrecipitationWindows[1].PeriodEndsHourLabel != "2:00 PM" || rainy.PrecipitationWindows[1].MaxPopPercent == nil || *rainy.PrecipitationWindows[1].MaxPopPercent != 80 || rainy.PrecipitationWindows[1].MaxPopHourLabel != "12:00 PM" {
t.Fatalf("second precipitation window = %#v, want noon-2 PM at 80%%", rainy.PrecipitationWindows[1])
}
if rainy.PrecipitationWindows[1].PrecipitationType != "showers and thunderstorms" || rainy.PrecipitationWindows[1].ExpectationPhrase != "Expect showers and thunderstorms." {
t.Fatalf("second precipitation window phrase = %#v, want expect showers and thunderstorms", rainy.PrecipitationWindows[1])
}
data, err := json.Marshal(output.Value)
if err != nil {
t.Fatalf("marshal precip timing: %v", err)
@@ -115,6 +121,9 @@ func TestPrecipTimingModuleHandlesRainyAndDryForecasts(t *testing.T) {
if !strings.Contains(string(data), "precipitation_windows") || !strings.Contains(string(data), "probability_threshold") || !strings.Contains(string(data), "period_begins_hour_label") || !strings.Contains(string(data), "max_pop_hour_label") {
t.Fatalf("precip timing json = %s, want threshold and windows", string(data))
}
if !strings.Contains(string(data), "precipitation_type") || !strings.Contains(string(data), "expectation_phrase") {
t.Fatalf("precip timing json = %s, want precipitation type and expectation phrase", string(data))
}
if strings.Contains(string(data), `"start"`) || strings.Contains(string(data), `"end"`) {
t.Fatalf("precip timing json = %s, want period_begins/period_ends instead of start/end", string(data))
}
@@ -136,6 +145,100 @@ func TestPrecipTimingModuleHandlesRainyAndDryForecasts(t *testing.T) {
}
}
func TestPrecipTimingModuleBuildsExpectationPhrases(t *testing.T) {
now := mustParseModuleTime("2026-05-29T08:00:00-05:00")
tests := []struct {
name string
maxPop float64
descriptions []string
wantType string
wantPhrase string
}{
{
name: "chance lower bound",
maxPop: 40,
descriptions: []string{"Scattered showers"},
wantType: "showers",
wantPhrase: "Chance of showers.",
},
{
name: "chance upper bound",
maxPop: 49,
descriptions: []string{"Rain possible"},
wantType: "rain",
wantPhrase: "Chance of rain.",
},
{
name: "likely lower bound",
maxPop: 50,
descriptions: []string{"Drizzle"},
wantType: "drizzle",
wantPhrase: "Drizzle likely.",
},
{
name: "likely upper bound",
maxPop: 69,
descriptions: []string{"Freezing rain"},
wantType: "freezing rain",
wantPhrase: "Freezing rain likely.",
},
{
name: "expect lower bound",
maxPop: 70,
descriptions: []string{"Snow"},
wantType: "snow",
wantPhrase: "Expect snow.",
},
{
name: "showers and thunderstorms preferred",
maxPop: 100,
descriptions: []string{"Showers likely", "Thunderstorms possible"},
wantType: "showers and thunderstorms",
wantPhrase: "Expect showers and thunderstorms.",
},
{
name: "thunderstorms only",
maxPop: 80,
descriptions: []string{"Thunderstorms"},
wantType: "thunderstorms",
wantPhrase: "Expect thunderstorms.",
},
{
name: "unknown fallback",
maxPop: 95,
descriptions: []string{"Unsettled conditions"},
wantType: "precipitation",
wantPhrase: "Expect precipitation.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value := precipTimingValue(forecast.PrecipTiming{
ProbabilityThreshold: forecast.DefaultPrecipWindowProbabilityThreshold,
PrecipitationWindows: []forecast.PrecipitationWindow{
{
Start: now,
MaxPrecipitationProbability: forecast.TimedValue{
Value: tt.maxPop,
Time: now,
},
ProbabilityThreshold: forecast.DefaultPrecipWindowProbabilityThreshold,
TextDescriptions: tt.descriptions,
},
},
}, "America/Chicago")
if len(value.PrecipitationWindows) != 1 {
t.Fatalf("PrecipitationWindows = %#v, want one window", value.PrecipitationWindows)
}
window := value.PrecipitationWindows[0]
if window.PrecipitationType != tt.wantType || window.ExpectationPhrase != tt.wantPhrase {
t.Fatalf("window = %#v, want type %q and phrase %q", window, tt.wantType, tt.wantPhrase)
}
})
}
}
func TestPrecipTimingModuleUsesDerivedTimingWithoutDaypartSummaries(t *testing.T) {
registry := MustDefaultModuleRegistry()
ctx := derivedModuleContext(report.Hourly)