From 154d31c3e823b98a94164e296299f52ee277c56e Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 31 Jul 2026 01:07:32 +0000 Subject: [PATCH] Refresh report template guide --- docs/templates.md | 523 ++++++++++++---------------------------------- 1 file changed, 128 insertions(+), 395 deletions(-) diff --git a/docs/templates.md b/docs/templates.md index 0ff379a..8bee08d 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -1,446 +1,179 @@ # Report Templates -## Purpose +This guide is for maintainers editing Weatherreporter's embedded Markdown +templates. Templates format already validated report inputs; they do not select +sources, derive weather facts, or validate generated prose. For those details, +see [Generated Text internals](internal/generatedtext.md) and [Report Template +internals](internal/reporttemplate.md). -This guide describes the implemented Markdown report template surface for -`weatherreporter`. It is for maintainers editing embedded report templates, -especially generated-text-template reports. +## Template Assets -Templates are Go `text/template` files. The implemented top-level templates -are: +Only the generated-text reports use repository-native Markdown templates. +Each report has one matching template ID, generated-text schema ID, and prompt +source: -- `internal/reporttemplate/templates/daily.md.tmpl` -- `internal/reporttemplate/templates/today.md.tmpl` -- `internal/reporttemplate/templates/tomorrow.md.tmpl` -- `internal/reporttemplate/templates/hourly.md.tmpl` +| Report | Template | Schema | Prompt ID and source | +| --- | --- | --- | --- | +| Daily | `templates/daily.md.tmpl` (`daily`) | `daily` | `weather.daily_generated_text`; `prompts/daily.generated_text.md` | +| Today | `templates/today.md.tmpl` (`today`) | `today` | `weather.today_generated_text`; `prompts/today.generated_text.md` | +| Tomorrow | `templates/tomorrow.md.tmpl` (`tomorrow`) | `tomorrow` | `weather.tomorrow_generated_text`; `prompts/tomorrow.generated_text.md` | +| Hourly | `templates/hourly.md.tmpl` (`hourly`) | `hourly` | `weather.hourly_generated_text`; `prompts/hourly.generated_text.md` | -Shared named partials live under `internal/reporttemplate/templates/partials/`: +The matching schema files are under `internal/reporttemplate/schemas/`. The +generated-text catalog pairs each schema ID with its template ID; keep the +matching report prompt source aligned with that pair. -- `alert_digest.md.tmpl`, used by Daily, Today, Tomorrow, and Hourly for the - combined Alerts and Risk Products section -- `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 +Shared partials are under `internal/reporttemplate/templates/partials/`: -Templates are rendered from structured contexts such as `DailyRenderContext`, -`TodayRenderContext`, `TomorrowRenderContext`, and `HourlyRenderContext`. -Weather data collection, derivation, module execution, generated text -validation, and artifact paths are handled before template rendering. +| Partial | Used by | +| --- | --- | +| `alert_digest.md.tmpl` | Daily, Today, Tomorrow, and Hourly | +| `precipitation_timing.md.tmpl` | Daily, Today, Tomorrow, and Hourly | +| `daypart_forecast.md.tmpl` | Daily and Tomorrow | +| `today_daypart_forecast.md.tmpl` | Today | + +All shared partials are parsed whenever any top-level template is rendered. A +syntax error in a partial can therefore prevent every generated-text report +from rendering. ## Editing Rules -- Use Go `text/template` syntax. -- 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 registered. -- Named partials are invoked with `{{ template "name" . }}`. Pass the current - render context (`.`) unless the partial is intentionally designed for a - narrower value. -- Optional module stanzas are pointers and should be guarded with - `{{ with .Modules.WeatherStory }}...{{ end }}`. -- Slices can be rendered with `{{ range .Items }}...{{ else }}...{{ end }}`. +- Use Go `text/template` syntax and keep changes to Markdown structure, + ordering, and display conditions. +- Templates use `missingkey=error`; reference only documented fields and guard + optional module pointers with `with` or `if`. +- Prefer `.Modules` for deterministic display values. Do not add weather + calculations, source selection, or prompt-input shaping to a template. +- Keep generated prose in `.GeneratedText`; do not restate deterministic facts + in generated prose merely to compensate for a template change. +- When changing the generated-prose contract, update the matching prompt, + schema, validator, render context, and template together. The validation and + catalog rules are owned by [Generated Text internals](internal/generatedtext.md). +- Use `.Modules.Dayparts` for ordered daypart output. Do not range over + `.Modules.DerivedDaypartSummaries`, which is a map. -## 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: - -```gotemplate -{{ .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 uses the shared `alert_digest`, `daypart_forecast`, and -`precipitation_timing` partials. - -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. - -## Daily Context - -The Daily template receives the same five top-level values as Tomorrow, using -`DailyReportContext`, `Daily`, and `DailyTemplateModules`. - -Daily report metadata includes `.Report.Title`, `.Report.ForecastDate`, -`.Report.ForecastDateLabel`, `.Report.ForecastDayName`, -`.Report.GeneratedAt`, `.Report.GeneratedAtLabel`, `.Report.ValidPeriod`, and -`.Report.Timezone`. - -Daily 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`. - -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`. - -Daily 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.DailyDaypartContext | Ordered daypart summaries for deterministic template rendering. | -| `.Modules.DailyPlanning` | *briefing.DailyPlanningModule | Planning facts for the selected 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 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 `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`. - -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. - -Templates render from rich module values, not from the curated YAML data -package. Some fields documented below are deterministic wording helpers for -Markdown templates and are intentionally omitted from data packages passed to -Scriptorium. The data package is a prompt input, while the render context is the -template surface. - -| 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: +Minimal optional-value pattern: ```gotemplate {{ with .Modules.CurrentConditions }} -{{ .ConditionText }}{{ with .TemperatureF }}; {{ . }} F{{ end }}{{ with .WindDirection }}; wind {{ . }}{{ end }}{{ with .WindSpeedMph }} {{ . }} mph{{ end }} +Currently, it is {{ with .TemperatureF }}{{ . }}°F{{ end }}. {{ else }} -No current conditions available. +Current conditions are unavailable. {{ 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: +Minimal list pattern: ```gotemplate -{{ 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 }} +{{ range .GeneratedText.ForecastDiscussion }} +{{ . }} +{{ end }} ``` -### Precipitation Timing +## Registered Functions -Common fields: +Templates have these helpers in addition to Go template built-ins: -| Variable | Type | Description | +| Function | Accepts | Returns true when | | --- | --- | --- | -| `.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.PrecipitationWindows[].PrecipitationType` | string | Conservatively inferred precipitation type, such as `showers and thunderstorms`. | -| `.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. | +| `hasRelevantAlerts` | an alert-digest value or pointer | its `Relevant` slice is nonempty | +| `hasEnhancedOrHigherSPCRisk` | an SPC outlook value or pointer | its `RiskDigest` contains an Enhanced, Moderate, or High Risk entry | +| `isEnhancedOrHigherSPCRisk` | one SPC risk-digest entry | its `LabelText`, or fallback `RiskLabel`, is Enhanced, Moderate, or High Risk | -### Daypart Summaries +For example, the alert partial uses the first two functions to decide whether +to render the section: -Daily, Today, and Tomorrow templates should use `.Modules.Dayparts` for -ordered daypart rendering. Each item has `Key` and `Summary`; `Summary` is a -rich `briefing.DerivedDaypartSummaryModule`. +```gotemplate +{{ if hasRelevantAlerts .Modules.AlertDigest }} +## Alert Digest +{{ end }} +``` -The shared daypart partials render from these same `.Modules.Dayparts` values. -Edit `daypart_forecast.md.tmpl` for common Daily/Tomorrow wording, and edit -`today_daypart_forecast.md.tmpl` for Today-specific omission behavior. +## Render Context -Common rich daypart fields: +Every rendered template receives one typed context with these five top-level +fields: -| Variable | Type | Description | -| --- | --- | --- | -| `.Modules.Dayparts[].Summary.DisplayName` | string | Human-readable daypart label. | -| `.Modules.Dayparts[].Summary.PeriodBegins` | string | Friendly local daypart start. | -| `.Modules.Dayparts[].Summary.PeriodEnds` | string | Friendly local daypart end. | -| `.Modules.Dayparts[].Summary.TempRangeF` | string | Rounded temperature range or single temperature. | -| `.Modules.Dayparts[].Summary.TemperaturePhraseF` | string | Temperature phrase used for steady template wording. | -| `.Modules.Dayparts[].Summary.TemperatureTrend` | string | Trend category such as `rising`, `falling`, `peaking`, or `steady`. | -| `.Modules.Dayparts[].Summary.TemperatureStartPhraseF` | string | Starting temperature phrase for rising/falling wording. | -| `.Modules.Dayparts[].Summary.TemperatureEndPhraseF` | string | Ending temperature phrase for rising/falling wording. | -| `.Modules.Dayparts[].Summary.TemperaturePeakPhraseF` | string | Peak temperature phrase for peaking wording. | -| `.Modules.Dayparts[].Summary.TemperatureSteadyPhraseF` | string | Steady temperature phrase. | -| `.Modules.Dayparts[].Summary.MaxPopPercent` | *int | Highest precipitation probability in the daypart. | -| `.Modules.Dayparts[].Summary.MaxPopTime` | string | Friendly local time for the highest precipitation probability. | -| `.Modules.Dayparts[].Summary.MaxPopTimeLabel` | string | Clock-style label for deterministic precipitation timing text. | -| `.Modules.Dayparts[].Summary.MentionPrecipitation` | bool | True when precipitation probability should be mentioned by the template. | -| `.Modules.Dayparts[].Summary.DominantCondition` | string | Dominant condition text. | -| `.Modules.Dayparts[].Summary.DominantConditionLower` | string | Lower-case condition text for inline sentences. | -| `.Modules.Dayparts[].Summary.DominantConditionDisplay` | string | Display-case condition text for bullet starts. | -| `.Modules.Dayparts[].Summary.NotableConditions` | []string | Notable condition labels retained for the daypart. | +| Field | Purpose | +| --- | --- | +| `.Report` | Display labels and canonical report timing metadata. | +| `.GeneratedText` | Validated prose supplied by Scriptorium. | +| `.Modules` | Deterministic, typed values prepared for Markdown rendering. | +| `.Collected` | Normalized upstream facts for advanced use. | +| `.Derived` | Shared calculated facts for advanced use. | -Template-only daypart helpers such as `TemperaturePhraseF`, -`DominantConditionLower`, `DominantConditionDisplay`, and `MaxPopTimeLabel` -remain available here even though they are not serialized into data-package -YAML. +`.Collected` and `.Derived` are available for an exceptional display need, but +they are lower-level contracts. Keep reusable weather derivation in Go and use +the module surface for normal template work. -### Alert Digest +### Report Metadata -| 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. | -| `.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. | +All contexts provide `.Report.Title`, `.Report.GeneratedAt`, +`.Report.GeneratedAtLabel`, `.Report.ValidPeriod`, and `.Report.Timezone`. -### SPC Outlooks And Discussion +Hourly additionally provides `.Report.LocationName` and +`.Report.ValidPeriodLabel`. -| 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[].BackgroundDefinition` | *briefing.SPCOutlookBackgroundDefinition | Embedded background context for known SPC outlook products. | -| `.Modules.SPCConvectiveOutlooks.Outlooks[].BackgroundDefinition.PlainLanguage` | string | Plain-language outlook definition. | -| `.Modules.SPCConvectiveOutlooks.Outlooks[].BackgroundDefinition.OfficialDescription` | string | Official or source-aligned outlook definition. | -| `.Modules.SPCConvectiveOutlooks.Outlooks[].BackgroundDefinition.RelativeLevel` | string | Relative categorical risk level, when defined. | -| `.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.SPCConvectiveOutlooks.RiskDigest` | []briefing.SPCConvectiveOutlookDigest | Curated categorical outlooks for the shared Alerts and Risk Products section. | -| `.Modules.SPCConvectiveOutlooks.RiskDigest[].LabelText` | string | Human-readable outlook label. | -| `.Modules.SPCConvectiveOutlooks.RiskDigest[].RiskLabel` | string | Sentence-style risk label for report rendering. | -| `.Modules.SPCConvectiveOutlooks.RiskDigest[].PeriodBegins` | string | Friendly outlook period start. | -| `.Modules.SPCConvectiveOutlooks.RiskDigest[].PeriodEnds` | string | Friendly outlook period end. | -| `.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. | +Daily, Today, and Tomorrow additionally provide `.Report.ForecastDate`, +`.Report.ForecastDateLabel`, and `.Report.ForecastDayName`. Their valid-period +field remains canonical timing data; use the supplied display labels instead +of formatting timestamps in a template. -### Area Forecast Discussion +### Validated GeneratedText Prose -| 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. | +GeneratedText is prose returned by Scriptorium and validated before rendering. +It is not a source for deterministic weather facts. -### Weather Story +| Field | Hourly type | Daily, Today, and Tomorrow type | Notes | +| --- | --- | --- | --- | +| `.GeneratedText.Summary` | `string` | `string` | Required. | +| `.GeneratedText.ForecastDiscussion` | `string` | `[]string` | Required; range over the day-style paragraph slice. | +| `.GeneratedText.PrecipitationTiming` | `string` | `string` | Optional prose used by the precipitation partial when deterministic windows exist. | +| `.GeneratedText.Confidence` | `string` | `string` | Optional validated prose; the current templates do not render it. | -| 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. | +The JSON schema rejects unknown properties and defines the required fields, but +the schema body and validation behavior are documented in [Generated Text +internals](internal/generatedtext.md). -## Collected And Derived Facts +### Deterministic Module Values -The template also receives the full `facts.CollectedFacts` and -`facts.DerivedFacts` structs: +Module values are deterministic outputs built from collected and derived facts. +Module pointers can be nil when their source or policy permits omission. -- `.Collected` contains normalized source data and provenance from upstream - Weather API fetches. -- `.Derived` contains shared slices and calculations used across modules, such - as valid-period hourly periods, precipitation timing, alert overlaps, and SPC - filtering inputs. +| Module field | Available in | +| --- | --- | +| `.Modules.Metadata`, `.Modules.CurrentConditions`, `.Modules.HourlyForecast`, `.Modules.PrecipTiming`, `.Modules.AlertDigest`, `.Modules.SPCConvectiveOutlooks`, `.Modules.AreaForecastDiscussion`, `.Modules.SPCConvectiveDiscussion`, `.Modules.WeatherStory` | All four contexts | +| `.Modules.DerivedDailySummary`, `.Modules.DerivedDaypartSummaries`, `.Modules.Dayparts` | Daily, Today, Tomorrow | +| `.Modules.OutdoorWindows`, `.Modules.DailyPlanning` | Daily | +| `.Modules.TodayPlanning` | Today | +| `.Modules.TomorrowPlanning` | Tomorrow | -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. +The repository templates currently use the following nested display values. +They are the preferred surface for comparable edits: -## Validation +| Area | Values | +| --- | --- | +| Current conditions | `.TemperatureF`, `.ConditionText`, `.ConditionTextLower`, `.ApparentTemperatureF`, `.RelativeHumidityPercent`, `.WindDirectionText`, `.WindSpeedMph` | +| Hourly periods | `.Periods`, `.HourLabel`, `.Name`, `.TemperatureF`, `.TextDescription`, `.TextDescriptionLower`, `.MentionPrecipitation`, `.ProbabilityOfPrecipitationPercent` | +| Dayparts | `.Dayparts[].Key` and `.Dayparts[].Summary` fields `DisplayName`, `DominantCondition`, `DominantConditionDisplay`, `TemperatureTrend`, `TemperatureStartPhraseF`, `TemperatureEndPhraseF`, `TemperaturePeakPhraseF`, `TemperatureSteadyPhraseF`, `TemperaturePhraseF`, `MentionPrecipitation`, and `MaxPopPercent` | +| Precipitation timing | `.PrecipitationWindows`, plus each window's `PeriodBegins`, `PeriodBeginsHourLabel`, `PeriodEnds`, `PeriodEndsHourLabel`, `ExpectationPhrase`, `MaxPopPercent`, `MaxPopTime`, and `MaxPopHourLabel` | +| Alert digest | `.AlertDigest.Relevant` entries' `Event`, `Headline`, `PeriodBegins`, and `PeriodEnds` | +| SPC risk digest | `.SPCConvectiveOutlooks.RiskDigest` entries' `LabelText`, `RiskLabel`, `PeriodBegins`, and `PeriodEnds` | -After editing a template, run: +Other fields on these typed modules remain available when a template has a +well-defined display need. Their module contracts and weather derivation belong +to [Module contract internals](internal/module.md), [Module builder +internals](internal/briefing.md), and [Forecast derivation +internals](internal/forecast-derivation.md). -```bash +## Validate Changes + +Run the focused checks after editing templates, partials, prompts, or schemas: + +```sh go test ./internal/reporttemplate ./internal/generatedtext ./internal/app -``` - -For a full check, run: - -```bash -go test ./... -go run ./cmd/weatherreporter --help git diff --check ``` -Template render tests exercise the Daily, Today, Tomorrow, and Hourly -templates through `internal/generatedtext/render_context_test.go` and -`internal/reporttemplate/reporttemplate_test.go`. +The render-context and template tests cover Daily, Today, Tomorrow, and Hourly +contexts. Run the repository-wide test suite before merging a broader change.