From cff8c5f593a2c94b9a40473c979fadf73d219495 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 7 Feb 2026 19:36:50 -0600 Subject: [PATCH] Merged SCHEMA.md into API.md and made some tweaks to ensure consistency with the underlying domain model. --- API.md | 103 +++++++++++++++------------------ SCHEMA.md | 166 ------------------------------------------------------ 2 files changed, 45 insertions(+), 224 deletions(-) delete mode 100644 SCHEMA.md diff --git a/API.md b/API.md index ce66234..c2b204e 100644 --- a/API.md +++ b/API.md @@ -47,36 +47,22 @@ Each payload is described below using the JSON field names as the contract. --- -## Shared conventions +## Shared Conventions -### Optional fields - -Most non-identity measurements are optional. Optional fields are omitted when unknown (i.e., not present). - -### Units - -Canonical payloads are normalized to metric units: - -- Temperature: `*C` (Celsius) -- Wind speed/gust: `*Kmh` (kilometers/hour) -- Pressure: `*Pa` (Pascals) -- Visibility / distance / elevation: `*Meters` (meters) -- Precipitation amount / snowfall depth: `*Mm` (millimeters) -- Humidity / cloud cover / PoP: `*Percent` (0–100) - -### Float rounding - -For readability and stability, weatherfeeder rounds floating-point values in canonical payloads to -**2 digits after the decimal** during normalization finalization. - -### WMO condition codes - -`conditionCode` uses the WMO weather interpretation code vocabulary. - -- Type: integer -- Unknown/unmappable: `-1` - -Downstream consumers should treat unknown codes as “unknown conditions” rather than failing decoding. +- Timestamps are JSON strings in RFC3339Nano format. +- Optional fields are omitted when unknown (`omitempty` behavior). +- Numeric measurements are normalized to metric units: + - `*C` = Celsius + - `*Kmh` = kilometers/hour + - `*Pa` = Pascals + - `*Meters` = meters + - `*Mm` = millimeters + - `*Percent` = percent (0-100) +- `conditionCode` is a WMO weather interpretation code (`int`). + - Unknown/unmappable is `-1`. + - Downstream consumers should treat unknown codes as “unknown conditions” rather than failing decoding. +- For readability and stability, weatherfeeder rounds floating-point values in canonical payloads to +**4 digits after the decimal** during normalization. --- @@ -88,31 +74,31 @@ A `WeatherObservation` represents a point-in-time observation for a station/loca ### Fields -| Field | Type | Required | Units / Notes | +| Field | Type | Required | Notes | |---|---:|:---:|---| | `stationId` | string | no | Provider station/location identifier | -| `stationName` | string | no | Human name, if available | -| `timestamp` | string (timestamp) | yes | Observation time | -| `conditionCode` | int | yes | WMO code (`-1` for unknown) | -| `conditionText` | string | no | Canonical short text (often derived from WMO code) | -| `isDay` | bool | no | Day/night hint when available | -| `providerRawDescription` | string | no | Provider-specific “evidence” text | -| `textDescription` | string | no | Legacy/transitional human text | +| `stationName` | string | no | Human station name | +| `timestamp` | timestamp string | yes | Observation timestamp | +| `conditionCode` | int | yes | WMO code (`-1` unknown) | +| `conditionText` | string | no | Canonical short condition text | +| `isDay` | bool | no | Day/night hint | +| `providerRawDescription` | string | no | Provider-specific evidence text | +| `textDescription` | string | no | Legacy/transitional text description | | `iconUrl` | string | no | Legacy/transitional icon URL | -| `temperatureC` | number | no | °C | -| `dewpointC` | number | no | °C | -| `windDirectionDegrees` | number | no | Degrees (meteorological) | +| `temperatureC` | number | no | Celsius | +| `dewpointC` | number | no | Celsius | +| `windDirectionDegrees` | number | no | Degrees | | `windSpeedKmh` | number | no | km/h | | `windGustKmh` | number | no | km/h | -| `barometricPressurePa` | number | no | Pa | -| `seaLevelPressurePa` | number | no | Pa | -| `visibilityMeters` | number | no | meters | -| `relativeHumidityPercent` | number | no | percent (0–100) | -| `apparentTemperatureC` | number | no | °C | -| `elevationMeters` | number | no | meters | -| `rawMessage` | string | no | Provider raw message (e.g. METAR), if available | -| `presentWeather` | array | no | Provider-specific structured fragments | -| `cloudLayers` | array | no | Cloud layers (base + amount) | +| `barometricPressurePa` | number | no | Pascals | +| `seaLevelPressurePa` | number | no | Pascals | +| `visibilityMeters` | number | no | Meters | +| `relativeHumidityPercent` | number | no | Percent | +| `apparentTemperatureC` | number | no | Celsius | +| `elevationMeters` | number | no | Meters | +| `rawMessage` | string | no | Provider raw message (for example METAR) | +| `presentWeather` | array | no | Provider-specific structured weather fragments | +| `cloudLayers` | array | no | Cloud layer details | ### Nested: `cloudLayers[]` @@ -156,7 +142,7 @@ A `WeatherForecastRun` is a single issued forecast snapshot for a location and a | `locationName` | string | no | Human name, if available | | `issuedAt` | string (timestamp) | yes | When this run was generated/issued | | `updatedAt` | string (timestamp) | no | Optional later update time | -| `product` | string | yes | `"hourly"`, `"narrative"`, or `"daily"` | +| `product` | string | yes | One of `hourly`, `narrative`, `daily` | | `latitude` | number | no | Degrees | | `longitude` | number | no | Degrees | | `elevationMeters` | number | no | meters | @@ -220,14 +206,15 @@ A run may contain zero, one, or many alerts. | Field | Type | Required | Notes | |---|---:|:---:|---| | `id` | string | yes | Provider-stable identifier (often a URL/URI) | -| `event` | string | no | Classification | -| `headline` | string | no | Short headline | -| `severity` | string | no | e.g. Extreme/Severe/Moderate/Minor/Unknown | -| `urgency` | string | no | e.g. Immediate/Expected/Future/Past/Unknown | -| `certainty` | string | no | e.g. Observed/Likely/Possible/Unlikely/Unknown | -| `status` | string | no | e.g. Actual/Exercise/Test/System/Unknown | -| `messageType` | string | no | e.g. Alert/Update/Cancel | -| `category` | string | no | e.g. Met/Geo/Safety/... | +| `event` | string | no | Classification/event label | +| `headline` | string | no | Alert headline | +| `severity` | string | no | Example: Extreme/Severe/Moderate/Minor/Unknown | +| `urgency` | string | no | Example: Immediate/Expected/Future/Past/Unknown | +| `certainty` | string | no | Example: Observed/Likely/Possible/Unlikely/Unknown | +| `status` | string | no | Example: Actual/Exercise/Test/System/Unknown | +| `messageType` | string | no | Example: Alert/Update/Cancel | +| `category` | string | no | Example: Met/Geo/Safety/Rescue/Fire/Health/Env/Transport/Infra/CBRNE/Other | +| `response` | string | no | Example: Shelter/Evacuate/Prepare/Execute/Avoid/Monitor/Assess/AllClear/None | | `response` | string | no | e.g. Shelter/Evacuate/Prepare/... | | `description` | string | no | Narrative | | `instruction` | string | no | What to do | diff --git a/SCHEMA.md b/SCHEMA.md deleted file mode 100644 index 428b3d3..0000000 --- a/SCHEMA.md +++ /dev/null @@ -1,166 +0,0 @@ -# weatherfeeder Canonical Schemas - -This document expressly defines the JSON payload schemas emitted by weatherfeeder for: - -- `weather.observation.v1` -- `weather.forecast.v1` -- `weather.alert.v1` - -These schemas are defined by the `internal/model` structs and their JSON tags. - -## Shared Conventions - -- Timestamps are JSON strings in RFC3339Nano format. -- Optional fields are omitted when unknown (`omitempty` behavior). -- Numeric measurements are normalized to metric units: - - `*C` = Celsius - - `*Kmh` = kilometers/hour - - `*Pa` = Pascals - - `*Meters` = meters - - `*Mm` = millimeters - - `*Percent` = percent (0-100) -- `conditionCode` is a WMO weather interpretation code (`int`). - - Unknown/unmappable is `-1`. - -## `weather.observation.v1` - -Payload type: `model.WeatherObservation` - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `stationId` | string | no | Provider station/location identifier | -| `stationName` | string | no | Human station name | -| `timestamp` | timestamp string | yes | Observation timestamp | -| `conditionCode` | int | yes | WMO code (`-1` unknown) | -| `conditionText` | string | no | Canonical short condition text | -| `isDay` | bool | no | Day/night hint | -| `providerRawDescription` | string | no | Provider-specific evidence text | -| `textDescription` | string | no | Legacy/transitional text description | -| `iconUrl` | string | no | Legacy/transitional icon URL | -| `temperatureC` | number | no | Celsius | -| `dewpointC` | number | no | Celsius | -| `windDirectionDegrees` | number | no | Degrees | -| `windSpeedKmh` | number | no | km/h | -| `windGustKmh` | number | no | km/h | -| `barometricPressurePa` | number | no | Pascals | -| `seaLevelPressurePa` | number | no | Pascals | -| `visibilityMeters` | number | no | Meters | -| `relativeHumidityPercent` | number | no | Percent | -| `apparentTemperatureC` | number | no | Celsius | -| `elevationMeters` | number | no | Meters | -| `rawMessage` | string | no | Provider raw message (for example METAR) | -| `presentWeather` | array | no | Provider-specific structured weather fragments | -| `cloudLayers` | array | no | Cloud layer details | - -### `cloudLayers[]` (`model.CloudLayer`) - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `baseMeters` | number | no | Cloud base altitude in meters | -| `amount` | string | no | Provider amount code/text | - -### `presentWeather[]` (`model.PresentWeather`) - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `raw` | object | no | Provider-specific object | - -## `weather.forecast.v1` - -Payload type: `model.WeatherForecastRun` - -`product` values are: - -- `"hourly"` -- `"narrative"` -- `"daily"` - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `locationId` | string | no | Provider location identifier | -| `locationName` | string | no | Human location name | -| `issuedAt` | timestamp string | yes | Forecast run issue/generated time | -| `updatedAt` | timestamp string | no | Optional later update time | -| `product` | string | yes | One of `hourly`, `narrative`, `daily` | -| `latitude` | number | no | Degrees | -| `longitude` | number | no | Degrees | -| `elevationMeters` | number | no | Meters | -| `periods` | array | yes | Forecast periods, intended chronological order | - -### `periods[]` (`model.WeatherForecastPeriod`) - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `startTime` | timestamp string | yes | Start of validity window | -| `endTime` | timestamp string | yes | End of validity window | -| `name` | string | no | Human label (for example "Tonight") | -| `isDay` | bool | no | Day/night hint | -| `conditionCode` | int | yes | WMO code (`-1` unknown) | -| `conditionText` | string | no | Canonical short condition text | -| `providerRawDescription` | string | no | Provider-specific evidence text | -| `textDescription` | string | no | Short narrative text | -| `detailedText` | string | no | Longer narrative text | -| `iconUrl` | string | no | Legacy/transitional icon URL | -| `temperatureC` | number | no | Celsius | -| `temperatureCMin` | number | no | Celsius (aggregated periods) | -| `temperatureCMax` | number | no | Celsius (aggregated periods) | -| `dewpointC` | number | no | Celsius | -| `relativeHumidityPercent` | number | no | Percent | -| `windDirectionDegrees` | number | no | Degrees | -| `windSpeedKmh` | number | no | km/h | -| `windGustKmh` | number | no | km/h | -| `barometricPressurePa` | number | no | Pascals | -| `visibilityMeters` | number | no | Meters | -| `apparentTemperatureC` | number | no | Celsius | -| `cloudCoverPercent` | number | no | Percent | -| `probabilityOfPrecipitationPercent` | number | no | Percent | -| `precipitationAmountMm` | number | no | Liquid-equivalent millimeters | -| `snowfallDepthMm` | number | no | Millimeters | -| `uvIndex` | number | no | Unitless UV index | - -## `weather.alert.v1` - -Payload type: `model.WeatherAlertRun` - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `locationId` | string | no | Provider location identifier | -| `locationName` | string | no | Human location name | -| `asOf` | timestamp string | yes | Snapshot timestamp | -| `latitude` | number | no | Degrees | -| `longitude` | number | no | Degrees | -| `alerts` | array | yes | Active alerts in the snapshot | - -### `alerts[]` (`model.WeatherAlert`) - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `id` | string | yes | Provider-stable ID, often URL/URI | -| `event` | string | no | Classification/event label | -| `headline` | string | no | Alert headline | -| `severity` | string | no | Example: Extreme/Severe/Moderate/Minor/Unknown | -| `urgency` | string | no | Example: Immediate/Expected/Future/Past/Unknown | -| `certainty` | string | no | Example: Observed/Likely/Possible/Unlikely/Unknown | -| `status` | string | no | Example: Actual/Exercise/Test/System/Unknown | -| `messageType` | string | no | Example: Alert/Update/Cancel | -| `category` | string | no | Example: Met/Geo/Safety/Rescue/Fire/Health/Env/Transport/Infra/CBRNE/Other | -| `response` | string | no | Example: Shelter/Evacuate/Prepare/Execute/Avoid/Monitor/Assess/AllClear/None | -| `description` | string | no | Narrative description | -| `instruction` | string | no | Recommended actions | -| `sent` | timestamp string | no | Provider-dependent | -| `effective` | timestamp string | no | Provider-dependent | -| `onset` | timestamp string | no | Provider-dependent | -| `expires` | timestamp string | no | Provider-dependent | -| `areaDescription` | string | no | Provider area text | -| `senderName` | string | no | Alert sender/source name | -| `references` | array | no | Related alerts | - -### `references[]` (`model.AlertReference`) - -| Field | Type | Required | Notes | -|---|---:|:---:|---| -| `id` | string | no | Related alert ID/URI | -| `identifier` | string | no | Provider identifier when distinct | -| `sender` | string | no | Sender | -| `sent` | timestamp string | no | Timestamp | -