diff --git a/docs/api.md b/docs/api.md index 9343ce4..28cc868 100644 --- a/docs/api.md +++ b/docs/api.md @@ -230,19 +230,22 @@ GET /outlooks/convective/location ``` Returns the latest SPC convective outlook run reconstructed from -weatherfeeder-owned Postgres tables. +weatherfeeder-owned `weather.outlook.v2` Postgres tables. Route behavior: -- `/outlooks/convective` returns the latest run with all stored outlook - polygons unless user filters are supplied. +- `/outlooks/convective` returns the latest run with stored location-filtered + outlook polygons unless user filters are supplied. - `/outlooks/convective/active` adds an active-time filter using the server's current UTC time. Outlooks are active when `validFrom <= now < validTo`. - `/outlooks/convective/location` adds the same active-time filter and - `containsLocation=true`. + `containsLocation=true`. It remains as an active local-outlook compatibility + route under the v2 weatherfeeder contract. When no latest run exists, `data` is null. When a run exists but filters match -no outlooks, `data` remains an object and `outlooks` is an empty array. +no outlooks, `data` remains an object and `outlooks` and `discussions` are +empty arrays. Outlook endpoints use latest-run semantics and do not accumulate +historical active outlooks across older runs. Query parameters: @@ -253,9 +256,12 @@ Query parameters: | `outlookType` | all outlook routes | `categorical`, `tornado`, `hail`, or `wind` | | `containsLocation` | `/outlooks/convective`, `/outlooks/convective/active` | boolean | -`outlookType` values are normalized case-insensitively. `containsLocation` is -rejected on `/outlooks/convective/location` because that route always applies -`containsLocation=true`. `precision` and unknown parameters are rejected. +`outlookType` values are normalized case-insensitively. Weatherfeeder v2 +outlooks are already filtered for the configured location, so +`containsLocation` is expected to be true for stored v2 outlooks. +`containsLocation` is rejected on `/outlooks/convective/location` because that +route always applies `containsLocation=true`. `precision` and unknown +parameters are rejected. Run `data` fields: @@ -266,6 +272,7 @@ Run `data` fields: | `asOf` | RFC3339 datetime | required when `data` is not null | | `issuedAt` | RFC3339 datetime | optional | | `outlooks` | array | ordered outlook polygons, possibly empty | +| `discussions` | array | ordered day-level discussions, possibly empty | Outlook fields: @@ -280,10 +287,20 @@ Outlook fields: | `containsLocation` | boolean | whether the outlook polygon contains the configured location | | `geometry` | GeoJSON | stored outlook geometry | +Discussion fields: + +| Field | Type | Notes | +| --- | --- | --- | +| `day` | integer | SPC outlook day | +| `headline`, `summary`, `discussion` | string | optional | +| `updatedAt` | RFC3339 datetime | optional | + GeoJSON coordinates use standard GeoJSON coordinate order: longitude, then latitude. Timezone conversion applies to run `asOf`, run `issuedAt`, and each -outlook's `validFrom`, `validTo`, `issuedAt`, and `expiresAt`. Active filtering -compares instants and is not changed by the presentation timezone. +outlook's `validFrom`, `validTo`, `issuedAt`, and `expiresAt`, and discussion +`updatedAt`. Active filtering compares instants and is not changed by the +presentation timezone. Endpoint filters also filter `discussions` to days +represented by retained outlooks. Examples: @@ -328,6 +345,15 @@ Example JSON response: ] } } + ], + "discussions": [ + { + "day": 1, + "headline": "Severe storms possible", + "summary": "Scattered severe storms are possible.", + "discussion": "SPC discussion text.", + "updatedAt": "2026-06-11T17:30:00Z" + } ] } } @@ -354,10 +380,25 @@ Example location-filtered JSON response: "expiresAt": "2026-06-12T12:00:00Z", "containsLocation": true, "geometry": { - "type": "Point", - "coordinates": [-90.2, 38.6] + "type": "Polygon", + "coordinates": [ + [ + [-90.6, 38.4], + [-90.0, 38.4], + [-90.0, 38.8], + [-90.6, 38.4] + ] + ] } } + ], + "discussions": [ + { + "day": 1, + "headline": "Tornado risk near the configured location", + "discussion": "SPC tornado outlook discussion text.", + "updatedAt": "2026-06-11T17:30:00Z" + } ] } } diff --git a/docs/integrations/weatherfeeder-postgres.md b/docs/integrations/weatherfeeder-postgres.md index 120db2a..411c788 100644 --- a/docs/integrations/weatherfeeder-postgres.md +++ b/docs/integrations/weatherfeeder-postgres.md @@ -8,10 +8,12 @@ `go.mod` depends on: -- `gitea.maximumdirect.net/ejr/weatherfeeder v0.11.0` +- `gitea.maximumdirect.net/ejr/weatherfeeder v0.11.1-0.20260612044033-4358a7cdce2c` The repository code also depends on weatherfeeder canonical model types. Table compatibility must match the SQL in `internal/adapters/outbound/postgres`. +For convective outlooks, `weatherapi` assumes weatherfeeder's +`weather.outlook.v2` table reset has already been applied. ## Boundary @@ -37,7 +39,7 @@ Postgres owns persistence, backup, restore, and availability. | Forecast discussion | `forecast_discussions`, `forecast_discussion_key_messages` | | Weather story run | `weather_story_runs`, `weather_stories` | | Latest weather story | `weather_stories` | -| Convective outlook run | `outlook_runs`, `outlooks` | +| Convective outlook run | `outlook_runs`, `outlooks`, `outlook_discussions` | ## Latest Row Selection @@ -68,7 +70,8 @@ Child rows are loaded separately and attached in stored order: - forecast periods: `period_index ASC`; - forecast discussion key messages: `message_index ASC`; - weather stories for a run: `story_index ASC`; -- outlooks for a run: `outlook_index ASC`. +- outlooks for a run: `outlook_index ASC`; +- outlook discussions for a run: `discussion_index ASC`. ## Columns Read @@ -166,6 +169,11 @@ routes. `geometry_json` is copied into response GeoJSON without parsing or reserializing. It must contain valid JSON. +### `outlook_discussions` + +`discussion_index`, `day`, `headline`, `summary`, `discussion`, `updated_at`, +and `run_event_id`. + ## Nullability and Time Assumptions The repository scans nullable columns with `sql.Null*` types and maps them to diff --git a/docs/internal/http-adapter.md b/docs/internal/http-adapter.md index fede6e3..cf366fa 100644 --- a/docs/internal/http-adapter.md +++ b/docs/internal/http-adapter.md @@ -144,6 +144,11 @@ application service: The package variable `outlookNow` exists so endpoint tests can make active and location filtering deterministic. +The application service returns filtered outlook copies and trims run-level +discussions to days represented by retained outlooks. +`/outlooks/convective/location` is retained for compatibility and active +local-outlook behavior under the weatherfeeder outlook v2 contract. + ## Failure Behavior Binder failures become feedapi invalid-parameter responses. Handler service diff --git a/docs/internal/postgres-repository.md b/docs/internal/postgres-repository.md index 159ca7c..592cec4 100644 --- a/docs/internal/postgres-repository.md +++ b/docs/internal/postgres-repository.md @@ -90,7 +90,7 @@ successful responses with `data: null`. `weather_stories`. - `LatestWeatherStory`: latest individual row from `weather_stories`. - `LatestConvectiveOutlookRun`: latest row from `outlook_runs`, then child - `outlooks`. + `outlooks` and `outlook_discussions`. Latest parent rows are selected by descending weather timestamp and `event_emitted_at` where that tie-breaker is available in the query. @@ -105,7 +105,8 @@ Child queries preserve stored order: - forecast periods by `period_index`; - discussion key messages by `message_index`; - weather stories by `story_index`; -- outlooks by `outlook_index`. +- outlooks by `outlook_index`; +- outlook discussions by `discussion_index`. Alert references are attached after both alert and reference rows are loaded. References are grouped by alert index and attached to their corresponding alert. diff --git a/docs/internal/presenters.md b/docs/internal/presenters.md index 732db06..cc586cf 100644 --- a/docs/internal/presenters.md +++ b/docs/internal/presenters.md @@ -118,9 +118,11 @@ response envelope so renderers can produce `data: null`. - Discussions: full or focused payload shapes, section copy, key-message copy, timezone conversion. - Weather stories: run/story copy and timezone conversion. -- Convective outlooks: canonical model copy, pointer and geometry copy, and - timezone conversion. `units` is accepted by routes but ignored by the - presenter because outlook fields are not unit-bearing. +- Convective outlooks: canonical model copy, pointer and geometry copy, + run-level discussion copy, and timezone conversion for run, outlook, and + discussion timestamps. Outlook polygon prose is not handled by the presenter; + prose is carried by run-level discussions. `units` is accepted by routes but + ignored by the presenter because outlook fields are not unit-bearing. ## Templates diff --git a/docs/operations.md b/docs/operations.md index a7a11e6..9640453 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -91,8 +91,13 @@ build. ## Database Dependency `weatherapi` expects weatherfeeder-compatible tables for observations, current -conditions aggregation, active alerts, forecasts, forecast discussions, and -weather stories. It only reads those tables. +conditions aggregation, active alerts, forecasts, forecast discussions, weather +stories, and convective outlooks. It only reads those tables. + +Convective outlook endpoints require weatherfeeder's `weather.outlook.v2` table +shape, including `outlook_runs`, `outlooks`, and `outlook_discussions`. If +operators reset or recreate outlook tables during a weatherfeeder upgrade, +complete that weatherfeeder-side migration before starting `weatherapi`. Operational ownership is split: @@ -113,7 +118,7 @@ template named by each endpoint, with templates stored under `templates.base_dir The repository includes templates for all implemented endpoint families: observations, current conditions, active alerts, hourly forecasts, narrative -forecasts, forecast discussions, and weather stories. +forecasts, forecast discussions, weather stories, and convective outlooks. If text rendering fails or returns an unsupported-format error, verify: diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index d1fff6d..5769831 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,5 +1,12 @@ # Implement Weatherfeeder Outlook V2 Support +## Status + +Implementation and current-behavior documentation are complete. This file now +serves as the checklist and verification record for the outlook v2 compatibility +update. Authoritative implemented behavior is documented in `docs/api.md`, +`docs/integrations/weatherfeeder-postgres.md`, and `docs/internal/`. + ## Summary Implement `weatherapi` support for the `weatherfeeder` SPC outlook v2 contract described in `docs/roadmap/outlook.md`. diff --git a/docs/roadmap/outlook.md b/docs/roadmap/outlook.md index 2658263..6591a40 100644 --- a/docs/roadmap/outlook.md +++ b/docs/roadmap/outlook.md @@ -1,338 +1,36 @@ # Weatherfeeder Outlook V2 Support -## Summary - -Update `weatherapi` to read and serve the new `weatherfeeder` SPC outlook contract introduced by `weather.outlook.v2`. - -`weatherfeeder` now emits location-filtered convective outlook runs, removes polygon-level prose fields from outlook polygons, and stores day-level outlook discussions in a new `outlook_discussions` table. `weatherapi` must update its dependency, Postgres read adapter, application filtering, presenters, templates, endpoint tests, and documentation to match that contract. - -This roadmap preserves the existing public route family unless a later API roadmap explicitly changes it: - -- `GET /outlooks/convective` -- `GET /outlooks/convective/active` -- `GET /outlooks/convective/location` - -## Target Behavior - -- All outlook endpoints read the latest `weather.outlook.v2` run from weatherfeeder-owned Postgres tables. -- The repository loads `outlook_runs`, `outlooks`, and `outlook_discussions`. -- `outlooks[]` contains only location-relevant polygons written by weatherfeeder. -- `containsLocation` remains in responses and should normally be `true` for every returned outlook. -- `discussions[]` contains run-level SPC day discussions for days represented by returned outlooks. -- If no latest run exists, responses continue returning `{ "data": null }`. -- If a latest run exists but filters remove every outlook, responses return the run with `outlooks: []` and `discussions: []`. -- `/outlooks/convective` returns the latest run with optional user filters. -- `/outlooks/convective/active` filters the latest run to outlooks active at request time. -- `/outlooks/convective/location` remains available for API compatibility and applies the active filter; because v2 storage is already location-filtered, it is effectively the current active local-outlook endpoint. -- Public response timestamps continue honoring `tz` / `TZ` presentation conversion. -- `units=metric|us` remains accepted for consistency and has no payload effect. -- `precision` and unknown query parameters remain rejected. - -## Upstream Dependency - -Update `go.mod` to the first released `gitea.maximumdirect.net/ejr/weatherfeeder` version that contains: - -- `standards.SchemaWeatherOutlookV2`; -- `model.WeatherOutlookRun.Discussions`; -- `model.WeatherOutlookDiscussion`; -- `model.WeatherOutlook` without polygon-level `Headline`, `Summary`, or `Discussion` fields. - -Do not commit a local `replace` directive for weatherfeeder. If implementation begins before the upstream release is tagged, stop and tag/release weatherfeeder first or use a temporary local replace only outside the final committed diff. - -## Stage 1: Module And Compile Contract - -### Changes - -- Bump the `weatherfeeder` dependency in `go.mod` to the released version containing outlook v2. -- Run `go mod tidy`. -- Fix compile errors caused by removed `model.WeatherOutlook.Headline`, `Summary`, and `Discussion` fields. -- Update any references to `standards.SchemaWeatherOutlookV1` in outlook-specific code to use `SchemaWeatherOutlookV2` only where schema constants are needed. - -### Expected Compile Hotspots - -- `internal/adapters/outbound/postgres/outlooks_rows.go` -- `internal/adapters/outbound/postgres/outlooks_mapper.go` -- `internal/adapters/outbound/postgres/outlooks_queries.go` -- `internal/adapters/outbound/postgres/outlooks_read.go` -- `internal/adapters/inbound/httpapi/presenter/outlook.go` -- `internal/app/service.go` -- endpoint and presenter tests that construct `model.WeatherOutlook` -- `docs/integrations/weatherfeeder-postgres.md` -- `docs/api.md` - -### Verification - -```sh -go test ./internal/app ./internal/adapters/outbound/postgres ./internal/adapters/inbound/httpapi/presenter -``` - -## Stage 2: Postgres Repository V2 Reads - -### Queries And Rows - -Update the Postgres adapter to match the v2 table shape. - -Parent query: - -- Continue selecting latest parent row from `outlook_runs` ordered by `as_of DESC, event_emitted_at DESC`. -- Include `discussion_count` only if useful for tests or sanity checks; the read model does not need to expose it. - -Outlook child query: - -- Remove columns that no longer exist on `outlooks`: - - `headline` - - `summary` - - `discussion` -- Continue selecting child rows ordered by `outlook_index ASC`. -- Continue validating/copying `geometry_json` as JSON. -- Continue normalizing timestamps to UTC. - -Discussion child query: - -- Add `queryOutlookDiscussionsForRun` selecting from `outlook_discussions`: - - `discussion_index` - - `day` - - `headline` - - `summary` - - `discussion` - - `updated_at` -- Filter by `run_event_id = $1`. -- Order by `discussion_index ASC`. - -Row structs: - -- Remove `Headline`, `Summary`, and `Discussion` from `outlookRow`. -- Add `outlookDiscussionRow` with nullable string/time fields. - -Read flow: - -- `LatestConvectiveOutlookRun` should load parent, outlook rows, and discussion rows. -- Attach `run.Outlooks` and `run.Discussions` before returning. -- Missing latest parent still returns `nil, nil`. -- Child query/scan/iteration errors should include contextual wrapping. - -### Mapper Behavior - -- `mapOutlookRow` should map only polygon fields present in v2. -- Add `mapOutlookDiscussionRow` returning `model.WeatherOutlookDiscussion`. -- Normalize `updated_at` to UTC when present. -- Preserve nil/zero semantics from the canonical model. -- Preserve geometry byte copy behavior. - -### Tests - -Update Postgres mapper/read tests to cover: - -- parent mapping still normalizes `asOf` and optional `issuedAt` to UTC; -- v2 outlook row maps all polygon fields and no polygon-level prose fields; -- nullable optional outlook fields map to omitted/zero canonical values; -- invalid `geometry_json` returns a mapper error; -- discussion row maps `day`, `headline`, `summary`, `discussion`, and `updatedAt`; -- nullable discussion fields map to omitted/zero values; -- latest run loads outlook children by `outlook_index ASC`; -- latest run loads discussion children by `discussion_index ASC`; -- no parent row returns `nil, nil`; -- query errors and scan errors remain context-wrapped. - -### Verification - -```sh -go test ./internal/adapters/outbound/postgres -``` - -## Stage 3: Application Filtering Semantics - -### Changes - -Update `internal/app` outlook filtering so discussions stay coherent after endpoint filters. - -Current filtering should continue to clone the repository-returned run before mutation. Extend cloning and filtering to include `Discussions`: - -- Deep-copy `WeatherOutlookRun.Discussions`. -- After filtering `Outlooks`, rebuild `Discussions` to include only days still represented by retained outlooks. -- Preserve discussion order from the repository for retained days. -- If retained outlooks are empty, set `Discussions` to an empty non-nil slice when the original slice was non-nil or when the endpoint needs stable JSON empty-array behavior. - -Route implications: - -- `/outlooks/convective?day=2` should return only Day 2 outlooks and only the Day 2 discussion. -- `/outlooks/convective?outlookType=tornado` should return discussions only for days with retained tornado outlooks. -- `/outlooks/convective/active` should remove discussions for days with no active retained outlooks. -- `/outlooks/convective/location` should remain active plus local semantics. With v2 data, the explicit `containsLocation=true` filter is redundant but harmless. -- `containsLocation=false` on routes that allow the parameter should return an empty outlook/discussion run with v2 data. - -### Query Parameter Policy - -Preserve current public query behavior unless endpoint tests reveal a direct conflict: - -- `day=1|2|3` accepted on all outlook routes. -- `outlookType=categorical|tornado|hail|wind` accepted case-insensitively on all outlook routes. -- `containsLocation=true|false` accepted on `/outlooks/convective` and `/outlooks/convective/active` for backward-compatible filtering. -- `containsLocation` rejected on `/outlooks/convective/location`. -- `format`, `units`, and `tz` / `TZ` remain supported. -- `precision` and unknown query parameters remain rejected. - -### Tests - -Update app tests to cover: - -- repository delegation still happens once; -- filtering by day also filters discussions to that day; -- filtering by outlook type filters discussions to days with retained outlooks; -- active filtering filters discussions to days with active retained outlooks; -- no matching outlooks returns `outlooks: []` and `discussions: []`; -- clone behavior does not mutate repository-owned `Outlooks`, `Discussions`, severity pointers, geometry bytes, or time pointers. - -### Verification - -```sh -go test ./internal/app -``` - -## Stage 4: Presenter, Templates, And HTTP Responses - -### Presenter Changes - -Update `internal/adapters/inbound/httpapi/presenter/outlook.go`: - -- Copy `run.Discussions` into the presented payload. -- Convert `WeatherOutlookDiscussion.UpdatedAt` into the requested timezone. -- Continue converting `run.AsOf`, `run.IssuedAt`, and outlook `validFrom`, `validTo`, `issuedAt`, and `expiresAt`. -- Remove references to polygon-level `Headline`, `Summary`, and `Discussion`. -- Preserve geometry copy behavior. -- Return `nil` for nil input. - -### Text Template Changes - -Update `templates/outlooks_convective.txt.tmpl`: - -- Render run-level discussions, grouped/listed by day. -- Do not reference polygon-level `.Headline`, `.Summary`, or `.Discussion`. -- Keep output useful when `outlooks` is empty but `data` is present. -- Keep no-data text for `data: null`. - -### HTTP Tests - -Update endpoint tests to cover: - -- JSON response includes `discussions` at run level. -- JSON response no longer includes polygon-level `headline`, `summary`, or `discussion`. -- XML response renders run-level discussions without errors. -- Text response renders run-level discussion content. -- `tz` / `TZ` converts `asOf`, `issuedAt`, outlook times, and discussion `updatedAt`. -- `/outlooks/convective`, `/active`, and `/location` still register and route. -- `data: null` remains unchanged when no run exists. -- filtered no-match response returns `outlooks: []` and `discussions: []`. -- query validation behavior remains unchanged for supported/rejected params. - -### Presenter Tests - -Update presenter tests to cover: - -- copied run includes copied discussions; -- discussion `updatedAt` timezone conversion; -- input run is not mutated; -- geometry bytes remain copied; -- nil input returns nil. - -### Verification - -```sh -go test ./internal/adapters/inbound/httpapi ./internal/adapters/inbound/httpapi/presenter -``` - -## Stage 5: Documentation Updates - -After code behavior is updated, update permanent docs in the same change. - -### Public API Docs - -Update `docs/api.md`: - -- State that outlook endpoints serve weatherfeeder `weather.outlook.v2` data. -- Add `discussions` to run fields. -- Add `WeatherOutlookDiscussion` / discussion field definitions: - - `day` - - `headline` - - `summary` - - `discussion` - - `updatedAt` -- Remove polygon-level `headline`, `summary`, and `discussion` from outlook fields. -- State that v2 outlooks are already location-filtered by weatherfeeder. -- State `containsLocation` is expected to be true for v2 outlooks. -- Clarify that `/outlooks/convective/location` remains active local-outlook behavior and is mostly a compatibility route under v2. -- Document that filters also filter `discussions` to retained outlook days. -- Document latest-run semantics: current endpoints read the latest run and do not accumulate active historical outlook rows from previous runs. -- Update JSON and text examples to include run-level `discussions`. - -### Integration And Internal Docs - -Update `docs/integrations/weatherfeeder-postgres.md`: - -- Update weatherfeeder dependency version. -- Add `outlook_discussions` to the table family. -- Add `discussion_count` to `outlook_runs` if the doc lists columns read or storage assumptions. -- Remove `headline`, `summary`, and `discussion` from `outlooks` columns. -- Add `outlook_discussions` columns and ordering by `discussion_index ASC`. -- State that `weatherapi` expects the v2 table reset/migration to have been applied by operators/weatherfeeder deployment. - -Update `docs/internal/postgres-repository.md`: - -- State `LatestConvectiveOutlookRun` loads `outlook_runs`, `outlooks`, and `outlook_discussions`. -- State child order for outlook discussions. - -Update `docs/internal/presenters.md`: - -- State outlook presenter copies and timezone-converts run-level discussions. - -Update `docs/internal/http-adapter.md`: - -- Clarify that outlook filters also trim run-level discussions to retained days. - -Update `README.md` only if its outlook summary implies the old all-polygon behavior. - -Update `docs/roadmap/implementation.md` after implementation is complete if this repository continues using that file as the active implementation checklist. - -### Documentation Tests - -If docs consistency tests exist or are added, assert stable identifiers only: - -- `outlook_discussions` appears in `docs/integrations/weatherfeeder-postgres.md`. -- `weather.outlook.v2` appears in `docs/api.md` or the integration docs. - -## Stage 6: Full Verification - -Run focused tests: - -```sh -go test ./internal/app -go test ./internal/adapters/outbound/postgres -go test ./internal/adapters/inbound/httpapi -go test ./internal/adapters/inbound/httpapi/presenter -``` - -Run the full suite: - -```sh -go test ./... -``` - -Manual verification against a database populated by weatherfeeder v2 outlooks: - -- `GET /outlooks/convective` returns latest run with run-level `discussions`. -- `GET /outlooks/convective/active` returns only active outlooks and matching discussions. -- `GET /outlooks/convective/location` works and returns active local outlooks. -- `GET /outlooks/convective?containsLocation=false` returns an empty run for v2 data. -- Text and XML formats render successfully. - -## Assumptions - -- `weatherfeeder` has been released with outlook v2 before final implementation is committed. -- Weatherfeeder-owned Postgres outlook tables have been reset/recreated according to weatherfeeder's transition documentation. -- `weatherapi` remains read-only and does not create, migrate, or repair weatherfeeder tables. -- The existing outlook route family remains public and should not be removed in this compatibility update. -- Latest-run semantics are the correct public API behavior for current outlook endpoints. - -## Open Questions - -None. The roadmap preserves current route names and query compatibility while updating storage and response handling to the new weatherfeeder v2 outlook contract. +## Status + +Implemented in current behavior docs and code. The authoritative implemented +contracts now live in: + +- [`docs/api.md`](../api.md) for public outlook routes, query parameters, and + response fields; +- [`docs/integrations/weatherfeeder-postgres.md`](../integrations/weatherfeeder-postgres.md) + for weatherfeeder-owned table assumptions; +- [`docs/internal/postgres-repository.md`](../internal/postgres-repository.md), + [`docs/internal/http-adapter.md`](../internal/http-adapter.md), and + [`docs/internal/presenters.md`](../internal/presenters.md) for internal + behavior. + +`docs/roadmap/implementation.md` remains as the implementation checklist and +verification record for this compatibility update. + +## Delivered Behavior + +- Existing public routes remain available: + - `GET /outlooks/convective` + - `GET /outlooks/convective/active` + - `GET /outlooks/convective/location` +- The Postgres repository reads weatherfeeder outlook v2 tables: + `outlook_runs`, `outlooks`, and `outlook_discussions`. +- Outlook polygons no longer include prose fields. +- Run-level `discussions` are loaded, copied, presented, and timezone-converted. +- Application filtering trims `discussions` to days represented by retained + outlooks. +- Missing latest data still returns `data: null`. +- Filtered no-match responses return a run object with `outlooks: []` and + `discussions: []`. +- `weatherapi` remains read-only and does not create, migrate, or repair + weatherfeeder tables. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index cc6e983..c7b331e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -109,7 +109,7 @@ Likely causes: - unknown query parameter; - `precision` outside `0` through `2`, or not an integer; -- `precision` used on alerts, discussions, or weather stories; +- `precision` used on alerts, discussions, weather stories, or outlooks; - `tz` used on observations, current conditions, or alerts; - invalid timezone value; - both `tz` and `TZ` are present with different values. @@ -187,6 +187,10 @@ Safe fix: verify weatherfeeder is running and writing to the same database that `weatherapi` uses as its first configured database. For current conditions, wait for recent observations or inspect weatherfeeder ingestion. +For convective outlooks, also verify that weatherfeeder has applied its +`weather.outlook.v2` table shape and is writing `outlook_runs`, `outlooks`, and +`outlook_discussions`. + ## Forecast Day Routes Return Empty `periods` Symptom: `/forecast/hourly/today`, `/forecast/hourly/tomorrow`,