Update upstream weatherapi alert handling
This commit is contained in:
@@ -490,12 +490,16 @@ func parseAlert(raw json.RawMessage) (parsedAlert, bool) {
|
|||||||
Severity: stringField(fields, "severity"),
|
Severity: stringField(fields, "severity"),
|
||||||
Description: firstStringField(fields, "description", "instruction"),
|
Description: firstStringField(fields, "description", "instruction"),
|
||||||
}
|
}
|
||||||
start, startOK := firstTimeField(fields, "effective", "onset", "startsAt", "startTime", "sent")
|
start, startOK := firstTimeField(fields, "onset", "startsAt", "startTime", "effective", "sent")
|
||||||
end, endOK := firstTimeField(fields, "expires", "ends", "endsAt", "endTime")
|
end, endOK := firstTimeField(fields, "ends", "endsAt", "endTime", "expires")
|
||||||
if !startOK || !endOK {
|
if !startOK || !endOK {
|
||||||
return parsedAlert{}, false
|
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
|
return alert, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
func TestBuildPrecipTimingBuildsThresholdWindows(t *testing.T) {
|
||||||
location := time.FixedZone("Test", -5*60*60)
|
location := time.FixedZone("Test", -5*60*60)
|
||||||
periods := []weatherdata.ForecastPeriod{
|
periods := []weatherdata.ForecastPeriod{
|
||||||
|
|||||||
@@ -20,10 +20,8 @@ Current conditions are unavailable.
|
|||||||
## Hourly Forecast
|
## Hourly Forecast
|
||||||
{{ with .Modules.HourlyForecast }}{{ range .Periods }}
|
{{ 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 }}
|
- **{{ 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 }}
|
{{ else }}- No hourly forecast rows are available.
|
||||||
- No hourly forecast rows are available.
|
{{ end }}{{ else }}- No hourly forecast rows are available.
|
||||||
{{ end }}{{ else }}
|
|
||||||
- No hourly forecast rows are available.
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ template "precipitation_timing" . }}
|
{{ template "precipitation_timing" . }}
|
||||||
|
|||||||
Reference in New Issue
Block a user