Document current outlook schema behavior

This commit is contained in:
2026-06-12 04:38:44 +00:00
parent 97141c7a9b
commit dcea5261ab
12 changed files with 175 additions and 45 deletions

View File

@@ -37,7 +37,7 @@ Canonical schemas emitted after normalization:
| `forecast_discussion` | `weather.forecast_discussion.v1` | `WeatherForecastDiscussion` |
| `weather_story` | `weather.weather_story.v1` | `WeatherStoryRun` |
| `alert` | `weather.alert.v1` | `WeatherAlertRun` |
| `outlook` | `weather.outlook.v1` | `WeatherOutlookRun` |
| `outlook` | `weather.outlook.v2` | `WeatherOutlookRun` |
Raw upstream schemas emitted by current sources:
@@ -220,13 +220,15 @@ Payload type: `WeatherAlertRun`.
`references[]` entries contain optional `id`, `identifier`, `sender`, and
`sent` fields.
## `weather.outlook.v1`
## `weather.outlook.v2`
Payload type: `WeatherOutlookRun`.
The current producer is the SPC convective outlook normalizer. It emits Day 1-3
convective outlook polygons for categorical, tornado, hail, and wind products.
All timestamps are UTC.
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 |
|---|---|:---:|---|
@@ -236,7 +238,8 @@ All timestamps are UTC.
| `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. |
| `outlooks` | array | yes | Ordered outlook polygons containing the configured point. |
| `discussions` | array | yes | Run-level day discussions for retained outlook days. |
`outlooks[]` entries:
@@ -255,19 +258,53 @@ All timestamps are UTC.
| `issuedAt` | timestamp | yes | Feature issue time. |
| `expiresAt` | timestamp | yes | Expiration time; currently equal to `validTo`. |
| `forecaster` | string | no | SPC forecaster text, when present. |
| `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. |
| `sourceUrl` | string | no | GeoJSON product URL for this outlook feature. |
| `imageUrl` | string | no | Reserved for provider image URLs; currently empty. |
| `containsLocation` | boolean | yes | Whether the configured point is inside or on the boundary of the polygon. |
| `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. All outlook polygons are emitted, including polygons that do
not contain the configured point.
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

View File

@@ -23,7 +23,7 @@ Events are mapped only for canonical weather schemas:
- `weather.forecast_discussion.v1`
- `weather.weather_story.v1`
- `weather.alert.v1`
- `weather.outlook.v1`
- `weather.outlook.v2`
Unsupported schemas produce no writes for this sink. Mapped events are inserted
transactionally. Inserts use ordinary `INSERT`; duplicate primary keys fail the
@@ -59,6 +59,7 @@ Parent tables store the feed event envelope:
| `alert_references` | `run_event_id`, `alert_index`, `reference_index` | `as_of` |
| `outlook_runs` | `event_id` | `as_of` |
| `outlooks` | `run_event_id`, `outlook_index` | `as_of` |
| `outlook_discussions` | `run_event_id`, `discussion_index` | `as_of` |
## Table Contract
@@ -408,6 +409,7 @@ Indexes:
| `as_of` | `TIMESTAMPTZ` | no | `payload.asOf` |
| `issued_at` | `TIMESTAMPTZ` | yes | `payload.issuedAt` |
| `outlook_count` | `INTEGER` | no | `len(payload.outlooks)` |
| `discussion_count` | `INTEGER` | no | `len(payload.discussions)` |
### `outlooks`
@@ -442,14 +444,36 @@ Indexes:
| `issued_at` | `TIMESTAMPTZ` | no | `payload.outlooks[].issuedAt` |
| `expires_at` | `TIMESTAMPTZ` | no | `payload.outlooks[].expiresAt` |
| `forecaster` | `TEXT` | yes | `payload.outlooks[].forecaster` |
| `headline` | `TEXT` | yes | `payload.outlooks[].headline` |
| `summary` | `TEXT` | yes | `payload.outlooks[].summary` |
| `discussion` | `TEXT` | yes | `payload.outlooks[].discussion` |
| `source_url` | `TEXT` | yes | `payload.outlooks[].sourceUrl` |
| `image_url` | `TEXT` | yes | `payload.outlooks[].imageUrl` |
| `contains_location` | `BOOLEAN` | no | `payload.outlooks[].containsLocation` |
| `geometry_json` | `TEXT` | no | Compact JSON from `payload.outlooks[].geometry` |
### `outlook_discussions`
Primary key: `run_event_id`, `discussion_index`
Prune column: `as_of`
Foreign key: `run_event_id` references `outlook_runs(event_id)` with cascade
delete.
Indexes:
- `idx_wf_outlook_discussions_day_as_of` on `day`, `as_of`
- unique `idx_wf_outlook_discussions_run_day` on `run_event_id`, `day`
| Column | Type | Null | Source |
|---|---|:---:|---|
| `run_event_id` | `TEXT REFERENCES outlook_runs(event_id) ON DELETE CASCADE` | no | Parent event ID. |
| `discussion_index` | `INTEGER` | no | `payload.discussions[]` index. |
| `as_of` | `TIMESTAMPTZ` | no | Parent `payload.asOf` |
| `day` | `INTEGER` | no | `payload.discussions[].day` |
| `headline` | `TEXT` | yes | `payload.discussions[].headline` |
| `summary` | `TEXT` | yes | `payload.discussions[].summary` |
| `discussion` | `TEXT` | yes | `payload.discussions[].discussion` |
| `updated_at` | `TIMESTAMPTZ` | yes | `payload.discussions[].updatedAt` |
## Retention
When sink param `prune` is set, every successful write transaction deletes rows
@@ -472,5 +496,6 @@ binary does not provide CLI commands for them.
- `WeatherAlertRun`: read `alert_runs`, join `alerts` by `run_event_id` ordered
by `alert_index`, then join `alert_references` by `run_event_id` and
`alert_index` ordered by `reference_index`.
- `WeatherOutlookRun`: read `outlook_runs`, then join `outlooks` by
`run_event_id` ordered by `outlook_index`.
- `WeatherOutlookRun`: read `outlook_runs`, join `outlooks` by `run_event_id`
ordered by `outlook_index`, then join `outlook_discussions` by
`run_event_id` ordered by `discussion_index`.

View File

@@ -13,7 +13,7 @@ events are documented in [event wire contract](events.md).
| Driver | Kind | Raw schema | Canonical schema |
| --- | --- | --- | --- |
| `spc_convective_outlook` | `outlook` | `raw.spc.convective_outlook.v1` | `weather.outlook.v1` |
| `spc_convective_outlook` | `outlook` | `raw.spc.convective_outlook.v1` | `weather.outlook.v2` |
## Config Requirements
@@ -52,8 +52,8 @@ It also fetches three required print pages:
- Day 3 convective outlook print page
GeoJSON products are authoritative for outlook polygons, valid windows, issue
times, labels, and severity rank. Print pages are authoritative for headline,
summary, and discussion text.
times, labels, and severity rank. Print pages are authoritative for run-level
day discussion headline, summary, and discussion text.
## Accept Headers
@@ -99,23 +99,33 @@ Raw source `effective_at` prefers:
4. fetch time.
The normalizer sets canonical `asOf` and normalized event `effective_at` from
the latest valid outlook feature `issuedAt`, with fallback to print-page update
time and then the incoming event envelope.
the latest valid GeoJSON issue time across the complete raw bundle, including
empty no-risk placeholders, with fallback to print-page update time and then the
incoming event envelope.
## Mapping Notes
Each GeoJSON feature becomes one canonical outlook. Products are ordered by day,
then categorical, tornado, hail, and wind. Feature order is preserved within
each product.
The raw source fetches and envelopes the complete SPC bundle. The normalizer
decodes every configured GeoJSON product, skips empty no-risk
`GeometryCollection` placeholders, and emits canonical outlooks only when the
configured point is inside or on the boundary of a real feature. Products are
ordered by day, then categorical, tornado, hail, and wind. Retained feature order
is preserved within each product.
The normalizer computes `containsLocation` with the configured latitude and
longitude against compact GeoJSON `Polygon` or `MultiPolygon` geometry.
Coordinates use GeoJSON order, `[longitude, latitude]`, and boundary points
count as contained.
All outlook polygons are preserved, including polygons that do not contain the
configured point. Matching print-page headline, summary, and discussion text is
attached to every outlook for the same day.
Canonical outlook runs are emitted even when no polygons apply locally. In that
case the payload contains empty `outlooks` and `discussions` arrays.
Print-page prose is represented as run-level day discussions. Discussions are
included only for days represented by at least one retained outlook. Multiple
retained outlook types for the same day share one discussion entry.
For downstream current-state and historical supersession guidance, see the
[event wire contract](events.md#spc-outlook-supersession).
## Failure Behavior