195 lines
6.9 KiB
Markdown
195 lines
6.9 KiB
Markdown
# Weatherfeeder Postgres Contract
|
|
|
|
`weatherapi` reads weather data from Postgres tables owned and populated by
|
|
`weatherfeeder`. This document describes only the storage contract consumed by
|
|
`weatherapi`; it is not a full weatherfeeder schema reference.
|
|
|
|
## Version
|
|
|
|
`go.mod` depends on:
|
|
|
|
- `gitea.maximumdirect.net/ejr/weatherfeeder v0.11.0`
|
|
|
|
The repository code also depends on weatherfeeder canonical model types. Table
|
|
compatibility must match the SQL in `internal/adapters/outbound/postgres`.
|
|
|
|
## Boundary
|
|
|
|
`weatherapi` is read-only:
|
|
|
|
- it does not create tables;
|
|
- it does not migrate tables;
|
|
- it does not ingest provider data;
|
|
- it does not write weatherfeeder events.
|
|
|
|
Weatherfeeder owns provider polling, normalization, table shape, and writes.
|
|
Postgres owns persistence, backup, restore, and availability.
|
|
|
|
## Table Families Read
|
|
|
|
| Resource | Tables read |
|
|
| --- | --- |
|
|
| Latest observation | `observations`, `observation_present_weather` |
|
|
| Current conditions | `observations` |
|
|
| Active alerts | `alert_runs`, `alerts`, `alert_references` |
|
|
| Hourly forecast | `forecasts`, `forecast_periods` |
|
|
| Narrative forecast | `forecasts`, `forecast_periods` |
|
|
| 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` |
|
|
|
|
## Latest Row Selection
|
|
|
|
Latest parent resources use these ordering rules:
|
|
|
|
- observations: `observed_at DESC, event_emitted_at DESC`;
|
|
- alert runs: `as_of DESC, event_emitted_at DESC`;
|
|
- hourly forecasts: `product = 'hourly'`, then `issued_at DESC,
|
|
event_emitted_at DESC`;
|
|
- narrative forecasts: `product = 'narrative'`, then `issued_at DESC,
|
|
event_emitted_at DESC`;
|
|
- forecast discussions: `issued_at DESC, event_emitted_at DESC`;
|
|
- weather story runs: `as_of DESC, event_emitted_at DESC`;
|
|
- latest individual weather story: `updated_at DESC, as_of DESC,
|
|
story_order ASC, story_index ASC`.
|
|
- convective outlook runs: `as_of DESC, event_emitted_at DESC`.
|
|
|
|
Current conditions aggregate `observations` rows where `observed_at` is inside
|
|
the application-provided observation window.
|
|
|
|
## Child Ordering
|
|
|
|
Child rows are loaded separately and attached in stored order:
|
|
|
|
- observation present weather: `weather_index ASC`;
|
|
- alerts: `alert_index ASC`;
|
|
- alert references: `alert_index ASC, reference_index ASC`;
|
|
- 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`.
|
|
|
|
## Columns Read
|
|
|
|
The repository reads only these columns.
|
|
|
|
### `observations`
|
|
|
|
`event_id`, `station_id`, `station_name`, `observed_at`, `condition_code`,
|
|
`is_day`, `text_description`, `temperature_c`, `dewpoint_c`,
|
|
`wind_direction_degrees`, `wind_speed_kmh`, `wind_gust_kmh`,
|
|
`barometric_pressure_pa`, `visibility_meters`, `relative_humidity_percent`,
|
|
`apparent_temperature_c`, and `event_emitted_at`.
|
|
|
|
Current conditions additionally aggregate recent `observations` values for
|
|
temperature, apparent temperature, dewpoint, humidity, wind speed, wind
|
|
direction, condition code, and latest `is_day`.
|
|
|
|
### `observation_present_weather`
|
|
|
|
`weather_index`, `raw_text`, and `event_id`.
|
|
|
|
`raw_text` is decoded as JSON when present. Empty or null raw text maps to an
|
|
empty present-weather value; invalid JSON is returned as a repository error.
|
|
|
|
### `alert_runs`
|
|
|
|
`event_id`, `location_id`, `location_name`, `as_of`, `latitude`, `longitude`,
|
|
and `event_emitted_at`.
|
|
|
|
### `alerts`
|
|
|
|
`alert_index`, `alert_id`, `event`, `headline`, `severity`, `urgency`,
|
|
`certainty`, `status`, `message_type`, `category`, `response`, `description`,
|
|
`instruction`, `sent`, `effective`, `onset`, `expires`, `area_description`,
|
|
`sender_name`, and `run_event_id`.
|
|
|
|
### `alert_references`
|
|
|
|
`alert_index`, `reference_index`, `id`, `identifier`, `sender`, `sent`, and
|
|
`run_event_id`.
|
|
|
|
### `forecasts`
|
|
|
|
`event_id`, `location_id`, `location_name`, `issued_at`, `updated_at`,
|
|
`product`, `latitude`, `longitude`, `elevation_meters`, and
|
|
`event_emitted_at`.
|
|
|
|
Only `product = 'hourly'` and `product = 'narrative'` are read by implemented
|
|
routes.
|
|
|
|
### `forecast_periods`
|
|
|
|
`period_index`, `start_time`, `end_time`, `name`, `is_day`,
|
|
`condition_code`, `text_description`, `temperature_c`, `temperature_c_min`,
|
|
`temperature_c_max`, `dewpoint_c`, `relative_humidity_percent`,
|
|
`wind_direction_degrees`, `wind_speed_kmh`, `wind_gust_kmh`,
|
|
`barometric_pressure_pa`, `visibility_meters`, `apparent_temperature_c`,
|
|
`cloud_cover_percent`, `probability_of_precipitation_percent`,
|
|
`precipitation_amount_mm`, `snowfall_depth_mm`, `uv_index`, and
|
|
`run_event_id`.
|
|
|
|
### `forecast_discussions`
|
|
|
|
`event_id`, `office_id`, `office_name`, `issued_at`, `updated_at`, `product`,
|
|
`short_term_qualifier`, `short_term_issued_at`, `short_term_text`,
|
|
`long_term_qualifier`, `long_term_issued_at`, `long_term_text`, and
|
|
`event_emitted_at`.
|
|
|
|
### `forecast_discussion_key_messages`
|
|
|
|
`message_index`, `message_text`, and `run_event_id`.
|
|
|
|
### `weather_story_runs`
|
|
|
|
`event_id`, `office_id`, `as_of`, and `event_emitted_at`.
|
|
|
|
### `weather_stories`
|
|
|
|
`story_index`, `office_id`, `start_time`, `end_time`, `updated_at`, `title`,
|
|
`description`, `alt_text`, `priority`, `story_order`, `download_url`,
|
|
`run_event_id`, and `as_of`.
|
|
|
|
### `outlook_runs`
|
|
|
|
`event_id`, `location_id`, `location_name`, `latitude`, `longitude`, `as_of`,
|
|
`issued_at`, and `event_emitted_at`.
|
|
|
|
### `outlooks`
|
|
|
|
`outlook_index`, `outlook_id`, `provider`, `product`, `day`, `outlook_type`,
|
|
`label`, `label_text`, `severity_rank`, `valid_from`, `valid_to`, `issued_at`,
|
|
`expires_at`, `forecaster`, `headline`, `summary`, `discussion`, `source_url`,
|
|
`image_url`, `contains_location`, `geometry_json`, and `run_event_id`.
|
|
|
|
`geometry_json` is copied into response GeoJSON without parsing or
|
|
reserializing. It must contain valid JSON.
|
|
|
|
## Nullability and Time Assumptions
|
|
|
|
The repository scans nullable columns with `sql.Null*` types and maps them to
|
|
nil pointers or omitted zero values depending on the canonical model field.
|
|
|
|
Timestamps returned by the repository are normalized to UTC. Presentation
|
|
timezone conversion happens later in HTTP presenters.
|
|
|
|
Missing latest parent rows return `nil, nil` from repository methods. HTTP
|
|
rendering exposes this as a successful response with `data: null`.
|
|
|
|
## Compatibility Checklist
|
|
|
|
Before changing weatherfeeder storage or upgrading the weatherfeeder module:
|
|
|
|
- compare table names and columns with `internal/adapters/outbound/postgres`;
|
|
- preserve latest-row ordering columns used by `weatherapi`;
|
|
- preserve child index columns used for ordering;
|
|
- preserve nullable behavior expected by row mappers;
|
|
- run `go test ./internal/adapters/outbound/postgres` and affected HTTP tests.
|
|
|
|
## Related Docs
|
|
|
|
- [`docs/internal/postgres-repository.md`](../internal/postgres-repository.md)
|
|
- [`docs/operations.md`](../operations.md)
|
|
- [`docs/troubleshooting.md`](../troubleshooting.md)
|