Update upstream weatherapi alert handling

This commit is contained in:
2026-06-16 20:34:01 -05:00
parent b36e198bfe
commit 21a7748b2c
3 changed files with 89 additions and 7 deletions

View File

@@ -490,12 +490,16 @@ func parseAlert(raw json.RawMessage) (parsedAlert, bool) {
Severity: stringField(fields, "severity"),
Description: firstStringField(fields, "description", "instruction"),
}
start, startOK := firstTimeField(fields, "effective", "onset", "startsAt", "startTime", "sent")
end, endOK := firstTimeField(fields, "expires", "ends", "endsAt", "endTime")
start, startOK := firstTimeField(fields, "onset", "startsAt", "startTime", "effective", "sent")
end, endOK := firstTimeField(fields, "ends", "endsAt", "endTime", "expires")
if !startOK || !endOK {
return parsedAlert{}, false
}
alert.Period = timeutil.Period{Start: start, End: end}
period := timeutil.Period{Start: start, End: end}
if !period.IsValid() {
return parsedAlert{}, false
}
alert.Period = period
return alert, true
}

View File

@@ -210,6 +210,86 @@ func TestAlertOverlap(t *testing.T) {
}
}
func TestAlertOverlapUsesOnsetAndEnds(t *testing.T) {
raw := json.RawMessage(`{"event":"Wind Advisory","headline":"Wind Advisory issued June 16 at 12:39PM CDT until June 17 at 8:00PM CDT by NWS St Louis MO","severity":"Moderate","effective":"2026-06-16T17:39:00Z","onset":"2026-06-17T18:00:00Z","ends":"2026-06-18T01:00:00Z","expires":"2026-06-17T08:45:00Z"}`)
alertRun := &weatherdata.AlertRun{Alerts: []json.RawMessage{raw}}
period := timeutil.Period{
Start: mustParse("2026-06-17T12:00:00-05:00"),
End: mustParse("2026-06-17T21:00:00-05:00"),
}
overlaps := AlertOverlaps(alertRun, period)
if len(overlaps) != 1 {
t.Fatalf("overlaps length = %d, want 1", len(overlaps))
}
if overlaps[0].Period.Start.Format(time.RFC3339) != "2026-06-17T18:00:00Z" {
t.Fatalf("alert period start = %s, want onset", overlaps[0].Period.Start.Format(time.RFC3339))
}
if overlaps[0].Period.End.Format(time.RFC3339) != "2026-06-18T01:00:00Z" {
t.Fatalf("alert period end = %s, want ends", overlaps[0].Period.End.Format(time.RFC3339))
}
if overlaps[0].Overlap.Start.Format(time.RFC3339) != "2026-06-17T18:00:00Z" {
t.Fatalf("overlap start = %s, want onset", overlaps[0].Overlap.Start.Format(time.RFC3339))
}
if overlaps[0].Overlap.End.Format(time.RFC3339) != "2026-06-18T01:00:00Z" {
t.Fatalf("overlap end = %s, want alert ends", overlaps[0].Overlap.End.Format(time.RFC3339))
}
}
func TestAlertOverlapUsesOnsetAndEndsForLocalDateRelevance(t *testing.T) {
location, err := time.LoadLocation("America/Chicago")
if err != nil {
t.Fatalf("load location: %v", err)
}
raw := json.RawMessage(`{"event":"Wind Advisory","headline":"Wind Advisory issued June 16 at 12:39PM CDT until June 17 at 8:00PM CDT by NWS St Louis MO","severity":"Moderate","effective":"2026-06-16T17:39:00Z","onset":"2026-06-17T18:00:00Z","ends":"2026-06-18T01:00:00Z","expires":"2026-06-17T08:45:00Z"}`)
alertRun := &weatherdata.AlertRun{Alerts: []json.RawMessage{raw}}
june16 := timeutil.CivilDay(time.Date(2026, 6, 16, 12, 0, 0, 0, location), location)
june17Afternoon := timeutil.Period{
Start: time.Date(2026, 6, 17, 12, 0, 0, 0, location),
End: time.Date(2026, 6, 17, 21, 0, 0, 0, location),
}
if overlaps := AlertOverlaps(alertRun, june16); len(overlaps) != 0 {
t.Fatalf("June 16 overlaps length = %d, want none: %#v", len(overlaps), overlaps)
}
if overlaps := AlertOverlaps(alertRun, june17Afternoon); len(overlaps) != 1 {
t.Fatalf("June 17 afternoon overlaps length = %d, want 1: %#v", len(overlaps), overlaps)
}
}
func TestAlertOverlapSupportsOlderEffectiveExpiresPayload(t *testing.T) {
raw := json.RawMessage(`{"event":"Flood Watch","headline":"Flooding possible","severity":"Moderate","effective":"2026-05-29T07:00:00-05:00","expires":"2026-05-29T10:00:00-05:00"}`)
alertRun := &weatherdata.AlertRun{Alerts: []json.RawMessage{raw}}
period := timeutil.Period{
Start: mustParse("2026-05-29T06:00:00-05:00"),
End: mustParse("2026-05-29T11:00:00-05:00"),
}
overlaps := AlertOverlaps(alertRun, period)
if len(overlaps) != 1 {
t.Fatalf("overlaps length = %d, want 1", len(overlaps))
}
if overlaps[0].Period.Start.Format(time.RFC3339) != "2026-05-29T07:00:00-05:00" {
t.Fatalf("alert period start = %s, want effective fallback", overlaps[0].Period.Start.Format(time.RFC3339))
}
if overlaps[0].Period.End.Format(time.RFC3339) != "2026-05-29T10:00:00-05:00" {
t.Fatalf("alert period end = %s, want expires fallback", overlaps[0].Period.End.Format(time.RFC3339))
}
}
func TestAlertOverlapSkipsInvalidSelectedPeriod(t *testing.T) {
raw := json.RawMessage(`{"event":"Wind Advisory","headline":"Invalid event period","severity":"Moderate","onset":"2026-06-17T18:00:00Z","ends":"2026-06-17T08:45:00Z"}`)
alertRun := &weatherdata.AlertRun{Alerts: []json.RawMessage{raw}}
period := timeutil.Period{
Start: mustParse("2026-06-17T00:00:00Z"),
End: mustParse("2026-06-18T00:00:00Z"),
}
if overlaps := AlertOverlaps(alertRun, period); len(overlaps) != 0 {
t.Fatalf("overlaps length = %d, want invalid alert skipped: %#v", len(overlaps), overlaps)
}
}
func TestBuildPrecipTimingBuildsThresholdWindows(t *testing.T) {
location := time.FixedZone("Test", -5*60*60)
periods := []weatherdata.ForecastPeriod{

View File

@@ -20,10 +20,8 @@ Current conditions are unavailable.
## Hourly Forecast
{{ with .Modules.HourlyForecast }}{{ range .Periods }}
- **{{ if .HourLabel }}{{ .HourLabel }}{{ else }}{{ .Name }}{{ end }}:**{{ with .TemperatureF }} {{ . }}°F{{ end }}{{ with .TextDescriptionLower }} and {{ . }}{{ else }}{{ with .TextDescription }} and {{ . }}{{ end }}{{ end }}.{{ if .MentionPrecipitation }}{{ with .ProbabilityOfPrecipitationPercent }} Probability of precipitation is {{ . }}%.{{ end }}{{ end }}
{{ else }}
- No hourly forecast rows are available.
{{ end }}{{ else }}
- No hourly forecast rows are available.
{{ else }}- No hourly forecast rows are available.
{{ end }}{{ else }}- No hourly forecast rows are available.
{{ end }}
{{ template "precipitation_timing" . }}