7 Commits

24 changed files with 1282 additions and 2535 deletions

View File

@@ -1,21 +1,14 @@
# weatherreporter
`weatherreporter` is a Go application for preparing human-facing weather
reports from normalized forecast data.
The application can currently generate Daily Today, Daily Tomorrow, 3-Day
Outlook, Weekend Outlook, and manual Storm Report Markdown reports through `scriptorium`, with
inspectable briefing, prompt input, preflight, report, and metadata artifacts
under the configured workspace.
reports from normalized forecast data. It builds structured briefing packages,
runs them through `scriptorium`, and keeps inspectable artifacts under a local
workspace.
## Quickstart
```sh
weatherreporter generate daily --date 2026-05-29 --out ./daily.md
weatherreporter generate tomorrow --out ./tomorrow.md
weatherreporter generate three-day --out ./three-day.md
weatherreporter generate weekend --out ./weekend.md
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md
```
## Documentation
@@ -23,6 +16,6 @@ weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 -
- [CLI reference](docs/cli.md)
- [Configuration reference](docs/config.md)
- [Operations guide](docs/operations.md)
- [Troubleshooting](docs/troubleshooting.md)
- [Architecture policy](docs/policy/architecture.md)
- [Development policy](docs/policy/development.md)
- [Implementation roadmap](docs/roadmap/initial.md)

View File

