From 2aba52f5520a994020d6357779119628f192d01c Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 12 Jun 2026 09:42:43 -0500 Subject: [PATCH] Add roadmap and staged plan for implementing SPC convective outlook support --- docs/roadmap/implementation.md | 591 +++++++++++++++++++++++++++++++++ docs/roadmap/outlook.md | 190 +++++++++++ examples/config.yml | 2 +- internal/config/config_test.go | 4 +- 4 files changed, 784 insertions(+), 3 deletions(-) create mode 100644 docs/roadmap/implementation.md create mode 100644 docs/roadmap/outlook.md diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..1ef7ba3 --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,591 @@ +# 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_outlooks` in `applicable_risk_products`; + - `spc_convective_discussion` in `narrative_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=json` and the + configured `tz`; do not send `precision`. +- Treat the source as optional under existing missing-source policy. +- Treat `data: null` as no latest run and therefore missing/unavailable + optional source data. +- Treat a non-null run with empty `outlooks` and `discussions` arrays 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/active` or + `/outlooks/convective/location` for 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.go` +- `internal/facts/facts.go` +- `internal/module/module.go` +- `internal/briefing/modules.go` +- `internal/weatherdata` and `internal/facts` tests + +Implementation: + +- Add `ConvectiveOutlookRun` to `internal/weatherdata`. +- Add `ConvectiveOutlook` with fields matching the consumed Weather API + outlook fields: + - `id` + - `provider` + - `product` + - `day` + - `outlookType` + - `label` + - `labelText` + - `forecaster` + - `severityRank` + - `validFrom` + - `validTo` + - `issuedAt` + - `expiresAt` + - `sourceUrl` + - `imageUrl` + - `containsLocation` + - `geometry` +- Store `geometry` as `json.RawMessage` or an equivalent JSON-preserving type; + do not introduce a GeoJSON dependency. +- Add `ConvectiveOutlookDiscussion` with: + - `day` + - `headline` + - `summary` + - `discussion` + - `updatedAt` +- Add `SPCConvectiveOutlooks *ConvectiveOutlookRun` to + `weatherdata.Bundle`. +- Add matching fields to `facts.CollectedFacts`, `BuildCollected`, and + `CollectedFacts.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: + - `CollectedSPCConvectiveOutlooks` + - `RequiresDerivedSPCConvectiveOutlooks` +- 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: + +```bash +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.go` +- `internal/adapters/weatherapi/client_test.go` +- `internal/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=json` + - `tz=` +- Do not send `precision` to this endpoint. +- Do not send `units` to this endpoint. If existing query helpers add units by + default, add a route-specific option such as `omitUnits` so the final request + remains `format` plus `tz`. +- Decode the Weather API envelope and payload into + `weatherdata.ConvectiveOutlookRun`. +- Preserve `data: null` as 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 `issuedAt` when present, otherwise `asOf`; + - source hash over compact raw `data` JSON; + - source warnings when missing-source policy emits them. +- 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: null` follows 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: + +```bash +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.go` +- `internal/forecast/derive.go` +- `internal/timeutil/periods.go` +- `internal/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.ConvectiveOutlook` + - `DerivedFacts.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 `severityRank` as 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: + +```bash +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.go` +- `internal/promptinput/package.go` +- `internal/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 `defaultModuleDefinitions` until 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: + +```bash +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.go` +- `internal/briefing/weather_story_module.go` +- `internal/briefing/module_format_helpers.go` +- `internal/briefing/base_modules_test.go` +- `docs/roadmap/outlook.md` + +Implementation: + +- Add `internal/briefing/spc_convective_outlooks_module.go`. +- Add the `SPCConvectiveOutlooks` module definition to + `defaultModuleDefinitions` in the same change as its real builder. +- Register stanza `spc_convective_outlooks`. +- Require: + - `CollectedSPCConvectiveOutlooks`; + - `RequiresDerivedSPCConvectiveOutlooks`. +- Use `MissingDataEmpty` so 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 omitted + `outlooks` list 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: + +```bash +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.go` +- `internal/briefing/weather_story_module.go` +- `internal/briefing/module_format_helpers.go` +- `internal/briefing/base_modules_test.go` + +Implementation: + +- Add `internal/briefing/spc_convective_discussion_module.go`. +- Add the `SPCConvectiveDiscussion` module definition to + `defaultModuleDefinitions` in the same change as its real builder. +- Register stanza `spc_convective_discussion`. +- Require: + - `CollectedSPCConvectiveOutlooks`; + - `RequiresDerivedSPCConvectiveOutlooks`. +- Use `MissingDataOmit` so 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 `nil` output 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_outlooks` but + do not emit `spc_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: + +```bash +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.go` +- `internal/report/three_day_report.go` +- `internal/report/weekend_report.go` +- `internal/report/storm_report.go` +- `internal/config/reports.go` +- `internal/config/config_test.go` +- `examples/config.yml` + +Implementation: + +- Add `spc_convective_outlooks` to default report module lists for: + - Daily Today; + - Daily Tomorrow; + - 3-Day; + - Weekend; + - Storm. +- Place `spc_convective_outlooks` immediately after `alert_digest` when + `alert_digest` is present. +- Add `spc_convective_discussion` immediately after + `area_forecast_discussion` when `area_forecast_discussion` is 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: + +```bash +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.go` +- `internal/promptinput/package_test.go` +- Weather API test server fixtures in `internal/adapters/weatherapi` +- `internal/state` artifact 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_discussion` when severity rank + is at least `3`; + - 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 `3` and 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: + +```bash +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.md` +- `docs/internal/weather-data.md` +- `docs/internal/facts.md` +- `docs/internal/module.md` +- `docs/internal/briefing.md` +- `docs/internal/prompt-input.md` +- `docs/config.md` if example module override behavior changes +- `examples/config.yml` if 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_outlooks` under `applicable_risk_products`; + - `spc_convective_discussion` under `narrative_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: + +```bash +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: + +```bash +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/convective` is 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 `3` or 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 `day` or `outlookType`; +- 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 --help` still matches documented CLI syntax; +- `git diff --check` passes; +- 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. diff --git a/docs/roadmap/outlook.md b/docs/roadmap/outlook.md new file mode 100644 index 0000000..7c54b65 --- /dev/null +++ b/docs/roadmap/outlook.md @@ -0,0 +1,190 @@ +# SPC Convective Outlook Roadmap + +## Purpose + +This roadmap defines future work to add SPC convective outlook support to +`weatherreporter`. The feature is not implemented yet, so current user, +operator, integration, and internal documentation must not describe it as +available behavior until the implementation lands. + +The goal is to consume location-filtered SPC convective outlook facts from the +Weather API once per report run, preserve source provenance, and expose concise +prompt-facing risk and narrative stanzas without making modules responsible for +upstream fetching. + +## Upstream Contract + +The Weather API currently documents these convective outlook routes: + +- `GET /outlooks/convective` +- `GET /outlooks/convective/active` +- `GET /outlooks/convective/location` + +The initial `weatherreporter` integration should use the latest-run route, +`/outlooks/convective`, because report valid periods may target tomorrow, +multi-day, weekend, or event windows. The `/active` and `/location` routes +filter using the server's current UTC time, which is useful for "active right +now" views but is too narrow for report-period-oriented generation. + +Important response semantics: + +- `data: null` means no latest outlook run exists. +- a non-null `data` object with empty `outlooks` and `discussions` arrays means + the endpoint was checked successfully and no matching outlooks were present. +- `precision` and unknown query parameters are rejected by the upstream + outlook routes. +- outlook GeoJSON coordinates use longitude, latitude order. +- `format`, `units`, and `tz` are supported by the upstream contract, but the + initial weatherreporter request should send only values needed for JSON + decoding and local-time presentation. + +## Locked Decisions + +- Add the source as an optional Weather API source named + `spc_convective_outlooks`. +- Define the Weather API endpoint path with a package-level constant in the + Weather API adapter rather than embedding a string literal throughout the + implementation. +- Start with the base latest-run route, not `/active` or `/location`. +- Fetch outlook facts once in the Weather API adapter and expose them through + `weatherdata.Bundle`, `facts.CollectedFacts`, and report-scoped derived + filtering. +- Do not let module builders make Weather API calls. +- Keep GeoJSON geometry in collected facts and persisted bundle/debug artifacts, + but omit geometry from prompt-facing module output by default. +- Add two prompt-facing modules backed by the same collected source: + `spc_convective_outlooks` and `spc_convective_discussion`. +- Place `spc_convective_outlooks` under `applicable_risk_products`. +- Place `spc_convective_discussion` under `narrative_products`, immediately + after `area_forecast_discussion` in report module order when both are present. +- Include SPC outlook discussion text only when at least one retained outlook + for the report valid period has `severity_rank >= 3`. +- Define that threshold as an internal constant so it can be adjusted later + without searching through module code. +- Treat a non-null run with empty arrays as checked empty data, not missing + data. +- Treat `data: null`, HTTP errors, and malformed payloads as optional-source + missing or malformed conditions using the configured missing-source policy. + +## Target Internal Shape + +Add normalized collected facts to `internal/weatherdata`: + +- `ConvectiveOutlookRun` +- `ConvectiveOutlook` +- `ConvectiveOutlookDiscussion` + +The run should include upstream run metadata, ordered outlooks, ordered +discussions, and enough raw/provenance data for inspection. The bundle should +gain a field similar to: + +```go +SPCConvectiveOutlooks *weatherdata.ConvectiveOutlookRun +``` + +`facts.CollectedFacts` should expose the same collected source. Derived facts +should provide report-period-filtered outlooks and discussions, or a small +forecast/facts helper should perform that filtering before module builders +shape prompt output. The filtering rule should use overlap with the resolved +report valid period, not server-current active status. + +## Prompt-Facing Shape + +The risk-product module should be concise and location-oriented: + +```yaml +briefing: + applicable_risk_products: + spc_convective_outlooks: + checked: true + as_of: "2026-06-12 at 7:00 AM" + issued_at: "2026-06-12 at 6:00 AM" + outlooks: + - day: 1 + outlook_type: categorical + label: SLGT + label_text: Slight Risk + severity_rank: 3 + valid_start: "2026-06-12 at 8:00 AM" + valid_end: "2026-06-13 at 7:00 AM" + contains_location: true + source_url: "https://..." + image_url: "https://..." +``` + +The discussion module should be separate narrative context: + +```yaml +briefing: + narrative_products: + area_forecast_discussion: {} + spc_convective_discussion: + included_because: "severity_rank >= 3" + discussions: + - day: 1 + headline: "Severe storms possible" + summary: "Scattered severe storms are possible." + discussion: "SPC discussion text." + updated_at: "2026-06-12 at 6:30 AM" +``` + +If outlook data is checked successfully and no report-period outlooks apply, +the risk-product stanza should make that explicit with `checked: true` and an +empty outlook count or empty list. The discussion stanza should be omitted when +the severity threshold is not met or no relevant discussion is available. + +## Implementation Plan + +The staged implementation plan for this feature lives in +`docs/roadmap/implementation.md`. This document remains the feature roadmap: +it defines the target state, user intent, and policy decisions that future +implementation work should preserve. + +## Test Plan + +Add focused coverage for: + +- Weather API decode of run metadata, outlooks, discussions, and geometry; +- endpoint path/query construction using the endpoint constant; +- `data: null` optional-source policy behavior; +- non-null empty `outlooks`/`discussions` as checked empty data; +- source provenance and data hash recording; +- valid-period overlap filtering for today, tomorrow, 3-day, weekend, and + storm windows; +- risk-product module output, omitted geometry, checked-empty behavior, and + prompt category; +- discussion module severity threshold behavior and prompt category; +- report default composition validation; +- app or prompt-input workflow proving generated YAML contains the new stanzas + when fixture data warrants them. + +Run at minimum: + +```bash +go test ./internal/adapters/weatherapi ./internal/weatherdata ./internal/facts ./internal/briefing ./internal/module ./internal/report ./internal/app ./internal/promptinput +go test ./... +go run ./cmd/weatherreporter --help +git diff --check +``` + +## Deferred Work + +Do not include these in the first implementation unless a separate roadmap +expands the scope: + +- calling `/outlooks/convective/active` or `/outlooks/convective/location`; +- user-configurable SPC discussion severity threshold; +- prompt-facing GeoJSON geometry; +- polygon distance, area, or map-rendered risk summaries; +- Mesoscale Discussions, watches, WPC outlooks, radar, QPF, or other risk + products; +- module-owned upstream fetching; +- custom per-report Weather API query filters such as `day` or `outlookType`. + +## Open Questions + +No open question blocks implementation. The recommended defaults above should +be used for the first pass. If fixture testing shows that the base latest-run +route includes too much irrelevant data, the viable alternative is to add +report-aware adapter query filters later, but that should be driven by observed +payload size or prompt quality rather than by the initial design. diff --git a/examples/config.yml b/examples/config.yml index 8812581..e3cb998 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -21,7 +21,7 @@ notify: token_env: DISTRIBUTOR_UPLOAD_TOKEN timeout: 30s failure_policy: error - pipeline_id_template: "weatherreporter.{artifact_group}" + pipeline_id_template: "weatherreporter.{report_id}" bundle_id_template: "weatherreporter.{location_id}.{report_id}" idempotency_key_template: "{bundle_id}.{run_id}" report_path_templates: diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f616552..56b7784 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -85,7 +85,7 @@ func TestLoadExampleConfig(t *testing.T) { if cfg.Location.ID != "home" || cfg.Location.Name != "Brentwood" || cfg.Location.Region != "St. Louis Metro" { t.Fatalf("Location = %#v, want example location", cfg.Location) } - if cfg.Notify.Distributor.PipelineIDTemplate != "weatherreporter.{artifact_group}" { + if cfg.Notify.Distributor.PipelineIDTemplate != "weatherreporter.{report_id}" { t.Fatalf("PipelineIDTemplate = %q, want example pipeline template", cfg.Notify.Distributor.PipelineIDTemplate) } if len(cfg.Notify.Distributor.ReportPathTemplates) != 1 { @@ -599,7 +599,7 @@ func TestLoadFileLoadsSecretsBeforeReturningNotifyConfig(t *testing.T) { "notify:\n" + " distributor:\n" + " enabled: true\n" + - " pipeline_id_template: weatherreporter.{artifact_group}\n" + " pipeline_id_template: weatherreporter.{report_id}\n" if err := os.WriteFile(path, []byte(configYAML), 0o600); err != nil { t.Fatalf("write config fixture: %v", err) }