Files
weatherfeeder/docs/integrations/events.md

327 lines
14 KiB
Markdown

# Event Wire Contract
This document is the canonical JSON contract for events emitted by
`weatherfeeder` JSON sinks, including stdout and NATS. Postgres stores the same
event envelope fields in parent table columns; see
[Postgres integration](postgres.md).
Downstream consumers should read the envelope, switch on `schema`, and decode
`payload` according to that schema.
## Envelope
Every emitted event is a JSON object with these fields:
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `id` | string | yes | Stable event identifier. Treat as opaque. |
| `kind` | string | yes | Routing kind, such as `observation` or `alert`. |
| `source` | string | yes | Configured source name. |
| `emitted_at` | timestamp | yes | When the daemon emitted the event. |
| `effective_at` | timestamp | no | Timestamp the payload is about, when known. |
| `schema` | string | no | Schema identifier. Weatherfeeder sources and normalizers set this. |
| `payload` | object, array, string, or scalar | yes | Schema-specific payload. |
Timestamps are JSON strings using Go `time.Time` JSON encoding, which is
RFC3339Nano-compatible. Weatherfeeder normalizers use UTC timestamps for
canonical payloads.
## Kinds And Schemas
Canonical schemas emitted after normalization:
| Kind | Schema | Payload |
|---|---|---|
| `observation` | `weather.observation.v1` | `WeatherObservation` |
| `forecast` | `weather.forecast.v1` | `WeatherForecastRun` |
| `forecast_discussion` | `weather.forecast_discussion.v1` | `WeatherForecastDiscussion` |
| `weather_story` | `weather.weather_story.v1` | `WeatherStoryRun` |
| `alert` | `weather.alert.v1` | `WeatherAlertRun` |
| `outlook` | `weather.outlook.v2` | `WeatherOutlookRun` |
Raw upstream schemas emitted by current sources:
| Kind | Schema | Payload |
|---|---|---|
| `observation` | `raw.nws.observation.v1` | NWS observation JSON |
| `observation` | `raw.openmeteo.current.v1` | Open-Meteo current JSON |
| `observation` | `raw.openweather.current.v1` | OpenWeather current JSON |
| `forecast` | `raw.nws.hourly.forecast.v1` | NWS hourly forecast JSON |
| `forecast` | `raw.nws.narrative.forecast.v1` | NWS narrative forecast JSON |
| `forecast_discussion` | `raw.nws.forecast_discussion.v1` | NWS forecast discussion HTML string |
| `weather_story` | `raw.nws.weatherstories.v1` | NWS weather stories JSON |
| `forecast` | `raw.openmeteo.hourly.forecast.v1` | Open-Meteo hourly forecast JSON |
| `alert` | `raw.nws.alerts.v1` | NWS alerts JSON |
| `outlook` | `raw.spc.convective_outlook.v1` | SPC convective outlook raw bundle |
`standards.SchemaRawOpenWeatherHourlyForecastV1` exists in code, but no current
registered source emits it.
## Shared Conventions
- Canonical numeric measurements use metric units.
- Floating-point values in canonical payloads are rounded to 4 digits after the
decimal point during normalization.
- Optional fields use JSON `omitempty`; absent fields should be treated as
unknown.
- `conditionCode` is a WMO weather interpretation code. Unknown observation
conditions use `-1`. Forecast period `conditionCode` is optional.
- Additive fields are compatible within a schema version. Removing, renaming, or
changing the meaning of a field requires a new schema identifier.
## `weather.observation.v1`
Payload type: `WeatherObservation`.
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `stationId` | string | no | Provider station/location identifier. |
| `stationName` | string | no | Human station name. |
| `timestamp` | timestamp | yes | Observation timestamp. |
| `conditionCode` | integer | yes | WMO code; `-1` means unknown. |
| `isDay` | boolean | no | Day/night hint. |
| `textDescription` | string | no | Short human description. |
| `temperatureC` | number | no | Celsius. |
| `dewpointC` | number | no | Celsius. |
| `windDirectionDegrees` | number | no | Degrees. |
| `windSpeedKmh` | number | no | Kilometers per hour. |
| `windGustKmh` | number | no | Kilometers per hour. |
| `barometricPressurePa` | number | no | Pascals. |
| `visibilityMeters` | number | no | Meters. |
| `relativeHumidityPercent` | number | no | Percent from 0 to 100. |
| `apparentTemperatureC` | number | no | Celsius. |
| `presentWeather` | array | no | Provider-specific present weather fragments. |
`presentWeather[]` entries contain optional `raw` objects.
## `weather.forecast.v1`
Payload type: `WeatherForecastRun`.
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `locationId` | string | no | Provider location identifier. |
| `locationName` | string | no | Human location name. |
| `issuedAt` | timestamp | yes | When the forecast run was generated or issued. |
| `updatedAt` | timestamp | no | Subsequent provider update time. |
| `product` | string | yes | Current emitted values are `hourly` and `narrative`. |
| `latitude` | number | no | Degrees. |
| `longitude` | number | no | Degrees. |
| `elevationMeters` | number | no | Meters. |
| `periods` | array | yes | Ordered forecast periods. |
`periods[]` entries:
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `startTime` | timestamp | yes | Period start. |
| `endTime` | timestamp | yes | Period end. |
| `name` | string | no | Human label. |
| `isDay` | boolean | no | Day/night hint. |
| `conditionCode` | integer | no | WMO code when applicable. |
| `textDescription` | string | no | Human summary. |
| `temperatureC` | number | no | Celsius. |
| `temperatureCMin` | number | no | Celsius. |
| `temperatureCMax` | number | no | Celsius. |
| `dewpointC` | number | no | Celsius. |
| `relativeHumidityPercent` | number | no | Percent from 0 to 100. |
| `windDirectionDegrees` | number | no | Degrees. |
| `windSpeedKmh` | number | no | Kilometers per hour. |
| `windGustKmh` | number | no | Kilometers per hour. |
| `barometricPressurePa` | number | no | Pascals. |
| `visibilityMeters` | number | no | Meters. |
| `apparentTemperatureC` | number | no | Celsius. |
| `cloudCoverPercent` | number | no | Percent from 0 to 100. |
| `probabilityOfPrecipitationPercent` | number | no | Percent from 0 to 100. |
| `precipitationAmountMm` | number | no | Liquid-equivalent millimeters. |
| `snowfallDepthMm` | number | no | Millimeters. |
| `uvIndex` | number | no | Unitless index. |
## `weather.forecast_discussion.v1`
Payload type: `WeatherForecastDiscussion`.
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `officeId` | string | no | NWS office identifier. |
| `officeName` | string | no | Office name. |
| `product` | string | yes | Current value is `afd`. |
| `issuedAt` | timestamp | yes | Bulletin issue time. |
| `updatedAt` | timestamp | no | Subsequent update time. |
| `keyMessages` | array of strings | no | Extracted key messages. |
| `shortTerm` | object | no | Short-term section. |
| `longTerm` | object | no | Long-term section. |
`shortTerm` and `longTerm` sections contain optional `qualifier`, `issuedAt`,
and `text` fields.
## `weather.weather_story.v1`
Payload type: `WeatherStoryRun`.
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `officeId` | string | no | NWS office identifier. |
| `asOf` | timestamp | yes | Snapshot time. |
| `stories` | array | yes | Ordered story cards. |
`stories[]` entries:
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `officeId` | string | no | Office identifier. |
| `startTime` | timestamp | yes | Story start. |
| `endTime` | timestamp | yes | Story end. |
| `updatedAt` | timestamp | yes | Story update time. |
| `title` | string | no | Story title. |
| `description` | string | no | Story description. |
| `altText` | string | no | Image alternate text. |
| `priority` | boolean | yes | Provider priority flag. |
| `order` | integer | yes | Provider display order. |
| `downloadUrl` | string | no | Story image URL. |
## `weather.alert.v1`
Payload type: `WeatherAlertRun`.
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `locationId` | string | no | Provider location identifier. |
| `locationName` | string | no | Human location name. |
| `asOf` | timestamp | yes | Snapshot time. |
| `latitude` | number | no | Degrees. |
| `longitude` | number | no | Degrees. |
| `alerts` | array | yes | Active alerts. |
`alerts[]` entries:
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `id` | string | yes | Provider-stable alert identifier. |
| `event` | string | no | Alert event label. |
| `headline` | string | no | Alert headline. |
| `severity` | string | no | Provider severity. |
| `urgency` | string | no | Provider urgency. |
| `certainty` | string | no | Provider certainty. |
| `status` | string | no | Alert status. |
| `messageType` | string | no | Alert message type. |
| `category` | string | no | Alert category. |
| `response` | string | no | Recommended response. |
| `description` | string | no | Alert description. |
| `instruction` | string | no | Alert instruction. |
| `sent` | timestamp | no | Provider sent time. |
| `effective` | timestamp | no | Effective time. |
| `onset` | timestamp | no | Onset time. |
| `expires` | timestamp | no | Expiration time. |
| `areaDescription` | string | no | Affected area description. |
| `senderName` | string | no | Provider sender name. |
| `references` | array | no | Related alerts. |
`references[]` entries contain optional `id`, `identifier`, `sender`, and
`sent` fields.
## `weather.outlook.v2`
Payload type: `WeatherOutlookRun`.
The current producer is the SPC convective outlook normalizer. It emits Day 1-3
convective outlook snapshots for categorical, tornado, hail, and wind products
that apply to the configured forecast point. Raw SPC bundles remain complete;
canonical outlook payloads are filtered to local polygons. All timestamps are
UTC.
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `locationId` | string | no | Operator-configured location identifier. |
| `locationName` | string | no | Operator-configured location label. |
| `latitude` | number | no | Configured point latitude in decimal degrees. |
| `longitude` | number | no | Configured point longitude in decimal degrees. |
| `asOf` | timestamp | yes | Snapshot time. For SPC, this is the latest outlook issue time when available. |
| `issuedAt` | timestamp | no | Latest issue time across outlook features when any feature exists. |
| `outlooks` | array | yes | Ordered outlook polygons containing the configured point. |
| `discussions` | array | yes | Run-level day discussions for retained outlook days. |
`outlooks[]` entries:
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `id` | string | yes | Deterministic weatherfeeder outlook identifier. |
| `provider` | string | yes | Current value is `spc`. |
| `product` | string | yes | Current value is `convective`. |
| `day` | integer | yes | SPC outlook day, currently `1`, `2`, or `3`. |
| `outlookType` | string | yes | `categorical`, `tornado`, `hail`, or `wind`. |
| `label` | string | yes | SPC outlook label such as `SLGT` or `15`. |
| `labelText` | string | no | Human label text from SPC, when present. |
| `severityRank` | integer | no | SPC `DN` value, when present. |
| `validFrom` | timestamp | yes | Valid period start. |
| `validTo` | timestamp | yes | Valid period end. |
| `issuedAt` | timestamp | yes | Feature issue time. |
| `expiresAt` | timestamp | yes | Expiration time; currently equal to `validTo`. |
| `forecaster` | string | no | SPC forecaster text, when present. |
| `sourceUrl` | string | no | GeoJSON product URL for this outlook feature. |
| `imageUrl` | string | no | Reserved for provider image URLs; currently empty. |
| `containsLocation` | boolean | yes | Always `true` for emitted v2 outlooks. |
| `geometry` | object | yes | Compact GeoJSON `Polygon` or `MultiPolygon` geometry. |
`geometry` preserves the SPC feature geometry as compact GeoJSON using
`[longitude, latitude]` coordinate order. `containsLocation` is computed with
that geometry and the configured source `latitude`/`longitude`; boundary points
count as contained. Polygons that do not contain the configured point are not
included in canonical v2 payloads.
`discussions[]` entries:
| Field | Type | Required | Notes |
|---|---|:---:|---|
| `day` | integer | yes | SPC outlook day, currently `1`, `2`, or `3`. |
| `headline` | string | no | Matching Day 1-3 print-page product title. |
| `summary` | string | no | Text from the print-page `...SUMMARY...` section. |
| `discussion` | string | no | Cleaned full print-page product text. |
| `updatedAt` | timestamp | no | Print-page update time, when present. |
When no SPC polygons apply locally, the run is still emitted with `outlooks: []`
and `discussions: []`. Discussions are included only for days represented by at
least one retained outlook, and multiple retained outlook types for the same day
share one discussion entry.
### SPC Outlook Supersession
Consumers should prefer latest-run semantics for current conditions: read the
latest `WeatherOutlookRun` for the configured location and use its `outlooks`
and `discussions` arrays together.
Historical SQL consumers that collapse older rows should identify superseded
outlooks by `provider`, `product`, `outlookType`, `validFrom`, and `validTo`,
then keep rows with the greatest `issuedAt`. `day` and `label` are not identity
fields. When multiple retained polygons share that latest `issuedAt`, preserve
the full group.
## Legacy `weather.outlook.v1`
`weather.outlook.v1` is a historical canonical schema retained as a standards
constant for older data and consumers. Current SPC normalization emits
`weather.outlook.v2`.
The v1 payload used `WeatherOutlookRun` and placed `headline`, `summary`, and
`discussion` on each `outlooks[]` polygon. It also represented the pre-v2 SPC
canonical behavior, where national polygons were preserved in canonical output.
## Compact Example
```json
{
"id": "NWSObservationKSTL:2026-06-10T12:00:00Z",
"kind": "observation",
"source": "NWSObservationKSTL",
"emitted_at": "2026-06-10T12:00:05Z",
"effective_at": "2026-06-10T12:00:00Z",
"schema": "weather.observation.v1",
"payload": {
"stationId": "KSTL",
"timestamp": "2026-06-10T12:00:00Z",
"conditionCode": 0,
"temperatureC": 22.5
}
}
```