Update the shared precipitation timing template

This commit is contained in:
2026-06-16 19:20:26 -05:00
parent f9d6d42b1b
commit b36e198bfe
5 changed files with 51 additions and 16 deletions

View File

@@ -76,9 +76,9 @@ 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, and Tomorrow 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 `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.
## Schema Contract

View File

@@ -18,7 +18,7 @@ Shared named partials live under `internal/reporttemplate/templates/partials/`:
- `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, and Tomorrow
- `precipitation_timing.md.tmpl`, used by Daily, Today, Tomorrow, and Hourly
Templates are rendered from structured contexts such as `DailyRenderContext`,
`TodayRenderContext`, `TomorrowRenderContext`, and `HourlyRenderContext`.
@@ -294,7 +294,7 @@ Common fields:
| `.Modules.PrecipTiming.PrecipitationWindows[].MaxPopTime` | string | Friendly local time for the window maximum. |
| `.Modules.PrecipTiming.PrecipitationWindows[].MaxPopHourLabel` | string | Friendly hour label for the window maximum. |
| `.Modules.PrecipTiming.PrecipitationWindows[].PrecipitationType` | string | Conservatively inferred precipitation type, such as `showers and thunderstorms`. |
| `.Modules.PrecipTiming.PrecipitationWindows[].ExpectationPhrase` | string | Probability-based sentence used by day-style templates. |
| `.Modules.PrecipTiming.PrecipitationWindows[].ExpectationPhrase` | string | Probability-based sentence used by precipitation timing templates. |
| `.Modules.PrecipTiming.ThunderMentioned` | bool | Whether thunder is mentioned in the forecast text. |
### Daypart Summaries

View File

