Files
weatherapi/docs/internal/postgres-repository.md
Eric Rakestraw 2a33fe01cf
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
Update to support upstream weatherfeeder v0.12.1 and add ends field to the alerts schema
2026-06-16 20:08:03 -05:00

5.9 KiB

Postgres Repository

This document describes the outbound read adapter under internal/adapters/outbound/postgres. It documents repository behavior and storage assumptions without duplicating full schema documentation.

Purpose

The Postgres repository implements internal/app.Repository against weatherfeeder-owned tables. It reconstructs latest weather resources from SQL rows and returns canonical weatherfeeder model values or application read models.

Inputs and Outputs

Inputs:

  • a primary *sql.DB selected by runtime composition;
  • query contexts from application service calls;
  • weatherfeeder-populated Postgres rows.

Outputs:

  • latest observation, forecast, discussion, weather story, alert, convective outlook, and current conditions read models;
  • nil data with nil error when the latest resource does not exist;
  • contextual errors for query, scan, iteration, and JSON decode failures.

Boundaries

The repository may:

  • own SQL text and query ordering;
  • scan rows into adapter-local row structs;
  • map SQL nulls to pointers or omitted zero values;
  • normalize timestamps to UTC;
  • reconstruct child slices in database order.

The repository must not:

  • bind HTTP query parameters;
  • perform unit conversion or response rounding;
  • convert timestamps to requested presentation timezones;
  • render templates;
  • create or migrate weatherfeeder tables.

Config Fields Used

The repository does not read config directly. Runtime composition supplies the primary *sql.DB selected from the first configured database entry.

External Adapters Used

  • database/sql for query execution and nullable scan types.
  • github.com/lib/pq through runtime driver registration.
  • weatherfeeder/model for canonical return values.
  • internal/app for the repository interface and current-conditions model.

State

The repository stores only a database handle. It owns no durable data, no migrations, no polling state, and no retry queue.

Repository Contract

Repository stores a *sql.DB and satisfies app.Repository.

Every public method first checks that the repository and database are configured. A nil repository or nil database returns postgres repository is not configured.

Missing parent rows map to nil, nil. This is how HTTP endpoints can return successful responses with data: null.

Route-to-Query Mapping

  • LatestObservation: latest row from observations, then present-weather rows from observation_present_weather.
  • CurrentConditions: aggregates recent rows from observations using the application-provided observation window.
  • LatestAlertRun: latest row from alert_runs, then child alerts and alert_references. This is the latest stored alert snapshot. The repository maps both ends and expires; active-time filtering is performed by the application service.
  • LatestHourlyForecast: latest forecasts row where product = 'hourly', then child forecast_periods.
  • LatestNarrativeForecast: latest forecasts row where product = 'narrative', then child forecast_periods.
  • LatestForecastDiscussion: latest row from forecast_discussions, then child forecast_discussion_key_messages.
  • LatestWeatherStoryRun: latest row from weather_story_runs, then child weather_stories.
  • LatestWeatherStory: latest individual row from weather_stories.
  • LatestConvectiveOutlookRun: latest row from outlook_runs, then child 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.

Child Loading and Ordering

Child queries preserve stored order:

  • observation present weather by weather_index;
  • alerts by alert_index;
  • alert references by alert_index, then reference_index;
  • forecast periods by period_index;
  • discussion key messages by message_index;
  • weather stories by story_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.

Null and Timestamp Policy

Row structs use sql.Null* types for nullable columns. Mapper helpers convert:

  • invalid strings to empty strings;
  • invalid booleans, floats, times, and WMO codes to nil pointers;
  • valid times to UTC.

Required parent timestamps are normalized to UTC directly in mappers. Optional times go through timePtr, which also normalizes to UTC.

Current conditions return nil when the aggregate sample count is zero.

JSON Decode Behavior

Observation present-weather rows store raw JSON text. Empty or null text maps to an empty present-weather value. Invalid JSON returns a contextual decode error with the weather index.

Outlook rows store geometry_json as compact GeoJSON text. The repository validates and copies the JSON bytes into json.RawMessage without parsing or reserializing the geometry.

Failure Behavior

Repository methods wrap failures with operation context, for example:

  • query latest observation
  • query forecast periods
  • scan forecast period row
  • iterate weather story rows
  • decode observation present weather row

This context should be preserved when adding new reads so operator logs and tests identify the failing operation.

Tests to Inspect Before Changing

  • internal/adapters/outbound/postgres/repository_test.go
  • internal/app/service_test.go for repository port expectations
  • endpoint tests when no-data behavior or returned model shape affects HTTP responses

Invariants

  • Keep SQL and row structs in the Postgres adapter.
  • Keep weatherfeeder schema ownership outside weatherapi.
  • Preserve nil, nil no-data behavior for missing latest parent rows.
  • Preserve UTC normalization at the repository boundary.
  • Preserve child ordering from stored index columns.
  • Preserve contextual error wrapping for query, scan, iteration, and decode failures.