Document outlook v2 behavior
This commit is contained in:
65
docs/api.md
65
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`,
|
||||
|
||||
Reference in New Issue
Block a user