Move the Alert Digest into a shared partial template, and add it to the today, tomorrow, and daily reports
This commit is contained in:
@@ -76,9 +76,10 @@ fail rendering instead of producing incomplete Markdown.
|
||||
|
||||
Daily and Tomorrow call the shared `daypart_forecast` partial. Today calls
|
||||
`today_daypart_forecast` so it can omit elapsed or missing dayparts. Daily,
|
||||
Today, Tomorrow, and Hourly call the shared `precipitation_timing` partial.
|
||||
Partial files are parsed with each top-level template at render time and
|
||||
receive the same typed render context as the caller.
|
||||
Today, Tomorrow, and Hourly call the shared `alert_digest` and
|
||||
`precipitation_timing` partials. Partial files are parsed with each top-level
|
||||
template at render time and receive the same typed render context as the
|
||||
caller.
|
||||
|
||||
## Schema Contract
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ are:
|
||||
|
||||
Shared named partials live under `internal/reporttemplate/templates/partials/`:
|
||||
|
||||
- `alert_digest.md.tmpl`, used by Daily, Today, Tomorrow, and Hourly
|
||||
- `daypart_forecast.md.tmpl`, used by Daily and Tomorrow
|
||||
- `today_daypart_forecast.md.tmpl`, used by Today
|
||||
- `precipitation_timing.md.tmpl`, used by Daily, Today, Tomorrow, and Hourly
|
||||
@@ -113,8 +114,8 @@ Tomorrow generated text uses the same `.GeneratedText.Summary`,
|
||||
Hourly. `.GeneratedText.ForecastDiscussion` is a slice of paragraphs and should
|
||||
be rendered with `range`.
|
||||
|
||||
Tomorrow uses the shared `daypart_forecast` and `precipitation_timing`
|
||||
partials.
|
||||
Tomorrow uses the shared `alert_digest`, `daypart_forecast`, and
|
||||
`precipitation_timing` partials.
|
||||
|
||||
Tomorrow modules include the Hourly module fields plus:
|
||||
|
||||
@@ -144,7 +145,8 @@ Daily generated text uses `.GeneratedText.Summary`,
|
||||
`.GeneratedText.Confidence`. Forecast discussion is a slice of paragraphs and
|
||||
should be rendered with `range`.
|
||||
|
||||
Daily uses the shared `daypart_forecast` and `precipitation_timing` partials.
|
||||
Daily uses the shared `alert_digest`, `daypart_forecast`, and
|
||||
`precipitation_timing` partials.
|
||||
|
||||
Daily uses template ID `daily`, generated-text schema ID `daily`, and prompt
|
||||
source `internal/reporttemplate/prompts/daily.generated_text.md`.
|
||||
@@ -179,7 +181,7 @@ should be rendered with `range`.
|
||||
|
||||
Today uses the `today_daypart_forecast` partial so elapsed or missing dayparts
|
||||
can be omitted while Daily and Tomorrow keep their fallback row. It also uses
|
||||
the shared `precipitation_timing` partial.
|
||||
the shared `alert_digest` and `precipitation_timing` partials.
|
||||
|
||||
Today uses template ID `today`, generated-text schema ID `today`, and prompt
|
||||
source `internal/reporttemplate/prompts/today.generated_text.md`.
|
||||
|
||||
@@ -19,6 +19,7 @@ var templates = map[string]string{
|
||||
}
|
||||
|
||||
var templatePartials = []string{
|
||||
"templates/partials/alert_digest.md.tmpl",
|
||||
"templates/partials/daypart_forecast.md.tmpl",
|
||||
"templates/partials/precipitation_timing.md.tmpl",
|
||||
"templates/partials/today_daypart_forecast.md.tmpl",
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestTemplateLookup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Template() error = %v", err)
|
||||
}
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Updated:**", "## Current Conditions", "## Hourly Forecast", "## Forecast Discussion"} {
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Updated:**", `{{ template "alert_digest" . }}`, "## Current Conditions", "## Hourly Forecast", "## Forecast Discussion"} {
|
||||
if !strings.Contains(source, want) {
|
||||
t.Fatalf("template missing %q:\n%s", want, source)
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func TestTomorrowTemplateLookup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Template() error = %v", err)
|
||||
}
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", `{{ template "daypart_forecast" . }}`, `{{ template "precipitation_timing" . }}`, "## Forecast Discussion"} {
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", `{{ template "alert_digest" . }}`, `{{ template "daypart_forecast" . }}`, `{{ template "precipitation_timing" . }}`, "## Forecast Discussion"} {
|
||||
if !strings.Contains(source, want) {
|
||||
t.Fatalf("template missing %q:\n%s", want, source)
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func TestDailyTemplateLookup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Template() error = %v", err)
|
||||
}
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", `{{ template "daypart_forecast" . }}`, `{{ template "precipitation_timing" . }}`, "## Forecast Discussion"} {
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", `{{ template "alert_digest" . }}`, `{{ template "daypart_forecast" . }}`, `{{ template "precipitation_timing" . }}`, "## Forecast Discussion"} {
|
||||
if !strings.Contains(source, want) {
|
||||
t.Fatalf("template missing %q:\n%s", want, source)
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func TestTodayTemplateLookup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Template() error = %v", err)
|
||||
}
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", "## Current Conditions", `{{ template "today_daypart_forecast" . }}`, `{{ template "precipitation_timing" . }}`, "## Forecast Discussion"} {
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", `{{ template "alert_digest" . }}`, "## Current Conditions", `{{ template "today_daypart_forecast" . }}`, `{{ template "precipitation_timing" . }}`, "## Forecast Discussion"} {
|
||||
if !strings.Contains(source, want) {
|
||||
t.Fatalf("template missing %q:\n%s", want, source)
|
||||
}
|
||||
@@ -285,8 +285,8 @@ func TestRenderHourly(t *testing.T) {
|
||||
}
|
||||
assertOrderedText(t, text, []string{
|
||||
"# Hourly Report",
|
||||
"## Current Conditions",
|
||||
"## Active Alerts",
|
||||
"## Current Conditions",
|
||||
"## Hourly Forecast",
|
||||
"## Precipitation Timing",
|
||||
"## Forecast Discussion",
|
||||
@@ -351,6 +351,9 @@ func TestRenderTomorrow(t *testing.T) {
|
||||
{PeriodBeginsHourLabel: "3:00 PM", PeriodEndsHourLabel: "6:00 PM", MaxPopPercent: intPtr(70), MaxPopHourLabel: "3:00 PM", ExpectationPhrase: "Expect showers."},
|
||||
},
|
||||
},
|
||||
AlertDigest: &testAlertDigest{
|
||||
Relevant: []testAlert{{Event: "Wind Advisory", Headline: "Wind Advisory until 8:00 PM", Severity: "Moderate"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -362,6 +365,8 @@ func TestRenderTomorrow(t *testing.T) {
|
||||
"**Forecast date:** Monday, June 15, 2026",
|
||||
"**Updated:** Sunday, June 14, 2026 at 9:14 AM",
|
||||
"Tomorrow starts dry before showers return later in the day.",
|
||||
"## Active Alerts",
|
||||
"- **Wind Advisory**: Wind Advisory until 8:00 PM",
|
||||
"- **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.",
|
||||
"- **Afternoon:** Sunny, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
||||
@@ -384,6 +389,8 @@ func TestRenderTomorrow(t *testing.T) {
|
||||
}
|
||||
assertOrderedText(t, text, []string{
|
||||
"# Monday's Weather",
|
||||
"Tomorrow starts dry before showers return later in the day.",
|
||||
"## Active Alerts",
|
||||
"## Daypart Forecast",
|
||||
"- **Overnight:**",
|
||||
"- **Morning:**",
|
||||
@@ -442,6 +449,9 @@ func TestRenderDaily(t *testing.T) {
|
||||
{PeriodBeginsHourLabel: "3:00 PM", PeriodEndsHourLabel: "6:00 PM", MaxPopPercent: intPtr(70), MaxPopHourLabel: "3:00 PM", ExpectationPhrase: "Expect showers."},
|
||||
},
|
||||
},
|
||||
AlertDigest: &testAlertDigest{
|
||||
Relevant: []testAlert{{Event: "Flood Watch", Headline: "Flood Watch until 6:00 PM", Severity: "Moderate"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -453,6 +463,8 @@ func TestRenderDaily(t *testing.T) {
|
||||
"**Forecast date:** Monday, June 15, 2026",
|
||||
"**Updated:** Sunday, June 14, 2026 at 9:14 AM",
|
||||
"The selected day starts dry before showers return later in the day.",
|
||||
"## Active Alerts",
|
||||
"- **Flood Watch**: Flood Watch until 6:00 PM",
|
||||
"- **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%.",
|
||||
"- **3:00 PM** to **6:00 PM**: Expect showers. The peak precipitation chance is 70% at 3:00 PM.",
|
||||
@@ -466,6 +478,8 @@ func TestRenderDaily(t *testing.T) {
|
||||
}
|
||||
assertOrderedText(t, text, []string{
|
||||
"# Monday's Weather",
|
||||
"The selected day starts dry before showers return later in the day.",
|
||||
"## Active Alerts",
|
||||
"## Daypart Forecast",
|
||||
"- **Morning:**",
|
||||
"- **Afternoon:**",
|
||||
@@ -535,6 +549,9 @@ func TestRenderToday(t *testing.T) {
|
||||
{PeriodBeginsHourLabel: "3:00 PM", PeriodEndsHourLabel: "6:00 PM", MaxPopPercent: intPtr(70), MaxPopHourLabel: "3:00 PM", ExpectationPhrase: "Expect showers."},
|
||||
},
|
||||
},
|
||||
AlertDigest: &testAlertDigest{
|
||||
Relevant: []testAlert{{Event: "Wind Advisory", Headline: "Wind Advisory until 8:00 PM", Severity: "Moderate"}},
|
||||
},
|
||||
TodayPlanning: &testTodayPlanning{
|
||||
MorningReadiness: []string{"Morning weather looks routine."},
|
||||
LateDayChangeWatch: []string{"Watch late-day shower timing."},
|
||||
@@ -550,6 +567,8 @@ func TestRenderToday(t *testing.T) {
|
||||
"**Forecast date:** Monday, June 15, 2026",
|
||||
"**Updated:** Monday, June 15, 2026 at 7:14 AM",
|
||||
"Today starts dry before showers return later in the day.",
|
||||
"## Active Alerts",
|
||||
"- **Wind Advisory**: Wind Advisory until 8:00 PM",
|
||||
"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.",
|
||||
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
||||
@@ -564,6 +583,8 @@ func TestRenderToday(t *testing.T) {
|
||||
}
|
||||
assertOrderedText(t, text, []string{
|
||||
"# Today's Weather",
|
||||
"Today starts dry before showers return later in the day.",
|
||||
"## Active Alerts",
|
||||
"## Current Conditions",
|
||||
"## Daypart Forecast",
|
||||
"- **Morning:**",
|
||||
@@ -652,6 +673,12 @@ func TestRenderDaypartTemplatesUseRichHelperFields(t *testing.T) {
|
||||
t.Fatalf("rendered template missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
if strings.Contains(text, "## Active Alerts") {
|
||||
t.Fatalf("rendered template includes empty Active Alerts section:\n%s", text)
|
||||
}
|
||||
if strings.Contains(text, "\n\n\n") {
|
||||
t.Fatalf("rendered template includes excess blank lines:\n%s", text)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -903,18 +930,21 @@ type testModules struct {
|
||||
type testTomorrowModules struct {
|
||||
Dayparts []testTomorrowDaypart
|
||||
PrecipTiming *testPrecipTiming
|
||||
AlertDigest *testAlertDigest
|
||||
}
|
||||
|
||||
type testTodayModules struct {
|
||||
CurrentConditions *testCurrentConditions
|
||||
Dayparts []testTomorrowDaypart
|
||||
PrecipTiming *testPrecipTiming
|
||||
AlertDigest *testAlertDigest
|
||||
TodayPlanning *testTodayPlanning
|
||||
}
|
||||
|
||||
type testDailyModules struct {
|
||||
Dayparts []testDailyDaypart
|
||||
PrecipTiming *testPrecipTiming
|
||||
AlertDigest *testAlertDigest
|
||||
}
|
||||
|
||||
type testTodayPlanning struct {
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
{{ .GeneratedText.Summary }}
|
||||
|
||||
{{ template "daypart_forecast" . }}
|
||||
|
||||
{{ template "precipitation_timing" . }}
|
||||
{{ template "alert_digest" . }}{{ template "daypart_forecast" . }}{{ if and .Modules.PrecipTiming .Modules.PrecipTiming.PrecipitationWindows }}{{ template "precipitation_timing" . }}{{ else }}{{ end -}}
|
||||
## Forecast Discussion
|
||||
{{ range .GeneratedText.ForecastDiscussion }}
|
||||
{{ . }}
|
||||
|
||||
@@ -4,16 +4,10 @@
|
||||
|
||||
{{ .GeneratedText.Summary }}
|
||||
|
||||
## Current Conditions
|
||||
{{ template "alert_digest" . }}## Current Conditions
|
||||
|
||||
{{ with .Modules.CurrentConditions }}{{ with .TemperatureF }}Currently, it is {{ . }}°F{{ with $.Modules.CurrentConditions.ConditionTextLower }} and {{ . }}{{ end }}.{{ else }}{{ with .ConditionText }}Currently, it is {{ . }}.{{ else }}Current conditions are unavailable.{{ end }}{{ end }}{{ with .ApparentTemperatureF }} It feels like {{ . }}°F{{ with $.Modules.CurrentConditions.RelativeHumidityPercent }}, with a relative humidity of {{ . }}%{{ end }}{{ with $.Modules.CurrentConditions.WindDirectionText }} and winds from the {{ . }}{{ with $.Modules.CurrentConditions.WindSpeedMph }} at {{ . }} mph{{ end }}{{ end }}.{{ else }}{{ with .RelativeHumidityPercent }} Relative humidity is {{ . }}%.{{ end }}{{ with .WindDirectionText }} Winds are from the {{ . }}{{ with $.Modules.CurrentConditions.WindSpeedMph }} at {{ . }} mph{{ end }}.{{ end }}{{ end }}{{ else }}Current conditions are unavailable.{{ end }}
|
||||
|
||||
{{ if and .Modules.AlertDigest .Modules.AlertDigest.Relevant }}## Active Alerts
|
||||
{{ range .Modules.AlertDigest.Relevant -}}
|
||||
- **{{ if .Event }}{{ .Event }}{{ else }}{{ .Headline }}{{ end }}**{{ with .Headline }}: {{ . }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ end -}}
|
||||
## 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 }}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{{ define "alert_digest" }}{{ if and .Modules.AlertDigest .Modules.AlertDigest.Relevant }}## Active Alerts
|
||||
{{ range .Modules.AlertDigest.Relevant -}}
|
||||
- **{{ if .Event }}{{ .Event }}{{ else }}{{ .Headline }}{{ end }}**{{ with .Headline }}: {{ . }}{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}{{ end }}
|
||||
@@ -5,14 +5,12 @@
|
||||
|
||||
{{ .GeneratedText.Summary }}
|
||||
|
||||
{{ with .Modules.CurrentConditions }}
|
||||
{{ template "alert_digest" . }}{{ with .Modules.CurrentConditions }}
|
||||
## Current Conditions
|
||||
{{ with .TemperatureF }}Currently, it is {{ . }}°F{{ with $.Modules.CurrentConditions.ConditionTextLower }} and {{ . }}{{ end }}.{{ else }}{{ with .ConditionText }}Currently, it is {{ . }}.{{ else }}Current conditions are unavailable.{{ end }}{{ end }}{{ with .ApparentTemperatureF }} It feels like {{ . }}°F{{ with $.Modules.CurrentConditions.RelativeHumidityPercent }}, with a relative humidity of {{ . }}%{{ end }}{{ with $.Modules.CurrentConditions.WindDirectionText }} and winds from the {{ . }}{{ with $.Modules.CurrentConditions.WindSpeedMph }} at {{ . }} mph{{ end }}{{ end }}.{{ else }}{{ with .RelativeHumidityPercent }} Relative humidity is {{ . }}%.{{ end }}{{ with .WindDirectionText }} Winds are from the {{ . }}{{ with $.Modules.CurrentConditions.WindSpeedMph }} at {{ . }} mph{{ end }}.{{ end }}{{ end }}
|
||||
|
||||
{{ end }}
|
||||
{{ template "today_daypart_forecast" . }}
|
||||
|
||||
{{ template "precipitation_timing" . }}
|
||||
{{ end -}}
|
||||
{{ template "today_daypart_forecast" . }}{{ if and .Modules.PrecipTiming .Modules.PrecipTiming.PrecipitationWindows }}{{ template "precipitation_timing" . }}{{ else }}{{ end -}}
|
||||
## Forecast Discussion
|
||||
{{ range .GeneratedText.ForecastDiscussion }}
|
||||
{{ . }}
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
{{ .GeneratedText.Summary }}
|
||||
|
||||
{{ template "daypart_forecast" . }}
|
||||
|
||||
{{ template "precipitation_timing" . }}
|
||||
{{ template "alert_digest" . }}{{ template "daypart_forecast" . }}{{ if and .Modules.PrecipTiming .Modules.PrecipTiming.PrecipitationWindows }}{{ template "precipitation_timing" . }}{{ else }}{{ end -}}
|
||||
## Forecast Discussion
|
||||
{{ range .GeneratedText.ForecastDiscussion }}
|
||||
{{ . }}
|
||||
|
||||
Reference in New Issue
Block a user