Files
weatherreporter/docs/templates.md

185 lines
9.1 KiB
Markdown

# Report Templates
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).
## Template Assets
Only the generated-text reports use repository-native Markdown templates.
Each report has one matching template ID, generated-text schema ID, and prompt
source:
| Report | Template | Schema | Prompt ID and source |
| --- | --- | --- | --- |
| Daily | `templates/daily.md.tmpl` (`daily`) | `daily` | `weather.daily_generated_text`; `internal/promptassets/assets/prompts/daily/` |
| Today | `templates/today.md.tmpl` (`today`) | `today` | `weather.today_generated_text`; `internal/promptassets/assets/prompts/today/` |
| Tomorrow | `templates/tomorrow.md.tmpl` (`tomorrow`) | `tomorrow` | `weather.tomorrow_generated_text`; `internal/promptassets/assets/prompts/tomorrow/` |
| Hourly | `templates/hourly.md.tmpl` (`hourly`) | `hourly` | `weather.hourly_generated_text`; `internal/promptassets/assets/prompts/hourly/` |
The matching schemas and Promptkit definitions are embedded by
`internal/promptassets`. The generated-text catalog requires each report's
exact schema/template pair; keep the matching prompt definition aligned with
that report-specific triple.
Shared partials are under `internal/reporttemplate/templates/partials/`:
| 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 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.
- Render every `.GeneratedText` value through `plainText`. It preserves prose
and paragraph breaks while escaping Markdown and HTML syntax, removing code
indentation, and replacing control characters. Never interpolate generated
prose directly: repository templates alone own headings, lists, links, and
other Markdown structure.
- 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. The Today partial uses
`.Modules.HasDaypartDetails` to ensure its heading has either rows or the
explicit no-details fallback.
Minimal optional-value pattern:
```gotemplate
{{ with .Modules.CurrentConditions }}
Currently, it is {{ with .TemperatureF }}{{ . }}°F{{ end }}.
{{ else }}
Current conditions are unavailable.
{{ end }}
```
Minimal list pattern:
```gotemplate
{{ range .GeneratedText.ForecastDiscussion }}
{{ plainText . }}
{{ end }}
```
## Registered Functions
Templates have these helpers in addition to Go template built-ins:
| Function | Accepts | Returns true when |
| --- | --- | --- |
| `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 |
| `plainText` | a generated prose string | a readable plain-text rendering that preserves paragraph breaks without allowing dynamic Markdown or HTML structure |
For example, the alert partial uses the first two functions to decide whether
to render the section:
```gotemplate
{{ if hasRelevantAlerts .Modules.AlertDigest }}
## Alert Digest
{{ end }}
```
## Render Context
Every rendered template receives one typed context with these three top-level
fields:
| Field | Purpose |
| --- | --- |
| `.Report` | Display labels and canonical report timing metadata. |
| `.GeneratedText` | Validated prose supplied by Promptkit. |
| `.Modules` | Deterministic, typed values prepared for Markdown rendering. |
### Report Metadata
All contexts provide `.Report.Title`, `.Report.GeneratedAt`,
`.Report.GeneratedAtLabel`, `.Report.ValidPeriod`, and `.Report.Timezone`.
Hourly additionally provides `.Report.LocationName` and
`.Report.ValidPeriodLabel`.
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.
### Validated GeneratedText Prose
GeneratedText is prose returned by Promptkit and validated before rendering.
It is not a source for deterministic weather facts.
| Field | Hourly type | Daily, Today, and Tomorrow type | Notes |
| --- | --- | --- | --- |
| `.GeneratedText.Summary` | `string` | `string` | Required; at most 4,000 characters. |
| `.GeneratedText.ForecastDiscussion` | `string` | `[]string` | Required; Hourly permits 12,000 characters. Day-style values permit up to 12 paragraphs of 4,000 characters each. |
| `.GeneratedText.PrecipitationTiming` | `string` | `string` | Required field, at most 4,000 characters; an empty string represents no supported prose. The precipitation partial uses nonempty prose only when deterministic windows exist. |
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). All validated generated prose together
is limited to 20,000 characters, so template edits can rely on a bounded prose
surface.
### Deterministic Module Values
Module values are deterministic outputs built from collected and derived facts.
Module pointers can be nil when their source or policy permits omission.
| Module field | Available in |
| --- | --- |
| `.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.HasDaypartDetails` | Today |
| `.Modules.OutdoorWindows`, `.Modules.DailyPlanning` | Daily |
| `.Modules.TodayPlanning` | Today |
| `.Modules.TomorrowPlanning` | Tomorrow |
The repository templates currently use the following nested display values.
They are the preferred surface for comparable edits:
| 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` |
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).
## Validate Changes
Run the focused checks after editing templates, partials, prompts, or schemas:
```sh
go test ./internal/reporttemplate ./internal/generatedtext ./internal/app
git diff --check
```
The render-context and template tests cover Daily, Today, Tomorrow, and Hourly
contexts. Run the repository-wide test suite before merging a broader change.