Document implemented integration contracts
This commit is contained in:
@@ -1,354 +1,122 @@
|
||||
# weatherapi External API
|
||||
# Weather API Integration
|
||||
|
||||
This document describes the public HTTP API exposed by `weatherapi` for external consumers.
|
||||
This document describes the external Weather API contract used by
|
||||
`weatherreporter`.
|
||||
|
||||
## Purpose
|
||||
|
||||
`weatherreporter` uses a configured Weather API base URL to fetch normalized
|
||||
weather source data and assemble a `forecast.Bundle`. This is an integration
|
||||
contract for the project adapter, not a complete public API reference for the
|
||||
upstream service.
|
||||
|
||||
## Base URL
|
||||
|
||||
The service is typically served at your deployment host, for example:
|
||||
`weather_api.base_url` must be an absolute URL. Adapter requests join this base
|
||||
URL with the endpoint paths listed below. Generation and explicit bundle fetches
|
||||
fail before any HTTP request when the base URL is empty or not absolute.
|
||||
|
||||
- `https://weather.api.rakestrawhome.com`
|
||||
The HTTP client uses `weather_api.timeout`.
|
||||
|
||||
All paths below are relative to the service root.
|
||||
## Response Envelope
|
||||
|
||||
## Common Conventions
|
||||
|
||||
### Response envelope
|
||||
|
||||
All endpoints return a top-level envelope:
|
||||
|
||||
- `data`: endpoint payload or `null` when no current/latest resource is available.
|
||||
|
||||
JSON example:
|
||||
Every response used by the adapter must be JSON with a top-level `data` field:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {"...": "..."}
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Output format
|
||||
`data: null` is treated as a missing source. Missing optional sources follow the
|
||||
configured missing-source policy. Missing hourly forecast data fails bundle
|
||||
fetching because hourly periods are required for report generation.
|
||||
|
||||
Supported via `format` query parameter (case-insensitive):
|
||||
Malformed JSON envelopes, non-2xx statuses, and response read failures include
|
||||
endpoint context in returned errors. Decode errors include source context when
|
||||
they fail the fetch; optional malformed sources follow the missing-source policy.
|
||||
|
||||
- `json` (default)
|
||||
- `xml`
|
||||
- `text`
|
||||
## Query Parameters
|
||||
|
||||
### Units
|
||||
The adapter sends these query parameters:
|
||||
|
||||
Supported via `units` query parameter (case-insensitive):
|
||||
- `format`: from `weather_api.format`; the implemented configuration requires
|
||||
`json`
|
||||
- `units`: from `weather_api.units`
|
||||
- `precision`: from `weather_api.precision` on observations, current
|
||||
conditions, hourly forecast, and narrative forecast requests
|
||||
- `tz`: from `weather_api.timezone` on hourly forecast, narrative forecast, and
|
||||
discussion requests
|
||||
|
||||
- `metric` (default)
|
||||
- `us`
|
||||
Alerts do not receive `precision` or `tz`.
|
||||
|
||||
Endpoints that include unit-based numeric fields return either metric or US field variants depending on this value.
|
||||
## Endpoints Used
|
||||
|
||||
### Precision
|
||||
The adapter fetches these endpoints once per bundle:
|
||||
|
||||
Supported where documented via `precision` query parameter:
|
||||
- `/observations`
|
||||
- `/conditions/current`
|
||||
- `/forecast/hourly`
|
||||
- `/forecast/narrative`
|
||||
- `/alerts/active`
|
||||
- `/discussion`
|
||||
|
||||
- integer range: `0` to `2`
|
||||
- controls decimal rounding of numeric output fields
|
||||
`weatherreporter` does not call day-slice forecast endpoints or discussion
|
||||
subsection endpoints. Report-period selection and daypart summarization happen
|
||||
inside Go after the full hourly and narrative products are fetched.
|
||||
|
||||
### Timezone (`tz` / `TZ`)
|
||||
## Required And Optional Sources
|
||||
|
||||
Supported where documented:
|
||||
Hourly forecast is required:
|
||||
|
||||
- accepted values include:
|
||||
- IANA timezone names (example: `America/Chicago`)
|
||||
- common US abbreviations (example: `CDT`, `EST`)
|
||||
- UTC offsets in `±H`, `±HH`, or `±HH:MM` (example: `-5`, `+09:30`)
|
||||
- aliases including `Chicago` and `Stl`
|
||||
- `tz` and `TZ` are treated equivalently
|
||||
- if both are provided, they must match exactly or the request fails
|
||||
- `data: null` for `/forecast/hourly` fails the fetch.
|
||||
- an hourly forecast with no `periods` fails the fetch.
|
||||
- malformed hourly data fails the fetch.
|
||||
|
||||
Timezone affects datetime rendering and day-slice filtering for `/today` and `/tomorrow` forecast routes.
|
||||
Other fetched sources are optional and follow `missing_source.default` or a
|
||||
source-specific `missing_source.sources` policy:
|
||||
|
||||
### Query validation
|
||||
- `observations` for `/observations`
|
||||
- `current` for `/conditions/current`
|
||||
- `narrative` for `/forecast/narrative`
|
||||
- `alerts` for `/alerts/active`
|
||||
- `discussion` for `/discussion`
|
||||
|
||||
- Unknown query parameters are rejected with `400 Bad Request`.
|
||||
- Invalid parameter values are rejected with `400 Bad Request`.
|
||||
The adapter also creates missing stub source records for `daily` and
|
||||
`weather_story` because those source slots exist in the internal bundle but are
|
||||
not fetched from the Weather API.
|
||||
|
||||
Error response body follows the service error envelope; exact fields may vary by error type.
|
||||
Policy behavior:
|
||||
|
||||
## Endpoints
|
||||
- `error`: fail the fetch for that source
|
||||
- `warn`: omit the source data, add a warning, and continue
|
||||
- `none`: omit the source data and continue without a warning
|
||||
|
||||
## `GET /observations`
|
||||
## Source Identity
|
||||
|
||||
Returns the latest weather observation.
|
||||
For non-null source payloads, the adapter records:
|
||||
|
||||
Query parameters:
|
||||
- source name
|
||||
- endpoint path
|
||||
- query parameters sent
|
||||
- fetch time
|
||||
- source issue and update timestamps when present in the payload
|
||||
- SHA-256 hash of the compact raw `data` JSON
|
||||
|
||||
- `units`: `metric` | `us`
|
||||
- `format`: `json` | `xml` | `text`
|
||||
- `precision`: `0..2`
|
||||
Warnings are recorded both on the affected source and on the bundle-level
|
||||
warnings list.
|
||||
|
||||
Response `data` fields:
|
||||
## Compatibility Assumptions
|
||||
|
||||
- `stationId` (string, optional)
|
||||
- `stationName` (string, optional)
|
||||
- `timestamp` (RFC3339 datetime, required)
|
||||
- `conditionCode` (integer WMO code, required)
|
||||
- `isDay` (boolean, optional)
|
||||
- `textDescription` (string, optional)
|
||||
- Metric mode fields:
|
||||
- `temperatureC`, `dewpointC`, `windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`, `relativeHumidityPercent`, `apparentTemperatureC` (number, optional)
|
||||
- `windDirectionDegrees` (number, optional)
|
||||
- US mode fields:
|
||||
- `temperatureF`, `dewpointF`, `windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`, `relativeHumidityPercent`, `apparentTemperatureF` (number, optional)
|
||||
- `windDirectionDegrees` (number, optional)
|
||||
- `presentWeather` (array, optional)
|
||||
The adapter expects payload fields compatible with the internal forecast bundle
|
||||
types in `internal/forecast/bundle.go`, including:
|
||||
|
||||
## `GET /alerts/active`
|
||||
- observation timestamps and observation values
|
||||
- current condition values
|
||||
- forecast run metadata and `periods`
|
||||
- active alert run data
|
||||
- discussion metadata, key messages, and short/long-term sections
|
||||
|
||||
Returns the latest active alert run.
|
||||
|
||||
Query parameters:
|
||||
|
||||
- `units`: `metric` | `us` (accepted; does not materially alter alert payload)
|
||||
- `format`: `json` | `xml` | `text`
|
||||
|
||||
Response `data` fields:
|
||||
|
||||
- Weather alert run object from canonical model (includes run metadata and active alerts list).
|
||||
|
||||
## `GET /conditions/current`
|
||||
|
||||
Returns current conditions synthesized from latest observation/forecast data.
|
||||
|
||||
Query parameters:
|
||||
|
||||
- `units`: `metric` | `us`
|
||||
- `format`: `json` | `xml` | `text`
|
||||
- `precision`: `0..2`
|
||||
|
||||
Response `data` fields:
|
||||
|
||||
- Common:
|
||||
- `conditionText` (string, optional)
|
||||
- `isDay` (boolean, optional)
|
||||
- `relativeHumidityPercent` (number, optional)
|
||||
- `windDirectionDegrees` (number, optional)
|
||||
- Metric mode:
|
||||
- `temperatureC`, `apparentTemperatureC`, `dewpointC`, `windSpeedKmh` (number, optional)
|
||||
- US mode:
|
||||
- `temperatureF`, `apparentTemperatureF`, `dewpointF`, `windSpeedMph` (number, optional)
|
||||
|
||||
## Forecast endpoints
|
||||
|
||||
- `GET /forecast/hourly`
|
||||
- `GET /forecast/hourly/today`
|
||||
- `GET /forecast/hourly/tomorrow`
|
||||
- `GET /forecast/narrative`
|
||||
- `GET /forecast/narrative/today`
|
||||
- `GET /forecast/narrative/tomorrow`
|
||||
|
||||
Query parameters:
|
||||
|
||||
- `units`: `metric` | `us`
|
||||
- `format`: `json` | `xml` | `text`
|
||||
- `precision`: `0..2`
|
||||
- `tz` or `TZ`: timezone selector
|
||||
|
||||
Day-slice routes:
|
||||
|
||||
- `/today` returns periods with `period.startTime` in the current calendar day for the resolved timezone.
|
||||
- `/tomorrow` returns periods with `period.startTime` in the next calendar day for the resolved timezone.
|
||||
|
||||
Response `data` fields:
|
||||
|
||||
- Run-level:
|
||||
- `locationId` (string, optional)
|
||||
- `locationName` (string, optional)
|
||||
- `issuedAt` (RFC3339 datetime, required)
|
||||
- `updatedAt` (RFC3339 datetime, optional)
|
||||
- `product` (string, required; e.g. `hourly`, `narrative`)
|
||||
- `latitude`, `longitude` (number, optional)
|
||||
- Metric mode: `elevationMeters` (number, optional)
|
||||
- US mode: `elevationFeet` (number, optional)
|
||||
- `periods` (array, required)
|
||||
- Period fields:
|
||||
- `startTime`, `endTime` (RFC3339 datetime, required)
|
||||
- `name` (string, optional)
|
||||
- `isDay` (boolean, optional)
|
||||
- `conditionCode` (integer WMO code, optional)
|
||||
- `textDescription` (string, optional)
|
||||
- Metric mode (optional):
|
||||
- `temperatureC`, `temperatureCMin`, `temperatureCMax`, `dewpointC`, `windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`, `apparentTemperatureC`, `cloudCoverPercent`, `probabilityOfPrecipitationPercent`, `precipitationAmountMm`, `snowfallDepthMM`, `uvIndex`, `relativeHumidityPercent`, `windDirectionDegrees`
|
||||
- US mode (optional):
|
||||
- `temperatureF`, `temperatureFMin`, `temperatureFMax`, `dewpointF`, `windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`, `apparentTemperatureF`, `cloudCoverPercent`, `probabilityOfPrecipitationPercent`, `precipitationAmountIn`, `snowfallDepthIn`, `uvIndex`, `relativeHumidityPercent`, `windDirectionDegrees`
|
||||
|
||||
Notes:
|
||||
|
||||
- Narrative periods may omit `conditionCode`.
|
||||
- Text format uses forecast-specific templates (`hourly` and `narrative`).
|
||||
|
||||
## Discussion endpoints
|
||||
|
||||
- `GET /discussion`
|
||||
- `GET /discussion/key-messages`
|
||||
- `GET /discussion/short-term`
|
||||
- `GET /discussion/long-term`
|
||||
|
||||
Query parameters:
|
||||
|
||||
- `units`: `metric` | `us` (accepted; does not materially alter discussion payload)
|
||||
- `format`: `json` | `xml` | `text`
|
||||
- `tz` or `TZ`: timezone selector
|
||||
|
||||
Response `data` fields:
|
||||
|
||||
- `/discussion`:
|
||||
- `officeId` (string, optional)
|
||||
- `officeName` (string, optional)
|
||||
- `product` (string, required)
|
||||
- `issuedAt` (RFC3339 datetime, required)
|
||||
- `updatedAt` (RFC3339 datetime, optional)
|
||||
- `keyMessages` (array of string)
|
||||
- `shortTerm` (object, optional)
|
||||
- `longTerm` (object, optional)
|
||||
- `/discussion/key-messages`:
|
||||
- `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt`
|
||||
- `keyMessages` (array of string)
|
||||
- `/discussion/short-term`:
|
||||
- `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt`
|
||||
- `shortTerm` (object, optional)
|
||||
- `/discussion/long-term`:
|
||||
- `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt`
|
||||
- `longTerm` (object, optional)
|
||||
|
||||
Discussion section object fields:
|
||||
|
||||
- `title` (string, optional)
|
||||
- `narrative` (string, optional)
|
||||
- `issuedAt` (RFC3339 datetime, optional)
|
||||
|
||||
## Examples
|
||||
|
||||
### Observation (JSON, metric)
|
||||
|
||||
```http
|
||||
GET /observations?format=json&units=metric&precision=1
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"stationId": "KSTL",
|
||||
"timestamp": "2026-05-29T14:00:00Z",
|
||||
"conditionCode": 3,
|
||||
"isDay": true,
|
||||
"textDescription": "Partly cloudy",
|
||||
"temperatureC": 24.4,
|
||||
"windSpeedKmh": 17.2,
|
||||
"relativeHumidityPercent": 56.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Alerts (JSON)
|
||||
|
||||
```http
|
||||
GET /alerts/active?format=json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"asOf": "2026-05-29T14:00:00Z",
|
||||
"alerts": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Current conditions (JSON, US)
|
||||
|
||||
```http
|
||||
GET /conditions/current?format=json&units=us&precision=1
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"conditionText": "Partly cloudy",
|
||||
"isDay": true,
|
||||
"temperatureF": 75.9,
|
||||
"apparentTemperatureF": 76.1,
|
||||
"windSpeedMph": 10.7,
|
||||
"relativeHumidityPercent": 56.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Forecast narrative (JSON, optional `conditionCode`)
|
||||
|
||||
```http
|
||||
GET /forecast/narrative?format=json&units=metric&precision=1&tz=America/Chicago
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"locationId": "nws-lsx-grid-90-74",
|
||||
"issuedAt": "2026-05-29T10:30:00-05:00",
|
||||
"product": "narrative",
|
||||
"periods": [
|
||||
{
|
||||
"startTime": "2026-05-29T13:00:00-05:00",
|
||||
"endTime": "2026-05-29T19:00:00-05:00",
|
||||
"name": "Today",
|
||||
"isDay": true,
|
||||
"textDescription": "Partly sunny, with a high near 81.",
|
||||
"temperatureC": 27.2,
|
||||
"windSpeedKmh": 18.0,
|
||||
"probabilityOfPrecipitationPercent": 10.0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Forecast hourly today (text)
|
||||
|
||||
```http
|
||||
GET /forecast/hourly/today?format=text&units=us&precision=1&tz=CDT
|
||||
```
|
||||
|
||||
```text
|
||||
<plain text forecast output>
|
||||
```
|
||||
|
||||
### Discussion key messages (JSON)
|
||||
|
||||
```http
|
||||
GET /discussion/key-messages?format=json&tz=Chicago
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"officeId": "LSX",
|
||||
"product": "discussion",
|
||||
"issuedAt": "2026-05-29T09:25:00-05:00",
|
||||
"keyMessages": [
|
||||
"Scattered showers possible this evening.",
|
||||
"Warmer temperatures this weekend."
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid timezone error example
|
||||
|
||||
```http
|
||||
GET /forecast/narrative?tz=not-a-timezone
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "invalid_parameter",
|
||||
"message": "tz must be a valid timezone"
|
||||
}
|
||||
}
|
||||
```
|
||||
The adapter intentionally keeps upstream transport and envelope details inside
|
||||
`internal/adapters/weatherapi`; downstream packages consume the normalized
|
||||
bundle.
|
||||
|
||||
Reference in New Issue
Block a user