Add roadmap and staged plan for implementing SPC convective outlook support

This commit is contained in:
2026-06-12 09:42:43 -05:00
parent f149563c68
commit 2aba52f552
4 changed files with 784 additions and 3 deletions

View File

@@ -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=<configured weather_api.timezone>`
- 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.

190
docs/roadmap/outlook.md Normal file
View File

@@ -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.

View File

@@ -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:

View File

@@ -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)
}