Removed detection for "thunder" as a special weather indicator

This commit is contained in:
2026-05-29 19:15:32 -05:00
parent 8476dab844
commit 1bc0739d31
12 changed files with 39 additions and 66 deletions

View File

@@ -176,9 +176,6 @@ func readinessNotes(daypart forecast.DaypartSummary) []string {
if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 { if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 {
notes = append(notes, fmt.Sprintf("Morning gusts may reach %.0f mph.", daypart.PeakWindGust.Value)) notes = append(notes, fmt.Sprintf("Morning gusts may reach %.0f mph.", daypart.PeakWindGust.Value))
} }
if daypart.Indicators.Thunder {
notes = append(notes, "Morning thunder could affect departure timing.")
}
if daypart.Indicators.Snow || daypart.Indicators.Ice { if daypart.Indicators.Snow || daypart.Indicators.Ice {
notes = append(notes, "Morning wintry weather could affect surfaces and travel.") notes = append(notes, "Morning wintry weather could affect surfaces and travel.")
} }
@@ -203,9 +200,6 @@ func concernNotes(daypart forecast.DaypartSummary) []string {
if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 { if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 {
notes = append(notes, fmt.Sprintf("%s gusts may reach %.0f mph.", prefix, daypart.PeakWindGust.Value)) notes = append(notes, fmt.Sprintf("%s gusts may reach %.0f mph.", prefix, daypart.PeakWindGust.Value))
} }
if daypart.Indicators.Thunder {
notes = append(notes, prefix+" thunder may disrupt outdoor plans.")
}
if daypart.Indicators.Snow || daypart.Indicators.Ice { if daypart.Indicators.Snow || daypart.Indicators.Ice {
notes = append(notes, prefix+" wintry weather may affect travel.") notes = append(notes, prefix+" wintry weather may affect travel.")
} }
@@ -229,9 +223,6 @@ func overnightWatchNotes(daypart forecast.DaypartSummary) []string {
if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 { if daypart.PeakWindGust != nil && daypart.PeakWindGust.Value >= 30 {
notes = append(notes, fmt.Sprintf("Overnight gusts may reach %.0f mph before morning plans begin.", daypart.PeakWindGust.Value)) notes = append(notes, fmt.Sprintf("Overnight gusts may reach %.0f mph before morning plans begin.", daypart.PeakWindGust.Value))
} }
if daypart.Indicators.Thunder {
notes = append(notes, "Overnight storms could change morning impacts.")
}
if daypart.Indicators.Snow || daypart.Indicators.Ice { if daypart.Indicators.Snow || daypart.Indicators.Ice {
notes = append(notes, "Overnight wintry weather could leave morning travel impacts.") notes = append(notes, "Overnight wintry weather could leave morning travel impacts.")
} }
@@ -293,10 +284,6 @@ func scoreOutdoorWindow(daypart forecast.DaypartSummary) OutdoorWindow {
score += float64(len(daypart.AlertOverlaps)) * 100 score += float64(len(daypart.AlertOverlaps)) * 100
reasons = append(reasons, "alert overlap") reasons = append(reasons, "alert overlap")
} }
if daypart.Indicators.Thunder {
score += 75
reasons = append(reasons, "thunder risk")
}
if daypart.Indicators.Heat || daypart.Indicators.Cold { if daypart.Indicators.Heat || daypart.Indicators.Cold {
score += 25 score += 25
if daypart.Indicators.Heat { if daypart.Indicators.Heat {
@@ -334,9 +321,6 @@ func bottomLineText(conditions []string, hazards []string) string {
func hazardsForIndicators(indicators forecast.Indicators) []string { func hazardsForIndicators(indicators forecast.Indicators) []string {
var hazards []string var hazards []string
if indicators.Thunder {
hazards = append(hazards, "thunder")
}
if indicators.Snow { if indicators.Snow {
hazards = append(hazards, "snow") hazards = append(hazards, "snow")
} }

View File

@@ -163,7 +163,7 @@ func TestTomorrowBriefingIncludesPlanningInputs(t *testing.T) {
Value: wind, Value: wind,
Time: mustParse("2026-05-30T09:00:00-05:00"), Time: mustParse("2026-05-30T09:00:00-05:00"),
}, },
Indicators: forecast.Indicators{Thunder: true}, Indicators: forecast.Indicators{Snow: true},
}, },
}, },
} }

View File

@@ -139,9 +139,6 @@ func reasonableWorstCase(alerts []forecast.AlertOverlap, summary forecast.Daypar
items = appendUnique(items, "Alert scenario to consider: "+label+".") items = appendUnique(items, "Alert scenario to consider: "+label+".")
} }
} }
if summary.Indicators.Thunder {
items = appendUnique(items, "Thunderstorm timing or intensity could be more disruptive than the baseline forecast.")
}
if summary.Indicators.Wind { if summary.Indicators.Wind {
items = appendUnique(items, "Wind impacts could be higher where stronger gusts occur.") items = appendUnique(items, "Wind impacts could be higher where stronger gusts occur.")
} }