@@ -1,11 +1,7 @@
# Weatherreporter CLI
`weatherreporter generate daily`, `weatherreporter generate tomorrow`,
`weatherreporter generate three-day`, `weatherreporter generate weekend`,
`weatherreporter generate storm`,
`weatherreporter run morning`, and `weatherreporter run evening` currently
write Markdown reports through `scriptorium`, after writing managed preparation
artifacts and running `scriptorium render` as a preflight check.
`weatherreporter` generates Markdown weather reports, runs scheduled report
batches, and inspects previously generated artifacts.
## Shortest Useful Command
@@ -13,82 +9,69 @@ artifacts and running `scriptorium render` as a preflight check.
weatherreporter generate daily --date 2026-05-29 --out ./daily.md
```
The command parses flags, loads configuration, fetches weather data, builds a
Daily briefing, writes workspace artifacts, invokes
`scriptorium render --input data_package=<managed_path> --format json`, then
invokes `scriptorium run --input data_package=<managed_path> --out <managed_report>`.
When `--out` is supplied, it also writes a copy of the Markdown report to that
path.
This loads configuration, fetches weather data, writes managed workspace
artifacts, runs `scriptorium render` as a preflight check, runs
`scriptorium run`, and writes an extra Markdown copy to `./daily.md`.
For tomorrow planning:
```sh
weatherreporter generate tomorrow --out ./tomorrow.md
weatherreporter run evening
```
For the 3-Day Outlook:
```sh
weatherreporter generate three-day --out ./three-day.md
weatherreporter generate weekend --out ./weekend.md
```
For a focused manual Storm Report:
```sh
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md
```
## Command Overview
## Commands
```text
weatherreporter generate daily
weatherreporter generate tomorrow
weatherreporter generate three-day
weatherreporter generate weekend
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
weatherreporter run morning
weatherreporter run evening
weatherreporter inspect reports
weatherreporter inspect metadata RUN_ID
weatherreporter inspect briefing RUN_ID
weatherreporter inspect data-package RUN_ID
weatherreporter inspect prior RUN_ID
weatherreporter inspect sources RUN_ID
weatherreporter --help
weatherreporter generate daily [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate three-day [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate weekend [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate storm [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] --start TIME --end TIME
weatherreporter run morning [--config PATH] [--units VALUE] [--tz NAME] [--out-dir PATH]
weatherreporter run evening [--config PATH] [--units VALUE] [--tz NAME] [--out-dir PATH]
weatherreporter inspect reports [--config PATH] [--limit N]
weatherreporter inspect metadata [--config PATH] RUN_ID
weatherreporter inspect briefing [--config PATH] RUN_ID
weatherreporter inspect data-package [--config PATH] RUN_ID
weatherreporter inspect prior [--config PATH] RUN_ID
weatherreporter inspect sources [--config PATH] RUN_ID
```
`generate daily`, `generate tomorrow`, `generate three-day`,
`generate weekend`, and `generate storm` write a briefing snapshot, prompt
input data package, render preflight output, Markdown report, and metadata file
under the configured workspace. `generate storm` requires explicit `--start`
and `--end` bounds for the event window. `run evening` generates the Tomorrow
Planning Brief. `run morning` generates Daily Today and the 3-Day Outlook, plus
Weekend Outlook except on Sunday. Run commands continue remaining reports after
an independent report failure, print a JSON aggregate summary to stdout, write
compact report status logs to stderr, and return nonzero when any report
`generate` commands write briefing, data package, preflight, report, and
metadata artifacts under the configured workspace. `generate storm` requires
explicit event-window bounds with `--start` and `--end`.
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
except on Sunday. `run evening` generates the Tomorrow Planning Brief. Batch
runs continue independent reports after a failure, print a JSON summary to
stdout, write compact status lines to stderr, and return nonzero when any report
failed.
`inspect` commands read the configured workspace and emit JSON to stdout. They
do not fetch weather data or invoke `scriptorium`.
`inspect` commands read existing workspace artifacts and emit JSON to stdout.
They do not fetch weather data or invoke `scriptorium`.
## Flags
- `-h`, `--help`: show help.
- `--config PATH`: load configuration from `PATH` instead of `/usr/local/etc/weatherreporter/config.yml`.
- `--units VALUE`: override configured Weather API units.
- `--tz NAME`: override configured Weather API timezone.
- `--out PATH`: optional Markdown report copy for `generate daily`, `generate tomorrow`, `generate three-day`, `generate weekend`, and `generate storm`.
- `--out-dir PATH`: optional directory for extra Markdown report copies from `run morning` and `run evening`.
- `--units VALUE`: override configured Weather API units for `generate` and `run`.
- `--tz NAME`: override configured Weather API timezone for `generate` and `run`.
- `--out PATH`: write an extra Markdown report copy for `generate` commands.
- `--out-dir PATH`: write extra Markdown report copies for `run morning` and `run evening`.
- `--date YYYY-MM-DD`: optional date for `generate daily`; defaults to the current local date in the configured timezone.
- `--start TIME`: required start time for `generate storm`.
- `--end TIME`: required end time for `generate storm`.
- `--limit N`: maximum report records for `inspect reports`; defaults to 20,
and `0` means no limit.
- `--limit N`: maximum records for `inspect reports`; defaults to `20`, and `0` means no limit.
Storm times accept `YYYY-MM-DDTHH:MM` in the configured timezone or RFC3339
timestamps with explicit offsets.
## Common Workflows
```sh
weatherreporter generate tomorrow --out ./tomorrow.md
weatherreporter generate three-day --out ./three-day.md
weatherreporter generate weekend --out ./weekend.md
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md
weatherreporter run morning --out-dir ./reports
weatherreporter run evening --out-dir ./reports
```
## Inspection
```sh
@@ -100,8 +83,8 @@ weatherreporter inspect prior 20260529T100000.000000000Z_daily_today
weatherreporter inspect sources 20260529T100000.000000000Z_daily_today
```
`inspect reports` lists recent generated runs with artifact paths and warning
counts. The other commands require a RunID. `inspect prior` returns the prior
comparable snapshot metadata selected from stored metadata, or `null` when no
prior comparable snapshot exists. `inspect sources` shows source provenance and
source warnings without dumping full weather payloads.
`inspect reports` lists recent generated runs with artifact paths and source
warning counts. The other inspect commands require a RunID. `inspect prior`
returns the prior comparable snapshot metadata selected from stored metadata, or
`null` when none exists. `inspect sources` shows source provenance and source
warnings without dumping full weather payloads.

View File

@@ -1,46 +1,115 @@
# Weatherreporter Configuration
Configuration is loaded from `/usr/local/etc/weatherreporter/config.yml` by
default. Use `--config PATH` to load a different file. CLI flags override file
values.
Configuration is YAML. By default, `weatherreporter` reads:
If the default file is absent, built-in defaults are used.
```text
/usr/local/etc/weatherreporter/config.yml
```
Use `--config PATH` to load a different file. If the default file is absent,
built-in defaults are used. If `--config PATH` points to a missing file, loading
fails.
Precedence is:
1. CLI flags
2. configuration file
3. built-in defaults
The implemented configuration overrides are `--units` and `--tz`. Output flags
control report copies for the current command but do not change configuration
files. Environment-variable configuration is not implemented.
## Minimal Config
See [examples/minimal-config.yml](../examples/minimal-config.yml).
```yaml
weather_api:
base_url: https://weather.api.example.com/
```
`weather_api.base_url` is required for commands that fetch weather data. Other
fields fall back to defaults.
## Production-Oriented Config
See [examples/config.yml](../examples/config.yml).
See [examples/config.yml](../examples/config.yml). The example is loaded by the
config test suite.
## Reference
## Field Reference
- `weather_api.base_url`: single Weather API endpoint base URL, required when fetching weather data.
- `weather_api.timeout`: HTTP timeout duration. Default: `10s`.
- `weather_api.precision`: numeric precision hint. Default: `1`.
- `weather_api.units`: Weather API units. Default: `us`.
- `weather_api.timezone`: report timezone. Accepts IANA names, configured aliases such as `Chicago` and `Stl`, US timezone abbreviations, and UTC offsets such as `-5` or `+09:30`. Default: `Chicago`.
- `weather_api.format`: Weather API response format. Default: `json`.
- `missing_source.default`: one of `error`, `warn`, or `none`. Default: `warn`.
- `missing_source.sources`: optional per-source missing-source policy overrides.
- `scriptorium.binary`: `scriptorium` executable name. Default: `scriptorium`.
- `scriptorium.config_path`: optional `scriptorium` config path.
- `scriptorium.profile`: optional `scriptorium` profile.
- `scriptorium.timeout`: subprocess timeout. Default: `2m`.
- `scriptorium.extra_args`: optional extra arguments reserved for the adapter.
- `workspace.root`: workspace root. Default: `workspace`.
- `workspace.snapshots_dir`: snapshot directory under the workspace.
- `workspace.reports_dir`: managed report directory under the workspace.
- `workspace.data_packages_dir`: prompt input package directory under the workspace.
- `workspace.preflight_dir`: preflight output directory under the workspace.
- `reports.output_dir`: report output directory. Default: `reports`.
- `reports.paths`: optional report-specific output paths.
- `dayparts`: named daypart definitions with `start` and `end` `HH:MM` values.
- `recent_change.temperature_degrees`: temperature change threshold.
- `recent_change.precip_probability_points`: precipitation probability threshold.
- `recent_change.wind_gust_miles_per_hour`: wind gust change threshold.
- `recent_change.precip_timing_shift_minutes`: precipitation timing shift threshold.
### `weather_api`
- `base_url`: absolute base URL for the Weather API. Required for generation and fetch workflows.
- `timeout`: HTTP timeout duration. Default: `10s`.
- `precision`: numeric precision query value. Default: `1`.
- `units`: Weather API units query value. Default: `us`.
- `timezone`: report timezone and Weather API timezone query value where supported. Default: `Chicago`.
- `format`: Weather API response format. Must be `json`. Default: `json`.
Timezone values may be IANA names, configured aliases such as `Chicago` and
`Stl`, US timezone abbreviations, or UTC offsets such as `-5` and `+09:30`.
### `missing_source`
- `default`: missing-source behavior for optional sources. One of `error`, `warn`, or `none`. Default: `warn`.
- `sources`: optional map of source-specific overrides, using the same policy values.
Hourly forecast data is required for generated reports. Optional sources and
stub source slots use the missing-source policy.
### `scriptorium`
- `binary`: `scriptorium` executable name or path. Default: `scriptorium`.
- `config_path`: optional Scriptorium config path passed to the adapter.
- `profile`: optional Scriptorium profile passed to the adapter.
- `timeout`: subprocess timeout. Default: `2m`.
- `extra_args`: optional additional arguments passed to Scriptorium commands.
### `workspace`
- `root`: workspace root for managed artifacts. Default: `workspace`.
- `snapshots_dir`: briefing and metadata directory under `workspace.root`. Default: `snapshots`.
- `reports_dir`: managed Markdown report directory under `workspace.root`. Default: `reports`.
- `data_packages_dir`: prompt input package directory under `workspace.root`. Default: `data-packages`.
- `preflight_dir`: Scriptorium render output directory under `workspace.root`. Default: `preflight`.
Workspace subdirectories must be relative paths that stay inside
`workspace.root`.
### `dayparts`
`dayparts` is a list of named local-time windows used by forecast derivation.
Each entry has:
- `name`
- `start`
- `end`
`start` and `end` use `HH:MM`. The default entries are overnight, morning,
afternoon, and evening.
### `recent_change`
- `temperature_degrees`: temperature change threshold. Default: `5`.
- `precip_probability_points`: precipitation probability threshold. Default: `20`.
- `wind_gust_miles_per_hour`: wind gust change threshold. Default: `10`.
- `precip_timing_shift_minutes`: precipitation timing shift threshold. Default: `120`.
Recent Changes are added to prompt input when a prior comparable briefing
snapshot exists and a threshold is crossed.
## Secrets
Configuration files should not contain secrets. The current Weather API and
Scriptorium integration settings do not require secret fields.
## Maintained Examples
- [examples/minimal-config.yml](../examples/minimal-config.yml): smallest
useful config for generation and fetching.
- [examples/config.yml](../examples/config.yml): production-oriented config
covering implemented fields.
Both example files are loaded by the config test suite.

View File

@@ -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.

View File

@@ -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.

View File

@@ -0,0 +1,89 @@
# App Orchestration Internals
This document describes the implemented workflow coordinator in `internal/app`.
## Purpose
`internal/app` coordinates top-level use cases: generating one report, running
morning or evening batches, building inspectable briefing artifacts, fetching
weather bundles, and reading existing artifacts for inspection.
## Inputs And Outputs
Inputs:
- app request structs containing config, report or batch selection, clock time,
optional report date, optional Storm Report bounds, output paths, renderer
fakes, or state-store fakes
- resolved report definitions from `internal/report`
- forecast bundles from the Weather API adapter
- prior briefing snapshots from `internal/state`
Outputs:
- report results with briefing, data package, preflight, report, metadata,
prior snapshot, Recent Changes, and Scriptorium results
- batch summaries with per-report status and artifact paths
- inspection JSON values for reports, metadata, briefings, data packages, prior
snapshots, and source provenance
## Boundaries
- The package coordinates workflow order.
- It does not parse CLI flags, load YAML files directly, implement HTTP calls,
derive forecast facts, define report periods, compare Markdown, or construct
Scriptorium argv.
## Config Fields Used
- `weather_api.*` for Weather API client construction and briefing metadata
- `scriptorium.*` for renderer construction
- `workspace.*` for filesystem state
- `dayparts` for daily and outlook summarization
- `recent_change.*` for structured Recent Changes thresholds
## External Adapters Used
- `internal/adapters/weatherapi` for forecast bundle fetching
- `internal/adapters/scriptorium` for render preflight and report generation
- `internal/state` filesystem store for persisted artifacts
## State Or Manifest Behavior
Generation saves the briefing snapshot, data package, preflight result when
available, rendered report, and metadata. Metadata links all managed artifact
paths. Inspection workflows read existing state and do not fetch weather data or
invoke Scriptorium.
## Skip And Resume Behavior
There is no resume workflow. Batch generation continues remaining independent
reports after one report fails, then reports aggregate success and failure
counts.
## Failure Behavior
- Resolve errors stop the requested workflow before fetching weather data.
- Weather API or briefing errors stop that report before Scriptorium is called.
- Render preflight runs before Scriptorium report generation.
- If render preflight returns a result and an error, preflight JSON and metadata
are persisted before the error is returned.
- If Scriptorium report generation returns an error after writing output,
metadata and the managed report path remain inspectable.
- Batch failures are recorded per report and surfaced through aggregate batch
failure.
## Tests
Inspect:
- `internal/app/app_test.go`
- `internal/cli/root_test.go`
- `internal/state/filesystem_test.go`
## Invariants
- Report behavior is resolved through `internal/report`.
- Render preflight precedes Scriptorium report generation.
- Recent Changes are computed from structured briefing snapshots.
- Metadata links artifacts produced for a run.

View File

@@ -5,63 +5,58 @@ This document describes the implemented briefing package boundary.
## Purpose
`internal/briefing` builds structured report-specific briefing packages from
forecast summaries and report metadata. The package currently implements Daily
Today, Daily Tomorrow, 3-Day Outlook, Weekend Outlook, and Storm Report
briefing content.
resolved report metadata, forecast bundles, and derived forecast summaries.
Briefings are curated inputs for prompt data packages, not rendered report
prose.
## Inputs and Outputs
## Inputs And Outputs
Inputs:
- resolved report definition and valid period
- forecast bundle
- derived forecast summary or summaries
- resolved report definition, generation time, timezone, and valid period
- forecast bundle with source provenance and warnings
- derived daily or period summaries where required
- configured units and timezone
Output:
Outputs:
- `briefing.Package` JSON containing common metadata and report-specific
briefing content
- `briefing.Package` with common metadata and one report-specific content
object for Daily, 3-Day, Weekend, or Storm Report
- optional JSON file written by `briefing.Save`
## Boundaries
- Briefings are structured weather facts and context for later prompt input.
- This package does not fetch weather data, compare prior snapshots, build
`scriptorium` data packages, or render final report prose.
- This package selects and shapes weather facts for prompts.
- It does not fetch weather data, compare prior snapshots, build
`data_package` files, invoke Scriptorium, or write workflow metadata.
## Behavior
## Config Fields Used
- Common metadata includes schema version, RunID, report ID, variant, prompt ID,
generation time, units, timezone, valid period, source location, source
provenance, hashes, and source warnings.
- Daily content includes bottom-line inputs, daypart summaries, relevant alerts,
outdoor window inputs, narrative periods, discussion context, and weather
story context when available.
- Daily Tomorrow also includes planning inputs for morning readiness,
commute/school/workday concerns, and what may change overnight.
- 3-Day content includes one summary per local day or partial day, with overall
character, temperature range, precipitation, wind, risk, outdoor-window, and
alert inputs, plus broader discussion and weather-story context when
available.
- Weekend content uses the same daily outlook summaries and adds planning
inputs for best outdoor windows, worst weather windows, rain/storm timing,
comfort concerns, and confidence or uncertainty context.
- Storm content uses the explicit event window and includes event headline
inputs, hazards, most-likely scenario inputs, reasonable worst-case inputs,
confidence and uncertainty inputs, watch items, active alerts, relevant
hourly and narrative forecast periods, and available discussion or weather
story context.
- Briefing JSON is written atomically by `briefing.Save`.
The package receives configured units and timezone from the app layer. Daypart
configuration is consumed by `internal/forecast` before briefing builders run.
## External Adapters Used
None directly.
## State Or Manifest Behavior
`briefing.Save` writes briefing JSON atomically. Managed workspace placement is
owned by `internal/state`.
## Skip And Resume Behavior
None. Builders either return a complete briefing package or an error.
## Failure Behavior
- Daily briefing construction requires a Daily report definition and a derived
daily forecast summary.
- Daily briefing construction requires a Daily report definition and derived
daily summary.
- 3-Day briefing construction requires a 3-Day report definition and at least
one derived daily summary in the outlook period.
one derived summary.
- Weekend briefing construction requires a Weekend report definition and at
least one derived daily summary in the weekend period.
- Storm briefing construction requires a Storm Report definition and a forecast
least one derived summary.
- Storm briefing construction requires a Storm Report definition and forecast
bundle.
- Save failures include path and operation context.
@@ -74,11 +69,11 @@ Inspect:
- `internal/briefing/weekend_test.go`
- `internal/briefing/storm_test.go`
- `internal/app/app_test.go`
- `internal/cli/root_test.go`
## Invariants
- Weather facts come from normalized and derived source data.
- Briefing output remains JSON-inspectable.
- LLM prompt input packaging and `scriptorium` execution remain outside this
- Briefings contain structured weather facts and source context.
- Common metadata includes RunID, report ID, prompt ID, valid period, source
provenance, source hashes, and source warnings.
- LLM prompt input packaging and Scriptorium execution remain outside this
boundary.

View File

@@ -1,62 +1,61 @@
# Changes Internals
This document describes the implemented structured change comparison boundary.
This document describes structured Recent Changes comparison.
## Purpose
`internal/changes` compares current and prior structured briefing snapshots and
produces compact change records for prompt input data packages.
`internal/changes` compares current and prior briefing packages and emits
compact change records for prompt input data packages.
## Inputs and Outputs
## Inputs And Outputs
Inputs:
- prior briefing package
- current briefing package
- configured Recent Changes thresholds
- comparison thresholds from configuration
Output:
Outputs:
- ordered `changes.Change` records with type, message, previous value, and
current value where useful
- ordered `changes.Change` items with type, message, previous value, and current
value where useful
## Boundaries
- This package compares structured briefing data only.
- It does not read state directly, render Markdown, invoke `scriptorium`, or
compare generated report text.
- It does not read filesystem state, find prior snapshots, render Markdown,
invoke Scriptorium, or compare generated report text.
## Config Fields Used
The app maps these config fields into comparison thresholds:
The app maps these fields into comparison thresholds:
- `recent_change.temperature_degrees`
- `recent_change.precip_probability_points`
- `recent_change.wind_gust_miles_per_hour`
- `recent_change.precip_timing_shift_minutes`
## Behavior
## External Adapters Used
Daily, 3-Day, and Weekend comparison currently detect:
None.
- temperature changes crossing configured thresholds
- precipitation probability and timing changes
- alert additions and removals
- peak wind gust changes
- snow, ice, and thunder risk changes
## State Or Manifest Behavior
When no prior comparable snapshot exists, the app sends an empty Recent Changes
section in the data package. Daily Today and Daily Tomorrow are compatible for
same-valid-date comparison through the report registry. 3-Day Outlook compares
with prior 3-Day Outlook snapshots for the same valid local date. Weekend
Outlook compares with prior Weekend Outlook snapshots for the same weekend
window.
None directly. The app loads prior briefing snapshots through `internal/state`
before calling comparison functions.
## Skip And Resume Behavior
No resume behavior. When the app has no prior comparable snapshot, it sends an
empty Recent Changes list without calling a comparison function.
## Failure Behavior
Daily comparison requires both inputs to contain Daily briefing content. 3-Day
comparison requires both inputs to contain 3-Day briefing content. Weekend
comparison requires both inputs to contain Weekend briefing content.
- Daily comparison requires both inputs to contain Daily briefing content.
- 3-Day comparison requires both inputs to contain 3-Day briefing content.
- Weekend comparison requires both inputs to contain Weekend briefing content.
- Storm Report currently has no comparison implementation, so the app leaves
Recent Changes empty for Storm reports.
## Tests
@@ -70,5 +69,6 @@ Inspect:
## Invariants
- Recent Changes are based on structured snapshots, not Markdown report text.
- Comparison thresholds come from configuration.
- The comparison output remains compact enough for prompt input.
- Report compatibility is determined outside this package by report definitions
and state lookup.
- Output stays compact enough for prompt input.

View File

@@ -1,57 +1,66 @@
# Forecast Derivation Internals
This document describes the implemented deterministic forecast summarization
boundary.
This document describes deterministic forecast summarization in
`internal/forecast`.
## Purpose
`internal/forecast` converts a normalized forecast bundle into inspectable
daily and multi-day daypart summaries. These summaries are structured data for
later briefing builders; they are not rendered report text.
`internal/forecast` converts normalized bundle data into daily and period
summaries used by briefing builders.
## Inputs and Outputs
## Inputs And Outputs
Inputs:
- `forecast.Bundle`
- local date and timezone
- report period, for multi-day summaries
- configured daypart definitions with `HH:MM` start and end values
- local date or resolved report period
- timezone
- configured daypart definitions
Output:
Outputs:
- `forecast.DailySummary` with a civil-day period, daypart summaries, selected
narrative periods, alert overlaps, discussion context, source warnings, and
source provenance.
- `forecast.BuildPeriodDailySummaries` output with one clipped daily summary
for each local day or partial day in a report period.
- `forecast.DailySummary` for one local civil day
- one clipped daily summary per local day or partial day from
`BuildPeriodDailySummaries`
- daypart summaries with selected hourly periods, ranges, timed maximums,
conditions, indicators, and alert overlaps
## Boundaries
- This package groups and summarizes already-normalized forecast data.
- It does not fetch weather data, resolve report definitions, compare prior
snapshots, build prompt input packages, or call `scriptorium`.
- This package groups, selects, and summarizes already-normalized forecast
data.
- It does not perform HTTP calls, parse CLI flags, resolve report definitions,
compare prior snapshots, build prompt input packages, or invoke Scriptorium.
## Behavior
## Config Fields Used
- Daypart windows use half-open intervals.
- Overnight dayparts are supported when the end clock is not after the start
clock.
- Hourly forecast periods are selected by overlap with the daypart window.
- Each daypart computes temperature range, apparent-temperature range, maximum
precipitation probability, peak wind speed, peak wind gust, dominant
condition, notable conditions, and basic weather indicators.
- Alerts are selected by overlap with the daily period and each daypart.
- Narrative periods and discussion context are selected as broader source
context for later briefing builders.
- Multi-day period summaries clip the first and last local days to the resolved
report period before selecting hourly periods and alerts.
- `dayparts[].name`
- `dayparts[].start`
- `dayparts[].end`
Threshold constants for basic indicators live in forecast code rather than
configuration.
## External Adapters Used
None directly. Forecast data arrives through `forecast.Bundle`.
## State Or Manifest Behavior
None. Source warnings and provenance from the bundle are carried into summaries
for later metadata and briefing output.
## Skip And Resume Behavior
None. Missing optional source context can produce empty selections, but missing
required hourly data fails summarization.
## Failure Behavior
- Missing hourly forecast data returns an error.
- Invalid daypart definitions return actionable parse errors.
- Alert records without parseable RFC3339 start/end fields are skipped.
- A nil bundle or missing hourly forecast data returns an error.
- Invalid daypart definitions return parse errors with context.
- Alert records without parseable RFC3339 timing are skipped.
- Empty selected periods produce empty summaries rather than generated prose.
## Tests
@@ -62,7 +71,6 @@ Inspect:
## Invariants
- Weather facts come from normalized source data, not generated prose.
- Outputs remain JSON-inspectable.
- Forecast derivation remains independent of CLI, HTTP adapters, and report
registry behavior.
- Go owns report-period selection and meteorological summarization.
- Weather facts come from normalized source data.
- Outputs remain JSON-inspectable and independent of CLI, state, and adapters.

View File

@@ -1,54 +1,64 @@
# Prompt Input Internals
This document describes the implemented prompt input package boundary.
This document describes prompt input data package construction.
## Purpose
`internal/promptinput` converts a structured briefing package into the
`data_package` JSON file passed to `scriptorium` prompts.
`internal/promptinput` converts a structured briefing package and optional
Recent Changes into the `data_package` JSON passed to Scriptorium prompts.
## Inputs and Outputs
## Inputs And Outputs
Input:
Inputs:
- `briefing.Package` containing Daily-family, 3-Day Outlook, Weekend Outlook,
or Storm Report content
- `briefing.Package`
- optional `[]changes.Change`
Output:
Outputs:
- `promptinput.Package` JSON with report metadata, briefing content, source
warnings, RunID, and a Recent Changes section.
- `promptinput.Package` containing schema version, RunID, report metadata,
briefing content, Recent Changes, and source warnings
- optional JSON file written by `promptinput.Save`
## Boundaries
- This package owns the prompt input schema and required-field validation.
- It does not fetch weather data, compute forecast summaries, compare prior
snapshots, or invoke `scriptorium`.
- This package owns the prompt input schema and validation.
- It does not fetch weather data, derive forecast summaries, find prior
snapshots, compare changes, or invoke Scriptorium.
## Behavior
## Config Fields Used
- `promptinput.Build` copies report metadata from the briefing package.
- `promptinput.Validate` rejects missing or inconsistent required fields before
render preflight.
- `promptinput.Save` writes JSON atomically where practical.
- Recent Changes is present as an `items` list. It is empty when no prior
comparable snapshot exists or no meaningful changes are detected.
None directly. Config-derived values are already present in briefing metadata
before this package runs.
## External Adapters Used
None.
## State Or Manifest Behavior
`promptinput.Save` writes JSON atomically. Managed workspace paths are owned by
`internal/state`.
## Skip And Resume Behavior
None. Recent Changes is always present as an `items` list and may be empty.
## Failure Behavior
Validation errors name the missing or inconsistent field. Save failures include
the filesystem operation and path context.
Validation fails before render preflight when required top-level or briefing
metadata fields are missing or inconsistent, or when no report content is
present. Save failures include filesystem operation and path context.
## Tests
Inspect:
- `internal/promptinput/package_test.go`
- `internal/changes/daily_test.go`
- `internal/app/app_test.go`
## Invariants
- Prompt input data remains structured JSON.
- Scriptorium receives structured `data_package` JSON.
- Briefing metadata and top-level report metadata must agree.
- Recent Changes is not inferred from rendered report text.
- Recent Changes are not inferred from rendered report text.

View File

@@ -1,53 +1,59 @@
# Report Registry Internals
This document describes the implemented report identity and valid-period
boundary.
This document describes report identity, valid-period resolution, batch
membership, and comparison declarations in `internal/report`.
## Purpose
`internal/report` centralizes report IDs, prompt IDs, comparison strategies,
valid-period resolution, report metadata, and scheduled batch membership.
`internal/report` centralizes report definitions so report IDs, prompt IDs,
default output names, comparison strategies, and valid periods are declared in
one package.
## Inputs and Outputs
## Inputs And Outputs
Inputs:
- report ID or batch name
- generation time
- configured timezone
- timezone
- optional Daily date override
- optional manual storm start and end times
- optional Storm Report start and end times
Outputs:
- `report.Resolved` values with definition metadata and half-open valid periods
- `report.Metadata` values suitable for later persisted run metadata
- `report.Metadata` values used by briefing and persisted metadata builders
## Boundaries
- This package defines report identity and time coverage only.
- It does not fetch weather data, build briefings, compare snapshots, write
state, or call `scriptorium`.
state, parse CLI flags, or invoke Scriptorium.
## Behavior
## Config Fields Used
- Daily Today covers one configured local civil day.
- Daily Tomorrow covers the next configured local civil day.
- 3-Day Outlook covers generation time through local midnight after the second
following local civil day.
- Weekend Outlook covers Saturday 00:00 to Monday 00:00 Monday through
Thursday; Friday and Saturday cover the remaining weekend from Friday 18:00
or generation time, whichever is later.
- Manual Storm Report uses explicit start and end times.
- Morning batch resolves Daily Today and 3-Day Outlook, plus Weekend Outlook
except on Sunday.
- Evening batch resolves Daily Tomorrow.
The app supplies `weather_api.timezone` as a loaded `time.Location`. Report
output path copying uses default output names from report definitions.
## External Adapters Used
None.
## State Or Manifest Behavior
None directly. Resolved metadata contributes RunID, report ID, prompt ID,
generation time, timezone, and valid period to later briefing and state
metadata.
## Skip And Resume Behavior
No resume behavior. Morning batch resolution skips Weekend Outlook on Sunday.
## Failure Behavior
- Unknown report and batch names return actionable errors.
- Sunday Weekend Outlook resolution returns an error.
- Storm windows require start and end, with end after start.
- Unknown reports and batch names return actionable errors.
- Weekend Outlook resolution returns an error when resolved directly on Sunday.
- Storm Report resolution requires start and end, with end after start.
## Tests
@@ -55,9 +61,11 @@ Inspect:
- `internal/report/period_test.go`
- `internal/app/app_test.go`
- `internal/cli/root_test.go`
## Invariants
- Report selection goes through the registry.
- Valid periods are independent of rendered report text.
- Prompt IDs and comparison strategies are declared with report definitions.
- Daily Today and Daily Tomorrow both use `weather.daily_report`.
- Valid periods are half-open intervals independent of rendered report text.
- Comparison strategy is declared by report definition.

View File

@@ -1,59 +1,67 @@
# Scriptorium Adapter Internals
This document describes the implemented `scriptorium` subprocess adapter.
This document describes the subprocess adapter in
`internal/adapters/scriptorium`.
## Purpose
`internal/adapters/scriptorium` runs `scriptorium render` to preflight prompt
wiring and `scriptorium run` to generate report artifacts.
The adapter runs `scriptorium render` for prompt preflight and `scriptorium run`
for Markdown report generation while isolating subprocess details from domain
packages.
## Inputs and Outputs
## Inputs And Outputs
Input:
Inputs:
- prompt ID
- prompt input data package path
- report output path for `run`
- configured binary, config path, profile, timeout, and extra arguments
Output:
Outputs:
- captured stdout, with truncation tracking
- captured stderr, with truncation tracking
- argv used for execution
- captured stdout and stderr with truncation flags
- exit code
- full argv used for inspection
- report output path for `run`
## Boundaries
- This adapter owns `scriptorium` CLI flag construction and subprocess
execution.
- It does not choose report types, build prompt input, fetch weather data, or
decide workflow order.
- This adapter owns Scriptorium argv construction and subprocess execution.
- It does not choose report types, build prompt input, fetch weather data,
decide workflow order, or persist workflow metadata.
## Behavior
## Config Fields Used
The render invocation shape is:
- `scriptorium.binary`
- `scriptorium.config_path`
- `scriptorium.profile`
- `scriptorium.timeout`
- `scriptorium.extra_args`
```text
scriptorium render --prompt <prompt_id> --input data_package=<path> --format json
```
## External Adapters Used
The run invocation shape is:
- external `scriptorium` CLI
```text
scriptorium run --prompt <prompt_id> --input data_package=<path> --out <artifact_path>
```
See [Scriptorium integration](../integrations/scriptorium.md) for the external
CLI contract used by this project.
Configured `--config` and `--profile` values are added when present. Arguments
are passed directly as argv, not through a shell. Stdout and stderr are captured
separately. `SaveRenderResult` writes the captured result as JSON for inspection.
## State Or Manifest Behavior
`SaveRenderResult` can write render results atomically. The app and state store
own managed preflight paths and metadata links.
## Skip And Resume Behavior
None. Context cancellation and configured timeout stop subprocess execution.
## Failure Behavior
Nonzero render and run exits return both the captured result and an error
containing the exit code and stderr. Run exit code `2` is treated as an error
but may still produce a report artifact. Command execution respects context
cancellation and the configured timeout.
- Missing prompt ID, data package path, or run output path returns an error.
- Subprocess start or context errors are wrapped with operation context.
- Nonzero render and run exits return captured output plus an error containing
exit code and stderr.
- Captured stdout and stderr are size-limited and marked when truncated.
## Tests
@@ -65,7 +73,6 @@ Inspect:
## Invariants
- `scriptorium` details stay inside the adapter package.
- The input name for prompt packages is always `data_package`.
- Render preflight remains orchestration behavior; this adapter only exposes the
subprocess operations.
- No shell interpolation is used.
- The Scriptorium input name is `data_package`.
- Scriptorium-specific flags stay inside adapter and config boundaries.

View File

@@ -1,13 +1,13 @@
# State Internals
This document describes the implemented filesystem state boundary.
This document describes filesystem state in `internal/state`.
## Purpose
`internal/state` owns durable artifact paths, atomic JSON writes, metadata, and
prior comparable snapshot lookup.
`internal/state` owns managed artifact paths, atomic JSON writes, persisted
metadata, prior snapshot lookup, and read-only artifact inspection helpers.
## Inputs and Outputs
## Inputs And Outputs
Inputs:
@@ -15,26 +15,27 @@ Inputs:
- resolved report definition and valid period
- briefing package
- prompt input data package
- `scriptorium render` result
- rendered report path preparation
- Scriptorium render result
- rendered report path preparation request
- RunID for inspection lookups
Outputs:
- briefing snapshot JSON
- prompt input data package JSON
- render preflight JSON
- Markdown report path
- metadata JSON
- prior comparable snapshot metadata when available
- prior briefing package when loaded by path
- briefing snapshot JSON path
- prompt input data package JSON path
- render preflight JSON path
- managed Markdown report path
- metadata JSON path
- prior comparable snapshot metadata
- loaded briefing or data package
- recent report records for inspection
- metadata and data package lookup by RunID
## Boundaries
- This package owns managed workspace layout and narrow path validation.
- It does not fetch weather data, derive forecasts, build prompt inputs, invoke
`scriptorium`, or compare briefing contents.
- This package owns managed workspace layout, path validation, filesystem
writes, and metadata reads.
- It does not fetch weather data, derive forecasts, build prompt input content,
compare briefing contents, invoke Scriptorium, or parse CLI flags.
## Config Fields Used
@@ -47,25 +48,35 @@ Outputs:
Workspace subdirectories must be relative paths that stay under
`workspace.root`.
## State Behavior
## External Adapters Used
Managed artifact names use RunID, which is generated from report generation time
and report ID. Metadata is stored beside briefing snapshots by report group and
valid local date. Prior snapshot lookup reads metadata for the same valid local
date and returns the latest earlier compatible run. Daily Today and Daily
Tomorrow are compatible with each other; 3-Day Outlook is compatible with prior
3-Day Outlook snapshots; Weekend Outlook is compatible with prior Weekend
Outlook snapshots for the same weekend window. The store can load a briefing
snapshot by path for structured comparison. The store can list metadata-backed
report records and load metadata or data packages by RunID for inspection. The
store prepares the managed Markdown report path before `scriptorium run` writes
it.
- local filesystem
## State Or Manifest Behavior
Managed paths are grouped by report family and valid-period start date for JSON
artifacts. Reports are written under the report group. Metadata is stored beside
briefing snapshots and links briefing, data package, preflight, and report
paths. Report listing walks metadata files under the snapshots directory.
Prior snapshot lookup reads metadata and selects the latest earlier compatible
snapshot. Daily Today and Daily Tomorrow are compatible with each other for the
same valid local date. 3-Day Outlook compares with prior 3-Day snapshots for
the same valid local date. Weekend Outlook compares with prior Weekend snapshots
for the same weekend window. Storm Report currently has no prior lookup.
## Skip And Resume Behavior
There is no resume workflow. Missing metadata directories return no inspection
records or no prior snapshot rather than creating state.
## Failure Behavior
Writes are atomic where practical: JSON is written to a temporary file in the
target directory and then renamed into place. Invalid workspace paths and
missing required metadata fields produce actionable errors.
- Invalid workspace paths return validation errors.
- Missing required metadata fields prevent metadata writes.
- JSON writes use a temporary file followed by rename where practical.
- Read and decode failures include path context.
- Unknown RunIDs produce an actionable lookup error.
## Tests
@@ -77,5 +88,5 @@ Inspect:
## Invariants
- Managed paths stay under the configured workspace root.
- Metadata links the artifacts produced for a run.
- Metadata links artifacts produced for a run.
- Prior lookup is based on structured metadata, not rendered report text.

View File

@@ -1,59 +1,74 @@
# Weather Data Internals
This document describes the implemented weather data ingestion boundary.
This document describes Weather API ingestion into `forecast.Bundle`.
## Purpose
`internal/adapters/weatherapi` fetches normalized weather data from one
configured weather API endpoint and assembles a `forecast.Bundle`.
configured Weather API endpoint and assembles the bundle consumed by forecast
derivation and briefing builders.
## Inputs and Outputs
## Inputs And Outputs
Input:
Inputs:
- `config.Config` with `weather_api.base_url`, `format`, `units`, `timezone`,
`precision`, timeout, and missing-source policy.
- `config.Config` with Weather API URL, timeout, format, units, timezone,
precision, and missing-source policy
- HTTP responses using the Weather API `data` envelope
Output:
Outputs:
- `forecast.Bundle` containing observation, current conditions, hourly forecast,
narrative forecast, alerts, discussion, stub source slots, provenance, and
source warnings.
- `forecast.Bundle` with observation, current conditions, hourly forecast,
narrative forecast, active alerts, discussion, source records, and source
warnings
- stub source records for daily forecast and weather story source slots
- optional saved bundle JSON through app fetch helpers
## Boundaries
- The adapter performs HTTP calls and decoding only.
- Forecast derivation, daypart grouping, report periods, report rendering, and
`scriptorium` execution are outside this boundary.
- Hourly forecast data is required. Other missing or malformed source sections
use the configured missing-source policy.
- The adapter owns HTTP calls, response-envelope handling, source hashing, and
decoding into internal bundle types.
- It does not derive dayparts, resolve report periods, build briefings, compare
snapshots, write report state, or invoke Scriptorium.
## External Adapter
## Config Fields Used
The adapter calls:
- `weather_api.base_url`
- `weather_api.timeout`
- `weather_api.format`
- `weather_api.units`
- `weather_api.timezone`
- `weather_api.precision`
- `missing_source.default`
- `missing_source.sources`
- `/observations`
- `/conditions/current`
- `/forecast/hourly`
- `/forecast/narrative`
- `/alerts/active`
- `/discussion`
## External Adapters Used
Forecast routes use the full-product endpoints, not day-slice endpoints.
- Weather API HTTP service
## State
See [Weather API integration](../integrations/weatherapi.md) for the external
contract used by this project.
`app.FetchAndSaveBundle` can save an inspectable bundle JSON file using an
atomic rename. No report state, snapshots, or prompt input packages are written
yet.
## State Or Manifest Behavior
The adapter records source name, endpoint, query, fetch time, source timestamps
when available, SHA-256 hash over compact raw `data` JSON, missing status, and
source warnings. `app.FetchAndSaveBundle` can write bundle JSON atomically for
inspection.
## Skip And Resume Behavior
No resume behavior. Optional missing or malformed sources may be omitted,
warned, or treated as errors according to missing-source policy. Hourly forecast
data is required and cannot be skipped.
## Failure Behavior
- HTTP and envelope decode failures return actionable errors with endpoint
context.
- Missing hourly data fails the fetch.
- Missing or malformed optional sources follow `error`, `warn`, or `none`.
- Source identity uses SHA-256 over compacted raw `data` JSON.
- Missing or invalid `weather_api.base_url` prevents client construction.
- HTTP errors, response read failures, and envelope decode failures include
endpoint context.
- Missing hourly data or hourly forecasts with no periods fail bundle fetch.
- Optional and stub sources follow missing-source policy.
## Tests
@@ -65,5 +80,6 @@ Inspect:
## Invariants
- Weather facts come from normalized source data.
- External API details stay inside `internal/adapters/weatherapi`.
- Source provenance and warnings remain inspectable for later briefing builders.
- Full hourly and narrative products are fetched; Go owns report-period
selection.
- Source provenance and warnings remain inspectable downstream.

View File

@@ -1,8 +1,12 @@
# Weatherreporter Operations
This guide covers normal operation, generated artifacts, inspection, recovery,
and current operational caveats. For symptom-specific diagnosis, see
[Troubleshooting](troubleshooting.md).
## Normal Workflow
The implemented generation workflows are:
Implemented generation commands:
```text
weatherreporter generate daily --date 2026-05-29
@@ -10,22 +14,27 @@ weatherreporter generate tomorrow
weatherreporter generate three-day
weatherreporter generate weekend
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
```
Each command resolves a report period, fetches a Weather API bundle, builds a
briefing, builds a prompt input data package, runs `scriptorium render`, runs
`scriptorium run`, and writes managed artifacts under the configured workspace.
`--out PATH` writes an extra Markdown copy for the current generated report.
Implemented batch commands:
```text
weatherreporter run morning
weatherreporter run evening
```
These commands fetch weather data, build a briefing for the resolved valid
period, build the prompt input data package, run `scriptorium render`, run
`scriptorium run`, and write inspectable artifacts under the configured
workspace. The evening run resolves only the Tomorrow Planning Brief. The
morning run generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
except on Sunday. Storm Report generation is manual and uses the explicit
`--start` and `--end` bounds as its valid period.
Scheduled run commands print a JSON aggregate summary to stdout and compact
per-report status lines to stderr. If one report fails, remaining independent
reports are still attempted. The command returns nonzero after the run when any
report failed.
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
except on Sunday. `run evening` generates the Tomorrow Planning Brief. Batch
commands print a JSON summary to stdout, write compact per-report status lines
to stderr, continue independent reports after one report fails, and return
nonzero when any report failed. `--out-dir PATH` writes extra Markdown copies
using report default filenames such as `daily.md`, `three-day.md`,
`weekend.md`, and `tomorrow.md`.
## Filesystem Layout
@@ -87,51 +96,37 @@ workspace/
<run_id>.md
```
The Markdown report is written to a RunID-managed report path. When `--out` is
provided to `generate daily`, `generate tomorrow`, `generate three-day`,
`generate weekend`, or `generate storm`, the managed report is also copied to
that path.
Managed artifact filenames use the RunID, so repeated runs for the same valid
period do not overwrite each other.
For `run morning` and `run evening`, `--out-dir PATH` writes extra Markdown
copies using each report definition's default filename, such as `daily.md`,
`three-day.md`, `weekend.md`, or `tomorrow.md`.
## RunID And Metadata
## Run Identifiers
Run IDs are based on generation time plus report ID, such as:
RunIDs are based on generation time plus report ID:
```text
20260529T100000.123456789Z_daily_today
```
Managed artifact filenames use the RunID so repeated runs for the same valid
date do not overwrite each other.
## Metadata
Each generated report writes metadata that links:
- RunID
- report ID and prompt ID
- generation time and valid period
- RunID, report ID, variant, and prompt ID
- generation time, timezone, and valid period
- source location, source hashes, and source warnings
- briefing snapshot path
- prompt input data package path
- preflight output path
- rendered report path
- managed Markdown report path
Run summaries include each report ID, prompt ID, RunID, status, error text when
applicable, valid period, and artifact paths known to the application.
Batch summaries include report status, error text when applicable, valid
period, and known artifact paths for each attempted report.
## Inspection
Use `weatherreporter inspect reports` to list recent generated runs from the
configured workspace. The output includes RunID, report ID, valid period,
metadata path, briefing path, report path, and source warning count.
Run-specific inspection commands emit JSON for a single RunID:
Inspection commands read existing workspace artifacts and emit JSON to stdout.
They do not fetch weather data or run `scriptorium`.
```text
weatherreporter inspect reports --limit 10
weatherreporter inspect metadata RUN_ID
weatherreporter inspect briefing RUN_ID
weatherreporter inspect data-package RUN_ID
@@ -139,44 +134,54 @@ weatherreporter inspect prior RUN_ID
weatherreporter inspect sources RUN_ID
```
`inspect prior` shows the prior comparable snapshot selected from stored
metadata, or `null` when none exists. `inspect sources` shows source provenance
and source warnings without dumping full weather payloads.
Use `inspect reports` to find recent RunIDs and artifact paths. Use
`inspect metadata` to see the artifact links recorded for a run. Use
`inspect briefing` and `inspect data-package` to review the exact structured
inputs used for rendering. Use `inspect prior` to see the prior comparable
snapshot selected for Recent Changes, or `null` when none exists. Use
`inspect sources` to review source provenance and warnings without dumping full
weather payloads.
## Recent Changes
When a prior comparable Daily briefing snapshot exists for the same valid local
date, the app compares structured briefing data before writing the prompt input
data package. Daily Today and Daily Tomorrow can compare with each other when
they cover the same valid local date. Meaningful changes are included under
`recentChanges.items`.
Recent Changes are computed from structured briefing snapshots, not rendered
Markdown text.
3-Day Outlook generation compares against a prior compatible 3-Day briefing
snapshot for the same valid local date when one exists.
Weekend Outlook generation compares against a prior compatible Weekend briefing
snapshot for the same weekend window when one exists. Friday evening and
Saturday runs may narrow the valid start while keeping the same Monday endpoint.
Storm Report generation currently leaves Recent Changes empty. Its explicit
event window is still recorded in briefing and metadata artifacts.
Daily Today and Daily Tomorrow can compare with each other when they cover the
same valid local date. 3-Day Outlook compares with prior compatible 3-Day
snapshots for the same valid local date. Weekend Outlook compares with prior
compatible Weekend snapshots for the same weekend window. Storm Report currently
leaves Recent Changes empty.
When no prior comparable snapshot exists, or no configured threshold is crossed,
the Recent Changes list is empty.
`recentChanges.items` is empty.
## Recovery
If render preflight exits nonzero after producing a result, the captured stdout,
stderr, exit code, and command are still written to the preflight artifact, and
metadata is still written for inspection.
A failed generation run may still leave useful artifacts:
If `scriptorium run` exits nonzero after writing a report, the generated report
and metadata remain available for inspection. Exit code `2` is still returned as
an error because it indicates validation failed, even if report output exists.
- If `scriptorium render` returns a result with a nonzero exit code, the
preflight JSON and metadata are written for inspection.
- If `scriptorium run` exits nonzero after writing a report, the managed report
and metadata remain available.
- For batch commands, inspect the stdout JSON summary first, then inspect the
artifact paths for each failed report.
For scheduled runs, inspect stdout first for the aggregate JSON summary, then
use the per-report artifact paths in that summary to inspect briefing,
data-package, preflight, metadata, and rendered report files.
For a bad report, start with:
The application does not currently implement resume, cleanup, archive, or
remote storage behavior.
```text
weatherreporter inspect metadata RUN_ID
weatherreporter inspect sources RUN_ID
weatherreporter inspect briefing RUN_ID
weatherreporter inspect data-package RUN_ID
weatherreporter inspect prior RUN_ID
```
## Operational Caveats
- The application uses one configured Weather API endpoint.
- The application writes local filesystem state only.
- The application does not implement resume, cleanup, archive, remote storage,
daemon operation, or automatic storm monitoring.
- Generated reports and Scriptorium stderr can contain sensitive operational
context. Store workspace artifacts with appropriate filesystem permissions.

View File

@@ -46,12 +46,11 @@ Centralize configuration loading, processing, precedence, defaults, and validati
The goal is to make configuration discoverable and avoid implicit or hidden operational values. User-visible defaults and cross-package operational defaults should be defined in `internal/config/defaults.go`.
Unless documented otherwise, precedence is:
Configuration precedence is:
1. CLI flags
2. environment variables
3. configuration file
4. built-in defaults
2. configuration file
3. built-in defaults
Prefer YAML configuration unless the project has a strong reason to use another format. Config files should be discovered at `/usr/local/etc/<app_name>/config.yml`, with a CLI override via `--config`.
@@ -65,13 +64,17 @@ External adapters belong under `internal/adapters/<name>`. If an adapter uses an
Adapters should be thin. Domain decisions belong in application/domain packages, not inside adapter glue.
## Modules, Stages, and Registries
## Components and Registries
When the application has stages or modules, each major stage/module should live in its own package and have an explicit input/output contract.
When the application has major workflow components, each component should live
near the package that owns its contract and have explicit inputs and outputs.
The orchestrator should be able to compose, skip, resume, or run individual stages/modules when their prerequisites are satisfied. Ordering should be explicit: use a default sequence, dependency graph, or documented orchestration rule.
The orchestrator should compose components in an explicit order using a default
sequence, dependency graph, or documented orchestration rule.
If users can select modules, stages, validators, renderers, or adapters, selection should go through a registry or equivalent mechanism rather than scattered conditionals.
If users can select components, validators, renderers, or adapters, selection
should go through a registry or equivalent mechanism rather than scattered
conditionals.
## Embedded Assets
@@ -87,11 +90,15 @@ Use structured logging where practical. Logs should describe operations, paths,
## Context, Timeouts, and Cancellation
Long-running operations should accept `context.Context`. External calls, subprocesses, HTTP requests, storage operations, and multi-stage workflows should respect cancellation and timeouts.
Long-running operations should accept `context.Context`. External calls,
subprocesses, HTTP requests, storage operations, and multi-step workflows should
respect cancellation and timeouts.
## State, Files, and Safety
If the application writes durable state, writes should be atomic where practical. Multi-step workflows should preserve enough state to support inspection, retry, or resume after failure.
If the application writes durable state, writes should be atomic where
practical. Multi-step workflows should preserve enough state to support
inspection and retry diagnosis after failure.
Code that deletes, moves, or overwrites files must use narrow, explicit paths. Avoid broad parent-directory operations. Cleanup that can cause data loss must be opt-in.
@@ -99,11 +106,14 @@ Code that deletes, moves, or overwrites files must use narrow, explicit paths. A
Core logic should be testable without real external services. Use fakes, fixtures, or local test doubles for adapters where practical.
Config examples should be load-tested. Important CLI workflows should have parser or command tests. Stage/module contracts should have focused tests that do not require running the full application unless end-to-end coverage is intentional.
Config examples should be load-tested. Important CLI workflows should have
parser or command tests. Component contracts should have focused tests that do
not require running the full application unless end-to-end coverage is
intentional.
## Documentation
Documentation should follow the project documentation policy. Keep user docs focused on implemented behavior. Put future, planned, or aspirational work only under `docs/roadmap/`.
When changing architecture, config, CLI behavior, adapters, or stage/module contracts, update the relevant docs and examples in the same change.
When changing architecture, config, CLI behavior, adapters, or component
contracts, update the relevant docs and examples in the same change.

View File

@@ -1,691 +1,192 @@
# Weatherreporter Package Layout
# Development Policy
This document defines the proposed package layout for `weatherreporter`, a Go application that prepares human-facing weather reports from normalized weather data collected by `weatherfeeder` and rendered through `scriptorium`.
This document is the contributor workflow policy for `weatherreporter`.
Developers and LLM coding agents should use it with
`docs/policy/architecture.md` and `docs/policy/documentation.md`.
The application should remain a small, explicit, dependency-light Go program. Domain logic should live outside CLI, transport, and external-adapter packages. External systems should be isolated behind narrow adapters. Report-specific behavior should be selected through a registry or equivalent mechanism rather than scattered conditionals.
## Repository Layout
## Architectural Summary
- `cmd/weatherreporter`: binary entry point.
- `internal/app`: orchestration for generation, batches, fetch helpers, and
inspection.
- `internal/cli`: command parsing, flag handling, help text, and JSON output.
- `internal/config`: configuration structs, defaults, loading, overrides, and
validation.
- `internal/adapters/weatherapi`: Weather API HTTP adapter.
- `internal/adapters/scriptorium`: Scriptorium subprocess adapter.
- `internal/forecast`: normalized bundle types and deterministic forecast
derivation.
- `internal/report`: report definitions, valid periods, batches, output names,
and comparison declarations.
- `internal/briefing`: report-specific briefing package builders.
- `internal/changes`: structured Recent Changes comparison.
- `internal/promptinput`: Scriptorium `data_package` construction and
validation.
- `internal/state`: filesystem paths, atomic JSON writes, metadata, lookup, and
inspection support.
- `internal/timeutil`: clock, date, timezone, and period helpers.
- `docs`: user, operator, developer, integration, internal, policy, and roadmap
documentation.
- `examples`: maintained copyable examples.
`weatherreporter` is a deterministic weather briefing and report-preparation application. It should:
## Local Validation
1. Fetch normalized weather data from a single configured internal weather API endpoint backed by `weatherfeeder`.
2. Derive report-specific briefing packages from the normalized forecast bundle.
3. Compare current briefing snapshots against prior comparable snapshots to produce optional Recent Changes.
4. Build structured prompt input data packages for a specific report type.
5. Invoke `scriptorium` as an external prompt runner.
6. Persist the rendered Markdown report, briefing snapshot, prompt input package, and generation metadata.
Use focused checks while editing and broader checks before committing:
The preferred data flow is:
```text
weatherfeeder-backed internal API
-> weather API adapter
-> forecast bundle
-> report-specific briefing builder
-> recent-change comparison
-> prompt input data package
-> scriptorium subprocess adapter
-> Markdown report + metadata + stored snapshot
```bash
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
The application should not treat the LLM as the source of weather facts. The Go code should select the relevant data, compute daypart and period summaries, attach alerts and NWS context, identify meaningful changes, and send the LLM a curated briefing package. The LLM should synthesize and phrase the report for humans.
Useful focused checks:
## Proposed Directory Layout
```text
cmd/weatherreporter/
main.go
internal/app/
generate.go
scheduled.go
storm.go
internal/cli/
root.go
generate.go
run.go
inspect.go
internal/config/
config.go
defaults.go
load.go
validate.go
internal/adapters/weatherapi/
client.go
types.go
internal/adapters/scriptorium/
runner.go
types.go
internal/forecast/
bundle.go
dayparts.go
derive.go
select.go
thresholds.go
internal/report/
definition.go
registry.go
period.go
daily.go
tomorrow.go
three_day.go
weekend.go
storm.go
internal/briefing/
package.go
daily.go
tomorrow.go
three_day.go
weekend.go
storm.go
internal/changes/
compare.go
thresholds.go
summary.go
internal/state/
store.go
filesystem.go
metadata.go
internal/promptinput/
build.go
schema.go
internal/timeutil/
clock.go
periods.go
```bash
go test ./internal/cli ./internal/config
go test ./internal/app ./internal/state
go test ./internal/adapters/weatherapi ./internal/adapters/scriptorium
go test ./internal/forecast ./internal/report ./internal/briefing ./internal/changes ./internal/promptinput
```
This layout can be simplified during early prototyping if a package has only one file, but the package boundaries should remain conceptually stable.
Run `gofmt -w` on changed Go files before committing.
## Dependency Direction
## Coding Conventions
The intended dependency direction is:
- Keep domain logic out of `cmd`, `internal/cli`, and adapter packages.
- Prefer small explicit structs and functions over broad framework-style
abstractions.
- Keep package APIs narrow and named around implemented behavior.
- Return errors with operation, path, endpoint, report, or RunID context.
- Do not log or expose secrets.
- Use `context.Context` for external calls, subprocesses, and orchestrated
workflows that may be canceled.
- Use atomic writes for durable JSON artifacts where practical.
- Keep report selection and prompt IDs centralized in `internal/report`.
- Keep Scriptorium argv construction inside `internal/adapters/scriptorium`.
- Keep Weather API transport and envelope handling inside
`internal/adapters/weatherapi`.
```text
cmd/weatherreporter
-> internal/cli
-> internal/app
-> internal/config
-> internal/report
-> internal/briefing
-> internal/forecast
-> internal/changes
-> internal/state
-> internal/adapters/*
```
## Dependency Policy
Rules:
Prefer the Go standard library. Add dependencies only when they materially
improve correctness, interoperability, security, or maintainability.
- `cmd/weatherreporter` should only bootstrap the CLI.
- `internal/cli` should parse commands and flags, then call `internal/app`.
- `internal/app` should orchestrate workflows but avoid embedding detailed forecast logic.
- `internal/adapters/*` should not contain domain policy.
- `internal/forecast`, `internal/report`, `internal/briefing`, and `internal/changes` should be testable without real external services.
- `internal/state` should expose a storage interface so filesystem state can later be replaced or supplemented.
- `scriptorium` details should not leak outside `internal/adapters/scriptorium`.
Current external dependency:
## Package Responsibilities
- `gopkg.in/yaml.v3` for YAML configuration parsing.
### `cmd/weatherreporter`
When adding a dependency:
Entry point for the compiled binary.
- explain why the standard library is not enough;
- keep dependency types from leaking across unrelated package boundaries;
- add tests for the behavior the dependency supports;
- update this policy if the dependency becomes part of contributor workflow.
Responsibilities:
## Configuration Changes
- Construct the root command from `internal/cli`.
- Execute the command.
- Handle final process exit behavior.
Configuration is owned by `internal/config`.
Non-responsibilities:
When adding or changing a field:
- No configuration loading details.
- No forecast logic.
- No direct calls to weather APIs, state stores, or `scriptorium`.
- update `Config` and the nested config struct in `config.go`;
- add or adjust defaults in `defaults.go` when the field has a safe default;
- update loading or CLI override behavior in `load.go` only when needed;
- validate required values and accepted ranges in `validate.go`;
- add or update config tests;
- update `docs/config.md` and maintained examples when the field is user
visible;
- keep secrets out of example config files.
### `internal/cli`
Configuration precedence is:
Defines the user-facing command tree, flags, arguments, and command wiring.
1. CLI overrides supported by `config.LoadOptions`;
2. configuration file values;
3. built-in defaults.
Responsibilities:
The default config path is `/usr/local/etc/weatherreporter/config.yml`.
- Define commands such as:
- `weatherreporter generate daily`
- `weatherreporter generate tomorrow`
- `weatherreporter generate three-day`
- `weatherreporter generate weekend`
- `weatherreporter generate storm`
- `weatherreporter run morning`
- `weatherreporter run evening`
- `weatherreporter inspect snapshot`
- Use the Go standard library for CLI parsing unless future complexity justifies a dependency.
- Parse flags such as `--config`, `--units`, `--tz`, `--out`, optional Daily `--date`, and storm `--start`/`--end`, then convert them into app-layer request structs.
- Load configuration through `internal/config`.
- Present concise user-facing errors.
## CLI Changes
Non-responsibilities:
The CLI is owned by `internal/cli`.
- No report-building logic.
- No direct subprocess execution.
- No direct weather API calls.
- No state comparison logic.
When adding or changing a command or flag:
Suggested command shape:
- update help text and parser behavior together;
- convert parsed values into app-layer request structs;
- keep domain decisions in `internal/app` or domain packages;
- add parser or command tests in `internal/cli`;
- update `docs/cli.md`;
- update `docs/operations.md` or `docs/troubleshooting.md` when behavior affects
operators.
```text
weatherreporter generate daily --date 2026-05-29 --out ./daily.md
weatherreporter generate tomorrow --out ./tomorrow.md
weatherreporter generate three-day --out ./three_day.md
weatherreporter generate weekend --out ./weekend.md
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md
weatherreporter run morning
weatherreporter run evening
```
CLI commands should return concise actionable errors and avoid printing partial
JSON when command construction fails.
The MVP should not expose location selection. Source `locationId` and `locationName` values returned by the weather API may be retained as provenance.
## Components And Adapters
For `generate daily`, `--date` is optional. When provided, it must use `YYYY-MM-DD`; when omitted, it resolves to the current local date in the configured timezone.
Use existing package boundaries before adding a package.
### `internal/config`
Add a new internal component only when it owns a distinct implemented contract.
Define its inputs, outputs, state behavior, failure behavior, tests, and
invariants in `docs/internal/`.
Owns configuration structures, defaults, loading, precedence, and validation.
Adapters should stay thin:
Responsibilities:
- HTTP adapters own transport, request construction, envelope handling, and
decode boundaries.
- subprocess adapters own argv construction, timeout handling, stdout/stderr
capture, and exit-code interpretation.
- adapter packages should not own report selection, forecast summarization,
Recent Changes, or prompt input schema decisions.
- Define application configuration structs.
- Provide built-in defaults in `defaults.go`.
- Load YAML configuration from `/usr/local/etc/weatherreporter/config.yml` or a CLI-supplied path.
- Use `gopkg.in/yaml.v3` for YAML parsing.
- Apply precedence rules.
- Validate required settings.
- Normalize paths, durations, report settings, weather API units/timezone, missing-source policy, and daypart definitions.
When an external contract changes, update the matching file under
`docs/integrations/`.
Suggested configuration areas:
## Tests
- Weather API base URL, timeout, units, timezone, precision, and missing-source policy.
- `scriptorium` binary, config path, profile, timeout, and optional extra arguments.
- Workspace and output directories.
- Report enablement and output naming.
- Daypart definitions.
- Recent-change thresholds.
Core tests must not require live Weather API or Scriptorium services.
Initial defaults:
Preferred test patterns:
- Weather API units: `us`.
- Weather API timezone: `Chicago`.
- Weather API format: `json`.
- Missing-source policy: `warn`.
- fake command runners for subprocess behavior;
- `httptest.Server` for Weather API behavior;
- filesystem temp directories for state behavior;
- deterministic clocks for report periods and RunIDs;
- table tests for config validation, CLI parsing, period resolution, and
threshold behavior.
Missing-source policy should support a global default and per-source overrides. Valid policy values are `error`, `warn`, and `none`.
Add focused tests near the package that owns the behavior. Use app-level tests
for workflow ordering, persistence, and cross-package contracts.
Non-responsibilities:
## Examples
- No command execution.
- No HTTP calls.
- No report-building logic.
Examples under `examples/` must be real, maintained, and free of secrets.
### `internal/app`
When updating examples:
Application orchestration and top-level use cases.
- use implemented config fields only;
- avoid private endpoints and credentials;
- keep comments short and operationally useful;
- add or update validation coverage when a new example file is introduced;
- link maintained examples from `docs/config.md`.
Responsibilities:
Do not add generated report examples unless they can be kept current without
live external services.
- Implement use cases such as:
- Generate one report.
- Run the morning batch.
- Run the evening batch.
- Generate a manual storm report.
- Coordinate config, weather API adapter, report registry, briefing builders, state store, change comparison, prompt input builder, and `scriptorium` runner.
- Enforce workflow order.
- Ensure each generation run persists enough artifacts for inspection and future comparison.
## Documentation Checklist
The core generation workflow should be approximately:
Documentation updates are part of behavior changes.
```text
resolve report definition
resolve valid period
fetch current weather bundle
build current briefing package
load prior comparable briefing snapshot
compute recent changes
build prompt input data package
write data package
run scriptorium render preflight
invoke scriptorium run
persist report metadata, briefing snapshot, data package, preflight output, and rendered report
```
Update:
Non-responsibilities:
- `README.md` for project orientation or quickstart changes;
- `docs/cli.md` for command and flag changes;
- `docs/config.md` for config fields, defaults, and precedence changes;
- `docs/operations.md` for state, artifact, batch, inspection, and recovery
behavior;
- `docs/troubleshooting.md` for recurring operator-facing failure modes;
- `docs/internal/` for component contracts and invariants;
- `docs/integrations/` for external Weather API or Scriptorium contract changes;
- `docs/roadmap/` only for unimplemented or deferred work.
- No detailed daypart calculations.
- No direct parsing of NWS text unless delegated to domain packages.
- No direct shell command construction outside the `scriptorium` adapter.
### `internal/adapters/weatherapi`
HTTP adapter for the internal weather API backed by `weatherfeeder`.
Responsibilities:
- Fetch normalized weather data from the configured API base URL.
- Fan out to multiple weather API endpoints and assemble one internal `forecast.Bundle`.
- Decode API responses into adapter-owned DTOs or directly into stable internal types if those types are intentionally owned by `weatherreporter`.
- Apply request timeouts and context cancellation.
- Apply configured query defaults, including `format=json`, `units=us`, and `tz=Chicago` unless overridden.
- Fetch full `/forecast/hourly` and `/forecast/narrative` products, not day-slice endpoints, so Go domain code owns report-period selection.
- Record per-source provenance: endpoint, query, fetch time, issued/updated time when available, SHA-256 over canonical/minified raw `data` JSON, warnings, and missing-source status.
- Represent source warnings as first-class records with source name, code, severity, message, endpoint, and completeness impact.
- Require hourly forecast data for normal scheduled reports.
- Apply missing-source policy for `data:null`, malformed non-required sections, or unavailable upstream products.
- Return actionable errors containing endpoint and operation context.
Initial data categories:
- Latest observation.
- Current conditions.
- Hourly forecast data.
- NWS narrative forecast periods.
- NWS alerts.
- NWS forecast discussion.
Stubbed source slots until upstream support exists:
- Daily forecast data.
- NWS weather story.
Non-responsibilities:
- No daypart grouping.
- No Recent Changes comparison.
- No prompt input construction.
- No `scriptorium` calls.
### `internal/adapters/scriptorium`
Subprocess adapter for invoking `scriptorium`.
Responsibilities:
- Provide a narrow runner interface, such as:
```go
type Runner interface {
Render(ctx context.Context, req RenderRequest) (*RenderResult, error)
Run(ctx context.Context, req RunRequest) (*RunResult, error)
}
```
- Execute `scriptorium render` for preflight/debug output without LLM generation.
- Execute `scriptorium run` for report generation.
- Run `scriptorium render` as an always-on preflight before `scriptorium run` for MVP generated reports.
- Pass arguments as an argv slice, not through a shell.
- Pass large prompt input as `--input data_package=<path>`.
- Capture stdout/stderr with reasonable size limits.
- Treat nonzero exits as actionable errors, including exit code `2` from `run`, which may still produce output.
- Keep all `scriptorium`-specific flag details inside the adapter.
Suggested command forms:
```text
scriptorium render \
--prompt weather.daily_report \
--input data_package=./workspace/data-packages/daily/2026-05-29T050000-0500.data_package.json \
--format json
scriptorium run \
--prompt weather.daily_report \
--input data_package=./workspace/data-packages/daily/2026-05-29T050000-0500.data_package.json \
--out ./workspace/reports/daily/2026-05-29T050000-0500.md
```
Non-responsibilities:
- No weather logic.
- No report registry logic.
- No decision about which prompt to run.
Future note:
- A native LLM client can later replace or supplement this adapter behind a similar interface.
### `internal/forecast`
Core forecast-domain processing.
Responsibilities:
- Define the normalized `Bundle` consumed by report builders.
- Group hourly forecast data into configured dayparts.
- Compute derived facts, including:
- Temperature ranges.
- Apparent-temperature ranges, if available.
- Max precipitation probability.
- Peak wind and wind gusts.
- Precipitation windows.
- Thunder mentions.
- Snow/ice/freezing risk indicators.
- Alert overlap with relevant periods.
- Select forecast elements relevant to a report period.
- Provide threshold helpers for impact detection.
Non-responsibilities:
- No CLI behavior.
- No external API calls.
- No rendered prose.
- No direct `scriptorium` calls.
### `internal/report`
Report definitions, registry, period resolution, and report-level contracts.
Responsibilities:
- Define report IDs and report definition contracts.
- Register report types and variants.
- Resolve valid periods for each report.
- Associate report types with prompt IDs.
- Define comparison strategies and output naming behavior.
Suggested report definitions:
```text
daily_today -> prompt weather.daily_report
daily_tomorrow -> prompt weather.daily_report
three_day -> prompt weather.three_day_outlook
weekend -> prompt weather.weekend_outlook
storm -> prompt weather.storm_report
```
`weather.daily_report` should be the standard prompt for one local civil day, regardless of whether that day is today or tomorrow.
A report definition should describe:
- Report ID.
- Human-readable name.
- Prompt ID.
- Valid-period resolver.
- Briefing builder ID or function.
- Recent-change comparison strategy.
- Default output naming pattern.
- Whether the report participates in morning or evening scheduled batches.
Non-responsibilities:
- No detailed forecast computation.
- No state storage.
- No subprocess execution.
### `internal/briefing`
Builds report-specific briefing packages from forecast bundles and report definitions.
Responsibilities:
- Convert a forecast bundle into a report-specific structured briefing package.
- Keep each report's briefing shape explicit and testable.
- Attach relevant NWS narrative periods, alerts, forecast discussion context, and weather story context when available.
- Include metadata such as schema version, configured units/timezone, source warnings, and source provenance.
- Provide inputs suitable for `scriptorium` data packages.
Report-specific builders should exist for:
- Daily Report.
- Tomorrow Planning Brief.
- 3-Day Outlook.
- Weekend Outlook.
- Storm Report.
Non-responsibilities:
- No external API fetching.
- No final prose rendering.
- No state persistence, except through app orchestration.
Design note:
- This package is the architectural center of the application. A clean briefing package makes `scriptorium` a renderer rather than a source of weather reasoning.
### `internal/changes`
Structured comparison of current and prior briefing snapshots.
Responsibilities:
- Compare current briefing packages against prior comparable snapshots.
- Apply meaningful-change thresholds.
- Produce compact structured change summaries for prompt input data packages.
- Avoid comparison of rendered Markdown report text.
Comparable snapshot matching should be declared by each report definition. Daily Today, Daily Tomorrow, and compatible date slices from multi-day reports may compare by same valid local date when the report registry marks them compatible. Weekend compares by same weekend window. Storm compares by explicit event window.
Meaningful changes may include:
- Temperature changes crossing configured thresholds.
- Precipitation probability changes by category.
- Precipitation timing shifts.
- New, canceled, extended, upgraded, or expanded alerts.
- Wind gust threshold crossings.
- Snow/ice/freezing risk changes.
- Severe-weather wording or risk changes.
- Confidence or uncertainty changes, if represented in structured briefing data.
Non-responsibilities:
- No fetching prior state directly unless mediated through app/state contracts.
- No final report prose.
- No external calls.
### `internal/state`
Durable state store for reports, snapshots, data packages, preflight output, metadata, and comparison lookup.
Responsibilities:
- Persist generated report metadata.
- Persist briefing snapshots.
- Persist prompt input data packages.
- Persist `scriptorium render` preflight output for generated reports.
- Locate prior comparable snapshots for Recent Changes.
- Track RunID as generation timestamp plus report ID.
- Use timestamped managed report names to avoid overwriting prior runs for the same valid period.
- Use atomic writes where practical.
- Keep filesystem layout narrow and predictable.
Initial backend:
- Filesystem state.
Potential future backend:
- SQLite or another state database, behind the same store interface.
Suggested state layout:
```text
workspace/
snapshots/
daily/
2026-05-30/
2026-05-29T050000-0500.briefing.json
2026-05-29T050000-0500.metadata.json
three-day/
weekend/
storm/
reports/
daily/
2026-05-29T050000-0500.md
three-day/
weekend/
storm/
data-packages/
daily/
2026-05-29T050000-0500.data_package.json
preflight/
daily/
2026-05-29T050000-0500.render.json
```
Non-responsibilities:
- No weather derivation.
- No report prose generation.
- No CLI formatting decisions.
### `internal/promptinput`
Builds the final data package passed to `scriptorium`.
Responsibilities:
- Combine report metadata, briefing package, Recent Changes, selected source context, and source warnings into a prompt input document.
- Validate required data package fields before invoking `scriptorium`.
- Keep data package schemas explicit enough to test.
- Write data package files to the workspace when requested by the app layer.
Non-responsibilities:
- No weather API calls.
- No forecast derivation.
- No subprocess execution.
### `internal/timeutil`
Time, clock, and period helpers.
Responsibilities:
- Provide an injectable clock for deterministic tests.
- Resolve local dates using the configured report timezone.
- Handle daypart spans, including overnight windows.
- Normalize valid periods.
- Provide helpers for recurring scheduled batches.
Non-responsibilities:
- No report-specific forecast logic unless delegated by `internal/report`.
- No external calls.
## Report Types and Valid-Period Identity
Each generated report must be associated with explicit metadata:
- RunID.
- Report type.
- Report variant, if applicable.
- Generation time.
- Configured report timezone.
- Valid period start.
- Valid period end.
- Source location ID/name when provided by upstream.
- Source product timestamps and/or SHA-256 hashes.
- Source warnings.
- Briefing snapshot path.
- Prompt input data package path.
- Preflight output path.
- Rendered report path.
All valid periods should use the configured local timezone, default `Chicago`, and half-open `[start,end)` intervals.
Initial valid-period rules:
- Daily Today: current local civil day, `[00:00, next 00:00)`.
- Daily Tomorrow: next local civil day.
- 3-Day Outlook: generation time through local midnight after the second following local civil day.
- Weekend Outlook: Monday through Thursday covers Saturday 00:00 to Monday 00:00; Friday and Saturday cover `max(generation time, Friday 18:00)` to Monday 00:00; scheduled Sunday morning skips Weekend Outlook.
- Manual Storm Report: requires explicit `--start` and `--end`; accept `YYYY-MM-DDTHH:MM` interpreted in the configured timezone and RFC3339 timestamps with explicit offsets.
The valid period should identify what weather period the report covers, independent of when the report was generated.
Examples:
- A 5 PM Tomorrow Planning Brief for Saturday and a 5 AM Saturday Daily Report both cover the same valid date.
- A Saturday Weekend Outlook covers the remaining weekend, while a Friday Weekend Outlook may cover Friday evening through Sunday night.
- A Storm Report covers an explicit forecast event window, not a fixed calendar day.
This identity is required for reliable Recent Changes behavior.
## Scheduled Batch Semantics
The app should support scheduled batches but should not need to be a daemon in the initial version.
Suggested batches:
```text
morning:
- daily_today
- three_day
- weekend, except Sunday
evening:
- daily_tomorrow
```
Scheduled batches should continue independent reports after a report failure. The CLI should return nonzero if any report failed and should emit an aggregate run summary.
External scheduling should be handled by systemd timers, cron, or another orchestrator. `weatherreporter` should simply provide deterministic commands that can be scheduled.
## Storm Report Direction
The initial version should support manual Storm Report generation:
```text
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
```
Future storm monitoring should use a staged design:
```text
incoming weather data
-> deterministic candidate detector
-> LLM event evaluator
-> storm lifecycle state
-> storm report generation or skip decision
```
Potential storm lifecycle states:
```text
none -> monitoring -> active_report -> escalated -> deescalating -> resolved
```
This future behavior should not be built before the core scheduled reports are stable, but the package layout should leave room for it.
## Testing Expectations
Core tests should not require real external services.
Priority test areas:
- Configuration loading and validation, including defaults for `units=us`, `tz=Chicago`, and missing-source policy `warn`.
- Standard-library CLI command parsing, including `--units`, `--tz`, and storm `--start`/`--end`.
- Weather API fan-out, source provenance, `data:null`, and missing-source policy behavior.
- Daypart grouping, especially overnight periods.
- Valid-period resolution for each report type.
- Briefing package construction from fixtures.
- Recent Changes threshold behavior and compatible snapshot matching.
- Prior snapshot lookup.
- `scriptorium` adapter behavior using a fake executable or command runner, including both `render` and `run` with `--input data_package=<path>`.
- Batch partial-failure behavior and aggregate exit status.
## Design Invariants
Preserve these invariants as the project evolves:
- Weather facts come from normalized source data, not from the LLM.
- The LLM receives curated briefing packages, not unbounded raw weather payloads.
- Recent Changes are based on structured snapshot comparison, not Markdown diffing.
- Report types are registered or otherwise centrally defined.
- External integrations are thin adapters.
- CLI code wires workflows but does not own domain logic.
- The first durable state backend is filesystem-based and inspectable.
- `scriptorium` is an adapter boundary, not an application dependency that leaks across packages.
Non-roadmap docs must describe implemented behavior only.

View File

@@ -66,10 +66,8 @@ unimplemented work belongs only under `docs/roadmap/`.
the canonical contributor workflow document.
- `docs/policy/documentation.md` - keep. It is the controlling documentation
policy for this migration.
- `docs/roadmap/initial.md` - split and delete or replace. Much of it now
describes completed implementation work; extract only still-future content
into a future-only roadmap and remove this stale initial roadmap from the
current documentation map.
- `docs/roadmap/future.md` - keep as the future-only project roadmap. The stale
implementation roadmap was removed after deferred work was extracted.
- `examples/config.yml` - keep and lightly update. It is a maintained
production-oriented example config and should be validated against the
implemented config loader.
@@ -79,45 +77,36 @@ unimplemented work belongs only under `docs/roadmap/`.
## Policy Compliance Assessment
Required documents for a modular, staged, CLI/config-driven, stateful
application are mostly present: `README.md`, `docs/cli.md`, `docs/config.md`,
Required documents for a modular, CLI/config-driven, stateful application are
present: `README.md`, `docs/cli.md`, `docs/config.md`,
`docs/operations.md`, `docs/internal/`, and `docs/policy/development.md`.
The main required gap is that `docs/policy/development.md` is not in the
canonical contributor-workflow shape required by `docs/policy/documentation.md`.
Recommended documentation gaps:
Recommended documentation is also present:
- `docs/troubleshooting.md` is missing.
- Validated examples are limited to `examples/config.yml`; there is no minimal
example config.
- `docs/internal/app-orchestration.md` is missing for the implemented
application workflow layer.
- `docs/troubleshooting.md` covers recurring operator-facing failure modes.
- `examples/config.yml` and `examples/minimal-config.yml` are validated by
config tests.
- `docs/internal/app-orchestration.md` documents the workflow coordinator.
Documents that are stale or in the wrong canonical home:
Documents that were stale or in the wrong canonical home have been corrected:
- `docs/policy/development.md` contains proposed layout, MVP wording, and future
design notes that should live in roadmap docs or be removed once implemented.
- `docs/roadmap/initial.md` should not be treated as a current implementation
guide. It mixes completed MVP planning with deferred work.
- `README.md` links to the implementation roadmap. The README should link to
current user/operator/developer docs and, if useful, a future-work roadmap
that is clearly labeled as such.
- `docs/integrations/weatherapi.md` includes endpoint details beyond the
implemented adapter fan-out and should be narrowed.
- `docs/policy/development.md` is the contributor workflow policy.
- `docs/roadmap/future.md` is the current home for deferred project work.
- `README.md` links to current user/operator/developer docs.
- `docs/integrations/weatherapi.md` is limited to the implemented adapter
contract.
Content that appears planned, historical, or aspirational outside
`docs/roadmap/`:
- `docs/policy/development.md` uses proposed/future/MVP language and describes
some behavior as intended rather than current contributor practice.
- Non-roadmap docs should be searched for future-oriented terms during the
migration. Some instances in policy docs are legitimate policy wording, but
feature-specific plans should move under `docs/roadmap/`.
- Non-roadmap docs should remain limited to implemented behavior. Some
policy-level wording about future work is legitimate; feature-specific
deferred behavior belongs under `docs/roadmap/`.
Examples:
- `examples/config.yml` exists and matches the implemented configuration shape,
but the documentation should state how it is validated.
- `examples/config.yml` and `examples/minimal-config.yml` exist and load
through the config test suite.
- No generated report examples should be added unless they can be maintained
without live Weather API and Scriptorium dependencies.
- No workflow examples should be added for unimplemented daemon, cleanup,
@@ -125,7 +114,8 @@ Examples:
Links likely needing verification:
- README links to `docs/roadmap/initial.md`.
- README should link only to current user/operator/developer docs unless a
clearly labeled future-work link is needed.
- Internal docs should link to canonical integration docs instead of repeating
Scriptorium or Weather API details.
- Operations and troubleshooting should link to CLI and config reference rather
@@ -380,7 +370,8 @@ Links likely needing verification:
- Purpose: future-only project work extracted from stale roadmap material.
- Canonical scope: deferred features such as automatic storm monitoring, if
still desired.
- Source-of-truth areas: `docs/roadmap/initial.md` and current code boundaries.
- Source-of-truth areas: current code boundaries and deferred work already
extracted into this file.
- Acceptance criteria: no completed MVP tasks; no claims of current behavior;
each item is clearly labeled proposed, accepted, deferred, or rejected.
@@ -436,9 +427,8 @@ Links likely needing verification:
current project behavior.
- `docs/integrations/scriptorium.md`: keep `--input data_package=<path>` as the
documented input contract. Do not reintroduce `--vars-file` or `promptvars`.
- `docs/roadmap/initial.md`: do not update it as current docs. Extract
still-future work into `docs/roadmap/future.md`, then delete or replace the
stale file.
- `docs/roadmap/future.md`: keep only deferred work and avoid completed
implementation history.
- `examples/config.yml`: keep as production-oriented config. Validate it with
config-loading tests or an equivalent non-secret check.
- `examples/minimal-config.yml`: add only if the implementation agent also adds
@@ -729,11 +719,11 @@ archive, remote storage, or resume behavior because those are not implemented.
- Goal: validate examples and ensure roadmap docs contain only future/planned
material.
- Files to create/update/delete/move: update `examples/config.yml`; optionally
create `examples/minimal-config.yml` with validation coverage; extract
future-only content from `docs/roadmap/initial.md` into
`docs/roadmap/future.md`; delete or replace `docs/roadmap/initial.md`.
create `examples/minimal-config.yml` with validation coverage; keep
`docs/roadmap/future.md` as the future-only roadmap after removing stale
implementation-history material.
- Repository areas to inspect: `internal/config/*`, config tests,
`docs/roadmap/initial.md`, current implemented feature set.
roadmap docs, current implemented feature set.
- Acceptance criteria: examples load successfully; roadmap files are clearly
future-only; no completed MVP stage plan is linked as current docs.
- Suggested validation commands: config example loading test, `go test ./...`,
@@ -800,7 +790,5 @@ Manual review items:
No questions block a correct documentation roadmap or migration.
Recommendation: during the migration, delete or replace
`docs/roadmap/initial.md` after extracting any still-valid future work into a
future-only roadmap. Do not treat `docs/roadmap/initial.md` as current project
documentation.
Recommendation: keep `docs/roadmap/future.md` future-only. Do not reintroduce
completed implementation-history material as current project documentation.

51
docs/roadmap/future.md Normal file
View File

@@ -0,0 +1,51 @@
# Future Roadmap
This roadmap contains project work that is not implemented. Current behavior is
documented outside `docs/roadmap/`.
## Deferred: Automatic Storm Monitoring
Manual Storm Report generation is implemented through
`weatherreporter generate storm --start TIME --end TIME`. Automatic storm-event
evaluation remains deferred.
Proposed direction:
1. detect candidate events deterministically from alerts, forecast discussion,
weather story context when available, hourly thresholds, and material
forecast changes;
2. evaluate candidates through Scriptorium or another narrow evaluator adapter;
3. persist storm lifecycle state;
4. generate or update Storm Reports only when a meaningful event is present;
5. suppress ordinary low-impact thunder or rain chances.
Possible lifecycle states:
- `none`
- `monitoring`
- `active_report`
- `escalated`
- `deescalating`
- `resolved`
Acceptance criteria before implementation:
- scheduled reports and manual Storm Reports remain stable;
- candidate detection has fixture coverage;
- evaluator failures are inspectable and do not create noisy report output;
- manual Storm Report generation remains available.
## Deferred: Alternate Runtime Integrations
These ideas are not current behavior:
- native LLM client inside `weatherreporter`;
- database-backed state;
- public HTTP API;
- multi-location selection;
- daemon mode;
- multi-user authorization;
- plugin system.
Each item needs its own design note before implementation. Non-roadmap docs
must not describe these as available behavior.

File diff suppressed because it is too large Load Diff

235
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,235 @@
# Weatherreporter Troubleshooting
This guide lists recurring failures with likely causes, diagnostics, and safe
fixes. See [CLI reference](cli.md), [Configuration reference](config.md), and
[Operations guide](operations.md) for normal usage.
## `weather_api.base_url is required`
Symptom: a generation command fails before fetching weather data.
Likely cause: no Weather API base URL is configured.
Diagnostic:
```sh
weatherreporter generate daily --config ./config.yml --date 2026-05-29
```
Safe fix: add `weather_api.base_url` to the config file, or pass the intended
config path with `--config`.
Relevant docs: [Configuration reference](config.md).
## `weather_api.base_url must be an absolute URL`
Symptom: config loading fails with a base URL validation error.
Likely cause: `weather_api.base_url` is missing a scheme or host.
Diagnostic: inspect the configured value in the file passed to `--config`.
Safe fix: use an absolute URL such as `https://weather.api.example.com/`.
Relevant docs: [Configuration reference](config.md).
## Invalid Timezone
Symptom: config loading fails with `weather_api.timezone` context, or a CLI
timezone override fails.
Likely cause: `weather_api.timezone` or `--tz` is not recognized.
Diagnostic:
```sh
weatherreporter generate daily --tz America/Chicago --date 2026-05-29
```
Safe fix: use an accepted timezone value, such as an IANA timezone name,
`Chicago`, `Stl`, a US timezone abbreviation, or a UTC offset.
Relevant docs: [Configuration reference](config.md).
## Storm Command Rejects Time Bounds
Symptom: `generate storm` fails with `requires --start`, `requires --end`, or
`requires --end after --start`.
Likely cause: the manual event window is missing or invalid.
Diagnostic:
```sh
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
```
Safe fix: provide both bounds. Use `YYYY-MM-DDTHH:MM` in the configured
timezone, or RFC3339 timestamps with explicit offsets.
Relevant docs: [CLI reference](cli.md).
## Weather API Fetch Fails
Symptom: generation fails with `fetch /...`, an HTTP status, or request context.
Likely cause: the configured Weather API endpoint is unreachable, returned a
non-2xx response, or returned an invalid response envelope.
Diagnostic:
```sh
weatherreporter generate daily --config ./config.yml --date 2026-05-29
```
Safe fix: verify `weather_api.base_url`, network access, and the Weather API
service response. The adapter fetches `/observations`, `/conditions/current`,
`/forecast/hourly`, `/forecast/narrative`, `/alerts/active`, and `/discussion`.
Relevant docs: [Configuration reference](config.md).
## Hourly Forecast Is Missing
Symptom: generation fails with hourly forecast context, such as missing hourly
data or an hourly forecast containing no periods.
Likely cause: hourly forecast data is required for generated reports.
Diagnostic: check the Weather API response for `/forecast/hourly`.
Safe fix: restore hourly forecast data at the Weather API. Missing-source
policy cannot make hourly optional.
Relevant docs: [Configuration reference](config.md), [Operations guide](operations.md).
## Source Warnings Appear
Symptom: generation succeeds, but metadata or `inspect sources` shows source
warnings.
Likely cause: an optional source was missing or malformed under a warning
missing-source policy.
Diagnostic:
```sh
weatherreporter inspect sources RUN_ID
weatherreporter inspect metadata RUN_ID
```
Safe fix: inspect the warning `source`, `code`, `message`, and `endpoint`. Fix
the upstream optional source, or intentionally change the relevant
`missing_source` policy.
Relevant docs: [Configuration reference](config.md), [Operations guide](operations.md).
## `scriptorium` Is Not Found Or Cannot Start
Symptom: generation fails with `run scriptorium render` or `run scriptorium`
and an executable or OS error.
Likely cause: the configured Scriptorium binary is unavailable or not
executable.
Diagnostic: check `scriptorium.binary` in config and run the same binary outside
`weatherreporter`.
Safe fix: install Scriptorium, update `scriptorium.binary`, or fix executable
permissions.
Relevant docs: [Configuration reference](config.md),
[Scriptorium integration](integrations/scriptorium.md).
## Render Preflight Fails
Symptom: generation fails with `scriptorium render exited with code ...`.
Likely cause: Scriptorium rejected the prompt, config, profile, or
`data_package` input before report generation.
Diagnostic:
```sh
weatherreporter inspect metadata RUN_ID
weatherreporter inspect data-package RUN_ID
```
Then read the preflight path from metadata. It contains captured stdout, stderr,
exit code, and command.
Safe fix: fix the Scriptorium configuration, prompt ID, profile, or data package
input indicated by stderr.
Relevant docs: [Operations guide](operations.md),
[Scriptorium integration](integrations/scriptorium.md).
## Scriptorium Run Fails
Symptom: generation fails with `scriptorium run exited with code ...`.
Likely cause: Scriptorium failed during report generation or validation.
Diagnostic:
```sh
weatherreporter inspect metadata RUN_ID
weatherreporter inspect data-package RUN_ID
```
If metadata includes a rendered report path, inspect that report as well. A
nonzero run can still leave a managed report artifact.
Safe fix: use the captured stderr and data package to fix the Scriptorium
prompt, profile, model configuration, or validation issue.
Relevant docs: [Operations guide](operations.md),
[Scriptorium integration](integrations/scriptorium.md).
## Batch Command Returns Nonzero
Symptom: `run morning` or `run evening` returns nonzero.
Likely cause: at least one report in the batch failed.
Diagnostic: inspect stdout for the JSON summary and stderr for compact status
lines.
Safe fix: use the failed report's artifact paths from the summary, then inspect
metadata, sources, briefing, and data package for that RunID.
Relevant docs: [CLI reference](cli.md), [Operations guide](operations.md).
## Unknown RunID
Symptom: an inspect command fails with `metadata for run id ... was not found`.
Likely cause: the RunID is mistyped or the command is reading a different
workspace.
Diagnostic:
```sh
weatherreporter inspect reports --config ./config.yml --limit 20
```
Safe fix: copy a RunID from `inspect reports`, or use the same `--config` and
workspace that generated the report.
Relevant docs: [Operations guide](operations.md).
## Workspace Path Error
Symptom: startup or inspection fails with workspace path validation or
filesystem read/write context.
Likely cause: a workspace subdirectory is absolute, escapes `workspace.root`, or
the process cannot read or write the configured path.
Diagnostic: review `workspace.root`, `workspace.snapshots_dir`,
`workspace.reports_dir`, `workspace.data_packages_dir`, and
`workspace.preflight_dir`.
Safe fix: keep workspace subdirectories relative to `workspace.root`, and grant
the process appropriate filesystem permissions.
Relevant docs: [Configuration reference](config.md), [Operations guide](operations.md).

View File

@@ -22,9 +22,6 @@ workspace:
data_packages_dir: data-packages
preflight_dir: preflight
reports:
output_dir: reports
dayparts:
- name: overnight
start: "00:00"

View File

@@ -0,0 +1,2 @@
weather_api:
base_url: https://weather.api.example.com/

View File

@@ -45,6 +45,26 @@ func TestLoadExampleConfig(t *testing.T) {
}
}
func TestLoadMinimalExampleConfig(t *testing.T) {
cfg, err := LoadFile(filepath.Join("..", "..", "examples", "minimal-config.yml"))
if err != nil {
t.Fatalf("LoadFile() error = %v", err)
}
if cfg.WeatherAPI.BaseURL != "https://weather.api.example.com/" {
t.Fatalf("BaseURL = %q, want example URL", cfg.WeatherAPI.BaseURL)
}
if cfg.WeatherAPI.Units != "us" {
t.Fatalf("Units = %q, want default us", cfg.WeatherAPI.Units)
}
if cfg.Scriptorium.Binary != "scriptorium" {
t.Fatalf("Scriptorium.Binary = %q, want default scriptorium", cfg.Scriptorium.Binary)
}
if cfg.Workspace.Root != "workspace" {
t.Fatalf("Workspace.Root = %q, want default workspace", cfg.Workspace.Root)
}
}
func TestExplicitMissingConfigReturnsError(t *testing.T) {
_, err := LoadFile(filepath.Join(t.TempDir(), "missing.yml"))
if err == nil {