17 KiB
Report Templates
Purpose
This guide describes the implemented Markdown report template surface for
weatherreporter. It is for maintainers editing embedded report templates,
especially generated-text-template reports.
Templates are Go text/template files. The current implemented templates are:
internal/reporttemplate/templates/today.md.tmplinternal/reporttemplate/templates/tomorrow.md.tmplinternal/reporttemplate/templates/hourly.md.tmpl
Templates are rendered from structured contexts such as TodayRenderContext,
TomorrowRenderContext, and HourlyRenderContext. Weather data collection,
derivation, module execution, generated text validation, and artifact paths are
handled before template rendering.
Editing Rules
- Use Go
text/templatesyntax. - Keep templates focused on Markdown layout, headings, ordering, and simple conditional display.
- Do not put weather derivation, source selection, or path construction logic in templates.
- Missing template keys are errors. A misspelled variable will fail rendering.
- No custom template functions are currently registered.
- Optional module stanzas are pointers and should be guarded with
{{ with .Modules.WeatherStory }}...{{ end }}. - Slices can be rendered with
{{ range .Items }}...{{ else }}...{{ end }}.
Hourly Context
The hourly template receives five top-level values:
| Variable | Type | Description |
|---|---|---|
.Report |
HourlyReportContext | Display metadata and friendly labels for the rendered report. |
.GeneratedText |
Hourly | Structured text returned by Scriptorium. |
.Modules |
HourlyTemplateModules | Preferred deterministic template surface, keyed by module purpose. |
.Collected |
facts.CollectedFacts | Normalized upstream facts for advanced template use. |
.Derived |
facts.DerivedFacts | Shared derived facts for advanced template use. |
Prefer .Modules for normal template edits. .Collected and .Derived are
available when a template needs lower-level facts, but templates should still
avoid nontrivial derivation.
Report
| Variable | Type | Description |
|---|---|---|
.Report.Title |
string | Display title. Currently Hourly Report. |
.Report.LocationName |
string | Prompt/report location label, such as Brentwood, MO. |
.Report.GeneratedAt |
time.Time | Canonical generation timestamp. |
.Report.GeneratedAtLabel |
string | Friendly local generation time label. |
.Report.ValidPeriod |
timeutil.Period | Canonical valid period. |
.Report.ValidPeriodLabel |
string | Friendly local valid period label, such as 2026-05-29 at 8:30 AM to 2026-05-29 at 2:30 PM. |
.Report.Timezone |
string | Effective report timezone. |
GeneratedText
These fields are written by Scriptorium as structured JSON, validated by weatherreporter, and then inserted into the render context.
| Variable | Type | Description |
|---|---|---|
.GeneratedText.Summary |
string | Required short prose summary. |
.GeneratedText.ForecastDiscussion |
string | Required prose for the Forecast Discussion section. |
.GeneratedText.PrecipitationTiming |
string | Optional prose rendered after deterministic precipitation windows. |
.GeneratedText.Confidence |
string | Optional confidence or uncertainty note. Empty when omitted by the LLM; not rendered by the current hourly template. |
Example:
{{ .GeneratedText.Summary }}
## Forecast Discussion
{{ .GeneratedText.ForecastDiscussion }}
Tomorrow Context
The Tomorrow template receives five top-level values:
| Variable | Type | Description |
|---|---|---|
.Report |
TomorrowReportContext | Display metadata and friendly labels for the rendered report. |
.GeneratedText |
Tomorrow | Structured text returned by Scriptorium. |
.Modules |
TomorrowTemplateModules | Preferred deterministic template surface, keyed by module purpose. |
.Collected |
facts.CollectedFacts | Normalized upstream facts for advanced template use. |
.Derived |
facts.DerivedFacts | Shared derived facts for advanced template use. |
Tomorrow report metadata includes .Report.Title, .Report.ForecastDate,
.Report.ForecastDateLabel, .Report.ForecastDayName,
.Report.GeneratedAt, .Report.GeneratedAtLabel, .Report.ValidPeriod, and
.Report.Timezone.
Tomorrow generated text uses the same .GeneratedText.Summary,
.GeneratedText.PrecipitationTiming, and .GeneratedText.Confidence fields as
Hourly. .GeneratedText.ForecastDiscussion is a slice of paragraphs and should
be rendered with range.
Tomorrow modules include the Hourly module fields plus:
| Variable | Type | Description |
|---|---|---|
.Modules.DerivedDailySummary |
*briefing.DerivedDailySummaryModule | Daily summary facts for the forecast date. |
.Modules.DerivedDaypartSummaries |
*map[string]briefing.DerivedDaypartSummaryModule | Raw daypart summary map, when direct keyed access is needed. |
.Modules.Dayparts |
[]generatedtext.TomorrowDaypartContext | Ordered daypart summaries for deterministic template rendering. |
.Modules.TomorrowPlanning |
*briefing.TomorrowPlanningModule | Planning facts for the next local civil day. |
Prefer .Modules.Dayparts over ranging through
.Modules.DerivedDaypartSummaries; it follows configured daypart order and
falls back to sorted keys for any unmatched entries.
Today Context
The Today template receives the same five top-level values as Tomorrow, using
TodayReportContext, Today, and TodayTemplateModules.
Today report metadata includes .Report.Title, .Report.ForecastDate,
.Report.ForecastDateLabel, .Report.ForecastDayName,
.Report.GeneratedAt, .Report.GeneratedAtLabel, .Report.ValidPeriod, and
.Report.Timezone.
Today generated text uses .GeneratedText.Summary,
.GeneratedText.ForecastDiscussion, .GeneratedText.PrecipitationTiming, and
.GeneratedText.Confidence. Forecast discussion is a slice of paragraphs and
should be rendered with range.
Today uses template ID today, generated-text schema ID today, and prompt
source internal/reporttemplate/prompts/today.generated_text.md.
Today modules include the Hourly module fields plus:
| Variable | Type | Description |
|---|---|---|
.Modules.DerivedDailySummary |
*briefing.DerivedDailySummaryModule | Daily summary facts for the forecast date. |
.Modules.DerivedDaypartSummaries |
*map[string]briefing.DerivedDaypartSummaryModule | Raw daypart summary map, when direct keyed access is needed. |
.Modules.Dayparts |
[]generatedtext.TodayDaypartContext | Ordered daypart summaries for deterministic template rendering. |
.Modules.TodayPlanning |
*briefing.TodayPlanningModule | Planning facts for the current local civil day. |
Prefer .Modules.Dayparts over ranging through
.Modules.DerivedDaypartSummaries; it follows configured daypart order and
falls back to sorted keys for any unmatched entries.
Modules
.Modules exposes typed outputs from the same module pipeline used for the
prompt data package. Module fields are pointers because missing-data policy may
omit a stanza.
| Variable | Type | Description |
|---|---|---|
.Modules.Metadata |
*briefing.MetadataModule | Report metadata module output, when present. |
.Modules.CurrentConditions |
*briefing.CurrentConditionsModule | Current conditions from /conditions/current. |
.Modules.HourlyForecast |
*briefing.HourlyForecastModule | Hourly forecast periods overlapping the report valid period. |
.Modules.PrecipTiming |
*briefing.PrecipTimingModule | Derived precipitation timing facts and threshold windows. |
.Modules.AlertDigest |
*briefing.AlertDigestModule | Active alert status and relevant alert overlaps. |
.Modules.SPCConvectiveOutlooks |
*briefing.SPCConvectiveOutlooksModule | SPC outlooks that overlap the report valid period. |
.Modules.AreaForecastDiscussion |
*briefing.AreaForecastDiscussionModule | AFD key messages and configured discussion sections. |
.Modules.SPCConvectiveDiscussion |
*briefing.SPCConvectiveDiscussionModule | SPC discussions retained for qualifying overlapping categorical risk days. |
.Modules.WeatherStory |
*briefing.WeatherStoryModule | Latest NWS weather story, when available. |
Current Conditions
Common fields:
| Variable | Type | Description |
|---|---|---|
.Modules.CurrentConditions.ConditionText |
string | Current condition text. |
.Modules.CurrentConditions.ConditionTextLower |
string | Lower-case current condition text for inline sentences. |
.Modules.CurrentConditions.TemperatureF |
*int | Rounded current temperature. |
.Modules.CurrentConditions.ApparentTemperatureF |
*int | Rounded apparent temperature. |
.Modules.CurrentConditions.RelativeHumidityPercent |
*int | Rounded relative humidity. |
.Modules.CurrentConditions.WindDirection |
string | 16-point compass wind direction. |
.Modules.CurrentConditions.WindDirectionText |
string | Lower-case full wind direction text, such as northwest. |
.Modules.CurrentConditions.WindSpeedMph |
*int | Rounded wind speed. |
Example:
{{ with .Modules.CurrentConditions }}
{{ .ConditionText }}{{ with .TemperatureF }}; {{ . }} F{{ end }}{{ with .WindDirection }}; wind {{ . }}{{ end }}{{ with .WindSpeedMph }} {{ . }} mph{{ end }}
{{ else }}
No current conditions available.
{{ end }}
Hourly Forecast
Common period fields:
| Variable | Type | Description |
|---|---|---|
.Modules.HourlyForecast.Periods |
[]briefing.HourlyForecastPeriod | Ordered periods for the hourly report valid period. |
.Modules.HourlyForecast.Periods[].HourLabel |
string | Friendly hour label such as 4:00 PM. |
.Modules.HourlyForecast.Periods[].PeriodBegins |
string | Friendly local period start label. |
.Modules.HourlyForecast.Periods[].PeriodEnds |
string | Friendly local period end label. |
.Modules.HourlyForecast.Periods[].Name |
string | Source period name. |
.Modules.HourlyForecast.Periods[].TextDescription |
string | Hourly forecast text. |
.Modules.HourlyForecast.Periods[].TextDescriptionLower |
string | Lower-case hourly forecast text for inline sentences. |
.Modules.HourlyForecast.Periods[].TemperatureF |
*float64 | Forecast temperature. |
.Modules.HourlyForecast.Periods[].ProbabilityOfPrecipitationPercent |
*float64 | Forecast precipitation probability. |
.Modules.HourlyForecast.Periods[].MentionPrecipitation |
bool | True when precipitation probability meets the hourly mention threshold. |
.Modules.HourlyForecast.Periods[].WindDirection |
string | 16-point compass wind direction. |
.Modules.HourlyForecast.Periods[].WindSpeedMph |
*float64 | Wind speed. |
.Modules.HourlyForecast.Periods[].WindGustMph |
*float64 | Wind gust. |
Example:
{{ with .Modules.HourlyForecast }}{{ range .Periods }}
- **{{ .HourLabel }}:**{{ with .TemperatureF }} {{ . }}°F{{ end }} and {{ .TextDescriptionLower }}.{{ if .MentionPrecipitation }}{{ with .ProbabilityOfPrecipitationPercent }} Probability of precipitation is {{ . }}%.{{ end }}{{ end }}
{{ else }}
- No hourly forecast rows available.
{{ end }}{{ end }}
Precipitation Timing
Common fields:
| Variable | Type | Description |
|---|---|---|
.Modules.PrecipTiming.MaxPopPercent |
*int | Highest hourly precipitation probability in the valid period. |
.Modules.PrecipTiming.MaxPopTime |
string | Friendly local time for the highest hourly precipitation probability. |
.Modules.PrecipTiming.ProbabilityThreshold |
float64 | Threshold used to define precipitation windows. |
.Modules.PrecipTiming.PrecipitationWindows |
[]briefing.PrecipitationWindowModule | One or more threshold precipitation windows. |
.Modules.PrecipTiming.PrecipitationWindows[].PeriodBegins |
string | Friendly local window start. |
.Modules.PrecipTiming.PrecipitationWindows[].PeriodBeginsHourLabel |
string | Friendly window start hour, such as 4:00 PM. |
.Modules.PrecipTiming.PrecipitationWindows[].PeriodEnds |
string | Friendly local window end; omitted for open windows. |
.Modules.PrecipTiming.PrecipitationWindows[].PeriodEndsHourLabel |
string | Friendly window end hour; omitted for open windows. |
.Modules.PrecipTiming.PrecipitationWindows[].MaxPopPercent |
*int | Highest precipitation probability inside the window. |
.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.ThunderMentioned |
bool | Whether thunder is mentioned in the forecast text. |
Alert Digest
| Variable | Type | Description |
|---|---|---|
.Modules.AlertDigest.Checked |
bool | Whether alert data was checked successfully. |
.Modules.AlertDigest.ActiveCount |
int | Active alert count from the source. |
.Modules.AlertDigest.RelevantCount |
int | Alert count overlapping the report period. |
.Modules.AlertDigest.Missing |
bool | True when alert data is unavailable. |
.Modules.AlertDigest.Relevant |
[]briefing.AlertSummary | Relevant alert summaries. |
.Modules.AlertDigest.Relevant[].Event |
string | Alert event name. |
.Modules.AlertDigest.Relevant[].Headline |
string | Alert headline. |
.Modules.AlertDigest.Relevant[].Severity |
string | Alert severity. |
SPC Outlooks And Discussion
| Variable | Type | Description |
|---|---|---|
.Modules.SPCConvectiveOutlooks.Checked |
bool | Whether SPC outlook data was checked successfully. |
.Modules.SPCConvectiveOutlooks.AsOf |
string | Friendly source as-of time. |
.Modules.SPCConvectiveOutlooks.IssuedAt |
string | Friendly source issue time. |
.Modules.SPCConvectiveOutlooks.Outlooks |
[]briefing.SPCConvectiveOutlookRecord | Overlapping outlook records. |
.Modules.SPCConvectiveOutlooks.Outlooks[].Day |
int | SPC day number. |
.Modules.SPCConvectiveOutlooks.Outlooks[].OutlookType |
string | Outlook type, such as categorical. |
.Modules.SPCConvectiveOutlooks.Outlooks[].Label |
string | Short outlook label. |
.Modules.SPCConvectiveOutlooks.Outlooks[].LabelText |
string | Human-readable outlook label. |
.Modules.SPCConvectiveOutlooks.Outlooks[].PeriodBegins |
string | Friendly outlook period start. |
.Modules.SPCConvectiveOutlooks.Outlooks[].PeriodEnds |
string | Friendly outlook period end. |
.Modules.SPCConvectiveOutlooks.Outlooks[].ImageURL |
string | Source image URL. |
.Modules.SPCConvectiveDiscussion.IncludedBecause |
string | Criterion used to include discussions. |
.Modules.SPCConvectiveDiscussion.Discussions |
[]briefing.SPCConvectiveDiscussionRecord | Retained discussion records. |
.Modules.SPCConvectiveDiscussion.Discussions[].Headline |
string | Discussion headline. |
.Modules.SPCConvectiveDiscussion.Discussions[].Summary |
string | Discussion summary. |
.Modules.SPCConvectiveDiscussion.Discussions[].Discussion |
string | Full discussion text. |
Area Forecast Discussion
| Variable | Type | Description |
|---|---|---|
.Modules.AreaForecastDiscussion.Product |
string | Source product identifier. |
.Modules.AreaForecastDiscussion.KeyMessages |
[]string | AFD key messages. |
.Modules.AreaForecastDiscussion.ShortTerm |
string | AFD short-term section text. |
.Modules.AreaForecastDiscussion.LongTerm |
string | AFD long-term section text. |
Weather Story
| Variable | Type | Description |
|---|---|---|
.Modules.WeatherStory.Available |
bool | True when a story is available. |
.Modules.WeatherStory.OfficeID |
string | Source office ID. |
.Modules.WeatherStory.PeriodBegins |
string | Friendly story period start. |
.Modules.WeatherStory.PeriodEnds |
string | Friendly story period end. |
.Modules.WeatherStory.UpdatedAt |
*time.Time | Canonical update timestamp. |
.Modules.WeatherStory.Title |
string | Story title. |
.Modules.WeatherStory.Description |
string | Story description. |
.Modules.WeatherStory.AltText |
string | Story image alt text. |
.Modules.WeatherStory.Priority |
bool | Source priority flag. |
.Modules.WeatherStory.Order |
int | Source order. |
.Modules.WeatherStory.DownloadURL |
string | Source download URL. |
Collected And Derived Facts
The template also receives the full facts.CollectedFacts and
facts.DerivedFacts structs:
.Collectedcontains normalized source data and provenance from upstream Weather API fetches..Derivedcontains shared slices and calculations used across modules, such as valid-period hourly periods, precipitation timing, alert overlaps, and SPC filtering inputs.
These values are intentionally lower-level than .Modules. Use them when a
template needs a specific field that is not exposed by a module, but keep
calculation-heavy changes in Go.
Validation
After editing a template, run:
go test ./internal/reporttemplate ./internal/generatedtext ./internal/app
For a full check, run:
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
Template render tests currently exercise the hourly template through
internal/generatedtext/render_context_test.go and
internal/reporttemplate/reporttemplate_test.go.