View File

@@ -39,7 +39,7 @@ func TestThreeDayBriefingBuildsOutlookDays(t *testing.T) {
Value: gust, Value: gust,
Time: mustParse("2026-05-29T10:00:00-05:00"), Time: mustParse("2026-05-29T10:00:00-05:00"),
}, },
Indicators: forecast.Indicators{Thunder: true, Wind: true}, Indicators: forecast.Indicators{Wind: true},
}, },
}, },
AlertOverlaps: []forecast.AlertOverlap{{Event: "Flood Watch"}}, AlertOverlaps: []forecast.AlertOverlap{{Event: "Flood Watch"}},
@@ -74,7 +74,7 @@ func TestThreeDayBriefingBuildsOutlookDays(t *testing.T) {
t.Fatalf("Days length = %d, want 2", len(pkg.ThreeDay.Days)) t.Fatalf("Days length = %d, want 2", len(pkg.ThreeDay.Days))
} }
first := pkg.ThreeDay.Days[0] first := pkg.ThreeDay.Days[0]
if !strings.Contains(first.OverallCharacter, "Showers") || !strings.Contains(strings.Join(first.Risks, ","), "thunder") { if !strings.Contains(first.OverallCharacter, "Showers") || !strings.Contains(strings.Join(first.Risks, ","), "wind") {
t.Fatalf("first day = %#v, want conditions and risks", first) t.Fatalf("first day = %#v, want conditions and risks", first)
} }
if len(pkg.ThreeDay.RelevantAlerts) != 1 { if len(pkg.ThreeDay.RelevantAlerts) != 1 {

View File

@@ -96,9 +96,6 @@ func weekendRainStormNotes(date string, daypart forecast.DaypartSummary) []strin
if daypart.MaxPrecipitationProbability != nil && daypart.MaxPrecipitationProbability.Value >= 30 { if daypart.MaxPrecipitationProbability != nil && daypart.MaxPrecipitationProbability.Value >= 30 {
notes = append(notes, fmt.Sprintf("%s precipitation chance peaks near %.0f%%.", label, daypart.MaxPrecipitationProbability.Value)) notes = append(notes, fmt.Sprintf("%s precipitation chance peaks near %.0f%%.", label, daypart.MaxPrecipitationProbability.Value))
} }
if daypart.Indicators.Thunder {
notes = append(notes, label+" thunder risk is present.")
}
return notes return notes
} }

View File

