Update the alert digest wording
This commit is contained in:
@@ -349,6 +349,10 @@ YAML.
|
|||||||
| `.Modules.AlertDigest.Relevant[].Event` | string | Alert event name. |
|
| `.Modules.AlertDigest.Relevant[].Event` | string | Alert event name. |
|
||||||
| `.Modules.AlertDigest.Relevant[].Headline` | string | Alert headline. |
|
| `.Modules.AlertDigest.Relevant[].Headline` | string | Alert headline. |
|
||||||
| `.Modules.AlertDigest.Relevant[].Severity` | string | Alert severity. |
|
| `.Modules.AlertDigest.Relevant[].Severity` | string | Alert severity. |
|
||||||
|
| `.Modules.AlertDigest.Relevant[].PeriodBegins` | string | Friendly local alert applicability start. |
|
||||||
|
| `.Modules.AlertDigest.Relevant[].PeriodEnds` | string | Friendly local alert applicability end. |
|
||||||
|
| `.Modules.AlertDigest.Relevant[].Instruction` | string | Alert instruction text, when provided. |
|
||||||
|
| `.Modules.AlertDigest.Relevant[].Description` | string | Alert description text, when provided. |
|
||||||
|
|
||||||
### SPC Outlooks And Discussion
|
### SPC Outlooks And Discussion
|
||||||
|
|
||||||
|
|||||||
@@ -567,7 +567,7 @@ func TestGenerateHourlyReportUsesGeneratedTextTemplateWorkflow(t *testing.T) {
|
|||||||
"# Hourly Report",
|
"# Hourly Report",
|
||||||
"Storm chances increase through late morning.",
|
"Storm chances increase through late morning.",
|
||||||
"## Active Alerts",
|
"## Active Alerts",
|
||||||
"- **Flood Watch**: Flooding possible",
|
"- **Flood Watch**: Flood Watch in effect from May 29 at 11:00 AM to May 29 at 3:00 PM. Avoid low-water crossings.",
|
||||||
"## Precipitation Timing",
|
"## Precipitation Timing",
|
||||||
"A cold front is moving into the region.",
|
"A cold front is moving into the region.",
|
||||||
"A front will keep the region unsettled.",
|
"A front will keep the region unsettled.",
|
||||||
@@ -2046,7 +2046,7 @@ func hourlyBundleServer(t *testing.T) *httptest.Server {
|
|||||||
case "/forecast/narrative":
|
case "/forecast/narrative":
|
||||||
_, _ = w.Write([]byte(`{"data":{"issuedAt":"2026-05-29T08:00:00-05:00","product":"narrative","periods":[{"startTime":"2026-05-29T06:00:00-05:00","endTime":"2026-05-29T18:00:00-05:00","textDescription":"Storms are possible today."}]}}`))
|
_, _ = w.Write([]byte(`{"data":{"issuedAt":"2026-05-29T08:00:00-05:00","product":"narrative","periods":[{"startTime":"2026-05-29T06:00:00-05:00","endTime":"2026-05-29T18:00:00-05:00","textDescription":"Storms are possible today."}]}}`))
|
||||||
case "/alerts/active":
|
case "/alerts/active":
|
||||||
_, _ = w.Write([]byte(`{"data":{"alerts":[{"event":"Expired Advisory","headline":"Ends at valid start","severity":"Minor","effective":"2026-05-29T06:00:00-05:00","expires":"2026-05-29T08:30:00-05:00"},{"event":"Flood Watch","headline":"Flooding possible","severity":"Moderate","effective":"2026-05-29T11:00:00-05:00","expires":"2026-05-29T15:00:00-05:00"},{"event":"Evening Advisory","headline":"Starts at valid end","severity":"Minor","effective":"2026-05-29T14:30:00-05:00","expires":"2026-05-29T18:00:00-05:00"}]}}`))
|
_, _ = w.Write([]byte(`{"data":{"alerts":[{"event":"Expired Advisory","headline":"Ends at valid start","severity":"Minor","effective":"2026-05-29T06:00:00-05:00","expires":"2026-05-29T08:30:00-05:00"},{"event":"Flood Watch","headline":"Flooding possible","severity":"Moderate","instruction":"Avoid low-water crossings.","effective":"2026-05-29T11:00:00-05:00","expires":"2026-05-29T15:00:00-05:00"},{"event":"Evening Advisory","headline":"Starts at valid end","severity":"Minor","effective":"2026-05-29T14:30:00-05:00","expires":"2026-05-29T18:00:00-05:00"}]}}`))
|
||||||
case "/discussion":
|
case "/discussion":
|
||||||
_, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T08:05:00-05:00","keyMessages":["Storms are most likely late this morning."],"shortTerm":{"qualifier":"(Short Term)","text":"Short-term AFD narrative for hourly report."},"longTerm":{"qualifier":"(Long Term)","text":"Long-term AFD narrative for hourly report."}}}`))
|
_, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T08:05:00-05:00","keyMessages":["Storms are most likely late this morning."],"shortTerm":{"qualifier":"(Short Term)","text":"Short-term AFD narrative for hourly report."},"longTerm":{"qualifier":"(Long Term)","text":"Long-term AFD narrative for hourly report."}}}`))
|
||||||
case "/weatherstories/latest":
|
case "/weatherstories/latest":
|
||||||
|
|||||||
@@ -16,20 +16,24 @@ type AlertDigestModule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertSummary struct {
|
type AlertSummary struct {
|
||||||
Event string `json:"event,omitempty"`
|
Event string `json:"event,omitempty"`
|
||||||
Headline string `json:"headline,omitempty"`
|
Headline string `json:"headline,omitempty"`
|
||||||
Severity string `json:"severity,omitempty"`
|
Severity string `json:"severity,omitempty"`
|
||||||
|
PeriodBegins string `json:"period_begins,omitempty"`
|
||||||
|
PeriodEnds string `json:"period_ends,omitempty"`
|
||||||
|
Instruction string `json:"instruction,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAlertDigestModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
func buildAlertDigestModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
||||||
value := alertDigest(ctx.Collected, ctx.Derived.AlertOverlaps)
|
value := alertDigest(ctx.Collected, ctx.Derived.AlertOverlaps, ctx.Timezone)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
value = &AlertDigestModule{}
|
value = &AlertDigestModule{}
|
||||||
}
|
}
|
||||||
return &module.Output{ID: module.AlertDigest, StanzaName: "alert_digest", Value: *value}, nil
|
return &module.Output{ID: module.AlertDigest, StanzaName: "alert_digest", Value: *value}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func alertDigest(collected facts.CollectedFacts, overlaps []forecast.AlertOverlap) *AlertDigestModule {
|
func alertDigest(collected facts.CollectedFacts, overlaps []forecast.AlertOverlap, timezone string) *AlertDigestModule {
|
||||||
missing := sourceMissing(collected.SourceProvenance, "alerts")
|
missing := sourceMissing(collected.SourceProvenance, "alerts")
|
||||||
if collected.Alerts == nil && !missing {
|
if collected.Alerts == nil && !missing {
|
||||||
return nil
|
return nil
|
||||||
@@ -42,9 +46,13 @@ func alertDigest(collected facts.CollectedFacts, overlaps []forecast.AlertOverla
|
|||||||
value.RelevantCount = len(overlaps)
|
value.RelevantCount = len(overlaps)
|
||||||
for _, overlap := range overlaps {
|
for _, overlap := range overlaps {
|
||||||
value.Relevant = append(value.Relevant, AlertSummary{
|
value.Relevant = append(value.Relevant, AlertSummary{
|
||||||
Event: overlap.Event,
|
Event: overlap.Event,
|
||||||
Headline: overlap.Headline,
|
Headline: overlap.Headline,
|
||||||
Severity: overlap.Severity,
|
Severity: overlap.Severity,
|
||||||
|
PeriodBegins: friendlyMonthDayTimeLabel(overlap.Period.Start, timezone),
|
||||||
|
PeriodEnds: friendlyMonthDayTimeLabel(overlap.Period.End, timezone),
|
||||||
|
Instruction: overlap.Instruction,
|
||||||
|
Description: overlap.Description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -367,6 +367,39 @@ func TestAlertDigestDistinguishesCheckedEmptyAndMissing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlertDigestIncludesPeriodAndGuidance(t *testing.T) {
|
||||||
|
registry := MustDefaultModuleRegistry()
|
||||||
|
ctx := testModuleContext()
|
||||||
|
ctx.Collected.Alerts = &weatherdata.AlertRun{Alerts: []json.RawMessage{json.RawMessage(`{"event":"Wind Advisory"}`)}}
|
||||||
|
ctx.Derived.AlertOverlaps = []forecast.AlertOverlap{{
|
||||||
|
Event: "Wind Advisory",
|
||||||
|
Headline: "Wind Advisory until 8 PM",
|
||||||
|
Severity: "Moderate",
|
||||||
|
Period: timeutil.Period{Start: mustParseModuleTime("2026-06-17T18:00:00Z"), End: mustParseModuleTime("2026-06-18T01:00:00Z")},
|
||||||
|
Instruction: "Secure outdoor objects.",
|
||||||
|
Description: "Gusty winds may blow around unsecured objects.",
|
||||||
|
}}
|
||||||
|
|
||||||
|
output, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.AlertDigest})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildModule(alert digest) error = %v", err)
|
||||||
|
}
|
||||||
|
value := moduleValue[AlertDigestModule](t, output)
|
||||||
|
if len(value.Relevant) != 1 {
|
||||||
|
t.Fatalf("Relevant length = %d, want 1", len(value.Relevant))
|
||||||
|
}
|
||||||
|
alert := value.Relevant[0]
|
||||||
|
if alert.Event != "Wind Advisory" || alert.Headline != "Wind Advisory until 8 PM" || alert.Severity != "Moderate" {
|
||||||
|
t.Fatalf("alert identity = %#v, want preserved event/headline/severity", alert)
|
||||||
|
}
|
||||||
|
if alert.PeriodBegins != "June 17 at 1:00 PM" || alert.PeriodEnds != "June 17 at 8:00 PM" {
|
||||||
|
t.Fatalf("alert period = %q/%q, want friendly local labels", alert.PeriodBegins, alert.PeriodEnds)
|
||||||
|
}
|
||||||
|
if alert.Instruction != "Secure outdoor objects." || alert.Description != "Gusty winds may blow around unsecured objects." {
|
||||||
|
t.Fatalf("alert guidance = %#v, want instruction and description preserved", alert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBaseModulesOmitMissingOptionalOutputs(t *testing.T) {
|
func TestBaseModulesOmitMissingOptionalOutputs(t *testing.T) {
|
||||||
registry := MustDefaultModuleRegistry()
|
registry := MustDefaultModuleRegistry()
|
||||||
ctx := testModuleContext()
|
ctx := testModuleContext()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func buildMetadataModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
|||||||
ValidPeriod: metadata.ValidPeriod,
|
ValidPeriod: metadata.ValidPeriod,
|
||||||
Location: copyLocation(ctx.Location),
|
Location: copyLocation(ctx.Location),
|
||||||
SourceWarnings: sourceWarningSummaries(ctx.Collected.SourceWarnings),
|
SourceWarnings: sourceWarningSummaries(ctx.Collected.SourceWarnings),
|
||||||
Alerts: alertDigest(ctx.Collected, ctx.Derived.AlertOverlaps),
|
Alerts: alertDigest(ctx.Collected, ctx.Derived.AlertOverlaps, ctx.Timezone),
|
||||||
}
|
}
|
||||||
return &module.Output{ID: module.Metadata, StanzaName: "metadata", Value: value}, nil
|
return &module.Output{ID: module.Metadata, StanzaName: "metadata", Value: value}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,17 @@ func friendlyDateTimeLabel(value time.Time, timezone string) string {
|
|||||||
return value.In(location).Format("2006-01-02 at 3:04 PM")
|
return value.In(location).Format("2006-01-02 at 3:04 PM")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func friendlyMonthDayTimeLabel(value time.Time, timezone string) string {
|
||||||
|
if value.IsZero() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
location, err := timeutil.LoadLocation(timezone)
|
||||||
|
if err != nil {
|
||||||
|
location = time.UTC
|
||||||
|
}
|
||||||
|
return value.In(location).Format("January 2 at 3:04 PM")
|
||||||
|
}
|
||||||
|
|
||||||
func friendlyDateLabel(date string, timezone string) string {
|
func friendlyDateLabel(date string, timezone string) string {
|
||||||
location, err := timeutil.LoadLocation(timezone)
|
location, err := timeutil.LoadLocation(timezone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ type AlertOverlap struct {
|
|||||||
Period timeutil.Period `json:"period"`
|
Period timeutil.Period `json:"period"`
|
||||||
Overlap timeutil.Period `json:"overlap"`
|
Overlap timeutil.Period `json:"overlap"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
|
Instruction string `json:"instruction,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrecipTiming struct {
|
type PrecipTiming struct {
|
||||||
@@ -463,6 +464,7 @@ func AlertOverlaps(alertRun *weatherdata.AlertRun, period timeutil.Period) []Ale
|
|||||||
Period: alert.Period,
|
Period: alert.Period,
|
||||||
Overlap: intersect(alert.Period, period),
|
Overlap: intersect(alert.Period, period),
|
||||||
Description: alert.Description,
|
Description: alert.Description,
|
||||||
|
Instruction: alert.Instruction,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.SliceStable(overlaps, func(i int, j int) bool {
|
sort.SliceStable(overlaps, func(i int, j int) bool {
|
||||||
@@ -476,6 +478,7 @@ type parsedAlert struct {
|
|||||||
Headline string
|
Headline string
|
||||||
Severity string
|
Severity string
|
||||||
Description string
|
Description string
|
||||||
|
Instruction string
|
||||||
Period timeutil.Period
|
Period timeutil.Period
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +491,8 @@ func parseAlert(raw json.RawMessage) (parsedAlert, bool) {
|
|||||||
Event: stringField(fields, "event"),
|
Event: stringField(fields, "event"),
|
||||||
Headline: firstStringField(fields, "headline", "title"),
|
Headline: firstStringField(fields, "headline", "title"),
|
||||||
Severity: stringField(fields, "severity"),
|
Severity: stringField(fields, "severity"),
|
||||||
Description: firstStringField(fields, "description", "instruction"),
|
Description: stringField(fields, "description"),
|
||||||
|
Instruction: stringField(fields, "instruction"),
|
||||||
}
|
}
|
||||||
start, startOK := firstTimeField(fields, "onset", "startsAt", "startTime", "effective", "sent")
|
start, startOK := firstTimeField(fields, "onset", "startsAt", "startTime", "effective", "sent")
|
||||||
end, endOK := firstTimeField(fields, "ends", "endsAt", "endTime", "expires")
|
end, endOK := firstTimeField(fields, "ends", "endsAt", "endTime", "expires")
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ func TestAlertOverlap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertOverlapUsesOnsetAndEnds(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"}`)
|
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","description":"Southwest winds 20 to 30 mph with gusts up to 45 mph expected.","instruction":"Secure outdoor objects.","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}}
|
alertRun := &weatherdata.AlertRun{Alerts: []json.RawMessage{raw}}
|
||||||
period := timeutil.Period{
|
period := timeutil.Period{
|
||||||
Start: mustParse("2026-06-17T12:00:00-05:00"),
|
Start: mustParse("2026-06-17T12:00:00-05:00"),
|
||||||
@@ -234,6 +234,12 @@ func TestAlertOverlapUsesOnsetAndEnds(t *testing.T) {
|
|||||||
if overlaps[0].Overlap.End.Format(time.RFC3339) != "2026-06-18T01:00:00Z" {
|
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))
|
t.Fatalf("overlap end = %s, want alert ends", overlaps[0].Overlap.End.Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
|
if overlaps[0].Description != "Southwest winds 20 to 30 mph with gusts up to 45 mph expected." {
|
||||||
|
t.Fatalf("Description = %q, want preserved description", overlaps[0].Description)
|
||||||
|
}
|
||||||
|
if overlaps[0].Instruction != "Secure outdoor objects." {
|
||||||
|
t.Fatalf("Instruction = %q, want preserved instruction", overlaps[0].Instruction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertOverlapUsesOnsetAndEndsForLocalDateRelevance(t *testing.T) {
|
func TestAlertOverlapUsesOnsetAndEndsForLocalDateRelevance(t *testing.T) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func TestBuildHourlyRenderContext(t *testing.T) {
|
|||||||
"**Updated:** Friday, May 29, 2026 at 8:30 AM",
|
"**Updated:** Friday, May 29, 2026 at 8:30 AM",
|
||||||
"Storm chances increase through late morning.",
|
"Storm chances increase through late morning.",
|
||||||
"- **10:00 AM:** 75°F and showers. Probability of precipitation is 70%.",
|
"- **10:00 AM:** 75°F and showers. Probability of precipitation is 70%.",
|
||||||
"- **Flood Watch**: Flood Watch until early afternoon",
|
"- **Flood Watch**: Flood Watch in effect from May 29 at 10:00 AM to May 29 at 2:30 PM. Avoid low-water crossings.",
|
||||||
"A cold front is moving into the region.",
|
"A cold front is moving into the region.",
|
||||||
"A front will keep the region unsettled.",
|
"A front will keep the region unsettled.",
|
||||||
} {
|
} {
|
||||||
@@ -1190,7 +1190,7 @@ func testSnapshot(t *testing.T) module.Snapshot {
|
|||||||
ActiveCount: 1,
|
ActiveCount: 1,
|
||||||
RelevantCount: 1,
|
RelevantCount: 1,
|
||||||
Relevant: []briefing.AlertSummary{
|
Relevant: []briefing.AlertSummary{
|
||||||
{Event: "Flood Watch", Headline: "Flood Watch until early afternoon", Severity: "Moderate"},
|
{Event: "Flood Watch", Headline: "Flood Watch until early afternoon", Severity: "Moderate", PeriodBegins: "May 29 at 10:00 AM", PeriodEnds: "May 29 at 2:30 PM", Instruction: "Avoid low-water crossings."},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ func TestRenderHourly(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
AlertDigest: &testAlertDigest{
|
AlertDigest: &testAlertDigest{
|
||||||
Relevant: []testAlert{{Event: "Flood Watch", Headline: "Flood Watch until 2:30 PM", Severity: "Moderate"}},
|
Relevant: []testAlert{{Event: "Flood Watch", Headline: "Flood Watch until 2:30 PM", Severity: "Moderate", PeriodBegins: "May 29 at 10:00 AM", PeriodEnds: "May 29 at 2:30 PM", Instruction: "Avoid low-water crossings."}},
|
||||||
},
|
},
|
||||||
SPCConvectiveOutlooks: &testSPCOutlooks{
|
SPCConvectiveOutlooks: &testSPCOutlooks{
|
||||||
Outlooks: []testSPCOutlook{{LabelText: "Slight Risk", PeriodBegins: "8 AM", PeriodEnds: "2 PM"}},
|
Outlooks: []testSPCOutlook{{LabelText: "Slight Risk", PeriodBegins: "8 AM", PeriodEnds: "2 PM"}},
|
||||||
@@ -269,7 +269,7 @@ func TestRenderHourly(t *testing.T) {
|
|||||||
"Storm chances increase through late morning.",
|
"Storm chances increase through late morning.",
|
||||||
"Currently, it is 74°F and partly cloudy. It feels like 76°F, with a relative humidity of 71% and winds from the south at 8 mph.",
|
"Currently, it is 74°F and partly cloudy. It feels like 76°F, with a relative humidity of 71% and winds from the south at 8 mph.",
|
||||||
"## Active Alerts",
|
"## Active Alerts",
|
||||||
"- **Flood Watch**: Flood Watch until 2:30 PM",
|
"- **Flood Watch**: Flood Watch in effect from May 29 at 10:00 AM to May 29 at 2:30 PM. Avoid low-water crossings.",
|
||||||
"- **9:00 AM:** 74°F and cloudy.",
|
"- **9:00 AM:** 74°F and cloudy.",
|
||||||
"- **10:00 AM:** 75°F and showers. Probability of precipitation is 70%.",
|
"- **10:00 AM:** 75°F and showers. Probability of precipitation is 70%.",
|
||||||
"- **10:00 AM** to **12:00 PM**: Expect showers. The peak precipitation chance is 70% at 10:00 AM.",
|
"- **10:00 AM** to **12:00 PM**: Expect showers. The peak precipitation chance is 70% at 10:00 AM.",
|
||||||
@@ -352,7 +352,7 @@ func TestRenderTomorrow(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
AlertDigest: &testAlertDigest{
|
AlertDigest: &testAlertDigest{
|
||||||
Relevant: []testAlert{{Event: "Wind Advisory", Headline: "Wind Advisory until 8:00 PM", Severity: "Moderate"}},
|
Relevant: []testAlert{{Event: "Wind Advisory", Headline: "Wind Advisory until 8:00 PM", Severity: "Moderate", PeriodBegins: "June 15 at 1:00 PM", PeriodEnds: "June 15 at 8:00 PM", Instruction: "Secure outdoor objects."}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -366,7 +366,7 @@ func TestRenderTomorrow(t *testing.T) {
|
|||||||
"**Updated:** Sunday, June 14, 2026 at 9:14 AM",
|
"**Updated:** Sunday, June 14, 2026 at 9:14 AM",
|
||||||
"Tomorrow starts dry before showers return later in the day.",
|
"Tomorrow starts dry before showers return later in the day.",
|
||||||
"## Active Alerts",
|
"## Active Alerts",
|
||||||
"- **Wind Advisory**: Wind Advisory until 8:00 PM",
|
"- **Wind Advisory**: Wind Advisory in effect from June 15 at 1:00 PM to June 15 at 8:00 PM. Secure outdoor objects.",
|
||||||
"- **Overnight:** Partly cloudy, with temperatures falling from the mid 60s to the upper 50s.",
|
"- **Overnight:** Partly cloudy, with temperatures falling from the mid 60s to the upper 50s.",
|
||||||
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
||||||
"- **Afternoon:** Sunny, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
"- **Afternoon:** Sunny, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
||||||
@@ -450,7 +450,7 @@ func TestRenderDaily(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
AlertDigest: &testAlertDigest{
|
AlertDigest: &testAlertDigest{
|
||||||
Relevant: []testAlert{{Event: "Flood Watch", Headline: "Flood Watch until 6:00 PM", Severity: "Moderate"}},
|
Relevant: []testAlert{{Event: "Flood Watch", Headline: "Flood Watch until 6:00 PM", Severity: "Moderate", PeriodBegins: "June 15 at 3:00 PM", PeriodEnds: "June 15 at 6:00 PM", Description: "Monitor creek levels."}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -464,7 +464,7 @@ func TestRenderDaily(t *testing.T) {
|
|||||||
"**Updated:** Sunday, June 14, 2026 at 9:14 AM",
|
"**Updated:** Sunday, June 14, 2026 at 9:14 AM",
|
||||||
"The selected day starts dry before showers return later in the day.",
|
"The selected day starts dry before showers return later in the day.",
|
||||||
"## Active Alerts",
|
"## Active Alerts",
|
||||||
"- **Flood Watch**: Flood Watch until 6:00 PM",
|
"- **Flood Watch**: Flood Watch in effect from June 15 at 3:00 PM to June 15 at 6:00 PM. Monitor creek levels.",
|
||||||
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
||||||
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
||||||
"- **3:00 PM** to **6:00 PM**: Expect showers. The peak precipitation chance is 70% at 3:00 PM.",
|
"- **3:00 PM** to **6:00 PM**: Expect showers. The peak precipitation chance is 70% at 3:00 PM.",
|
||||||
@@ -550,7 +550,7 @@ func TestRenderToday(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
AlertDigest: &testAlertDigest{
|
AlertDigest: &testAlertDigest{
|
||||||
Relevant: []testAlert{{Event: "Wind Advisory", Headline: "Wind Advisory until 8:00 PM", Severity: "Moderate"}},
|
Relevant: []testAlert{{Event: "Wind Advisory", Headline: "Wind Advisory until 8:00 PM", Severity: "Moderate", PeriodBegins: "June 15 at 1:00 PM", PeriodEnds: "June 15 at 8:00 PM", Instruction: "Secure outdoor objects."}},
|
||||||
},
|
},
|
||||||
TodayPlanning: &testTodayPlanning{
|
TodayPlanning: &testTodayPlanning{
|
||||||
MorningReadiness: []string{"Morning weather looks routine."},
|
MorningReadiness: []string{"Morning weather looks routine."},
|
||||||
@@ -568,7 +568,7 @@ func TestRenderToday(t *testing.T) {
|
|||||||
"**Updated:** Monday, June 15, 2026 at 7:14 AM",
|
"**Updated:** Monday, June 15, 2026 at 7:14 AM",
|
||||||
"Today starts dry before showers return later in the day.",
|
"Today starts dry before showers return later in the day.",
|
||||||
"## Active Alerts",
|
"## Active Alerts",
|
||||||
"- **Wind Advisory**: Wind Advisory until 8:00 PM",
|
"- **Wind Advisory**: Wind Advisory in effect from June 15 at 1:00 PM to June 15 at 8:00 PM. Secure outdoor objects.",
|
||||||
"Currently, it is 58°F and clear. It feels like 57°F, with a relative humidity of 61% and winds from the northwest at 9 mph.",
|
"Currently, it is 58°F and clear. It feels like 57°F, with a relative humidity of 61% and winds from the northwest at 9 mph.",
|
||||||
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
||||||
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
||||||
@@ -1036,9 +1036,13 @@ type testAlertDigest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type testAlert struct {
|
type testAlert struct {
|
||||||
Event string
|
Event string
|
||||||
Headline string
|
Headline string
|
||||||
Severity string
|
Severity string
|
||||||
|
PeriodBegins string
|
||||||
|
PeriodEnds string
|
||||||
|
Instruction string
|
||||||
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
type testSPCOutlooks struct {
|
type testSPCOutlooks struct {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{ define "alert_digest" }}{{ if and .Modules.AlertDigest .Modules.AlertDigest.Relevant }}## Active Alerts
|
{{ define "alert_digest" }}{{ if and .Modules.AlertDigest .Modules.AlertDigest.Relevant }}## Active Alerts
|
||||||
{{ range .Modules.AlertDigest.Relevant -}}
|
{{ range .Modules.AlertDigest.Relevant -}}
|
||||||
- **{{ if .Event }}{{ .Event }}{{ else }}{{ .Headline }}{{ end }}**{{ with .Headline }}: {{ . }}{{ end }}
|
- **{{ if .Event }}{{ .Event }}{{ else }}{{ .Headline }}{{ end }}**: {{ if .Event }}{{ .Event }}{{ else }}{{ .Headline }}{{ end }} in effect{{ with .PeriodBegins }} from {{ . }}{{ end }}{{ with .PeriodEnds }} to {{ . }}{{ end }}.{{ with .Instruction }} {{ . }}{{ else }}{{ with .Description }} {{ . }}{{ end }}{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ end }}{{ end }}
|
{{ end }}{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user