Finalized the initial development roadmap

This commit is contained in:
2026-05-29 11:41:50 -05:00
parent 577b42e243
commit 05b56d6ea6
2 changed files with 413 additions and 237 deletions

View File

@@ -8,12 +8,12 @@ The application should remain a small, explicit, dependency-light Go program. Do
`weatherreporter` is a deterministic weather briefing and report-preparation application. It should:
1. Fetch normalized weather data from an internal weather API backed by `weatherfeeder`.
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 variables for a specific report type.
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, and generation metadata.
6. Persist the rendered Markdown report, briefing snapshot, prompt input package, and generation metadata.
The preferred data flow is:
@@ -23,7 +23,7 @@ weatherfeeder-backed internal API
-> forecast bundle
-> report-specific briefing builder
-> recent-change comparison
-> prompt variable package
-> prompt input data package
-> scriptorium subprocess adapter
-> Markdown report + metadata + stored snapshot
```
@@ -96,7 +96,7 @@ internal/state/
filesystem.go
metadata.go
internal/promptvars/
internal/promptinput/
build.go
schema.go
@@ -167,7 +167,8 @@ Responsibilities:
- `weatherreporter run morning`
- `weatherreporter run evening`
- `weatherreporter inspect snapshot`
- Parse CLI flags and convert them into app-layer request structs.
- 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.
@@ -181,15 +182,19 @@ Non-responsibilities:
Suggested command shape:
```text
weatherreporter generate daily --location home --date today --out ./daily.md
weatherreporter generate tomorrow --location home --out ./tomorrow.md
weatherreporter generate three-day --location home --out ./three_day.md
weatherreporter generate weekend --location home --out ./weekend.md
weatherreporter generate storm --location home --out ./storm.md
weatherreporter run morning --location home
weatherreporter run evening --location home
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
```
The MVP should not expose location selection. Source `locationId` and `locationName` values returned by the weather API may be retained as provenance.
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.
### `internal/config`
Owns configuration structures, defaults, loading, precedence, and validation.
@@ -199,20 +204,29 @@ Responsibilities:
- 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, locations, and daypart definitions.
- Normalize paths, durations, report settings, weather API units/timezone, missing-source policy, and daypart definitions.
Suggested configuration areas:
- Weather API base URL, timeout, and location endpoints.
- Locations and time zones.
- `scriptorium` binary, profile, timeout, and optional extra arguments.
- 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.
Initial defaults:
- Weather API units: `us`.
- Weather API timezone: `Chicago`.
- Weather API format: `json`.
- Missing-source policy: `warn`.
Missing-source policy should support a global default and per-source overrides. Valid policy values are `error`, `warn`, and `none`.
Non-responsibilities:
- No command execution.
@@ -230,7 +244,7 @@ Responsibilities:
- 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 variable builder, and `scriptorium` runner.
- 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.
@@ -238,15 +252,16 @@ The core generation workflow should be approximately:
```text
resolve report definition
resolve location and valid period
resolve valid period
fetch current weather bundle
build current briefing package
load prior comparable briefing snapshot
compute recent changes
build prompt variables
write vars file
invoke scriptorium
persist report metadata and briefing snapshot
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
```
Non-responsibilities:
@@ -261,25 +276,37 @@ HTTP adapter for the internal weather API backed by `weatherfeeder`.
Responsibilities:
- Fetch normalized weather data for a configured location.
- 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.
Expected data categories:
Initial data categories:
- Latest observation.
- Current conditions.
- Hourly forecast data.
- Daily 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 variable construction.
- No prompt input construction.
- No `scriptorium` calls.
### `internal/adapters/scriptorium`
@@ -292,24 +319,32 @@ Responsibilities:
```go
type Runner interface {
Render(ctx context.Context, req RenderRequest) (*RenderResult, error)
Run(ctx context.Context, req RunRequest) (*RunResult, error)
}
```
- Execute `scriptorium run` with `exec.CommandContext`.
- 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.
- Prefer a vars file path over large inline JSON.
- Pass large prompt input as `--input data_package=<path>`.
- Capture stdout/stderr with reasonable size limits.
- Treat nonzero exits as actionable errors.
- 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 form:
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 \
--vars-file ./workspace/daily.vars.json \
--out ./workspace/daily_report.md
--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:
@@ -365,12 +400,14 @@ Suggested report definitions:
```text
daily_today -> prompt weather.daily_report
daily_tomorrow -> prompt weather.daily_report or weather.tomorrow_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.
@@ -395,9 +432,10 @@ Builds report-specific briefing packages from forecast bundles and report defini
Responsibilities:
- Convert a forecast bundle into a report-specific structured briefing package.
- Keep each reports briefing shape explicit and testable.
- Attach relevant NWS narrative periods, alerts, forecast discussion context, and weather story context.
- Provide inputs suitable for LLM prompt variables.
- 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:
@@ -425,9 +463,11 @@ Responsibilities:
- Compare current briefing packages against prior comparable snapshots.
- Apply meaningful-change thresholds.
- Produce compact structured change summaries for prompt variables.
- 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.
@@ -447,14 +487,17 @@ Non-responsibilities:
### `internal/state`
Durable state store for reports, snapshots, metadata, and comparison lookup.
Durable state store for reports, snapshots, data packages, preflight output, metadata, and comparison lookup.
Responsibilities:
- Persist generated report metadata.
- Persist briefing snapshots.
- Persist prompt variable files when useful for inspection.
- 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.
@@ -470,25 +513,26 @@ Suggested state layout:
```text
workspace/
locations/
home/
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-30.md
three-day/
weekend/
storm/
vars/
daily/
2026-05-29T050000-0500.vars.json
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:
@@ -497,16 +541,16 @@ Non-responsibilities:
- No report prose generation.
- No CLI formatting decisions.
### `internal/promptvars`
### `internal/promptinput`
Builds the final variable payload passed to `scriptorium`.
Builds the final data package passed to `scriptorium`.
Responsibilities:
- Combine report metadata, briefing package, Recent Changes, and selected source context into a prompt variable document.
- Validate required prompt variables before invoking `scriptorium`.
- Keep prompt variable schemas explicit enough to test.
- Write vars files to the workspace when requested by the app layer.
- 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:
@@ -521,7 +565,7 @@ Time, clock, and period helpers.
Responsibilities:
- Provide an injectable clock for deterministic tests.
- Resolve local dates using the configured location time zone.
- Resolve local dates using the configured report timezone.
- Handle daypart spans, including overnight windows.
- Normalize valid periods.
- Provide helpers for recurring scheduled batches.
@@ -535,24 +579,38 @@ Non-responsibilities:
Each generated report must be associated with explicit metadata:
- RunID.
- Report type.
- Report variant, if applicable.
- Location ID.
- Generation time.
- Configured report timezone.
- Valid period start.
- Valid period end.
- Source product timestamps and/or hashes.
- Source location ID/name when provided by upstream.
- Source product timestamps and/or SHA-256 hashes.
- Source warnings.
- Briefing snapshot path.
- Prompt variable 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 a forecast event window, not a fixed calendar day.
- A Storm Report covers an explicit forecast event window, not a fixed calendar day.
This identity is required for reliable Recent Changes behavior.
@@ -572,6 +630,8 @@ 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
@@ -579,7 +639,7 @@ External scheduling should be handled by systemd timers, cron, or another orches
The initial version should support manual Storm Report generation:
```text
weatherreporter generate storm --location home
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
```
Future storm monitoring should use a staged design:
@@ -606,14 +666,16 @@ Core tests should not require real external services.
Priority test areas:
- Configuration loading and validation.
- 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.
- Recent Changes threshold behavior and compatible snapshot matching.
- Prior snapshot lookup.
- `scriptorium` adapter behavior using a fake executable or command runner.
- CLI command parsing for major workflows.
- `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