@@ -43,7 +43,7 @@ func TestWeekendBriefingBuildsPlanningInputs(t *testing.T) {
Value: gust, Value: gust,
Time: mustParse("2026-05-30T16:00:00-05:00"), Time: mustParse("2026-05-30T16:00:00-05:00"),
}, },
Indicators: forecast.Indicators{Thunder: true, Wind: true}, Indicators: forecast.Indicators{Wind: true},
HourlyPeriods: []forecast.ForecastPeriod{ HourlyPeriods: []forecast.ForecastPeriod{
{ {
StartTime: mustParse("2026-05-30T15:00:00-05:00"), StartTime: mustParse("2026-05-30T15:00:00-05:00"),
@@ -79,8 +79,8 @@ func TestWeekendBriefingBuildsPlanningInputs(t *testing.T) {
if len(pkg.Weekend.Planning.WorstWeatherWindows) == 0 { if len(pkg.Weekend.Planning.WorstWeatherWindows) == 0 {
t.Fatalf("WorstWeatherWindows = %#v, want weather window", pkg.Weekend.Planning.WorstWeatherWindows) t.Fatalf("WorstWeatherWindows = %#v, want weather window", pkg.Weekend.Planning.WorstWeatherWindows)
} }
if !strings.Contains(strings.Join(pkg.Weekend.Planning.RainStormTiming, " "), "thunder") { if !strings.Contains(strings.Join(pkg.Weekend.Planning.RainStormTiming, " "), "precipitation") {
t.Fatalf("RainStormTiming = %#v, want thunder timing", pkg.Weekend.Planning.RainStormTiming) t.Fatalf("RainStormTiming = %#v, want precipitation timing", pkg.Weekend.Planning.RainStormTiming)
} }
if len(pkg.Weekend.Planning.UncertaintyInputs) == 0 { if len(pkg.Weekend.Planning.UncertaintyInputs) == 0 {
t.Fatal("UncertaintyInputs length = 0, want discussion context") t.Fatal("UncertaintyInputs length = 0, want discussion context")

View File

@@ -126,7 +126,6 @@ func compareIndicators(previous forecast.Indicators, current forecast.Indicators
previous bool previous bool
current bool current bool
}{ }{
{name: "thunder", previous: previous.Thunder, current: current.Thunder},
{name: "snow", previous: previous.Snow, current: current.Snow}, {name: "snow", previous: previous.Snow, current: current.Snow},
{name: "ice", previous: previous.Ice, current: current.Ice}, {name: "ice", previous: previous.Ice, current: current.Ice},
} { } {
@@ -146,7 +145,6 @@ func compareIndicators(previous forecast.Indicators, current forecast.Indicators
func aggregateIndicators(dayparts []forecast.DaypartSummary) forecast.Indicators { func aggregateIndicators(dayparts []forecast.DaypartSummary) forecast.Indicators {
out := forecast.Indicators{} out := forecast.Indicators{}
for _, daypart := range dayparts { for _, daypart := range dayparts {
out.Thunder = out.Thunder || daypart.Indicators.Thunder
out.Snow = out.Snow || daypart.Indicators.Snow out.Snow = out.Snow || daypart.Indicators.Snow
out.Ice = out.Ice || daypart.Indicators.Ice out.Ice = out.Ice || daypart.Indicators.Ice
} }

View File

@@ -62,14 +62,14 @@ func TestCompareDailyAlertAddedAndRemoved(t *testing.T) {
func TestCompareDailyIndicatorChange(t *testing.T) { func TestCompareDailyIndicatorChange(t *testing.T) {
previous := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{}) previous := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{})
current := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{Thunder: true}) current := dailyBriefing(60, 70, 10, at("2026-05-29T08:00:00Z"), nil, forecast.Indicators{Snow: true})
changes, err := CompareDaily(previous, current, testThresholds()) changes, err := CompareDaily(previous, current, testThresholds())
if err != nil { if err != nil {
t.Fatalf("CompareDaily() error = %v", err) t.Fatalf("CompareDaily() error = %v", err)
} }
if countType(changes, "thunder_risk_change") != 1 { if countType(changes, "snow_risk_change") != 1 {
t.Fatalf("changes = %#v, want thunder risk change", changes) t.Fatalf("changes = %#v, want snow risk change", changes)
} }
} }

View File

@@ -34,7 +34,7 @@ func TestCompareThreeDayDetectsDayChanges(t *testing.T) {
Value: currentPrecip, Value: currentPrecip,
Time: time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC), Time: time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC),
}, },
Dayparts: []forecast.DaypartSummary{{Indicators: forecast.Indicators{Thunder: true}}}, Dayparts: []forecast.DaypartSummary{{Indicators: forecast.Indicators{Snow: true}}},
}}}, }}},
} }
@@ -51,16 +51,16 @@ func TestCompareThreeDayDetectsDayChanges(t *testing.T) {
t.Fatal("changes length = 0, want detected 3-day changes") t.Fatal("changes length = 0, want detected 3-day changes")
} }
var foundPrecip bool var foundPrecip bool
var foundThunder bool var foundSnow bool
for _, change := range changes { for _, change := range changes {
if change.Type == "outlook_precip_probability_change" { if change.Type == "outlook_precip_probability_change" {
foundPrecip = true foundPrecip = true
} }
if change.Type == "outlook_thunder_risk_change" { if change.Type == "outlook_snow_risk_change" {
foundThunder = true foundSnow = true
} }
} }
if !foundPrecip || !foundThunder { if !foundPrecip || !foundSnow {
t.Fatalf("changes = %#v, want precipitation and thunder changes", changes) t.Fatalf("changes = %#v, want precipitation and snow changes", changes)
} }
} }

View File

