# Module Contract Internals This document describes the module contract in `internal/module`. ## Purpose `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. ## Inputs And Outputs Inputs: - ordered `module.ConfigItem` values from report definitions or config overrides - `module.Output` values produced by module builders Outputs: - 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` ## Rich Values And Prompt Exports Each `module.Output` has two value surfaces: - `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. `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. 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: - `current_conditions` - `hourly_forecast` - `derived_daypart_summaries` 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. ## 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 ``` 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`, `period_begins`, `period_ends`, `issued_at`, `contains_location`, and `image_url`. 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, fetch 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.