Files
weatherapi/docs/roadmap/outlook.md

6.8 KiB

SPC Convective Outlook API Roadmap

Status: implemented. This file is retained as planning history; the current public HTTP contract is documented in docs/api.md.

Summary

Add weatherapi support for SPC convective outlook data stored by weatherfeeder.

This was roadmap-only content while the endpoint work was pending. Current behavior now belongs in README.md, docs/api.md, and the relevant internal and integration docs.

Target endpoints:

  • GET /outlooks/convective
  • GET /outlooks/convective/active
  • GET /outlooks/convective/location

Each endpoint returns the standard weatherapi response envelope. Missing latest data returns {"data": null}. When a latest run exists but filters match no outlooks, return the run metadata with outlooks: [].

Architecture And Boundary Decisions

  • weatherapi remains read-only. It reads weatherfeeder Postgres tables and does not ingest SPC data, call SPC provider APIs, create tables, or run migrations.
  • The application service owns latest-run filtering policy. The Postgres adapter reconstructs the latest stored canonical run; it should not encode HTTP query semantics.
  • HTTP adapters own route registration, query binding, request validation, and filter construction.
  • Presenters own timezone conversion and copy semantics. Repository methods normalize database timestamps to UTC.
  • Geometry is returned as stored GeoJSON. weatherapi must not simplify, transform, or recompute polygons.

Public API Contract

Routes

  • /outlooks/convective: latest convective outlook run with all stored outlook polygons.
  • /outlooks/convective/active: latest run filtered to outlooks where validFrom <= now < validTo.
  • /outlooks/convective/location: latest run filtered to currently active outlooks where containsLocation=true.

Query Parameters

All routes support:

  • format=json|xml|text
  • units=metric|us, accepted for consistency and with no payload effect
  • tz / TZ, affecting response timestamp rendering only
  • day=1|2|3
  • outlookType=categorical|tornado|hail|wind

/outlooks/convective and /outlooks/convective/active also support:

  • containsLocation=true|false

Reject with 400 Bad Request:

  • precision
  • unknown query parameters
  • invalid day, outlookType, containsLocation, or timezone values
  • conflicting tz and TZ
  • containsLocation on /outlooks/convective/location

Response Shape

Return a model.WeatherOutlookRun-compatible payload:

  • run fields: locationId, locationName, latitude, longitude, asOf, issuedAt, outlooks
  • outlook fields: id, provider, product, day, outlookType, label, labelText, severityRank, validFrom, validTo, issuedAt, expiresAt, forecaster, headline, summary, discussion, sourceUrl, imageUrl, containsLocation, geometry

Timezone conversion applies to run asOf, run issuedAt, and outlook validFrom, validTo, issuedAt, and expiresAt. Active filtering compares instants and is not timezone-dependent.

Implementation Stages

Stage 1: Application Use Case

  • Extend internal/app.Repository with LatestConvectiveOutlookRun(ctx).
  • Add app.OutlookFilter with optional Day, OutlookType, ContainsLocation, and ActiveAt fields.
  • Add LatestConvectiveOutlook(ctx, filter) to app.Service.
  • Implement filtering by cloning the latest repository run and filtering the copied outlook slice while preserving order.
  • Use an injectable request-time value from the HTTP adapter for active/location filters.

Stage 2: Postgres Read Adapter

  • Add outlook SQL, row DTOs, mapper, and read methods under internal/adapters/outbound/postgres, following the weather stories read pattern.
  • Query the latest parent row from outlook_runs by as_of DESC, event_emitted_at DESC.
  • Load child rows from outlooks by run_event_id, ordered by outlook_index ASC.
  • Map all canonical outlook columns, including outlook_id, provider, contains_location, and geometry_json.
  • Normalize timestamps to UTC and preserve geometry_json as json.RawMessage.
  • Return nil, nil when no latest parent row exists.

Stage 3: HTTP Adapter And Presenter

  • Extend internal/adapters/inbound/httpapi.Service with LatestConvectiveOutlook(ctx, app.OutlookFilter).
  • Register the three outlook routes with JSON, XML, and text support.
  • Add an outlook query binder for common query params plus day, outlookType, and containsLocation validation.
  • Add outlookNow, defaulting to time.Now, for deterministic active/location endpoint tests.
  • Add presenter helpers that copy the model, convert timestamps to the requested timezone, preserve geometry bytes, and never mutate repository-returned values.
  • Add templates/outlooks_convective.txt.tmpl for all three outlook routes.

Stage 4: Documentation After Implementation

Update current-behavior docs only in the same change that implements the routes:

  • README.md: endpoint list and short query-parameter summary.
  • docs/api.md: route family, query params, validation behavior, response fields, examples, containsLocation semantics, active filtering semantics, and GeoJSON longitude/latitude coordinate order.
  • Internal or integration docs only if implementation changes repository assumptions or adapter boundaries beyond the planned latest-run reads.

Test Plan

  • App tests: delegation, no-data behavior, active/day/type/location filters, combined filters, and non-mutating clone behavior.
  • Postgres tests: parent and child row mapping, nullable fields, UTC normalization, geometry preservation, child ordering, and no-row behavior.
  • HTTP tests: route registration, JSON/XML/text responses, null data, empty filtered outlooks, timezone conversion, valid filters, rejected query params, invalid filter values, invalid timezone, and conflicting tz/TZ.
  • Presenter tests: nil input, timezone conversion, copy semantics, and exact geometry preservation.

Verification commands:

go test ./internal/app
go test ./internal/adapters/outbound/postgres
go test ./internal/adapters/inbound/httpapi
go test ./internal/adapters/inbound/httpapi/presenter
go test ./...

Assumptions And Defaults

  • weatherfeeder v0.11.0 or the active workspace module provides model.WeatherOutlookRun and model.WeatherOutlook.
  • The first implementation serves only latest-run views; historical browsing remains future work.
  • /outlooks/convective returns all stored latest-run polygons by default, including polygons that do not contain the configured location.
  • /outlooks/convective/location means active and containsLocation=true.
  • units is accepted but does not alter outlook payload values or field names.
  • No weatherapi database migration is required.

Open Questions

None. Route names, filtering semantics, adapter ownership, documentation timing, and verification expectations are decision-complete for implementation.