@@ -23,7 +23,7 @@ func TestCompareWeekendDetectsOutlookChanges(t *testing.T) {
Weekend: &briefing.Weekend{Days: []briefing.OutlookDay{{ Weekend: &briefing.Weekend{Days: []briefing.OutlookDay{{
Date: "2026-05-30", Date: "2026-05-30",
Temperature: forecast.Range{Max: &currentTemp}, Temperature: forecast.Range{Max: &currentTemp},
Dayparts: []forecast.DaypartSummary{{Indicators: forecast.Indicators{Thunder: true}}}, Dayparts: []forecast.DaypartSummary{{Indicators: forecast.Indicators{Snow: true}}},
}}}, }}},
} }
@@ -35,9 +35,9 @@ func TestCompareWeekendDetectsOutlookChanges(t *testing.T) {
t.Fatal("changes length = 0, want weekend changes") t.Fatal("changes length = 0, want weekend changes")
} }
for _, change := range changes { for _, change := range changes {
if change.Type == "weekend_outlook_thunder_risk_change" { if change.Type == "weekend_outlook_snow_risk_change" {
return return
} }
} }
t.Fatalf("changes = %#v, want thunder risk change", changes) t.Fatalf("changes = %#v, want snow risk change", changes)
} }

View File

@@ -47,7 +47,6 @@ type TimedValue struct {
} }
type Indicators struct { type Indicators struct {
Thunder bool `json:"thunder,omitempty"`
Snow bool `json:"snow,omitempty"` Snow bool `json:"snow,omitempty"`
Ice bool `json:"ice,omitempty"` Ice bool `json:"ice,omitempty"`
Fog bool `json:"fog,omitempty"` Fog bool `json:"fog,omitempty"`
@@ -291,7 +290,6 @@ func sortedKeys(values map[string]struct{}) []string {
func indicatorsForText(text string) Indicators { func indicatorsForText(text string) Indicators {
lower := strings.ToLower(text) lower := strings.ToLower(text)
return Indicators{ return Indicators{
Thunder: strings.Contains(lower, "thunder") || strings.Contains(lower, "storm"),
Snow: strings.Contains(lower, "snow"), Snow: strings.Contains(lower, "snow"),
Ice: strings.Contains(lower, "ice") || strings.Contains(lower, "freezing") || strings.Contains(lower, "sleet"), Ice: strings.Contains(lower, "ice") || strings.Contains(lower, "freezing") || strings.Contains(lower, "sleet"),
Fog: strings.Contains(lower, "fog"), Fog: strings.Contains(lower, "fog"),
@@ -318,7 +316,6 @@ func numericIndicators(period ForecastPeriod) Indicators {
func mergeIndicators(left Indicators, right Indicators) Indicators { func mergeIndicators(left Indicators, right Indicators) Indicators {
return Indicators{ return Indicators{
Thunder: left.Thunder || right.Thunder,
Snow: left.Snow || right.Snow, Snow: left.Snow || right.Snow,
Ice: left.Ice || right.Ice, Ice: left.Ice || right.Ice,
Fog: left.Fog || right.Fog, Fog: left.Fog || right.Fog,

View File

@@ -41,10 +41,10 @@ func TestBuildDailySummaryGroupsDaypartsAndComputesMetrics(t *testing.T) {
t.Fatalf("morning peak gust = %#v, want 40", morning.PeakWindGust) t.Fatalf("morning peak gust = %#v, want 40", morning.PeakWindGust)
} }
if morning.DominantCondition != "Thunderstorms and gusty wind" { if morning.DominantCondition != "Thunderstorms and gusty wind" {
t.Fatalf("morning dominant = %q, want thunderstorm condition", morning.DominantCondition) t.Fatalf("morning dominant = %q, want raw forecast condition", morning.DominantCondition)
} }
if !morning.Indicators.Thunder || !morning.Indicators.Wind { if !morning.Indicators.Wind {
t.Fatalf("morning indicators = %#v, want thunder and wind", morning.Indicators) t.Fatalf("morning indicators = %#v, want wind", morning.Indicators)
} }
afternoon := summary.Dayparts[2] afternoon := summary.Dayparts[2]
@@ -87,8 +87,8 @@ func TestBuildDailySummaryFromFixtureBundle(t *testing.T) {
if len(summary.Dayparts) != 2 { if len(summary.Dayparts) != 2 {
t.Fatalf("Dayparts length = %d, want 2", len(summary.Dayparts)) t.Fatalf("Dayparts length = %d, want 2", len(summary.Dayparts))
} }
if !summary.Dayparts[0].Indicators.Thunder { if summary.Dayparts[0].DominantCondition != "Showers and thunderstorms" {
t.Fatalf("morning indicators = %#v, want thunder", summary.Dayparts[0].Indicators) t.Fatalf("morning dominant = %q, want raw forecast condition", summary.Dayparts[0].DominantCondition)
} }
if len(summary.AlertOverlaps) != 1 { if len(summary.AlertOverlaps) != 1 {
t.Fatalf("AlertOverlaps length = %d, want 1", len(summary.AlertOverlaps)) t.Fatalf("AlertOverlaps length = %d, want 1", len(summary.AlertOverlaps))