20 KiB
SPC Convective Outlook Implementation Roadmap
Purpose
This roadmap defines the concrete implementation sequence for
docs/roadmap/outlook.md. It is written for an LLM coding agent that will
implement the stages in order.
This is a future-work roadmap. Until a stage is implemented, non-roadmap docs must not describe SPC convective outlook behavior as available.
Source Feature Roadmap
Use docs/roadmap/outlook.md as the authoritative feature roadmap for intent,
target shape, and policy decisions. This implementation roadmap is the
step-by-step work plan. If the two documents conflict, update the feature
roadmap first when the target behavior changes, then update this implementation
plan.
Locked Implementation Decisions
- Preserve existing CLI syntax, generated artifact paths, report IDs, prompt IDs, distributor behavior, and Scriptorium invocation.
- Use source key and module IDs based on
spc_convective_outlooks. - Add two modules:
spc_convective_outlooksinapplicable_risk_products;spc_convective_discussioninnarrative_products.
- Fetch the upstream Weather API source once in
internal/adapters/weatherapi; module builders must not fetch upstream data. - Use a Weather API adapter constant for the path:
/outlooks/convective. - Initial query parameters for the outlook endpoint are
format=jsonand the configuredtz; do not sendprecision. - Treat the source as optional under existing missing-source policy.
- Treat
data: nullas no latest run and therefore missing/unavailable optional source data. - Treat a non-null run with empty
outlooksanddiscussionsarrays as checked, non-missing empty data. - Preserve GeoJSON geometry in collected facts and persisted bundle artifacts, but omit geometry from prompt-facing module output.
- Filter outlooks by overlap with the resolved report valid period in Go.
- Do not depend on
/outlooks/convective/activeor/outlooks/convective/locationfor initial behavior. - Include SPC discussion text only when at least one retained report-period
outlook has
severity_rank >= 3. - Define the discussion threshold as an internal constant, initially
3, not a config field.
Stage 1: Weather Data Contract
Goal: add typed collected SPC outlook facts without changing fetching or prompt output yet.
Files to inspect:
internal/weatherdata/bundle.gointernal/facts/facts.gointernal/module/module.gointernal/briefing/modules.gointernal/weatherdataandinternal/factstests
Implementation:
- Add
ConvectiveOutlookRuntointernal/weatherdata. - Add
ConvectiveOutlookwith fields matching the consumed Weather API outlook fields:idproviderproductdayoutlookTypelabellabelTextforecasterseverityRankvalidFromvalidToissuedAtexpiresAtsourceUrlimageUrlcontainsLocationgeometry
- Store
geometryasjson.RawMessageor an equivalent JSON-preserving type; do not introduce a GeoJSON dependency. - Add
ConvectiveOutlookDiscussionwith:dayheadlinesummarydiscussionupdatedAt
- Add
SPCConvectiveOutlooks *ConvectiveOutlookRuntoweatherdata.Bundle. - Add matching fields to
facts.CollectedFacts,BuildCollected, andCollectedFacts.Bundle. - Add module constants in
internal/module:SPCConvectiveOutlooks ID = "spc_convective_outlooks"SPCConvectiveDiscussion ID = "spc_convective_discussion"
- Add option structs:
SPCConvectiveOutlooksOptions struct{}SPCConvectiveDiscussionOptions struct{}
- Add fact requirement constants:
CollectedSPCConvectiveOutlooksRequiresDerivedSPCConvectiveOutlooks
- Wire the new collected requirement into
briefing.collectedFactAvailable.
Acceptance criteria:
- No Weather API request is added in this stage.
- No report default module list changes in this stage.
- No prompt package shape changes in this stage.
- Existing tests pass.
Suggested validation:
go test ./internal/weatherdata ./internal/facts ./internal/module ./internal/briefing
This stage is small enough for one implementation prompt.
Stage 2: Weather API Adapter Fetch
Goal: fetch /outlooks/convective, decode it into the new weather data
contract, and preserve source provenance.
Files to inspect:
internal/adapters/weatherapi/client.gointernal/adapters/weatherapi/client_test.gointernal/adapters/weatherapi/testdata/docs/roadmap/outlook.md- upstream Weather API docs under the Convective Outlooks section
Implementation:
- Add a package-level adapter constant, for example:
convectiveOutlooksEndpoint = "/outlooks/convective". - Add source-name constant or narrowly scoped source key for
spc_convective_outlooks. - Add fan-out fetch for the new optional source.
- Build the query as:
format=jsontz=<configured weather_api.timezone>
- Do not send
precisionto this endpoint. - Do not send
unitsto this endpoint. If existing query helpers add units by default, add a route-specific option such asomitUnitsso the final request remainsformatplustz. - Decode the Weather API envelope and payload into
weatherdata.ConvectiveOutlookRun. - Preserve
data: nullas optional missing/unavailable source data through existing missing-source policy. - Preserve a non-null run with empty arrays as checked non-missing source data.
- Record source provenance:
- source name
spc_convective_outlooks; - endpoint constant path;
- exact query parameters sent;
- fetched time;
- issued time from run
issuedAtwhen present, otherwiseasOf; - source hash over compact raw
dataJSON; - source warnings when missing-source policy emits them.
- source name
- Add
internal/adapters/weatherapi/testdata/convective_outlooks.json.
Acceptance criteria:
- Complete fixture fetch includes
bundle.SPCConvectiveOutlooks. - Source record is present and non-missing when the payload has a non-null run.
data: nullfollows optional missing-source policy.- Non-null empty arrays do not produce a missing-source warning.
- Adapter tests assert request path and query, including absence of
precision. - Existing hourly required-source behavior is unchanged.
Suggested validation:
go test ./internal/adapters/weatherapi ./internal/weatherdata ./internal/facts
This stage is small enough for one implementation prompt.
Stage 3: Report-Period Outlook Filtering
Goal: derive report-scoped SPC outlook facts by valid-period overlap.
Files to inspect:
internal/facts/facts.gointernal/forecast/derive.gointernal/timeutil/periods.gointernal/report/*_report.go- existing forecast/facts tests for period slicing
Implementation:
- Add a report-scoped derived value for retained outlooks and discussions.
Recommended shape:
DerivedFacts.SPCConvectiveOutlooks []weatherdata.ConvectiveOutlookDerivedFacts.SPCConvectiveDiscussions []weatherdata.ConvectiveOutlookDiscussion
- Add a small deterministic helper under
internal/facts; this first implementation is report-scoped selection of already-collected facts, not a broader meteorological derivation. - Select outlooks whose half-open valid interval overlaps the resolved report valid period.
- Treat missing
severityRankas lower than the discussion threshold, but do not drop the outlook from the risk-product module solely because rank is missing. - Retain discussion records only for days represented by retained outlooks.
- Sort retained outlooks deterministically by:
- day;
- outlook type;
- severity rank descending when present;
- valid start;
- label;
- id.
- Sort retained discussions by day, then updated time when present.
- Keep empty retained slices distinct from a missing collected source.
Acceptance criteria:
- Daily Today, Daily Tomorrow, 3-Day, Weekend, and Storm valid periods select expected outlooks by overlap.
- Tomorrow and multi-day reports do not depend on server-current active filtering.
- Empty retained results are still available to modules as checked empty data when the collected source exists.
Suggested validation:
go test ./internal/facts ./internal/forecast ./internal/report
This stage is small enough for one implementation prompt.
Stage 4: Prompt Category Plumbing
Goal: prepare prompt-package category placement without registering builderless modules.
Files to inspect:
internal/module/module.gointernal/promptinput/package.gointernal/promptinput/package_test.go
Implementation:
- Add prompt input category mapping:
spc_convective_outlooks->applicable_risk_products;spc_convective_discussion->narrative_products.
- Use synthetic module snapshots in tests if needed; do not add module
definitions to
defaultModuleDefinitionsuntil the real builders are added in Stages 5 and 6.
Acceptance criteria:
- Prompt category tests prove both new stanzas route to the intended groups.
- The module registry still rejects unknown or builderless modules.
- No report default includes the new modules yet.
Suggested validation:
go test ./internal/module ./internal/promptinput
This stage is small enough for one implementation prompt.
Stage 5: SPC Convective Outlooks Module
Goal: add the prompt-facing risk-product module.
Files to inspect:
internal/briefing/alert_digest_module.gointernal/briefing/weather_story_module.gointernal/briefing/module_format_helpers.gointernal/briefing/base_modules_test.godocs/roadmap/outlook.md
Implementation:
- Add
internal/briefing/spc_convective_outlooks_module.go. - Add the
SPCConvectiveOutlooksmodule definition todefaultModuleDefinitionsin the same change as its real builder. - Register stanza
spc_convective_outlooks. - Require:
CollectedSPCConvectiveOutlooks;RequiresDerivedSPCConvectiveOutlooks.
- Use
MissingDataEmptyso checked empty data can emit an explicit empty risk-product stanza. - Build from collected source metadata plus derived retained outlooks.
- Emit concise prompt-facing fields:
checked;as_of;issued_at;location_id;location_name;outlook_count;outlooks.
- For each outlook, emit:
day;outlook_type;label;label_text;severity_rank;valid_start;valid_end;issued_at;expires_at;contains_location;source_url;image_url.
- Use human-readable local time helpers consistent with current modules.
- Do not emit GeoJSON geometry.
- If the source was checked and no retained outlooks overlap the report
period, emit
checked: true,outlook_count: 0, and an empty or omittedoutlookslist according to the existing YAML style for empty lists.
Acceptance criteria:
- Module output is deterministic and omits geometry.
- Checked empty data produces an explicit checked-empty stanza.
- Missing collected source follows registry missing-data behavior.
- Module tests cover populated, checked-empty, and missing cases.
Suggested validation:
go test ./internal/briefing ./internal/module ./internal/promptinput
This stage is small enough for one implementation prompt.
Stage 6: SPC Convective Discussion Module
Goal: add optional SPC discussion narrative context with a severity threshold.
Files to inspect:
internal/briefing/area_forecast_discussion_module.gointernal/briefing/weather_story_module.gointernal/briefing/module_format_helpers.gointernal/briefing/base_modules_test.go
Implementation:
- Add
internal/briefing/spc_convective_discussion_module.go. - Add the
SPCConvectiveDiscussionmodule definition todefaultModuleDefinitionsin the same change as its real builder. - Register stanza
spc_convective_discussion. - Require:
CollectedSPCConvectiveOutlooks;RequiresDerivedSPCConvectiveOutlooks.
- Use
MissingDataOmitso unavailable or below-threshold discussion text is omitted. - Define a package-private constant near the module, for example:
defaultSPCConvectiveDiscussionMinimumSeverityRank = 3. - Build from derived retained outlooks and discussions.
- Include discussion text only when at least one retained outlook has
severityRank >= defaultSPCConvectiveDiscussionMinimumSeverityRank. - When the threshold is not met, return
niloutput so the stanza is omitted. - When threshold is met, include discussions for retained outlook days with:
day;headline;summary;discussion;updated_at.
- Include a concise reason field such as:
included_because: "severity_rank >= 3".
Acceptance criteria:
- Slight Risk or higher retained outlooks include matching discussion records when available.
- Lower-risk retained outlooks still appear in
spc_convective_outlooksbut do not emitspc_convective_discussion. - Missing discussion text omits the stanza without failing report generation.
- Tests cover threshold below, threshold equal, threshold above, and missing discussion cases.
Suggested validation:
go test ./internal/briefing ./internal/promptinput
This stage is small enough for one implementation prompt.
Stage 7: Report Composition And Config Examples
Goal: add the implemented modules to default report definitions and maintained examples.
Files to inspect:
internal/report/daily_report.gointernal/report/three_day_report.gointernal/report/weekend_report.gointernal/report/storm_report.gointernal/config/reports.gointernal/config/config_test.goexamples/config.yml
Implementation:
- Add
spc_convective_outlooksto default report module lists for:- Daily Today;
- Daily Tomorrow;
- 3-Day;
- Weekend;
- Storm.
- Place
spc_convective_outlooksimmediately afteralert_digestwhenalert_digestis present. - Add
spc_convective_discussionimmediately afterarea_forecast_discussionwhenarea_forecast_discussionis present. - Update maintained example config module overrides if they enumerate module lists.
- Keep CLI syntax and config field names unchanged.
Acceptance criteria:
- All default report module compositions validate.
- Example config loads successfully.
- Config override tests can include both new module IDs.
- Existing report IDs, prompt IDs, output names, and valid-period behavior are unchanged.
Suggested validation:
go test ./internal/report ./internal/config ./internal/briefing ./internal/app
This stage is small enough for one implementation prompt.
Stage 8: App And Prompt Workflow Coverage
Goal: prove the end-to-end generated data package contains the new stanzas in the intended categories when fixture data warrants them.
Files to inspect:
internal/app/app_test.gointernal/promptinput/package_test.go- Weather API test server fixtures in
internal/adapters/weatherapi internal/stateartifact path behavior if tests inspect saved files
Implementation:
- Extend app-level Weather API fixtures to serve convective outlook data.
- Add or update workflow tests so saved YAML contains:
briefing.applicable_risk_products.spc_convective_outlooks;briefing.narrative_products.spc_convective_discussionwhen severity rank is at least3;- no geometry in prompt-facing YAML.
- Add a workflow case where outlook data is checked but empty and the risk-product stanza remains explicit.
- Add a workflow case where severity rank is below
3and the discussion stanza is omitted. - Keep existing Recent Changes behavior unchanged unless a later roadmap adds comparisons for SPC outlooks.
Acceptance criteria:
- Module snapshot and YAML data package remain deterministic.
- Prompt category grouping preserves module order within categories.
- No Scriptorium or distributor behavior changes are required.
Suggested validation:
go test ./internal/app ./internal/promptinput ./internal/state
This stage is small enough for one implementation prompt.
Stage 9: Implemented Documentation
Goal: update non-roadmap docs after the feature is implemented.
Files to inspect and update:
docs/integrations/weatherapi.mddocs/internal/weather-data.mddocs/internal/facts.mddocs/internal/module.mddocs/internal/briefing.mddocs/internal/prompt-input.mddocs/config.mdif example module override behavior changesexamples/config.ymlif not already updated in Stage 7
Documentation requirements:
- Describe only the implemented SPC behavior outside
docs/roadmap/. - In the Weather API integration doc, include only the route, query, response fields, and missing/empty semantics used by weatherreporter.
- In internal docs, distinguish:
- collected source facts and geometry/provenance;
- derived report-period filtering;
- prompt-facing module output that omits geometry.
- In prompt-input docs, list:
spc_convective_outlooksunderapplicable_risk_products;spc_convective_discussionundernarrative_products.
- Keep deferred route choices, geometry presentation, and user-configurable threshold ideas under roadmap docs only.
Acceptance criteria:
- Non-roadmap docs do not describe deferred SPC behavior as current behavior.
- Maintained examples load.
- Documentation links and module ID lists are consistent with code.
Suggested validation:
go test ./internal/config
git diff --check
This stage is small enough for one implementation prompt.
Stage 10: Final Validation
Goal: validate the complete feature and guard against regressions.
Run:
go test ./internal/adapters/weatherapi ./internal/weatherdata ./internal/facts ./internal/forecast ./internal/briefing ./internal/module ./internal/report ./internal/config ./internal/app ./internal/promptinput
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
Manual review:
- Confirm
/outlooks/convectiveis referenced through an adapter constant. - Confirm Weather API outlook requests do not send
precision. - Confirm no module builder performs Weather API calls.
- Confirm prompt YAML omits GeoJSON geometry.
- Confirm checked-empty outlook data is not represented as missing data.
- Confirm SPC discussion text appears only at severity rank
3or higher. - Confirm public CLI syntax, output paths, distributor upload behavior, and Scriptorium argv remain unchanged.
This stage is small enough for one implementation prompt.
Deferred Work
Out of scope for the initial implementation:
- use of
/outlooks/convective/active; - use of
/outlooks/convective/location; - per-report Weather API filters such as
dayoroutlookType; - user-configurable SPC discussion severity threshold;
- prompt-facing GeoJSON geometry;
- polygon distance, area, map summaries, or rendered images;
- Mesoscale Discussions, watches, WPC outlooks, radar, QPF, or other risk products;
- Recent Changes comparisons for SPC outlook changes;
- module-owned upstream fetching.
Global Validation Checklist
Before considering the feature complete:
- all focused package tests pass;
go test ./...passes;go run ./cmd/weatherreporter --helpstill matches documented CLI syntax;git diff --checkpasses;- examples load through config tests;
- non-roadmap docs describe only implemented behavior;
- no secret values or large raw geometry are introduced into prompt-facing output;
- source provenance and warnings remain inspectable through existing metadata and state artifacts.
Open Questions
No question blocks implementation.
The recommended approach is to implement the locked decisions exactly as
described above. The main viable alternative is to query
/outlooks/convective/active or /outlooks/convective/location, but that
would make tomorrow, multi-day, weekend, and event reports depend on
server-current active filtering rather than report valid periods. That
alternative should be deferred unless fixture payloads from the base latest-run
route prove too large or too irrelevant for prompt use.