diff --git a/docs/integrations/weatherapi.md b/docs/integrations/weatherapi.md index b40391d..2a84a49 100644 --- a/docs/integrations/weatherapi.md +++ b/docs/integrations/weatherapi.md @@ -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= -``` +### 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.