@@ -236,7 +236,7 @@ func TestRenderHourly(t *testing.T) {
MaxPopPercent: intPtr(70),
MaxPopTime: "10 AM",
PrecipitationWindows: []testPrecipWindow{
{PeriodBegins: "10 AM", PeriodBeginsHourLabel: "10:00 AM", PeriodEnds: "12 PM", PeriodEndsHourLabel: "12:00 PM", MaxPopPercent: intPtr(70), MaxPopTime: "10 AM", MaxPopHourLabel: "10:00 AM"},
{PeriodBegins: "10 AM", PeriodBeginsHourLabel: "10:00 AM", PeriodEnds: "12 PM", PeriodEndsHourLabel: "12:00 PM", MaxPopPercent: intPtr(70), MaxPopTime: "10 AM", MaxPopHourLabel: "10:00 AM", ExpectationPhrase: "Expect showers."},
},
},
AlertDigest: &testAlertDigest{
@@ -272,7 +272,7 @@ func TestRenderHourly(t *testing.T) {
"- **Flood Watch**: Flood Watch until 2:30 PM",
"- **9:00 AM:** 74°F and cloudy.",
"- **10:00 AM:** 75°F and showers. Probability of precipitation is 70%.",
"- **10:00 AM** to **12:00 PM**: Precipitation is expected during this period. 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.",
"A cold front is moving into the region.",
"A front will keep the region unsettled.",
} {
@@ -732,6 +732,48 @@ func TestRenderHourlyOmitsConditionalSectionsForClearWeather(t *testing.T) {
}
}
func TestRenderHourlyUsesSharedOpenEndedPrecipitationTiming(t *testing.T) {
rendered, err := Render("hourly", testRenderContext{
Report: testReportContext{
Title: "Hourly Report",
GeneratedAtLabel: "Saturday, June 14, 2026 at 9:14 AM",
},
GeneratedText: testGeneratedText{
Summary: "Rain chances increase through midday.",
ForecastDiscussion: "Showers may continue beyond the report period.",
PrecipitationTiming: "Plan for wet roads through the end of the period.",
},
Modules: testModules{
CurrentConditions: &testCurrentConditions{},
HourlyForecast: &testHourlyForecast{},
AlertDigest: &testAlertDigest{},
PrecipTiming: &testPrecipTiming{
PrecipitationWindows: []testPrecipWindow{
{
PeriodBeginsHourLabel: "8:00 AM",
MaxPopPercent: intPtr(60),
MaxPopHourLabel: "10:00 AM",
ExpectationPhrase: "Showers likely.",
},
},
},
},
})
if err != nil {
t.Fatalf("Render() error = %v", err)
}
text := string(rendered)
for _, want := range []string{
"## Precipitation Timing",
"- **Starting at 8:00 AM**: Showers likely. The peak precipitation chance is 60% at 10:00 AM.",
"Plan for wet roads through the end of the period.",
} {
if !strings.Contains(text, want) {
t.Fatalf("rendered template missing %q:\n%s", want, text)
}
}
}
func TestUnknownAssetsReturnActionableErrors(t *testing.T) {
if _, err := Template("missing"); err == nil || !strings.Contains(err.Error(), `unknown report template "missing"`) {
t.Fatalf("Template() error = %v, want unknown template", err)

View File

@@ -26,14 +26,7 @@ Current conditions are unavailable.
- No hourly forecast rows are available.
{{ end }}
{{ with .Modules.PrecipTiming }}{{ with .PrecipitationWindows }}
## Precipitation Timing
{{ range . }}{{ $window := . }}
- **{{ if .PeriodBeginsHourLabel }}{{ .PeriodBeginsHourLabel }}{{ else }}{{ .PeriodBegins }}{{ end }}**{{ with .PeriodEndsHourLabel }} to **{{ . }}**{{ else }}{{ with .PeriodEnds }} to **{{ . }}**{{ end }}{{ end }}: Precipitation is expected during this period.{{ with .MaxPopPercent }} The peak precipitation chance is {{ . }}%{{ with $window.MaxPopHourLabel }} at {{ . }}{{ else }}{{ with $window.MaxPopTime }} at {{ . }}{{ end }}{{ end }}.{{ end }}
{{ end }}{{ with $.GeneratedText.PrecipitationTiming }}
{{ . }}
{{ end }}
{{ end }}{{ end }}
{{ template "precipitation_timing" . }}
## Forecast Discussion
{{ .GeneratedText.ForecastDiscussion }}

View File

@@ -1,7 +1,7 @@
{{ define "precipitation_timing" }}{{ with .Modules.PrecipTiming }}{{ with .PrecipitationWindows }}
## Precipitation Timing
{{ range . }}{{ $window := . }}
- **{{ if .PeriodBeginsHourLabel }}{{ .PeriodBeginsHourLabel }}{{ else }}{{ .PeriodBegins }}{{ end }}**{{ with .PeriodEndsHourLabel }} to **{{ . }}**{{ else }}{{ with .PeriodEnds }} to **{{ . }}**{{ end }}{{ end }}: {{ with .ExpectationPhrase }}{{ . }}{{ else }}Chance of precipitation.{{ end }}{{ with .MaxPopPercent }} The peak precipitation chance is {{ . }}%{{ with $window.MaxPopHourLabel }} at {{ . }}{{ else }}{{ with $window.MaxPopTime }} at {{ . }}{{ end }}{{ end }}.{{ end }}
- **{{ if or .PeriodEndsHourLabel .PeriodEnds }}{{ if .PeriodBeginsHourLabel }}{{ .PeriodBeginsHourLabel }}{{ else }}{{ .PeriodBegins }}{{ end }}{{ else }}Starting at {{ if .PeriodBeginsHourLabel }}{{ .PeriodBeginsHourLabel }}{{ else }}{{ .PeriodBegins }}{{ end }}{{ end }}**{{ with .PeriodEndsHourLabel }} to **{{ . }}**{{ else }}{{ with .PeriodEnds }} to **{{ . }}**{{ end }}{{ end }}: {{ with .ExpectationPhrase }}{{ . }}{{ else }}Chance of precipitation.{{ end }}{{ with .MaxPopPercent }} The peak precipitation chance is {{ . }}%{{ with $window.MaxPopHourLabel }} at {{ . }}{{ else }}{{ with $window.MaxPopTime }} at {{ . }}{{ end }}{{ end }}.{{ end }}
{{ end }}{{ with $.GeneratedText.PrecipitationTiming }}
{{ . }}
{{ end }}