Files
weatherreporter/docs/roadmap/outlook.md

7.5 KiB

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:

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:

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:

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:

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.