Files
weatherapi/docs/roadmap/implementation.md
2026-06-28 16:44:34 -05:00

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_source and condition_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.md exactly;
  • ignore unrecognized WMO codes for family voting;
  • return model.WMOUnknown when no recognized candidates exist;
  • count each source at most once;
  • return model.WMOUnknown on 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_source inside the same observation window;
  • latest per source is ordered by observed_at DESC, event_emitted_at DESC;
  • candidate columns should include event_source and condition_code; timestamp columns may remain SQL-only if used only for ordering.

Required repository flow:

  1. query aggregate current conditions;
  2. return nil, nil when the aggregate sample count maps to no data, preserving current behavior;
  3. query condition-code candidates using the same observation window;
  4. run the Go selector;
  5. 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:

  • conditionCode remains present in JSON/XML/text responses through the existing presenter path;
  • conditionText continues to derive from selected conditionCode and isDay;
  • format, units, and precision behavior is unchanged;
  • no tz support 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 choose conditionCode by 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 use observations.event_source for 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, 2 returns 0 by clear/cloud ranking;
  • 1, 2, 95 returns 1 because clear/cloud family wins;
  • 0, 95 returns model.WMOUnknown because families tie;
  • 61, 63, 80 returns 61 by rain ranking;
  • 61, 95, 0 returns model.WMOUnknown because three families tie;
  • only 95 returns 95;
  • 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 isDay mapping 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_source is 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.