134 lines
6.8 KiB
Markdown
134 lines
6.8 KiB
Markdown
# 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`](../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:
|
|
|
|
```sh
|
|
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.
|