Refresh Weather API integration guide
This commit is contained in:
@@ -1,37 +1,51 @@
|
||||
# Weather API Integration
|
||||
|
||||
This document describes the external Weather API contract used by
|
||||
`weatherreporter`.
|
||||
Weatherreporter fetches normalized weather inputs from a configured Weather API
|
||||
base URL. This guide defines the HTTP contract the service must satisfy; it is
|
||||
not a general Weather API reference. Configuration values are defined in the
|
||||
[configuration reference](../config.md). Normalization and collection behavior
|
||||
are documented in [Weather data internals](../internal/weather-data.md) and
|
||||
[Collection internals](../internal/collect.md).
|
||||
|
||||
## Purpose
|
||||
## Base URL And Requests
|
||||
|
||||
`weatherreporter` uses a configured Weather API base URL to fetch normalized
|
||||
weather source data and assemble a `weatherdata.Bundle`. This is an integration
|
||||
contract for the project adapter, not a complete public API reference for the
|
||||
upstream service.
|
||||
`weather_api.base_url` must be an absolute URL. Weatherreporter joins each
|
||||
endpoint path to the configured base URL path, so a service hosted under a path
|
||||
prefix must keep that prefix available. Requests use `GET` and carry the
|
||||
configured timeout on every HTTP attempt.
|
||||
|
||||
## Base URL
|
||||
Every request sends `format` and, except where noted below, `units`. The
|
||||
configured format must be `json`.
|
||||
|
||||
`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.
|
||||
Before retrieving sources, Weatherreporter warms up
|
||||
`/conditions/current` with the same `format`, `units`, and `precision` query
|
||||
parameters used for current conditions. The warmup only requires a readable
|
||||
2xx response; its body is not decoded. Failure after its internal retry budget
|
||||
stops the fetch before source requests begin.
|
||||
|
||||
The HTTP client uses `weather_api.timeout`.
|
||||
## Endpoints And Query Parameters
|
||||
|
||||
Before fetching bundle sources, the adapter performs a warmup `GET` to
|
||||
`/conditions/current` with the same query parameters as the current-conditions
|
||||
source request. This is a temporary connectivity check for VPN wake-up behavior
|
||||
until the upstream service provides a dedicated health endpoint. A successful
|
||||
warmup requires a 2xx response whose body can be read; the adapter does not
|
||||
decode or validate the response envelope during warmup.
|
||||
The adapter makes one source request for each endpoint after a successful
|
||||
warmup, subject to retry on transient failures.
|
||||
|
||||
Warmup attempts, warmup delay, source-fetch retry attempts, and source-fetch
|
||||
retry delay are internal adapter defaults. They are not configuration-file
|
||||
fields or CLI flags yet. `weather_api.timeout` applies to each HTTP attempt.
|
||||
| Source | Endpoint | Query parameters | Availability |
|
||||
| --- | --- | --- | --- |
|
||||
| Observations | `/observations` | `format`, `units`, `precision` | Optional |
|
||||
| Current conditions | `/conditions/current` | `format`, `units`, `precision` | Optional |
|
||||
| Hourly forecast | `/forecast/hourly` | `format`, `units`, `precision`, `tz` | Required |
|
||||
| Narrative forecast | `/forecast/narrative` | `format`, `units`, `precision`, `tz` | Optional |
|
||||
| Active alerts | `/alerts/active` | `format`, `units` | Optional; `data: null` means checked with no active alerts |
|
||||
| Forecast discussion | `/discussion` | `format`, `units`, `tz` | Optional |
|
||||
| Weather story | `/weatherstories/latest` | `format` | Optional |
|
||||
| SPC convective outlooks | `/outlooks/convective` | `format`, `tz` | Optional; non-null empty lists are checked empty data |
|
||||
|
||||
`precision` comes from `weather_api.precision`; `tz` comes from
|
||||
`weather_api.timezone`. Weatherreporter does not call day-slice forecast or
|
||||
discussion-subsection endpoints.
|
||||
|
||||
## Response Envelope
|
||||
|
||||
Every response used by the adapter must be JSON with a top-level `data` field:
|
||||
Each endpoint response must be JSON with a top-level `data` member:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -39,181 +53,95 @@ Every response used by the adapter must be JSON with a top-level `data` field:
|
||||
}
|
||||
```
|
||||
|
||||
For most sources, `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.
|
||||
An absent `data` member is treated as a missing source. For ordinary sources,
|
||||
`data: null` is also missing. The active-alert exception is listed above: its
|
||||
explicit `null` payload represents an empty alert result.
|
||||
|
||||
`/alerts/active` is the exception: a successful response with `data: null`
|
||||
means the endpoint was checked and there are no current active alerts. The
|
||||
adapter records a non-missing alerts source and an empty alert run.
|
||||
Hourly forecast data must be present and contain at least one `period`; a
|
||||
missing, malformed, or empty hourly product fails collection. The remaining
|
||||
sources follow the configured missing-source policy. Under `error`, collection
|
||||
fails; under `warn`, the source is omitted and an inspectable warning is
|
||||
recorded; under `none`, the source is omitted without a warning. A per-source
|
||||
policy overrides the default. See [Configuration](../config.md) for policy
|
||||
settings and [Weather data internals](../internal/weather-data.md) for recorded
|
||||
source metadata.
|
||||
|
||||
For `/outlooks/convective`, `data: null` means no latest run is available and
|
||||
follows missing-source policy. A non-null run with empty `outlooks` and
|
||||
`discussions` arrays is checked empty data, not a missing source.
|
||||
Malformed top-level JSON envelopes and HTTP failures are direct request errors.
|
||||
Malformed `data` for an optional source follows its missing-source policy.
|
||||
|
||||
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.
|
||||
## Payload Fields Used
|
||||
|
||||
Source-fetch transport failures and retryable HTTP statuses are retried before
|
||||
the adapter returns an error. Retryable statuses are `408`, `429`, `500`,
|
||||
`502`, `503`, and `504`. Non-retryable statuses, malformed JSON envelopes,
|
||||
missing `data`, `data: null` missing-source outcomes, and source decode errors
|
||||
are not retried.
|
||||
Weatherreporter decodes only the fields below; additional upstream fields are
|
||||
ignored. Timestamps must be JSON values accepted by Go's `time.Time` decoder.
|
||||
|
||||
## Query Parameters
|
||||
### Observations And Current Conditions
|
||||
|
||||
The adapter sends these query parameters:
|
||||
`/observations` uses `stationId`, `stationName`, `timestamp`, `conditionCode`,
|
||||
`isDay`, `textDescription`, `temperatureC`, `temperatureF`, `dewpointC`,
|
||||
`dewpointF`, `windSpeedKmh`, `windSpeedMph`, `windGustKmh`, `windGustMph`,
|
||||
`windDirectionDegrees`, `barometricPressurePa`, `barometricPressureInHg`,
|
||||
`visibilityMeters`, `visibilityMiles`, `relativeHumidityPercent`,
|
||||
`apparentTemperatureC`, `apparentTemperatureF`, and `presentWeather`.
|
||||
|
||||
- `format`: from `weather_api.format`; configuration validation requires `json`
|
||||
- `units`: from `weather_api.units`
|
||||
- `precision`: from `weather_api.precision` on observations, current
|
||||
conditions, hourly forecast, and narrative forecast requests; the built-in
|
||||
default is `0`
|
||||
- `tz`: from `weather_api.timezone` on hourly forecast, narrative forecast,
|
||||
discussion, and SPC convective outlook requests
|
||||
`/conditions/current` uses `conditionText`, `isDay`,
|
||||
`relativeHumidityPercent`, `windDirectionDegrees`, `temperatureC`,
|
||||
`temperatureF`, `apparentTemperatureC`, `apparentTemperatureF`, `dewpointC`,
|
||||
`dewpointF`, `windSpeedKmh`, and `windSpeedMph`.
|
||||
|
||||
Alerts do not receive `precision` or `tz`. Weather story requests receive only
|
||||
`format=json`. SPC convective outlook requests receive only `format=json` and
|
||||
`tz`; they do not receive `units` or `precision`.
|
||||
### Hourly And Narrative Forecasts
|
||||
|
||||
## SPC Convective Outlooks
|
||||
Both forecast endpoints use run-level `locationId`, `locationName`, `issuedAt`,
|
||||
`updatedAt`, `product`, `latitude`, `longitude`, `elevationMeters`,
|
||||
`elevationFeet`, and `periods`.
|
||||
|
||||
The adapter fetches SPC convective outlook data from:
|
||||
Each `periods` item uses `startTime`, `endTime`, `name`, `isDay`,
|
||||
`conditionCode`, `textDescription`, `temperatureC`, `temperatureF`,
|
||||
`temperatureCMin`, `temperatureFMin`, `temperatureCMax`, `temperatureFMax`,
|
||||
`dewpointC`, `dewpointF`, `windSpeedKmh`, `windSpeedMph`, `windGustKmh`,
|
||||
`windGustMph`, `windDirectionDegrees`, `barometricPressurePa`,
|
||||
`barometricPressureInHg`, `visibilityMeters`, `visibilityMiles`,
|
||||
`apparentTemperatureC`, `apparentTemperatureF`, `cloudCoverPercent`,
|
||||
`probabilityOfPrecipitationPercent`, `precipitationAmountMm`,
|
||||
`precipitationAmountIn`, `snowfallDepthMM`, `snowfallDepthIn`, `uvIndex`, and
|
||||
`relativeHumidityPercent`.
|
||||
|
||||
```text
|
||||
GET /outlooks/convective?format=json&tz=<weather_api.timezone>
|
||||
```
|
||||
### Alerts, Discussion, And Weather Story
|
||||
|
||||
The response uses the standard `data` envelope. `data: null` means no latest
|
||||
run is available and follows missing-source policy. A non-null object with
|
||||
empty `outlooks` and `discussions` arrays is accepted as checked empty data.
|
||||
`/alerts/active` uses the `asOf` timestamp and keeps each item in `alerts` as
|
||||
an alert payload. Weatherreporter does not require a separate alert-item schema
|
||||
at this integration boundary.
|
||||
|
||||
Run fields consumed by weatherreporter:
|
||||
`/discussion` uses `officeId`, `officeName`, `product`, `issuedAt`,
|
||||
`updatedAt`, `keyMessages`, and the `shortTerm` and `longTerm` sections. Each
|
||||
section uses `qualifier`, `text`, and `issuedAt`.
|
||||
|
||||
- `locationId`
|
||||
- `locationName`
|
||||
- `asOf`
|
||||
- `issuedAt`
|
||||
- `updatedAt`
|
||||
- `product`
|
||||
- `outlooks`
|
||||
- `discussions`
|
||||
`/weatherstories/latest` uses `officeId`, `startTime`, `endTime`, `updatedAt`,
|
||||
`title`, `description`, `altText`, `priority`, `order`, and `downloadUrl`.
|
||||
|
||||
Outlook fields consumed:
|
||||
### SPC Convective Outlooks
|
||||
|
||||
- `id`
|
||||
- `provider`
|
||||
- `product`
|
||||
- `day`
|
||||
- `outlookType`
|
||||
- `label`
|
||||
- `labelText`
|
||||
- `forecaster`
|
||||
- `severityRank`
|
||||
- `validFrom`
|
||||
- `validTo`
|
||||
- `issuedAt`
|
||||
- `expiresAt`
|
||||
- `sourceUrl`
|
||||
- `imageUrl`
|
||||
- `containsLocation`
|
||||
- `geometry`
|
||||
`/outlooks/convective` uses run-level `locationId`, `locationName`, `asOf`,
|
||||
`issuedAt`, `updatedAt`, `product`, `outlooks`, and `discussions`.
|
||||
|
||||
Discussion fields consumed:
|
||||
Each outlook uses `id`, `provider`, `product`, `day`, `outlookType`, `label`,
|
||||
`labelText`, `forecaster`, `severityRank`, `validFrom`, `validTo`, `issuedAt`,
|
||||
`expiresAt`, `sourceUrl`, `imageUrl`, `containsLocation`, and GeoJSON
|
||||
`geometry`. Each discussion uses `day`, `headline`, `summary`, `discussion`,
|
||||
and `updatedAt`.
|
||||
|
||||
- `day`
|
||||
- `headline`
|
||||
- `summary`
|
||||
- `discussion`
|
||||
- `updatedAt`
|
||||
## Timeouts, Retries, And Failures
|
||||
|
||||
GeoJSON `geometry` is decoded into collected weather facts and persisted in
|
||||
bundle/debug artifacts, but prompt-facing SPC module output omits geometry.
|
||||
The configured Weather API timeout applies to each warmup and source HTTP
|
||||
attempt. Weatherreporter retries transient transport and response-read failures
|
||||
and these response statuses: `408`, `429`, `500`, `502`, `503`, and `504`.
|
||||
It does not retry other HTTP statuses, malformed envelopes, missing data, or
|
||||
payload decoding failures. A canceled context also stops an in-progress retry
|
||||
delay.
|
||||
|
||||
## Endpoints Used
|
||||
The adapter reads at most 10 MiB from one response body. A non-2xx response,
|
||||
request construction failure, read failure, or decode failure includes endpoint
|
||||
context in its error.
|
||||
|
||||
The adapter warms up `/conditions/current` once before bundle fetching begins,
|
||||
with retries if needed. It then fetches these source endpoints once per bundle,
|
||||
except when a source request is retried after a transient transport or server
|
||||
failure:
|
||||
|
||||
- `/observations`
|
||||
- `/conditions/current`
|
||||
- `/forecast/hourly`
|
||||
- `/forecast/narrative`
|
||||
- `/alerts/active`
|
||||
- `/discussion`
|
||||
- `/weatherstories/latest`
|
||||
- `/outlooks/convective`
|
||||
|
||||
`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.
|
||||
|
||||
## Required And Optional Sources
|
||||
|
||||
Hourly forecast is required:
|
||||
|
||||
- `data: null` for `/forecast/hourly` fails the fetch.
|
||||
- an hourly forecast with no `periods` fails the fetch.
|
||||
- malformed hourly data fails the fetch.
|
||||
|
||||
Other fetched sources are optional and follow `missing_source.default` or a
|
||||
source-specific `missing_source.sources` policy:
|
||||
|
||||
- `observations` for `/observations`
|
||||
- `current` for `/conditions/current`
|
||||
- `narrative` for `/forecast/narrative`
|
||||
- `alerts` for `/alerts/active`
|
||||
- `discussion` for `/discussion`
|
||||
- `weather_story` for `/weatherstories/latest`
|
||||
- `spc_convective_outlooks` for `/outlooks/convective`
|
||||
|
||||
Policy behavior:
|
||||
|
||||
- `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
|
||||
|
||||
For `/alerts/active`, an HTTP error or missing `data` field still fails or
|
||||
follows the relevant error path, but explicit `data: null` is not a
|
||||
missing-source condition.
|
||||
|
||||
For `/outlooks/convective`, a non-null data object with empty outlook and
|
||||
discussion arrays is accepted as checked empty data.
|
||||
|
||||
## Source Identity
|
||||
|
||||
For source payloads accepted into the bundle, including the explicit `null`
|
||||
alerts payload, the adapter records:
|
||||
|
||||
- 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
|
||||
|
||||
Warnings are recorded both on the affected source and on the bundle-level
|
||||
warnings list.
|
||||
|
||||
## Compatibility Assumptions
|
||||
|
||||
The adapter expects payload fields compatible with the internal weather data
|
||||
bundle types in `internal/weatherdata/bundle.go`, including:
|
||||
|
||||
- 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 section text
|
||||
- latest weather story title, description, timing, priority, order, alt text,
|
||||
and download URL
|
||||
- SPC convective outlook run metadata, outlooks, discussions, and GeoJSON
|
||||
geometry
|
||||
|
||||
The adapter intentionally keeps upstream transport and envelope details inside
|
||||
`internal/adapters/weatherapi`; downstream packages consume the normalized
|
||||
bundle.
|
||||
Retry counts and delays are adapter behavior rather than Weather API request
|
||||
parameters. Do not depend on a particular attempt count when implementing the
|
||||
service.
|
||||
|
||||
Reference in New Issue
Block a user