From 0759e1598f5eaae582addb9127999bd91ef90de3 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 29 May 2026 19:59:13 +0000 Subject: [PATCH] Align policy documentation with contributor workflow --- docs/policy/architecture.md | 36 +- docs/policy/development.md | 779 +++++++----------------------------- 2 files changed, 163 insertions(+), 652 deletions(-) diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index 697e925..56d80f7 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -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//config.yml`, with a CLI override via `--config`. @@ -65,13 +64,17 @@ External adapters belong under `internal/adapters/`. 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. diff --git a/docs/policy/development.md b/docs/policy/development.md index da1fc94..ea2302a 100644 --- a/docs/policy/development.md +++ b/docs/policy/development.md @@ -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=`. -- 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=`. -- 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.