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/convectiveGET /outlooks/convective/activeGET /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
weatherapiremains read-only. It readsweatherfeederPostgres 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.
weatherapimust 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 wherevalidFrom <= now < validTo./outlooks/convective/location: latest run filtered to currently active outlooks wherecontainsLocation=true.
Query Parameters
All routes support:
format=json|xml|textunits=metric|us, accepted for consistency and with no payload effecttz/TZ, affecting response timestamp rendering onlyday=1|2|3outlookType=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
tzandTZ containsLocationon/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.RepositorywithLatestConvectiveOutlookRun(ctx). - Add
app.OutlookFilterwith optionalDay,OutlookType,ContainsLocation, andActiveAtfields. - Add
LatestConvectiveOutlook(ctx, filter)toapp.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_runsbyas_of DESC, event_emitted_at DESC. - Load child rows from
outlooksbyrun_event_id, ordered byoutlook_index ASC. - Map all canonical outlook columns, including
outlook_id,provider,contains_location, andgeometry_json. - Normalize timestamps to UTC and preserve
geometry_jsonasjson.RawMessage. - Return
nil, nilwhen no latest parent row exists.
Stage 3: HTTP Adapter And Presenter
- Extend
internal/adapters/inbound/httpapi.ServicewithLatestConvectiveOutlook(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, andcontainsLocationvalidation. - Add
outlookNow, defaulting totime.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.tmplfor 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,containsLocationsemantics, 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.0or the active workspace module providesmodel.WeatherOutlookRunandmodel.WeatherOutlook.- The first implementation serves only latest-run views; historical browsing remains future work.
/outlooks/convectivereturns all stored latest-run polygons by default, including polygons that do not contain the configured location./outlooks/convective/locationmeans active andcontainsLocation=true.unitsis accepted but does not alter outlook payload values or field names.- No
weatherapidatabase migration is required.
Open Questions
None. Route names, filtering semantics, adapter ownership, documentation timing, and verification expectations are decision-complete for implementation.