From b605596bcb002e71ab0ba40f463ffd9446640bf1 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 31 Jul 2026 01:36:28 +0000 Subject: [PATCH] Refresh report and template internals documentation --- docs/internal/briefing.md | 197 +++++-------------- docs/internal/generatedtext.md | 174 ++++------------- docs/internal/module.md | 322 +++++-------------------------- docs/internal/prompt-input.md | 210 +++++--------------- docs/internal/report-registry.md | 183 ++++++------------ docs/internal/reporttemplate.md | 153 ++++----------- 6 files changed, 282 insertions(+), 957 deletions(-) diff --git a/docs/internal/briefing.md b/docs/internal/briefing.md index b893776..650ffef 100644 --- a/docs/internal/briefing.md +++ b/docs/internal/briefing.md @@ -1,164 +1,69 @@ # Module Builder Internals -This document describes module builder behavior in `internal/briefing`. +`internal/briefing` builds typed module outputs from resolved report context, +collected facts, and derived facts. It owns the module registry, including +module support, fact requirements, option types, missing-data policy, builders, +and prompt-export hooks. It does not collect data, derive periods, write a +snapshot, construct YAML, invoke Scriptorium, or render a report. -## Purpose +## Registry and construction -`internal/briefing` turns report metadata, collected weather data, and derived -forecast facts into prompt-facing module outputs. The package also owns the -module registry used to validate report composition and config overrides. +Every `ModuleDefinition` declares an ID, stanza name, default option value, +required collected and derived facts, supported report IDs, missing-data +behavior, duplicate policy, builder, and optional prompt exporter. -Module outputs are structured prompt inputs. They are not rendered report prose -and they are not persisted by this package. +`BuildModule` first verifies the requested module, report compatibility, and +option shape. It then applies the declared missing-data behavior: -## Inputs And Outputs +- `omit` returns no output for unavailable optional facts; +- `error` returns the missing fact requirements; and +- `empty` allows the builder to emit an explicit checked-empty value. -Inputs: +Unsupported `warn` behavior, missing builders, duplicate registry IDs or +stanza names, output ID or stanza mismatches, and exporter failures all return +errors with module context. A successful builder gets a pass-through prompt +value unless its definition supplies an exporter. -- resolved report definition, generation time, timezone, and valid period -- collected facts built from `weatherdata.Bundle` -- derived daily, daypart, precipitation, alert, and storm-window facts where - required -- configured units, timezone, and descriptive location context -- typed module options from report defaults or config overrides +## Built value families -Outputs: +Source-oriented builders shape report metadata, current conditions, narrative +and hourly forecasts, alert digest, SPC outlooks and discussion, area forecast +discussion, and weather story. Derived builders shape daily and daypart +summaries, precipitation timing, outdoor windows, and the report-specific +Daily, Today, and Tomorrow planning values. -- `ModuleDefinition` values with module ID, stanza name, option type, - supported reports, fact requirements, missing-data behavior, and builder -- `module.Output` values for source-oriented stanzas: - `metadata`, `current_conditions`, `narrative_forecast`, `hourly_forecast`, - `alert_digest`, `spc_convective_outlooks`, - `area_forecast_discussion`, `spc_convective_discussion`, and - `weather_story` -- `module.Output` values for derived stanzas: - `derived_daily_summary`, `derived_daypart_summaries`, `precip_timing`, - `outdoor_windows`, `today_planning`, `tomorrow_planning`, and - `daily_planning` +The module registry preserves rich values for templates and snapshots while +curating prompt exports where needed. In particular, source warnings are a +metadata summary, checked-empty alerts and SPC outlooks remain distinct from +missing sources, and prompt-safe SPC values omit geometry and other +template-only or source details. The complete module composition is in +[module internals](module.md); fact derivation is in [fact contracts](facts.md). -Every registered composition entry has a builder. Unknown or unimplemented -module IDs fail validation instead of being skipped. +`area_forecast_discussion` accepts an optional typed section filter. Planning +modules are report-specific: `daily_planning` supports Daily, +`today_planning` supports Today, and `tomorrow_planning` supports Tomorrow. -Daily Report supports the Daily-style civil-day modules plus `daily_planning` -and `hourly_forecast`; those outputs feed the dated Daily GeneratedText prompt -package and embedded Markdown template. +## Missing data and boundaries -Tomorrow Report supports the Daily-style civil-day modules plus -`tomorrow_planning` and `hourly_forecast`; those outputs feed the Tomorrow -GeneratedText prompt package and embedded Markdown template. +Optional current conditions, narrative products, discussions, and weather +stories may be omitted. Required derived modules fail when their declared facts +are unavailable. Empty alert and outlook runs can still produce checked-empty +modules. SPC discussion is omitted unless a retained categorical outlook meets +the package's severity criterion and matching discussion text exists. -Today Report supports the Daily-style civil-day modules plus `today_planning` -and `hourly_forecast`; those outputs feed the Today GeneratedText prompt -package and embedded Markdown template. +Effective units, timezone, and location context arrive in `ModuleContext` from +configuration and resolved report metadata. Field defaults are owned by +[configuration](../config.md), and prompt-package layout is owned by +[prompt input](prompt-input.md). -`today_planning` is a Today-specific deterministic planning stanza with -morning readiness, commute/school/workday concerns, outdoor planning, and -late-day change-watch fields. It is compatible with `report.Today` only. +## Verification and invariants -`daily_planning` is a dated Daily deterministic planning stanza with morning -readiness, commute/school/workday concerns, and overnight change-watch fields. -It is compatible only with the `daily` report ID value. The default Daily -Report composition includes it. +Focused tests cover source and derived values, registry validation, option +handling, prompt exporters, support rules, and missing-data behavior: -Hourly Report supports source and valid-period modules that operate over its -rolling six-hour period: `metadata`, `current_conditions`, `hourly_forecast`, -`precip_timing`, `alert_digest`, `spc_convective_outlooks`, -`area_forecast_discussion`, `spc_convective_discussion`, and `weather_story`. -It does not support daily/daypart-only modules such as -`derived_daily_summary`, `derived_daypart_summaries`, `outdoor_windows`, -`today_planning`, `tomorrow_planning`, or `daily_planning`. +```sh +go test ./internal/briefing +``` -Prompt-facing module values use local, human-readable date and time labels -where the LLM is expected to reason about report content. Canonical timestamps -remain in report metadata, source provenance, and integration artifacts. - -## Boundaries - -- This package selects and shapes already-collected weather facts for prompts. -- It validates module composition against report compatibility and option - types. -- It does not collect weather data, compare prior snapshots, write module - snapshots, build YAML data packages, invoke Scriptorium, or write workflow - metadata. - -## Config Fields Used - -The app layer passes effective units, timezone, and location context into the -module context. `internal/facts` consumes daypart configuration before module -builders run. Configured `location` values are prompt context only; Weather API -`sourceLocationId` and `sourceLocation` remain source provenance. -The `metadata` module carries report context and source warnings only; alert -status and relevant alert details belong in the `alert_digest` module. - -`area_forecast_discussion` uses optional `sections` configuration to include a -subset of discussion fields. Hourly Report defaults this module to -`key_messages` and `short_term`; Daily Report defaults it to `long_term`. - -`spc_convective_outlooks` uses collected SPC run metadata and derived -report-period outlooks. It emits `checked: true` for a successfully fetched -empty run, reports `outlook_count`, and includes prompt-facing outlook fields -such as risk label, `period_begins`, `period_ends`, image URL, and whether the -outlook contains the configured location. It enriches matching outlooks with -embedded background definitions owned by this package. It also emits a curated -`risk_digest` for categorical outlooks that overlap the report period, contain -the location, and meet the configured-in-code minimum severity for report -rendering. It does not emit GeoJSON geometry, source URL, expiration time, or -severity rank. - -Prompt-facing module intervals use friendly local `period_begins` and -`period_ends` labels. Canonical report metadata, source provenance, -`issued_at`, `updated_at`, and point-in-time fields remain separate. - -`spc_convective_discussion` uses the same derived report-period outlooks and -discussion records. It is omitted unless at least one retained categorical -outlook for the same SPC day has severity rank `3` or higher and matching -discussion text exists. - -## External Adapters Used - -None directly. - -## State Or Manifest Behavior - -None. `internal/app` collects module outputs into a `module.Snapshot`, and -`internal/state` persists that snapshot. - -## Skip And Resume Behavior - -None. Builders either emit a module output, omit optional unavailable data, or -return an error for invalid required inputs. - -## Failure Behavior - -- Required derived modules return errors when their dependent facts are not - available. -- Module registry construction rejects duplicate module IDs and duplicate - stanza names. -- Composition validation rejects unknown modules, duplicate modules, - incompatible report/module combinations, duplicate stanza names, and invalid - option shapes. -- Source-oriented module builders omit missing optional current conditions, - forecast discussion, and weather story stanzas. -- Alert digest output distinguishes checked empty alert data from missing alert - source data. -- SPC convective outlook output distinguishes checked empty outlook data from - missing outlook source data and omits GeoJSON geometry from prompt-facing - fields. -- SPC convective discussion output is omitted unless a retained outlook has - severity rank `3` or higher and matching discussion text is available. - -## Tests - -Inspect: - -- `internal/briefing/base_modules_test.go` -- `internal/briefing/derived_modules_test.go` -- `internal/briefing/modules_test.go` -- `internal/app/app_test.go` - -## Invariants - -- Module outputs contain structured weather facts and source context. -- Common metadata includes RunID, report ID, prompt ID, valid period, source - provenance, source hashes, source warnings, and configured prompt location. -- Prompt input packaging and Scriptorium execution remain outside this package. +Builders emit structured facts, never report prose. The app collects their +outputs into a module snapshot, and state persists that snapshot. diff --git a/docs/internal/generatedtext.md b/docs/internal/generatedtext.md index b122f19..78e3c7f 100644 --- a/docs/internal/generatedtext.md +++ b/docs/internal/generatedtext.md @@ -1,149 +1,51 @@ # Generated Text Internals -This document describes structured generated-text handling in -`internal/generatedtext`. +`internal/generatedtext` validates the structured prose produced for generated- +text reports and turns validated prose plus rich module values into typed render +contexts. It owns the catalog that pairs a generated-text report definition +with its validator, schema ID, template ID, and context builder. The complete +maintainer-facing context fields belong to [report templates](../templates.md). -## Purpose +## Catalog and validation -`internal/generatedtext` validates structured text returned for -generated-text-template reports and builds curated render contexts for -templates. It also owns the generated-text catalog that connects report -definitions to validators, render-context builders, schema assets, and template -assets. +Only the Daily, Today, Tomorrow, and Hourly report definitions use the +generated-text-template mode. `LookupDefinition` rejects a direct-Markdown +definition, unknown schema or template IDs, and unsupported schema/template +pairs before the run begins. A handler validates raw JSON, returns a typed +value and canonical normalized JSON, loads its schema, builds a render context, +and renders through `internal/reporttemplate`. -## Inputs And Outputs +Daily, Today, and Tomorrow use a day-style value with required trimmed summary +and one or more nonblank discussion paragraphs. Hourly requires trimmed summary +and a single trimmed discussion string. Each form permits optional trimmed +precipitation-timing and confidence prose. Typed decoding rejects unknown JSON +fields; no general-purpose JSON Schema engine is used at runtime. -Inputs: +## Render contexts -- raw GeneratedText JSON for Daily, Today, Tomorrow Report, or Hourly Report -- report metadata from `internal/briefing` -- a module snapshot from `internal/module` -- validated generated text +The catalog's report-specific builders receive briefing metadata, a rich module +snapshot, collected facts, derived facts, and the matching validated generated +text. They decode the module stanzas needed by the template and build typed +Daily, Today, Tomorrow, or Hourly contexts. Context construction validates +metadata and periods, preserves rich module values, and uses ordered slices for +template iteration rather than maps. -Outputs: +Optional source stanzas become nil or fallback context fields. Missing required +stanzas, type-decoding failures, invalid metadata, or a generated-text type +that does not match the chosen handler fail before template execution. Prompt +packages, raw Scriptorium output, state persistence, and template asset lookup +remain outside this package. -- typed `Daily` generated text -- typed `Today` generated text -- typed `Tomorrow` generated text -- typed `Hourly` generated text -- normalized stable JSON for validated generated text -- typed `DailyRenderContext` values for `internal/reporttemplate` -- typed `TodayRenderContext` values for `internal/reporttemplate` -- typed `TomorrowRenderContext` values for `internal/reporttemplate` -- typed `HourlyRenderContext` values for `internal/reporttemplate` -- generated-text catalog handlers for report definitions that use - `generated_text_template` +## Verification and invariants -## JSON Contracts +Focused tests cover the catalog, each report-specific validator, normalization, +schema/template mismatches, context construction, optional modules, and typed +stanza errors: -Daily, Today, and Tomorrow use the same day-style generated-text JSON shape: - -```json -{ - "summary": "string", - "forecast_discussion": ["string"], - "precipitation_timing": "string", - "confidence": "string" -} +```sh +go test ./internal/generatedtext ``` -The day-style contract requires `summary` after trimming whitespace. -`forecast_discussion` must contain at least one nonblank paragraph after -trimming blank items. `precipitation_timing` and `confidence` are optional and -omitted from normalized JSON when blank. Unknown fields are rejected. - -The report-specific Go API is: - -| Report | Type | Validator | Schema ID | Template ID | Prompt ID | -| --- | --- | --- | --- | --- | --- | -| Daily Report | `Daily` | `ValidateDaily` | `daily` | `daily` | `weather.daily_generated_text` | -| Today Report | `Today` | `ValidateToday` | `today` | `today` | `weather.today_generated_text` | -| Tomorrow Report | `Tomorrow` | `ValidateTomorrow` | `tomorrow` | `tomorrow` | `weather.tomorrow_generated_text` | - -Hourly generated text uses the same top-level field names, but -`forecast_discussion` is a single string: - -```json -{ - "summary": "string", - "forecast_discussion": "string", - "precipitation_timing": "string", - "confidence": "string" -} -``` - -Hourly `summary` and `forecast_discussion` are required after trimming -whitespace. `precipitation_timing` and `confidence` are optional and omitted -from normalized JSON when blank. Unknown fields are rejected. The Hourly catalog -entry uses type `Hourly`, validator `ValidateHourly`, schema ID `hourly`, -template ID `hourly`, and prompt ID `weather.hourly_generated_text`. - -## Render Contexts - -Daily, Today, Tomorrow, and Hourly render contexts all include: - -- display metadata derived from report metadata; -- validated generated text; -- typed module outputs decoded from the module snapshot; -- collected facts; -- derived facts. - -Daily, Today, and Tomorrow share common civil-day render-context fields such as -forecast date labels, valid period, generated-at labels, current conditions, -hourly forecast, precipitation timing, alert digest, SPC outlooks, AFD, SPC -discussion, weather story, daily summary, and ordered daypart summaries. - -Each civil-day report keeps its report-specific planning module: - -- Daily exposes `DailyPlanning`. -- Today exposes `TodayPlanning`. -- Tomorrow exposes `TomorrowPlanning`. - -Today's ordered daypart context omits unavailable or elapsed dayparts according -to Today report rules. Daily and Tomorrow use fallback daypart behavior. - -## Boundaries - -- This package owns typed generated-text validation and render-context shaping. -- It owns generated-text catalog lookup for schema/template combinations. -- It uses typed module snapshot decoding through `module.StanzaValue`. -- It does not invoke Scriptorium, write state artifacts, choose report - definitions, compare snapshots, or own embedded template/schema files. -- It renders through `internal/reporttemplate`; embedded asset lookup remains - in `internal/reporttemplate`. -- It does not use a Go JSON Schema dependency; schema enforcement in Go is - limited to typed JSON decoding, unknown-field rejection, and required-field - checks. - -## Failure Behavior - -- Malformed generated-text JSON fails with decode context. -- Unknown generated-text JSON fields fail during decoding. -- Empty required fields fail after trimming whitespace. -- Daily, Today, and Tomorrow forecast discussion fails when no nonblank - paragraphs remain. -- Missing optional render-context stanzas become nil module pointers. -- Invalid render metadata, including missing timezone, missing generated time, - or invalid valid period, fails before template rendering. -- Unsupported generated-text schema IDs, template IDs, or schema/template - combinations fail during catalog lookup with report ID context. - -## Tests - -Inspect: - -- `internal/generatedtext/hourly_test.go` -- `internal/generatedtext/daily_test.go` -- `internal/generatedtext/today_test.go` -- `internal/generatedtext/tomorrow_test.go` -- `internal/generatedtext/catalog_test.go` -- `internal/generatedtext/render_context_test.go` - -## Invariants - -- Render contexts are curated structs, not raw prompt-input packages. -- Required generated text is normalized before downstream artifact storage. -- Generated-text-template reports must have one catalog entry matching their - report definition schema and template IDs. -- Missing optional weather narrative stanzas produce empty or fallback render - context fields rather than forcing raw module data into templates. +Generated text supplies prose slots only; deterministic weather facts remain in +module and fact values. Every generated-text definition must resolve to exactly +one supported catalog pair. diff --git a/docs/internal/module.md b/docs/internal/module.md index c31723a..e232303 100644 --- a/docs/internal/module.md +++ b/docs/internal/module.md @@ -1,290 +1,68 @@ # Module Contract Internals -This document describes the module contract in `internal/module`. +`internal/module` defines the stable envelope between report composition, +module builders, snapshots, comparisons, templates, and prompt packages. It +does not define a report, execute a builder, or choose prompt-export policy; +those responsibilities belong to [report registry](report-registry.md) and +[briefing](briefing.md). -## Purpose +## Outputs and snapshots -`internal/module` defines the shared identifiers and data envelopes used for -report modules. Report definitions use module IDs for composition, module -builders produce rich outputs with stanza names, prompt input packages consume -runtime prompt export values, and Recent Changes compares snapshot stanzas. +Each `Output` has a module ID, stanza name, rich `Value`, and runtime-only +`PromptValue`. `DataPackageValue` returns the prompt value when present and +otherwise the rich value. This permits custom prompt exports without shrinking +the template and inspection value. -## Inputs And Outputs +`NewSnapshot` builds the ordered `weatherreporter.modules.v1` snapshot and +validates it. Snapshot JSON persists IDs, stanza names, and rich values only; +`PromptValue` is deliberately excluded. `StanzaValue` decodes a named rich +stanza into a caller-supplied type, reporting a missing stanza separately from +a decoding error. -Inputs: +Snapshots reject missing schema versions, empty IDs or stanza names, and +duplicate IDs or stanza names. Output order is caller-owned and preserved. -- ordered `module.ConfigItem` values from report definitions or config - overrides -- `module.Output` values produced by module builders +## Registered IDs and default composition -Outputs: +The registered IDs are `metadata`, `current_conditions`, +`narrative_forecast`, `hourly_forecast`, `derived_daily_summary`, +`derived_daypart_summaries`, `precip_timing`, `alert_digest`, +`spc_convective_outlooks`, `area_forecast_discussion`, +`spc_convective_discussion`, `weather_story`, `outdoor_windows`, +`today_planning`, `tomorrow_planning`, and `daily_planning`. -- stable `module.ID` constants -- typed option structs for registered modules -- `module.Snapshot` with schema version `weatherreporter.modules.v1` -- ordered snapshot outputs with module ID, stanza name, and typed value -- runtime-only prompt export values on module outputs -- `module.Output.DataPackageValue`, which selects the prompt export value and - falls back to the rich value for hand-built or loaded snapshots -- typed stanza lookup through `module.StanzaValue` +The registry declares these ordered default compositions: -## Rich Values And Prompt Exports +| Report | Ordered modules | +| --- | --- | +| Daily | metadata, current conditions, narrative forecast, daily summary, daypart summaries, precipitation timing, alert digest, SPC outlooks, AFD (long term), SPC discussion, weather story, outdoor windows, daily planning, hourly forecast | +| Today | metadata, current conditions, narrative forecast, daily summary, daypart summaries, precipitation timing, alert digest, SPC outlooks, AFD, SPC discussion, weather story, outdoor windows, hourly forecast, today planning | +| Tomorrow | metadata, current conditions, narrative forecast, daily summary, daypart summaries, precipitation timing, alert digest, SPC outlooks, AFD, SPC discussion, weather story, outdoor windows, tomorrow planning, hourly forecast | +| Hourly | metadata, current conditions, hourly forecast, precipitation timing, alert digest, SPC outlooks, AFD (key messages and short term), SPC discussion, weather story | +| Three-day and Weekend | metadata, current conditions, daypart summaries, precipitation timing, alert digest, SPC outlooks, AFD, SPC discussion, weather story, outdoor windows | +| Storm | metadata, current conditions, precipitation timing, alert digest, SPC outlooks, AFD, SPC discussion, weather story | -Each `module.Output` has two value surfaces: +The only non-empty default option is the AFD section selection. It accepts a +`sections` list; omitted or empty selects all available sections. Report +definitions may narrow it as shown above. Option shape and report compatibility +are validated by the briefing registry. -- `Value`: the rich module value used by templates, module snapshots, - inspection, Recent Changes, and render contexts. -- `PromptValue`: the runtime-only prompt export used when building Scriptorium - data packages. +## Rich and prompt-facing values -`PromptValue` is deliberately excluded from module snapshot JSON. Persisted -module snapshots keep only the rich `value` field so inspection and -render-context reconstruction keep the full deterministic template surface. +Rich values remain available to snapshots, comparisons, and render contexts. +Briefing attaches custom prompt exports only for current conditions, hourly +forecast, and derived daypart summaries; all other current builders use +pass-through values. The prompt package owns how exported stanzas are grouped +and serialized; see [prompt input](prompt-input.md). -The `internal/briefing` module registry attaches prompt export values when it -builds module outputs. Modules without a custom exporter use default -pass-through behavior, so their prompt value is the same as their rich value. -Modules with custom prompt export policy own typed prompt export structs near -the module builder. Custom prompt exports are: +## Verification and invariants -- `current_conditions` -- `hourly_forecast` -- `derived_daypart_summaries` +Focused tests cover snapshot validation and order, typed stanza lookup, and +prompt-value fallback: -Custom exporters remove template-only helpers or confusing duplicates from the -data package without shrinking the rich module structs used by templates. -Exporter failures include module ID and stanza context. - -## Registered Module IDs - -The registry recognizes these IDs: - -- `metadata` -- `current_conditions` -- `narrative_forecast` -- `hourly_forecast` -- `derived_daily_summary` -- `derived_daypart_summaries` -- `precip_timing` -- `alert_digest` -- `spc_convective_outlooks` -- `area_forecast_discussion` -- `spc_convective_discussion` -- `weather_story` -- `outdoor_windows` -- `today_planning` -- `tomorrow_planning` -- `daily_planning` - -Every registered module has a builder. Report composition entries that refer to -unknown or unimplemented module IDs fail validation instead of being skipped. - -## Daily Composition - -The default Daily Report module order is: - -1. `metadata` -2. `current_conditions` -3. `narrative_forecast` -4. `derived_daily_summary` -5. `derived_daypart_summaries` -6. `precip_timing` -7. `alert_digest` -8. `spc_convective_outlooks` -9. `area_forecast_discussion` -10. `spc_convective_discussion` -11. `weather_story` -12. `outdoor_windows` -13. `daily_planning` -14. `hourly_forecast` - -The embedded Daily template uses selected deterministic fields from these -module outputs after GeneratedText validation. Its `area_forecast_discussion` -item is configured to include only `long_term`. - -## Today Composition - -The default Today Report module order is: - -1. `metadata` -2. `current_conditions` -3. `narrative_forecast` -4. `derived_daily_summary` -5. `derived_daypart_summaries` -6. `precip_timing` -7. `alert_digest` -8. `spc_convective_outlooks` -9. `area_forecast_discussion` -10. `spc_convective_discussion` -11. `weather_story` -12. `outdoor_windows` -13. `hourly_forecast` -14. `today_planning` - -The embedded Today template uses selected deterministic fields from these -module outputs after GeneratedText validation. - -## Tomorrow Composition - -The default Tomorrow Report module order is: - -1. `metadata` -2. `current_conditions` -3. `narrative_forecast` -4. `derived_daily_summary` -5. `derived_daypart_summaries` -6. `precip_timing` -7. `alert_digest` -8. `spc_convective_outlooks` -9. `area_forecast_discussion` -10. `spc_convective_discussion` -11. `weather_story` -12. `outdoor_windows` -13. `tomorrow_planning` -14. `hourly_forecast` - -The embedded Tomorrow template uses selected deterministic fields from these -module outputs after GeneratedText validation. - -## Daily Planning - -`daily_planning` emits dated daily planning facts for the `daily` report ID. -Its output stanza is also named `daily_planning`. The module is supported only -by that report ID and depends on daily summaries for the selected local civil -day. The default Daily Report composition includes it. - -The output uses this shape: - -- `morning_readiness` -- `commute_school_workday_concerns` -- `overnight_change_watch` - -The type is `briefing.DailyPlanningModule`; it is independent from -`briefing.TomorrowPlanningModule`. - -## Today Planning - -`today_planning` emits current-day planning facts for Today Report. Its output -stanza is also named `today_planning`. The module is supported only by Today -Report and depends on daily and daypart summaries for the current local civil -day. - -The output uses this shape: - -- `morning_readiness` -- `commute_school_workday_concerns` -- `outdoor_planning` -- `late_day_change_watch` - -The type is `briefing.TodayPlanningModule`; it is independent from -`briefing.TomorrowPlanningModule`. - -## Hourly Composition - -The default Hourly Report module order is: - -1. `metadata` -2. `current_conditions` -3. `hourly_forecast` -4. `precip_timing` -5. `alert_digest` -6. `spc_convective_outlooks` -7. `area_forecast_discussion` -8. `spc_convective_discussion` -9. `weather_story` - -Hourly Report does not include daily or daypart summary modules by default. -Its `area_forecast_discussion` item is configured to include only -`key_messages` and `short_term`. - -## Options - -Most modules use an empty options struct, including -`spc_convective_outlooks` and `spc_convective_discussion`. -`area_forecast_discussion` accepts: - -```yaml -sections: - - product - - key_messages - - short_term - - long_term +```sh +go test ./internal/module ``` -An omitted or empty `sections` list includes all available discussion sections. -Invalid option shapes fail during config normalization or composition -validation. - -## SPC Convective Module Outputs - -`spc_convective_outlooks` emits a risk-product stanza with: - -- `checked` -- `as_of` -- `issued_at` -- `location_id` -- `location_name` -- `outlook_count` -- `outlooks` -- `risk_digest` - -Each outlook entry may include `day`, `outlook_type`, `label`, `label_text`, -`background_definition`, `period_begins`, `period_ends`, `issued_at`, -`contains_location`, and `image_url`. `background_definition` is embedded -briefing reference content for known outlook type/label pairs and may include -`plain_language`, `official_description`, and `relative_level`. It omits GeoJSON -geometry, source URL, expiration time, and severity rank. - -The optional `risk_digest` list is a curated report-rendering subset of -categorical outlooks that overlap the report period, contain the configured -location, and meet the minimum severity threshold. Entries include `label_text`, -`risk_label`, `period_begins`, and `period_ends`; they do not expose severity -rank. - -`spc_convective_discussion` emits a narrative stanza only when a retained -report-period categorical outlook has severity rank `3` or higher and matching -discussion text is available. Its output includes `included_because` and -`discussions`; each discussion may include `day`, `period_begins`, -`period_ends`, `headline`, `summary`, `discussion`, and `updated_at`. -Discussions are included only for SPC days whose retained categorical outlooks -meet the severity threshold. - -## Boundaries - -- This package owns module identifiers, config item envelopes, output - envelopes, snapshot validation, and typed stanza lookup. -- It does not define report IDs, execute builders, collect weather data, derive - forecast facts, write state, or invoke Scriptorium. - -## State Or Manifest Behavior - -`module.Snapshot` values are persisted by `internal/state` as JSON. Snapshot -validation rejects missing schema version, missing module IDs, missing stanza -names, duplicate module outputs, and duplicate stanza names while preserving -output order. Snapshot JSON contains rich module values only; runtime prompt -export values are not persisted. - -## Failure Behavior - -- Snapshot construction fails for duplicate module outputs or duplicate stanza - names. -- Typed stanza lookup returns `found=false` for missing stanzas. -- Typed stanza lookup wraps JSON marshal/decode failures with stanza context. - -## Tests - -Inspect: - -- `internal/module/module_test.go` -- `internal/briefing/modules_test.go` -- `internal/report/period_test.go` - -## Invariants - -- `internal/module` does not import `internal/report`. -- Module IDs are stable strings. -- Each emitted module output has exactly one stanza name and one rich typed - value. -- Built module outputs have a data-package value, either from a custom prompt - exporter or from default pass-through behavior. -- Snapshot output order is caller-owned and preserved. +Module IDs and stanza names are stable, every emitted output has one of each, +and this package never imports the report registry. diff --git a/docs/internal/prompt-input.md b/docs/internal/prompt-input.md index 5ea204d..8ea8fae 100644 --- a/docs/internal/prompt-input.md +++ b/docs/internal/prompt-input.md @@ -1,177 +1,61 @@ # Prompt Input Internals -This document describes YAML prompt data package construction in -`internal/promptinput`. +`internal/promptinput` converts report metadata, an ordered module snapshot, +Recent Changes, and source warnings into the YAML `data_package` consumed by +Scriptorium. It owns this package's schema, grouping, serialization, loading, +and validation—not weather collection, module construction, path choice, or +subprocess execution. -## Purpose +## Package construction -`internal/promptinput` converts report metadata, ordered module outputs, Recent -Changes, and source warnings into the `data_package` file passed to -Scriptorium. +`Build` produces `weatherreporter.data_package.v3`. It copies the run ID; +report ID, variant, prompt ID, generation time, timezone, local current date, +and valid period; ordered briefing stanzas; Recent Changes; and source +warnings. A nil Recent Changes slice becomes an empty `items` list. -The persisted data package is YAML with schema version -`weatherreporter.data_package.v3`. It is separate from the JSON module snapshot -used for inspection and comparison. Data packages serialize each module -output's prompt export value, not necessarily the full rich module value saved -in the module snapshot. +Briefing starts as a flat snapshot order and stanza-value map. `Build` uses +each output's `DataPackageValue`, so runtime prompt exports take precedence and +rich values are used only as a fallback. Prompt exports are selected by the +[briefing registry](briefing.md), while the rich-versus-prompt contract is in +[module internals](module.md). -## Inputs And Outputs +## YAML ordering and grouping -Inputs: +Serialization keeps `metadata` directly under `briefing`. Every other known +stanza is placed in exactly one category, emitted in category order and in its +original snapshot order within that category: -- report metadata from app/state orchestration -- `module.Snapshot` -- optional `[]changes.Change` +| Category | Current stanzas | +| --- | --- | +| `applicable_risk_products` | alert digest, SPC convective outlooks | +| `derived_summaries` | deterministic summaries, precipitation timing, outdoor windows, and planning values | +| `narrative_products` | narrative forecast, discussions, and weather story | +| `raw_data` | current conditions and hourly forecast | -Outputs: +This YAML presentation does not alter the flat snapshot model. `LoadYAML` +accepts the same category layout and reconstructs flat `Order` and `Values`, +rejecting misplaced, duplicate, unknown, or uncategorized stanzas. -- `promptinput.Package` with schema version, RunID, report metadata, named - module stanzas grouped for prompt presentation, Recent Changes, and source - warnings -- YAML bytes from `promptinput.MarshalYAML` -- YAML file written atomically by `promptinput.Save` +## Validation and persistence -The YAML shape includes: +`Validate` requires the current schema version, run and report identifiers, +prompt ID, generation timestamp, timezone, current local date, valid period, +and at least one ordered briefing stanza. It rejects duplicate stanza names, +missing values, and a missing category for every non-metadata stanza. -```yaml -schema_version: weatherreporter.data_package.v3 -run_id: -report: - id: - prompt_id: -briefing: - metadata: {} - applicable_risk_products: - alert_digest: {} - spc_convective_outlooks: {} - derived_summaries: - derived_daily_summary: {} - derived_daypart_summaries: {} - precip_timing: {} - outdoor_windows: {} - narrative_products: - narrative_forecast: {} - area_forecast_discussion: {} - spc_convective_discussion: {} - weather_story: {} - raw_data: - current_conditions: {} - hourly_forecast: {} -recent_changes: - items: [] +`MarshalYAML` and `LoadYAML` validate their result. `Save` writes the serialized +YAML atomically; managed workspace paths are owned by [state internals](state.md). +Generated-text artifacts and template render contexts are later workflow +artifacts, not members of this package. + +## Verification and invariants + +Focused tests cover construction, curated exports, category ordering, YAML +round trips, invalid layout, validation, and atomic saves: + +```sh +go test ./internal/promptinput ``` -The `briefing` mapping keeps `metadata` directly under `briefing` and groups -weather module stanzas under prompt-facing categories. This grouping is a YAML -presentation concern only: module snapshots remain flat, and loaded -`promptinput.Package` values expose flat stanza names in `Briefing.Values`. -Within each category, stanza order follows the module snapshot output order. -Prompt-facing module intervals use local `period_begins` and `period_ends` -labels; canonical report metadata and source timestamps remain structured -timestamps where applicable. - -## Module Export Boundary - -Data packages are curated prompt inputs. They are not full template render -contexts and should not be treated as a dump of every field available to Go -templates. - -When module outputs are built by `internal/briefing`, the registry attaches a -runtime prompt export value. `internal/promptinput` serializes -`output.DataPackageValue()` for each stanza. That helper prefers the runtime -prompt export and falls back to the rich `Value` when no prompt export is set, -which keeps loaded snapshots and hand-built tests usable. - -Modules without custom export policy use pass-through behavior. Modules with -custom exports currently include: - -- `current_conditions`: omits lower-case condition text and duplicate - wind-direction text. -- `hourly_forecast`: omits hour labels, lower-case description text, and the - template precipitation-mention helper while keeping forecast facts. -- `derived_daypart_summaries`: omits deterministic sentence-construction - helpers while keeping daypart period, condition, temperature trend, - precipitation, wind, notable-condition, hazard, and alert-relevance facts. - -The rich module snapshot and generated-template render context still contain -the helper fields used by deterministic Markdown templates. - -Daily Report, Today Report, Tomorrow Report, and Hourly Report module snapshots -use the same package schema and categories when converted into prompt input. -The default hourly module list places -`precip_timing` under `derived_summaries`, alert and SPC outlooks under -`applicable_risk_products`, AFD/SPC discussion/weather story under -`narrative_products`, and current/hourly data under `raw_data`. It does not -include civil-day summary stanzas. Generated-text and render context artifacts -are produced later in app orchestration and are not part of the YAML data -package. - -The default Daily, Today, and Tomorrow module lists include civil-day summary -stanzas, planning stanzas, and `hourly_forecast` in the data package before -structured GeneratedText is requested from Scriptorium. Daily uses -`daily_planning`, Today uses `today_planning`, and Tomorrow uses -`tomorrow_planning`. - -Current categories are: - -- `applicable_risk_products`: location-applicable alerts, warnings, outlooks, - and similar risk products. Current stanzas include `alert_digest` and - `spc_convective_outlooks`. -- `derived_summaries`: deterministic summaries and calculated report facts. -- `narrative_products`: official narrative text products and forecast stories. - Current stanzas include `narrative_forecast`, - `area_forecast_discussion`, `spc_convective_discussion`, and - `weather_story`. -- `raw_data`: minimally transformed underlying weather data. - -## Boundaries - -- This package owns prompt package schema, YAML marshaling, YAML loading, and - validation. -- It does not collect weather data, derive forecast summaries, execute modules, - choose module prompt export shapes, find prior snapshots, compare changes, - choose artifact paths, or invoke Scriptorium. - -## Config Fields Used - -None directly. Config-derived values such as timezone, units, and prompt -location are already present in report metadata and module stanzas before this -package runs. - -## External Adapters Used - -None. - -## State Or Manifest Behavior - -`promptinput.Save` writes YAML atomically. Managed workspace paths are owned by -`internal/state`. - -## Skip And Resume Behavior - -None. Recent Changes is always present as an `items` list and may be empty. - -## Failure Behavior - -Validation fails before render preflight when required top-level fields are -missing or inconsistent, when the valid period is invalid, or when no module -stanzas are present. Save failures include filesystem operation and path -context. - -## Tests - -Inspect: - -- `internal/promptinput/package_test.go` -- `internal/app/app_test.go` - -## Invariants - -- Scriptorium receives structured YAML through `--input data_package=`. -- Module stanza order is deterministic within each prompt-facing category. -- Every non-metadata module stanza has exactly one prompt-input category. -- Data-package stanzas use curated module prompt exports when present and rich - values only as pass-through or fallback values. -- Data packages are narrower than generated-template render contexts. -- Recent Changes are provided by `internal/changes`; this package does not - infer changes from rendered report text. +The package is narrower than a template render context and never infers changes +from report prose. diff --git a/docs/internal/report-registry.md b/docs/internal/report-registry.md index 0666ac2..d9a29ff 100644 --- a/docs/internal/report-registry.md +++ b/docs/internal/report-registry.md @@ -1,143 +1,76 @@ # Report Registry Internals -This document describes report identity, valid-period resolution, output -naming, artifact grouping, batch command names, and comparison declarations in -`internal/report`. +`internal/report` owns the registry of report identities and the data declared +for each one: resolution, generation mode, prompt identity, comparison policy, +artifact group, output-copy name, default module composition, and Distributor +path declarations. The public command syntax is owned by the +[CLI reference](../cli.md); configuration aliases and overrides are owned by +the [configuration reference](../config.md). -## Purpose +## Definitions and resolution -`internal/report` is the canonical source for report definitions, public -command names, config-key aliases, and batch command names. App, config, state, -module building, and CLI wiring consume report-owned helpers and resolved -definitions instead of owning report identity policy themselves. +Each `Definition` declares a stable ID and display name, prompt ID, generation +mode, optional template and generated-text schema IDs, valid-period resolver, +comparison strategy, artifact group, batch-copy filename, Distributor path +templates, generation eligibility, compatible prior IDs, default modules, and +batch eligibility flags. `Resolved` combines that definition with the valid +period and run metadata for one invocation. -## Definition Fields +| Report ID | Mode | Period policy | Comparison | Registry batch flag | Output copy | +| --- | --- | --- | --- | --- | --- | +| `daily` | Generated text + template | Explicit local civil day | Same valid date | Dynamic Daily inclusion is app-owned | `daily.md` | +| `today` | Generated text + template | Selected or current local civil day | Same valid date | Morning | `today.md` | +| `tomorrow` | Generated text + template | Next local civil day | Same valid date | Evening | `tomorrow.md` | +| `hourly` | Generated text + template | Rolling six-hour interval | Rolling window | — | `hourly.md` | +| `three_day` | Scriptorium Markdown | Generation time through the third following local midnight | Same valid date | Morning | `three-day.md` | +| `weekend` | Scriptorium Markdown | Upcoming weekend window | Weekend window | Morning | `weekend.md` | +| `storm` | Scriptorium Markdown | Caller-supplied event window | Explicit window | — | `storm.md` | -Each report definition declares: +The four generated-text reports pair their report ID with matching template and +schema IDs. The three direct-Markdown reports leave both IDs empty. Exact +template fields and schema assets belong to [report templates](../templates.md) +and [generated-text internals](generatedtext.md). -- report ID and display name -- Scriptorium prompt ID -- generation mode -- valid-period resolver -- comparison strategy -- managed artifact group -- batch output copy filename -- generated-report eligibility -- prior-report compatibility list -- default ordered module composition +All valid periods are half-open. Storm accepts local `YYYY-MM-DDTHH:MM` values +in the effective report timezone or offset-bearing RFC3339 values; its end +must follow its start. Resolving Weekend directly on Sunday is rejected. -Report-owned helpers map public command names and config keys to report IDs. -The generate command names are `daily`, `today`, `tomorrow`, `hourly`, -`three-day`, `weekend`, and `storm`. Config keys also accept selected -underscore and descriptive aliases such as `three_day_outlook`, -`weekend_outlook`, and `storm_report`. +## Registry collaborators -`daily` resolves to the dated Daily Report ID `daily`. `today` resolves to the -independent Today report ID `today`. `reports.today` is not an alias for -`reports.daily`, and retired report keys are not supported. +`DefaultRegistry` is the only source of the seven report definitions. +`Lookup`, `Resolve`, and report-name helpers prevent callers from duplicating +report identity rules. Registry overrides clone a definition and replace its +module list only after the report ID is recognized. -Markdown report definitions use the `scriptorium_markdown` generation mode. -Their template and structured-text schema identifiers are empty. Daily Report, -Today Report, Tomorrow Report, and Hourly Report declare -`generated_text_template`; the app uses their template and schema identifiers -to validate generated text and render embedded Markdown templates. +The definition's `DistributorPathTemplates` are internal declarations consumed +by app orchestration. Their rendered external bundle paths and compatibility +contract are documented in the [Distributor bundle guide](../integrations/distributor/pkg-bundle.md), not repeated here. -## Reports +`morning` and `evening` are registry-owned batch names. Registry flags declare +fixed report eligibility; app orchestration determines data-dependent Daily +membership and produces the actual batch plan. -| Report | ID | Prompt | Generation mode | Artifact group | Batch copy | Prior compatibility | -| --- | --- | --- | --- | --- | --- | --- | -| Daily Report | `daily` | `weather.daily_generated_text` | `generated_text_template` | `daily` | `daily.md` | Daily Report | -| Today Report | `today` | `weather.today_generated_text` | `generated_text_template` | `today` | `today.md` | Today Report | -| Tomorrow Report | `tomorrow` | `weather.tomorrow_generated_text` | `generated_text_template` | `tomorrow` | `tomorrow.md` | Tomorrow Report | -| Hourly Report | `hourly` | `weather.hourly_generated_text` | `generated_text_template` | `hourly` | `hourly.md` | Hourly Report | -| 3-Day Outlook | `three_day` | `weather.three_day_outlook` | `scriptorium_markdown` | `three-day` | `three-day.md` | 3-Day Outlook | -| Weekend Outlook | `weekend` | `weather.weekend_outlook` | `scriptorium_markdown` | `weekend` | `weekend.md` | Weekend Outlook | -| Storm Report | `storm` | `weather.storm_report` | `scriptorium_markdown` | `storm` | `storm.md` | Storm Report | +## Module composition and failures -All report definitions are eligible for generation. +Each definition supplies an ordered `[]module.ConfigItem`; the complete +report-to-module mapping is maintained in [module internals](module.md). +`ArtifactGroup`, `BatchOutputName`, `Generated`, and comparison compatibility +are likewise consumed by state and orchestration rather than recomputed there. -## Valid Periods +Unknown report IDs or batch names, an invalid weekend resolution, and invalid +storm windows return errors. The registry never collects weather data, builds +modules, parses CLI flags, writes state, executes Scriptorium, or delivers a +report. -- Daily Report covers the selected local civil day and requires an explicit - date. -- Today Report covers the selected local civil day, or the current local civil - day when no date override is supplied. -- Tomorrow Report covers the next local civil day from generation time. -- Hourly Report covers the half-open six-hour period from generation time in - the effective report timezone. The duration is an internal report constant, - not a configuration field. -- 3-Day Outlook covers the interval from generation time through local midnight - three days later. -- Weekend Outlook covers the upcoming weekend window. -- Storm Report covers an explicit event window supplied by the caller. +## Verification and invariants -Storm event windows can be parsed from local `YYYY-MM-DDTHH:MM` timestamps in -the configured timezone or RFC3339 timestamps with explicit offsets. End time -must be after start time. +Focused tests cover definition completeness, command and alias lookup, period +resolution, run IDs, path declarations, composition defaults, and override +validation: -## Boundaries +```sh +go test ./internal/report +``` -`internal/report` defines report metadata, public report names, batch command -names, output naming, and time coverage. It does not collect weather data, plan -batch membership, build module values, compare snapshot contents, write state, -parse CLI flags, or invoke Scriptorium. - -The CLI parses flags and command structure, then uses report-owned helpers for -report and batch command names. Config loading uses report-owned helpers for -report override keys. - -## Config Fields Used - -The app supplies `weather_api.timezone` as a loaded `time.Location`. Batch -output path copying uses batch output names from report definitions. Report -module overrides can use short keys such as `daily`, `today`, `tomorrow`, and -`hourly`, or descriptive names such as `three_day_outlook`. - -## Batch Commands - -`internal/report` owns the public batch command names `morning` and `evening` -and validates them through `BatchForCommandName`. Data-dependent batch -membership is owned by `internal/app`, because it depends on collected hourly -forecast coverage. - -Report definitions still declare default batch output copy filenames. App -batch planning uses those filenames for fixed report entries and supplies -date-qualified names for dynamic Daily entries. - -## State And App Usage - -- State paths use `ArtifactGroup`. -- Batch output copies use `BatchOutputName`. -- Generation checks `Generated`. -- Module composition defaults use `Modules`. -- Prior lookup checks `CompatiblePriorIDs` and the comparison strategy. -- RunIDs include the resolved report ID. - -## Failure Behavior - -- Unknown report IDs and batch names return actionable errors. -- Weekend Outlook resolution returns an error when resolved directly on Sunday. -- Storm Report resolution requires start and end, with end after start. - -## Tests - -Inspect: - -- `internal/report/period_test.go` -- `internal/app/app_test.go` -- `internal/cli/root_test.go` - -## Invariants - -- Report selection goes through the registry. -- Public command names, config-key aliases, and batch command names are owned - by `internal/report`. -- Direct Markdown reports have empty template and generated-text schema IDs. -- Generated-text-template reports declare prompt, template, and schema IDs in - their report definition. -- Valid periods are half-open intervals independent of rendered report text. -- Artifact grouping, batch output filenames, generated-report eligibility, - default module composition, comparison compatibility, and comparison strategy - are declared by report definition. -- App-owned batch planning uses report definitions but does not live in the - report registry. +All report selection goes through the registry, and the registry is the source +of truth for report identity—not rendered report text or app-local constants. diff --git a/docs/internal/reporttemplate.md b/docs/internal/reporttemplate.md index fae34c4..8daa63b 100644 --- a/docs/internal/reporttemplate.md +++ b/docs/internal/reporttemplate.md @@ -1,128 +1,51 @@ # Report Template Internals -This document describes embedded Markdown templates and GeneratedText schemas -in `internal/reporttemplate`. +`internal/reporttemplate` embeds and renders the repository's native Markdown +templates and exposes their companion generated-text schemas. The current asset +IDs are `daily`, `today`, `tomorrow`, and `hourly`. The template files, partials, +and complete render-context field reference are maintained in +[report templates](../templates.md). -## Purpose +## Assets and lookup -`internal/reporttemplate` owns repository-native report templates and companion -GeneratedText JSON schemas. The implemented template assets are Daily, Today, -Tomorrow, and Hourly. +The package embeds top-level templates, shared partials, and JSON schemas from +its asset directories. `Template` and `Schema` return the requested embedded +asset and fail with the requested ID when it is unknown or unreadable. -The package embeds assets from: +Generated-text catalog handlers obtain schema bytes and template source through +these APIs. Prompt source files are repository assets for prompt registration; +they are not reporttemplate lookup assets. Report definitions select IDs, while +[generated-text internals](generatedtext.md) verifies the supported +schema/template pairing. -- `internal/reporttemplate/templates/*.md.tmpl` -- `internal/reporttemplate/templates/partials/*.md.tmpl` -- `internal/reporttemplate/schemas/*.schema.json` +## Rendering -Generated-text prompt source files live under -`internal/reporttemplate/prompts/`. They are repository assets for prompt -registration, not embedded lookup APIs. +`Render` loads the top-level template, creates a `text/template` with helper +functions and `missingkey=error`, parses the template, parses every shared +partial, and executes the result against the typed render context. This makes +missing context fields, bad template syntax, unreadable partials, and execution +failures actionable with template or partial context. -## Inputs And Outputs +Top-level templates decide which shared partials they invoke. The current +partials cover daypart forecast variants, alert digest, and precipitation +timing. Template code receives curated typed contexts rather than raw data +packages, and it must not reimplement weather selection or generated-text +validation. -Inputs: +## Boundaries and verification -- template ID from a report definition -- typed render context built by `internal/generatedtext` +This package does not collect weather data, build modules, validate generated +text, construct contexts, resolve report definitions, write state, execute +Scriptorium, or upload reports. It produces Markdown bytes for application +orchestration to persist. -Outputs: +Focused tests cover asset lookup, schema availability, rendering, partial +behavior, missing keys, and malformed context: -- template source for inspection and tests -- GeneratedText schema bytes for prompt/schema configuration -- rendered Markdown bytes for app orchestration to persist +```sh +go test ./internal/reporttemplate +``` -The implemented template IDs are `daily`, `today`, `tomorrow`, and `hourly`. -The implemented schema IDs are also `daily`, `today`, `tomorrow`, and -`hourly`, backed by matching `*.generated_text.schema.json` files. - -Generated-text prompt sources are maintained under -`internal/reporttemplate/prompts/`, including Daily's -`daily.generated_text.md` source for prompt ID `weather.daily_generated_text`. - -## Boundaries - -This package owns embedded asset lookup, Go template parsing, and Markdown -template execution. It does not collect weather data, build module outputs, -validate GeneratedText, construct render contexts, choose report definitions, -write artifacts, invoke Scriptorium, or notify distributor. - -GeneratedText validation is owned by `internal/generatedtext`. App -orchestration uses `internal/generatedtext` catalog lookup to connect -`internal/report` definition schema/template IDs to the matching validator, -render-context builder, and embedded assets. - -## Template Contracts - -Daily, Today, Tomorrow, and Hourly rendering use typed render contexts with: - -- report metadata labels such as title, location, valid period, and generation - time -- validated GeneratedText prose slots -- deterministic labels derived from module outputs, including current - conditions, hourly forecast rows, precipitation timing, alerts, SPC outlooks, - forecast discussion, SPC discussion, and weather story - -Daily, Today, and Tomorrow additionally expose forecast-date labels, ordered -daypart forecast rows, daily/daypart summaries, planning facts, and a -multi-paragraph forecast discussion generated-text slot. The ordered daypart -slice is built in Go so templates do not range over maps. - -The Daily template asset uses the same Markdown structure as Tomorrow's -template and renders from `generatedtext.DailyRenderContext`. - -Templates use `text/template` with `missingkey=error`, so missing context fields -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 `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. The `alert_digest` partial renders the combined Alerts and Risk -Products section from relevant NWS alerts and curated SPC outlook digest -records. Rendered NWS alert bullets include alert identity and timing but omit -instruction and description text. Rendered SPC outlook bullets start at -Enhanced Risk; lower-risk SPC entries may still exist in module snapshots and -data packages. - -## Schema Contract - -The GeneratedText schemas describe the structured prose Scriptorium is expected -to write for each generated-text prompt. Hourly requires: - -- `summary` -- `forecast_discussion` - -Daily, Today, and Tomorrow require `summary` and a nonempty -`forecast_discussion` array. All generated-text schemas allow optional -`precipitation_timing` and `confidence`, and reject additional properties. -Weather truth remains in module outputs; GeneratedText is limited to prose -slots consumed by the template. - -## Failure Behavior - -- Unknown template IDs return actionable lookup errors. -- Unknown schema IDs return actionable lookup errors. -- Template parse errors include the template ID. -- Partial read or parse errors include the partial path. -- Template execution errors include the template ID and usually identify the - missing context field. - -## Tests - -Inspect: - -- `internal/reporttemplate/reporttemplate_test.go` -- `internal/generatedtext/render_context_test.go` -- `internal/app/app_test.go` -- `internal/cli/root_test.go` - -## Invariants - -- Embedded templates and schemas live as separate files, not inline Go strings. -- Shared Markdown partials live under `templates/partials/`. -- Report definitions select templates by ID. -- Templates render from curated render contexts, not raw data packages. -- GeneratedText schemas describe LLM prose slots, not deterministic weather - facts. +Embedded assets stay as separate files, shared fragments stay under the partial +directory, and generated-text schemas describe prose slots rather than +deterministic weather facts.