Document implemented integration contracts
This commit is contained in:
@@ -1,34 +1,17 @@
|
||||
# weatherreporter Subprocess Integration
|
||||
# Scriptorium Integration
|
||||
|
||||
This document describes the external Scriptorium CLI contract used by
|
||||
`weatherreporter`.
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines the supported subprocess contract for weatherreporter invoking Scriptorium through the public CLI.
|
||||
`weatherreporter` invokes Scriptorium as a subprocess to preflight prompt input
|
||||
and generate Markdown reports. This page documents the CLI surface the adapter
|
||||
uses, not the full Scriptorium product.
|
||||
|
||||
This is a CLI contract, not an internal Go package integration.
|
||||
## Commands Used
|
||||
|
||||
## Supported Commands
|
||||
|
||||
weatherreporter should invoke:
|
||||
|
||||
- `scriptorium run`
|
||||
- `scriptorium render`
|
||||
|
||||
Use `run` for generation.
|
||||
|
||||
Use `render` for preflight/debug output without LLM execution.
|
||||
|
||||
## Recommended Invocation Shapes
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
scriptorium run \
|
||||
--prompt <prompt_id> \
|
||||
--input data_package=<path> \
|
||||
--out <artifact_path>
|
||||
```
|
||||
|
||||
Render:
|
||||
Render preflight:
|
||||
|
||||
```bash
|
||||
scriptorium render \
|
||||
@@ -37,78 +20,78 @@ scriptorium render \
|
||||
--format json
|
||||
```
|
||||
|
||||
weatherreporter may add:
|
||||
Report generation:
|
||||
|
||||
- `--config <path>`
|
||||
- `--profile <profile_id>`
|
||||
- repeatable `--input name=path`
|
||||
- repeatable `--var name=value`
|
||||
- runtime overrides when explicitly needed (`--model`, `--llm-base-url`, `--timeout`, etc.)
|
||||
```bash
|
||||
scriptorium run \
|
||||
--prompt <prompt_id> \
|
||||
--input data_package=<path> \
|
||||
--out <artifact_path>
|
||||
```
|
||||
|
||||
## Config And Directory Behavior
|
||||
`weatherreporter` always passes prompt input as
|
||||
`--input data_package=<path>`. The data package is structured JSON created by
|
||||
`internal/promptinput`.
|
||||
|
||||
weatherreporter can rely on resolved app config or pass explicit paths.
|
||||
## Configured Arguments
|
||||
|
||||
- default config search order:
|
||||
1. `/usr/local/etc/scriptorium/config.yml`
|
||||
2. `/etc/scriptorium/config.yml`
|
||||
- explicit `--config` requires file existence and valid syntax
|
||||
- CLI flags override config values
|
||||
The adapter can prepend configured flags before prompt-specific arguments:
|
||||
|
||||
## Profile Selection
|
||||
- `--config <path>` from `scriptorium.config_path`
|
||||
- `--profile <profile>` from `scriptorium.profile`
|
||||
|
||||
Profile selection follows runner behavior:
|
||||
It appends `scriptorium.extra_args` after the built-in arguments. Extra
|
||||
arguments are passed directly as argv items.
|
||||
|
||||
1. explicit `--profile`
|
||||
2. prompt `default_profile`
|
||||
3. error if neither is available
|
||||
`scriptorium.binary` selects the executable name or path. If unset inside the
|
||||
adapter, it falls back to `scriptorium`.
|
||||
|
||||
weatherreporter should treat prompt/profile IDs as deployment configuration, not hardcoded logic.
|
||||
## Execution Behavior
|
||||
|
||||
## Input And Variable Contract
|
||||
The adapter runs Scriptorium without shell interpolation. Arguments are passed
|
||||
through `exec.CommandContext`.
|
||||
|
||||
- Inputs use repeated `--input name=path`.
|
||||
- Input names must match prompt definition input names.
|
||||
- Variables use repeated `--var name=value` for small metadata values.
|
||||
- Prefer file inputs for large content.
|
||||
`scriptorium.timeout` limits each subprocess call when configured. Context
|
||||
cancellation or timeout returns an execution error.
|
||||
|
||||
## Environment Contract
|
||||
Stdout and stderr are captured separately. Each stream is capped at 1 MiB and
|
||||
the result records whether truncation occurred.
|
||||
|
||||
- Pass through required API-key environment variables referenced by `api_key_env`.
|
||||
- Never pass raw API keys via CLI arguments.
|
||||
- Keep subprocess environment scoped to required variables.
|
||||
## Results
|
||||
|
||||
## Output And Error Handling
|
||||
Render results include:
|
||||
|
||||
`run`:
|
||||
- full argv recorded as `command`
|
||||
- stdout
|
||||
- stderr
|
||||
- exit code
|
||||
- truncation flags when applicable
|
||||
|
||||
- stdout: artifact body unless `--out` is used
|
||||
- `--out`: writes artifact to file
|
||||
- stderr: success summary and errors
|
||||
Run results include the same fields plus the requested output path.
|
||||
|
||||
`render`:
|
||||
`weatherreporter` persists render preflight JSON when orchestration reaches the
|
||||
preflight save point. The final Markdown artifact is written by Scriptorium to
|
||||
the `--out` path.
|
||||
|
||||
- stdout: prepared-run output unless `--out` is used
|
||||
- stderr: errors
|
||||
## Failure Behavior
|
||||
|
||||
weatherreporter should capture stdout and stderr separately.
|
||||
The adapter validates required request fields before starting Scriptorium:
|
||||
|
||||
## Exit Status Contract
|
||||
- prompt ID
|
||||
- data package path
|
||||
- output path for `run`
|
||||
|
||||
- `0`: success
|
||||
- `1`: parse/config/load/render/generation/IO/runtime error
|
||||
- `2`: run completed but validation failed
|
||||
Nonzero exits return both the captured result and an error containing the exit
|
||||
code and stderr. A `run` exit code such as `2` is still treated as an error by
|
||||
the adapter, even if Scriptorium wrote output to the requested artifact path.
|
||||
|
||||
A `run` exit code `2` can still produce output (stdout or `--out`).
|
||||
Subprocess start failures, context cancellation, and timeouts return errors
|
||||
without fabricating a successful result.
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Treat generated artifacts and stderr logs as potentially sensitive.
|
||||
- Avoid logging full rendered prompts by default in production contexts.
|
||||
- Use controlled output paths and access controls for persisted artifacts.
|
||||
|
||||
## Canonical References
|
||||
|
||||
- CLI behavior: [CLI reference](https://gitea.maximumdirect.net/eric/scriptorium/docs/cli.md)
|
||||
- Config behavior: [Configuration reference](https://gitea.maximumdirect.net/eric/scriptorium/docs/config.md)
|
||||
- Operations and failure handling: [Operations guide](https://gitea.maximumdirect.net/eric/scriptorium/docs/operations.md), [Troubleshooting](https://gitea.maximumdirect.net/eric/scriptorium/docs/troubleshooting.md)
|
||||
- The adapter does not invoke a shell.
|
||||
- Generated artifacts, rendered prompt context, stdout, and stderr can contain
|
||||
operationally sensitive data.
|
||||
- API keys should be provided through the Scriptorium environment or
|
||||
Scriptorium configuration, not through `weatherreporter` CLI arguments.
|
||||
|
||||
@@ -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