Update the alert digest wording

This commit is contained in:
2026-06-16 21:09:17 -05:00
parent d321492995
commit 6532e8824a
11 changed files with 97 additions and 27 deletions

View File

@@ -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

View File

@@ -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":

View File

@@ -19,17 +19,21 @@ 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
@@ -45,6 +49,10 @@ func alertDigest(collected facts.CollectedFacts, overlaps []forecast.AlertOverla
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

View File

@@ -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()

View File

@@ -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
} }

View File

@@ -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 {

View File

@@ -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")

View File

@@ -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) {

View File

@@ -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."},
}, },
}, },
}, },

View File

@@ -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%.",
@@ -1039,6 +1039,10 @@ 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 {

View File

@@ -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 }}