14 KiB
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/convectiveGET /outlooks/convective/activeGET /outlooks/convective/location
Target Behavior
- All outlook endpoints read the latest
weather.outlook.v2run from weatherfeeder-owned Postgres tables. - The repository loads
outlook_runs,outlooks, andoutlook_discussions. outlooks[]contains only location-relevant polygons written by weatherfeeder.containsLocationremains in responses and should normally betruefor 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: []anddiscussions: []. /outlooks/convectivereturns the latest run with optional user filters./outlooks/convective/activefilters the latest run to outlooks active at request time./outlooks/convective/locationremains 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/TZpresentation conversion. units=metric|usremains accepted for consistency and has no payload effect.precisionand 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.WeatherOutlookwithout polygon-levelHeadline,Summary, orDiscussionfields.
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
weatherfeederdependency ingo.modto the released version containing outlook v2. - Run
go mod tidy. - Fix compile errors caused by removed
model.WeatherOutlook.Headline,Summary, andDiscussionfields. - Update any references to
standards.SchemaWeatherOutlookV1in outlook-specific code to useSchemaWeatherOutlookV2only where schema constants are needed.
Expected Compile Hotspots
internal/adapters/outbound/postgres/outlooks_rows.gointernal/adapters/outbound/postgres/outlooks_mapper.gointernal/adapters/outbound/postgres/outlooks_queries.gointernal/adapters/outbound/postgres/outlooks_read.gointernal/adapters/inbound/httpapi/presenter/outlook.gointernal/app/service.go- endpoint and presenter tests that construct
model.WeatherOutlook docs/integrations/weatherfeeder-postgres.mddocs/api.md
Verification
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_runsordered byas_of DESC, event_emitted_at DESC. - Include
discussion_countonly 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:headlinesummarydiscussion
- Continue selecting child rows ordered by
outlook_index ASC. - Continue validating/copying
geometry_jsonas JSON. - Continue normalizing timestamps to UTC.
Discussion child query:
- Add
queryOutlookDiscussionsForRunselecting fromoutlook_discussions:discussion_indexdayheadlinesummarydiscussionupdated_at
- Filter by
run_event_id = $1. - Order by
discussion_index ASC.
Row structs:
- Remove
Headline,Summary, andDiscussionfromoutlookRow. - Add
outlookDiscussionRowwith nullable string/time fields.
Read flow:
LatestConvectiveOutlookRunshould load parent, outlook rows, and discussion rows.- Attach
run.Outlooksandrun.Discussionsbefore returning. - Missing latest parent still returns
nil, nil. - Child query/scan/iteration errors should include contextual wrapping.
Mapper Behavior
mapOutlookRowshould map only polygon fields present in v2.- Add
mapOutlookDiscussionRowreturningmodel.WeatherOutlookDiscussion. - Normalize
updated_atto 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
asOfand optionalissuedAtto 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_jsonreturns a mapper error; - discussion row maps
day,headline,summary,discussion, andupdatedAt; - 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
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, rebuildDiscussionsto include only days still represented by retained outlooks. - Preserve discussion order from the repository for retained days.
- If retained outlooks are empty, set
Discussionsto 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=2should return only Day 2 outlooks and only the Day 2 discussion./outlooks/convective?outlookType=tornadoshould return discussions only for days with retained tornado outlooks./outlooks/convective/activeshould remove discussions for days with no active retained outlooks./outlooks/convective/locationshould remain active plus local semantics. With v2 data, the explicitcontainsLocation=truefilter is redundant but harmless.containsLocation=falseon 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|3accepted on all outlook routes.outlookType=categorical|tornado|hail|windaccepted case-insensitively on all outlook routes.containsLocation=true|falseaccepted on/outlooks/convectiveand/outlooks/convective/activefor backward-compatible filtering.containsLocationrejected on/outlooks/convective/location.format,units, andtz/TZremain supported.precisionand 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: []anddiscussions: []; - clone behavior does not mutate repository-owned
Outlooks,Discussions, severity pointers, geometry bytes, or time pointers.
Verification
go test ./internal/app
Stage 4: Presenter, Templates, And HTTP Responses
Presenter Changes
Update internal/adapters/inbound/httpapi/presenter/outlook.go:
- Copy
run.Discussionsinto the presented payload. - Convert
WeatherOutlookDiscussion.UpdatedAtinto the requested timezone. - Continue converting
run.AsOf,run.IssuedAt, and outlookvalidFrom,validTo,issuedAt, andexpiresAt. - Remove references to polygon-level
Headline,Summary, andDiscussion. - Preserve geometry copy behavior.
- Return
nilfor 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
outlooksis empty butdatais present. - Keep no-data text for
data: null.
HTTP Tests
Update endpoint tests to cover:
- JSON response includes
discussionsat run level. - JSON response no longer includes polygon-level
headline,summary, ordiscussion. - XML response renders run-level discussions without errors.
- Text response renders run-level discussion content.
tz/TZconvertsasOf,issuedAt, outlook times, and discussionupdatedAt./outlooks/convective,/active, and/locationstill register and route.data: nullremains unchanged when no run exists.- filtered no-match response returns
outlooks: []anddiscussions: []. - query validation behavior remains unchanged for supported/rejected params.
Presenter Tests
Update presenter tests to cover:
- copied run includes copied discussions;
- discussion
updatedAttimezone conversion; - input run is not mutated;
- geometry bytes remain copied;
- nil input returns nil.
Verification
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.v2data. - Add
discussionsto run fields. - Add
WeatherOutlookDiscussion/ discussion field definitions:dayheadlinesummarydiscussionupdatedAt
- Remove polygon-level
headline,summary, anddiscussionfrom outlook fields. - State that v2 outlooks are already location-filtered by weatherfeeder.
- State
containsLocationis expected to be true for v2 outlooks. - Clarify that
/outlooks/convective/locationremains active local-outlook behavior and is mostly a compatibility route under v2. - Document that filters also filter
discussionsto 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_discussionsto the table family. - Add
discussion_counttooutlook_runsif the doc lists columns read or storage assumptions. - Remove
headline,summary, anddiscussionfromoutlookscolumns. - Add
outlook_discussionscolumns and ordering bydiscussion_index ASC. - State that
weatherapiexpects the v2 table reset/migration to have been applied by operators/weatherfeeder deployment.
Update docs/internal/postgres-repository.md:
- State
LatestConvectiveOutlookRunloadsoutlook_runs,outlooks, andoutlook_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_discussionsappears indocs/integrations/weatherfeeder-postgres.md.weather.outlook.v2appears indocs/api.mdor the integration docs.
Stage 6: Full Verification
Run focused tests:
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:
go test ./...
Manual verification against a database populated by weatherfeeder v2 outlooks:
GET /outlooks/convectivereturns latest run with run-leveldiscussions.GET /outlooks/convective/activereturns only active outlooks and matching discussions.GET /outlooks/convective/locationworks and returns active local outlooks.GET /outlooks/convective?containsLocation=falsereturns an empty run for v2 data.- Text and XML formats render successfully.
Assumptions
weatherfeederhas 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.
weatherapiremains 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.