5.3 KiB
Implement Source-Balanced Current Conditions Condition Codes
Summary
Implement the target behavior in docs/roadmap/current.md: keep /conditions/current response shape unchanged, continue aggregating numeric observations over the current window, and replace SQL MAX(condition_code) with Go-based source-balanced WMO family consensus.
This is a behavior change only. Do not add public fields, change query parameters, alter weatherfeeder tables, or add provider-specific blacklists.
Stage 1: Add Condition-Code Consensus Helpers
Add package-local helpers in internal/adapters/outbound/postgres for current-conditions condition-code selection.
Required data shape:
- define a small candidate row/type containing
event_sourceandcondition_code; - the selector accepts latest-per-source candidates and returns
model.WMOCode.
Required selector behavior:
- use the family mapping and tie ranking from
docs/roadmap/current.mdexactly; - ignore unrecognized WMO codes for family voting;
- return
model.WMOUnknownwhen no recognized candidates exist; - count each source at most once;
- return
model.WMOUnknownon tied family votes; - inside the winning family, choose the most frequent exact code;
- when exact codes tie inside the winning family, choose by family ranking.
Add focused unit tests for the selector before wiring SQL changes.
Stage 2: Split Current-Conditions Condition-Code Querying
Update the Postgres current-conditions read path so condition-code selection is no longer computed with MAX(condition_code).
Required SQL behavior:
- keep the existing aggregate query for sample count, numeric averages, circular wind direction, and latest
is_day; - remove
MAX(condition_code)from the aggregate query result; - add a separate query that returns one latest condition-code candidate per
event_sourceinside the same observation window; - latest per source is ordered by
observed_at DESC, event_emitted_at DESC; - candidate columns should include
event_sourceandcondition_code; timestamp columns may remain SQL-only if used only for ordering.
Required repository flow:
- query aggregate current conditions;
- return
nil, nilwhen the aggregate sample count maps to no data, preserving current behavior; - query condition-code candidates using the same observation window;
- run the Go selector;
- map numeric aggregate fields plus selected condition code into
app.CurrentConditions.
Keep row DTOs and mappers local to the Postgres adapter. Do not move SQL or row types into internal/app.
Stage 3: Preserve API And Presentation Behavior
Keep all existing /conditions/current API behavior except condition-code selection.
Required invariants:
conditionCoderemains present in JSON/XML/text responses through the existing presenter path;conditionTextcontinues to derive from selectedconditionCodeandisDay;format,units, andprecisionbehavior is unchanged;- no
tzsupport is added; - no public response fields are added or removed.
Update existing endpoint or presenter tests only if expected condition codes need to change because of the new selector.
Stage 4: Update Current-Behavior Documentation
After implementation is complete, update current-behavior docs outside roadmap:
docs/api.md: current conditions aggregate numeric fields and chooseconditionCodeby source-balanced WMO family consensus;docs/internal/postgres-repository.md: current conditions use an aggregate row plus a latest-per-source condition-code candidate query;docs/integrations/weatherfeeder-postgres.md: current conditions useobservations.event_sourcefor source-balanced condition-code selection.
Do not document any unimplemented options such as provider blacklists, trust weights, or source priorities.
Test Plan
Selector tests:
0,1,2returns0by clear/cloud ranking;1,2,95returns1because clear/cloud family wins;0,95returnsmodel.WMOUnknownbecause families tie;61,63,80returns61by rain ranking;61,95,0returnsmodel.WMOUnknownbecause three families tie;- only
95returns95; - unrecognized-only candidates return
model.WMOUnknown; - duplicate candidates from the same source do not produce multiple votes if the selector receives them.
Repository tests:
- current conditions no longer chooses the highest numeric condition code;
- latest condition-code candidate per source uses
observed_at DESC, event_emitted_at DESC; - aggregate no-sample behavior still returns nil;
- numeric aggregate fields, wind direction, and
isDaymapping remain unchanged; - SQL/query errors from the candidate query include operation context.
Verification commands:
go test ./internal/adapters/outbound/postgres ./internal/app
go test ./internal/adapters/inbound/httpapi ./internal/adapters/inbound/httpapi/presenter
go test ./...
Assumptions
observations.event_sourceis non-null in the weatherfeeder-owned schema and is safe to use as the source identity.- The latest-per-source query can be implemented with existing Postgres features and does not require a migration.
- Current conditions remain repository-owned because they are a Postgres aggregate read model; no new app service method or public interface is needed.
- The implementation should remain standard-library and SQL based; do not add dependencies.