Files
weatherreporter/docs/roadmap/outlook.md

191 lines
7.5 KiB
Markdown

# 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
categorical 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
period_begins: "2026-06-12 at 8:00 AM"
period_ends: "2026-06-13 at 7:00 AM"
contains_location: true
image_url: "https://..."
```
The discussion module should be separate narrative context:
```yaml
briefing:
narrative_products:
area_forecast_discussion: {}
spc_convective_discussion:
included_because: "categorical severity_rank >= 3"
discussions:
- day: 1
period_begins: "2026-06-12 at 8:00 AM"
period_ends: "2026-06-13 at 7:00 AM"
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.