diff --git a/docs/roadmap/generated-text.md b/docs/roadmap/generated-text.md deleted file mode 100644 index 8445a62..0000000 --- a/docs/roadmap/generated-text.md +++ /dev/null @@ -1,342 +0,0 @@ -# GeneratedText Report Rendering Roadmap - -## Purpose - -This roadmap defines planned work to move selected reports from full Markdown -LLM generation to deterministic Markdown rendering with structured LLM-filled -text slots. The first target report is the rolling next-hours report, named -`hourly`. - -This feature is not implemented yet, so this document lives under -`docs/roadmap/`. - -## Intent - -The current module and prompt-input system has reached the point where many -report facts can be calculated deterministically. The next step is to let Go -own report structure while using the LLM for only the prose that benefits from -language synthesis. - -The intended workflow is: - -1. collect upstream facts once; -2. derive deterministic report facts; -3. build module outputs and the prompt data package; -4. ask Scriptorium for structured JSON using the schema associated with the - prompt ID on the Scriptorium backend; -5. validate and persist that structured response as `GeneratedText`; -6. build a curated render context; -7. render final Markdown from an embedded human-editable template. - -This should improve consistency for headings, dates, ordering, units, recurring -sections, and report-specific format while preserving the LLM's role in -summarizing and explaining weather impacts. - -## Terminology - -- `CollectedFacts`: normalized upstream inputs from Weather API sources. -- `DerivedFacts`: deterministic Go-calculated facts sliced to a report period. -- `ModuleOutput`: prompt-facing deterministic module stanzas. -- `GeneratedText`: validated structured LLM output. It is not authoritative - weather data; it is prose or short text intended to fill named template - slots. -- `RenderContext`: the final deterministic object passed to a Markdown - template. It may expose selected values from CollectedFacts, DerivedFacts, - ModuleOutput, report metadata, and GeneratedText. - -The report template should not receive the unbounded raw data package directly. -It should receive a curated render context so templates remain readable and do -not become a second application logic layer. - -## Locked Decisions - -- Use `GeneratedText` as the name for the structured LLM output layer. -- Continue using Scriptorium behind `internal/adapters/scriptorium`. -- Use Scriptorium structured JSON output. The Scriptorium backend associates - the prompt ID with the JSON schema; weatherreporter does not pass a schema - file at runtime. -- Keep Scriptorium retries inside Scriptorium. -- Also validate the returned JSON inside `weatherreporter` before rendering. -- Use embedded, separate, human-editable Markdown template files. -- Use embedded, separate, human-readable JSON schema files. -- Use Go standard library `text/template` for Markdown rendering unless a - concrete limitation appears. -- Let Scriptorium enforce the prompt-associated JSON Schema and perform - retries, then have weatherreporter validate by unmarshalling into a typed - GeneratedText struct and checking required fields. Do not add a Go JSON - Schema dependency in the first implementation. -- Do not generate schemas or templates from inline Go. -- Start with the rolling next-hours report only. -- Use `hourly` as the only public report ID for the rolling next-hours - report. -- Do not keep compatibility aliases from retired roadmap terminology. -- Do not add a generic workflow engine or plugin system. - -## Hourly Report Target - -The rolling next-hours report target identity is: - -- report ID: `hourly` -- CLI command: `weatherreporter generate hourly` -- display name: `Hourly Report` -- prompt ID for structured text generation: `weather.hourly_generated_text` -- Markdown template ID: `hourly` -- GeneratedText schema ID: `hourly` -- artifact group: `hourly` -- batch output name: `hourly.md` -- default valid-period length: 6 hours -- valid period: `[generation_time, generation_time + hourlyReportHours)` - -Use a package-owned constant for the valid-period length, initially 6 hours. -The duration should not be user-configurable in the first implementation. - -The report remains explicit-generation only at first. Do not add it to morning -or evening batches until cadence and downstream behavior are proven. - -## Embedded Asset Layout - -Recommended asset package: - -```text -internal/reporttemplate/ - templates/ - hourly.md.tmpl - schemas/ - hourly.generated_text.schema.json -``` - -`internal/reporttemplate` should embed assets with `go:embed` and expose a -narrow API for: - -- looking up a template by template ID; -- looking up a schema by schema ID; -- rendering Markdown from a typed render context. - -Do not allow template or schema asset IDs to come from free-form user input in -the initial implementation. Report definitions should declare the IDs. - -## Report Definition Fields - -Extend report definitions only as needed for this generation mode. Required -fields: - -- `GenerationMode` -- `TemplateID` -- `GeneratedTextSchemaID` - -Recommended generation modes: - -- `GenerationModeScriptoriumMarkdown`: existing full Markdown generation path. -- `GenerationModeGeneratedTextTemplate`: structured GeneratedText plus - deterministic template rendering. - -Existing reports should stay on the current full Markdown path until migrated. -The `hourly` report should use the GeneratedText/template path from the start. -For generated-text reports, the existing `PromptID` field remains the -Scriptorium prompt ID and should be set to `weather.hourly_generated_text`. - -## GeneratedText Contract - -The hourly JSON schema should define only the text slots the template needs. -Keep the first schema small. Example shape: - -```json -{ - "summary": "Brief hourly overview.", - "timing": "Plain-language timing of notable changes.", - "impacts": "Practical impacts for the next few hours.", - "confidence": "Optional confidence or uncertainty note." -} -``` - -The exact schema should be locked during implementation based on the first -hourly template. Required fields should be genuinely required by the template. -Optional fields should be omitted from the rendered Markdown when empty. - -GeneratedText should be persisted as structured JSON exactly as validated by -weatherreporter. Raw Scriptorium output should also be persisted for debugging -when practical. - -## Template Contract - -The hourly Markdown template should be a readable file that a human can edit -without digging into Go code. It should own: - -- Markdown heading structure; -- section order; -- deterministic fact placement; -- conditional inclusion of optional GeneratedText slots; -- final report layout. - -The template should not own: - -- Weather API fetching; -- fact derivation; -- module composition; -- Scriptorium invocation; -- schema validation; -- path construction; -- distributor upload behavior. - -The render context should prepare strings and simple values so template logic -stays shallow. Prefer small conditionals over complex template functions. - -## Scriptorium Adapter Direction - -`internal/adapters/scriptorium` should expose weatherreporter-owned request and -result types for structured JSON generation. The adapter should hide -Scriptorium argv details from app, report, facts, module, prompt-input, and -template packages. - -Scriptorium uses the prompt ID to select the backend-associated structured -output schema. The adapter should not pass a schema path at runtime. - -## State And Artifacts - -Add managed artifacts for the new generation path. Recommended logical -artifacts: - -- module snapshot JSON; -- data package YAML; -- raw GeneratedText JSON written by Scriptorium; -- structured Scriptorium command result for the GeneratedText run; -- validated GeneratedText JSON; -- render context JSON; -- rendered Markdown report; -- metadata linking all of the above. - -The existing `preflight` artifact should remain as the rendered prompt/input -inspection artifact before structured generation. GeneratedText reports add -separate artifacts for raw GeneratedText, structured run result, validated -GeneratedText, and render context. Metadata should record the -`GeneratedTextSchemaID`; no per-run schema artifact is persisted. - -Artifact paths should remain under the workspace and use the report artifact -group, valid start date, and RunID conventions already used by state. - -## End-To-End Behavior - -Reports using `GenerationModeGeneratedTextTemplate` should follow the same -fact collection, derivation, module snapshot, prompt package, state, and -notification boundaries as current reports. The difference is limited to the -last rendering segment: Scriptorium returns structured GeneratedText, Go -validates that structure, Go builds a curated render context, and an embedded -template renders the final Markdown. - -The existing full-Markdown Scriptorium path should remain available for -reports that have not migrated. - -## Target Architecture - -GeneratedText rendering adds one new generation mode, not a replacement for the -whole report system. Reports that opt into this mode should continue to use -the existing report registry, fact pipeline, module snapshot, data package, -state store, and notification boundaries. - -The target flow is: - -1. `internal/report` declares whether a report uses the existing full-Markdown - Scriptorium path or the GeneratedText/template path. -2. The app builds CollectedFacts, DerivedFacts, module snapshots, and the - prompt data package using the same boundaries as existing reports. -3. `internal/adapters/scriptorium` asks Scriptorium for structured JSON using - the report's prompt ID. The Scriptorium backend owns the prompt-to-schema - association. -4. Weatherreporter validates the returned JSON as typed `GeneratedText`. -5. A report-specific render context is built from selected deterministic facts, - module outputs, metadata, and GeneratedText. -6. `internal/reporttemplate` renders the final Markdown from an embedded, - human-editable template. -7. The managed Markdown report remains the canonical output and the distributor - upload source. - -This structure keeps each layer understandable: - -- CollectedFacts and DerivedFacts remain weather-data contracts. -- ModuleOutput remains the prompt-facing deterministic facts contract. -- GeneratedText remains LLM-written prose slots, not weather truth. -- RenderContext remains a curated template input, not a second raw data package. -- Templates own presentation order and headings, not weather derivation logic. - -## Editing Surface - -The desired human editing surface is file-based and report-specific. For the -hourly report, the important files should be easy to find and inspect: - -- one Markdown template file for the report layout; -- one JSON schema file describing the expected GeneratedText payload; -- one report definition declaring the prompt ID, template ID, schema ID, module - composition, valid period, artifact group, and output naming policy. - -The template and schema should be embedded into the binary, but they should -remain ordinary files in the repository. A maintainer should be able to tweak -headings, reorder sections, or inspect the structured prose contract without -reading unrelated orchestration code. - -## Boundary Guidelines - -- Keep report identity, prompt IDs, generation mode, template IDs, schema IDs, - valid periods, module composition, and output naming in `internal/report`. -- Keep Scriptorium subprocess details in `internal/adapters/scriptorium`. -- Keep Markdown template lookup and rendering in `internal/reporttemplate`. -- Keep GeneratedText validation in a narrow package or component with typed - report-specific structs. -- Keep app orchestration responsible for sequencing and artifact persistence, - not for report-specific prose or template structure. -- Keep distributor upload behavior unchanged: upload the managed Markdown - report after it exists. -- Do not let templates fetch data, derive facts, perform source selection, or - construct workspace paths. -- Do not let Scriptorium-specific flags, schema paths, or subprocess details - leak into report definitions or domain packages. -- Do not add user-provided template or schema paths in the first - implementation. - -## Target Acceptance Criteria - -The feature is successful when: - -- `weatherreporter generate hourly` produces a deterministic Markdown report - whose headings, dates, and section order come from an embedded template; -- Scriptorium fills only the report's structured GeneratedText slots; -- weatherreporter validates GeneratedText before rendering; -- the raw GeneratedText, validated GeneratedText, render context, rendered - Markdown, and metadata are inspectable after a run; -- existing daily, tomorrow, three-day, weekend, and storm reports keep their - current full-Markdown Scriptorium path until explicitly migrated; -- failures in structured generation, validation, or template rendering are - diagnosable from saved artifacts where practical; -- the managed Markdown report remains the source for optional copies and - distributor uploads; -- the implementation does not introduce a generic workflow engine, plugin - system, or broad report rewrite. - -## Deferred Work - -Do not include these in the first implementation: - -- migrating daily, tomorrow, three-day, weekend, or storm to GeneratedText; -- user-configurable template or local schema source overrides; -- user-defined templates or schemas; -- a generic workflow engine; -- template hot reloading; -- dynamic module selection for templates; -- distributor-specific generated-text behavior; -- prompt/schema generation from Go structs; -- scheduled hourly batch membership; -- configurable hourly report duration. - -## Open Questions - -No questions block roadmap creation. - -Recommended approach: keep JSON schema enforcement in Scriptorium for retry -behavior, and also perform weatherreporter-side typed validation before -rendering. Weatherreporter should validate the returned JSON by unmarshalling -into the report's GeneratedText type and checking required fields; it should -not add a Go JSON Schema dependency in the first implementation. - -Viable alternative: trust Scriptorium validation and only unmarshal into typed -Go structs without explicit required-field checks. This is simpler, but it -weakens weatherreporter's rendering contract and may make empty or malformed -LLM output harder to diagnose. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md deleted file mode 100644 index 37ff421..0000000 --- a/docs/roadmap/implementation.md +++ /dev/null @@ -1,782 +0,0 @@ -# GeneratedText Hourly Report Implementation Roadmap - -## Purpose - -This roadmap defines the concrete implementation sequence for -`docs/roadmap/generated-text.md`. It is written for an LLM coding agent that -will implement each stage in order. - -This is a future-work roadmap. Until a stage is implemented, non-roadmap docs -must not describe `GeneratedText`, embedded report templates, or other -unimplemented GeneratedText behavior as available. - -## Source Feature Roadmap - -Use `docs/roadmap/generated-text.md` as the authoritative feature roadmap for -intent, terminology, target behavior, and policy choices. This document is the -step-by-step implementation plan. If target behavior changes, update -`generated-text.md` first, then update this plan. - -Future implementation agents should read `generated-text.md` before starting -Stage 1. Treat that file as the conceptual contract for why the feature exists -and what final shape it should have; treat this file as the ordered work plan -for reaching that shape. - -## Locked Implementation Decisions - -- Implement the first GeneratedText path for the rolling next-hours report, - named `hourly`. -- Use `hourly` as the only planned public name for the rolling next-hours - report; do not keep compatibility aliases from retired roadmap terminology. -- Use report ID `hourly`, artifact group `hourly`, batch output name - `hourly.md`, and CLI command `weatherreporter generate hourly`. -- Keep the rolling report explicit-generation only; do not add scheduled batch - membership in this implementation. -- Keep the valid-period length as a package-owned constant, initially 6 hours. -- Add report generation modes: - - `scriptorium_markdown` for existing reports; - - `generated_text_template` for `hourly`. -- Keep existing reports on the current Scriptorium Markdown path. -- For generated-text reports, keep `Definition.PromptID` as the Scriptorium - prompt ID and set the hourly value to `weather.hourly_generated_text`. -- Add `TemplateID` and `GeneratedTextSchemaID` to report definitions. -- Use embedded assets under `internal/reporttemplate`. -- Use standard-library `text/template`. -- Let Scriptorium enforce its prompt-associated JSON Schema and retries; - weatherreporter validates by unmarshalling into a typed GeneratedText struct - and checking required fields. Do not add a Go JSON Schema dependency in this - implementation. -- Keep the existing preflight artifact for prompt/input inspection. -- Add managed artifacts for raw GeneratedText JSON, structured Scriptorium run - result, validated GeneratedText JSON, and render context JSON. -- Distributor notification continues to upload the managed Markdown report only. -- Preserve current CLI behavior, artifact paths, and generated output behavior - for daily, tomorrow, three-day, weekend, and storm reports. - -## Stage 1: Roadmap Reconciliation And Hourly Identity - -Goal: remove conflicting future instructions and establish `hourly` as the only -planned public name for the rolling next-hours report. - -Files to inspect: - -- `docs/roadmap/generated-text.md` -- the superseded roadmap file for the retired rolling-report name -- `docs/roadmap/future.md` -- `docs/roadmap/implementation.md` -- `internal/app/app.go` -- `internal/report/definition.go` -- `internal/report/registry.go` - -Implementation: - -- Replace the superseded roadmap file for the retired rolling-report name with - a short note pointing to `docs/roadmap/generated-text.md`, or delete it if - the repository policy at implementation time prefers removing obsolete - roadmap files. -- Ensure no roadmap instructs a future agent to implement retired public - identifiers for the rolling report. -- If partial code using retired identifiers already exists, rename it to - `hourly` rather than adding aliases: - - app report kind -> `ReportHourly`; - - report ID constant -> `report.Hourly`; - - CLI command -> `hourly`; - - artifact group -> `hourly`; - - prompt IDs and tests updated to hourly names. -- Do not add compatibility aliases for retired command or config names. - -Acceptance criteria: - -- Active roadmap docs use `hourly` for the rolling report. -- No active roadmap describes a retired identifier as the target public report - ID. -- Existing code compiles after any rename work. - -Suggested validation: - -```bash -rg -n 'near[_-]term|Near''Term' docs internal -go test ./internal/app ./internal/report ./internal/cli -``` - -This stage is small enough for one implementation prompt. - -## Stage 2: Report Definition Generation Mode - -Goal: add generation-mode metadata to report definitions without changing the -current report generation path. - -Files to inspect: - -- `internal/report/definition.go` -- `internal/report/registry.go` -- `internal/report/*_report.go` -- `internal/report/period_test.go` -- `internal/state/filesystem.go` - -Implementation: - -- Add a `GenerationMode` type in `internal/report`. -- Add constants: - - `GenerationModeScriptoriumMarkdown` - - `GenerationModeGeneratedTextTemplate` -- Add fields to `report.Definition`: - - `GenerationMode GenerationMode` - - `TemplateID string` - - `GeneratedTextSchemaID string` -- Default or explicitly set existing reports to - `GenerationModeScriptoriumMarkdown`. -- Update report definition tests so every generated report declares a - generation mode. -- Do not wire app behavior in this stage. -- Keep existing `PromptID` semantics: it is the Scriptorium prompt ID for the - report's generation mode. - -Acceptance criteria: - -- Existing report definitions retain current prompt IDs and behavior. -- Registry tests fail if a generated report omits generation mode. -- Generated-text-only fields are empty for existing Markdown reports. - -Suggested validation: - -```bash -go test ./internal/report ./internal/state -``` - -This stage is small enough for one implementation prompt. - -## Stage 3: Hourly Report Definition, Modules, Facts, And CLI Shell - -Goal: add the `hourly` report identity, rolling valid period, module -composition, facts derivation, config override aliases, and CLI command shell. - -Files to inspect: - -- `internal/report/definition.go` -- `internal/report/registry.go` -- new `internal/report/hourly_report.go` -- `internal/report/period_test.go` -- `internal/briefing/modules.go` -- `internal/facts/facts.go` -- `internal/facts/facts_test.go` -- `internal/config/reports.go` -- `internal/config/config_test.go` -- `internal/app/app.go` -- `internal/cli/root.go` -- `internal/cli/root_test.go` - -Implementation: - -- Add report ID `Hourly ID = "hourly"`. -- Add comparison strategy `CompareRollingWindow = "rolling_window"`. -- Add `hourlyReportHours` constant, initially `6`. -- Add `hourlyDefinition()`: - - `Name: "Hourly Report"` - - `PromptID: "weather.hourly_generated_text"` - - `GenerationMode: GenerationModeGeneratedTextTemplate` - - `TemplateID: "hourly"` - - `GeneratedTextSchemaID: "hourly"` - - `ComparisonStrategy: CompareRollingWindow` - - `ArtifactGroup: "hourly"` - - `BatchOutputName: "hourly.md"` - - `Generated: true` - - `CompatiblePriorIDs: []ID{Hourly}` - - no morning/evening batch membership -- Resolve valid period as `[generation_time, generation_time + 6h)` in the - effective timezone. -- Add default hourly modules in order: - 1. `metadata` - 2. `current_conditions` - 3. `hourly_forecast` - 4. `precip_timing` - 5. `alert_digest` - 6. `spc_convective_outlooks` - 7. `area_forecast_discussion` - 8. `spc_convective_discussion` - 9. `weather_story` -- Configure hourly `area_forecast_discussion` options to include only: - - `key_messages` - - `short_term` -- Update module supported-report lists: - - include `report.Hourly` in all broadly compatible modules; - - include `report.Hourly` for `hourly_forecast`; - - keep daily/daypart modules incompatible. -- Add `facts.BuildDerived` support for hourly: - - valid-period hourly slice; - - precipitation timing from that slice; - - alert overlap filtering; - - SPC outlook/discussion overlap filtering; - - no daily summaries, daypart summaries, or storm summary. -- Add config report aliases: - - `hourly` - - reject or do not recognize retired rolling-report config keys. -- Add app report kind `ReportHourly = "hourly"` and map it to - `report.Hourly`. -- Add CLI parsing and help for: - - ```bash - weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] - ``` - -- Do not add `--date`, `--start`, `--end`, or duration flags. -- It is acceptable for `generate hourly` to return an explicit - "generation mode not implemented" error until the app workflow stage, but - parser and resolution tests should pass. - -Acceptance criteria: - -- `report.DefaultRegistry().Lookup(report.Hourly)` succeeds. -- hourly valid period is exactly six hours and not civil-day truncated. -- hourly default module composition validates. -- daily-only modules reject hourly. -- facts derivation supports the hourly module list. -- CLI help includes `generate hourly`. -- Existing command behavior is unchanged. - -Suggested validation: - -```bash -go test ./internal/report ./internal/briefing ./internal/facts -go test ./internal/config ./internal/app ./internal/cli -go run ./cmd/weatherreporter --help -``` - -This stage is large but still suitable for one implementation prompt if kept -strictly to report identity/facts/CLI shell. If it becomes unwieldy, split it -after report/facts and implement CLI/config aliases in a follow-up prompt. - -## Stage 4: Embedded Template And Schema Assets - -Goal: create the embedded asset package and first hourly template/schema. - -Files to create: - -- `internal/reporttemplate/reporttemplate.go` -- `internal/reporttemplate/templates/hourly.md.tmpl` -- `internal/reporttemplate/schemas/hourly.generated_text.schema.json` -- `internal/reporttemplate/reporttemplate_test.go` - -Implementation: - -- Use `go:embed` for templates and schemas. -- Expose a narrow API: - - `Template(id string) (string, error)` or equivalent; - - `Schema(id string) ([]byte, error)` or equivalent; - - `Render(id string, data any) ([]byte, error)`. -- Use `text/template`. -- Register only `hourly` assets initially. -- Missing or unknown asset IDs must return actionable errors. -- Keep template functions minimal. Prefer prepared render-context strings over - complex template logic. -- Add an initial hourly JSON Schema with: - - required `summary`; - - required `timing`; - - required `impacts`; - - optional `confidence`; - - all fields strings; - - `additionalProperties: false`. -- Add an initial hourly Markdown template that is readable and deterministic. - It should include fixed headings, report valid period, selected deterministic - facts, and GeneratedText slots. - -Acceptance criteria: - -- schema asset can be looked up for tests, local validation, and out-of-band - Scriptorium registration; -- template asset can be looked up; -- template renders from a test render context; -- missing template/schema IDs fail clearly; -- no generated schema or template content is inline in Go. - -Suggested validation: - -```bash -go test ./internal/reporttemplate -``` - -This stage is small enough for one implementation prompt. - -## Stage 5: GeneratedText Contract And Render Context - -Goal: add typed GeneratedText validation and a typed hourly render context. - -Files to create or inspect: - -- new `internal/generatedtext` package, or another narrow package if the - implementation chooses a better local name -- `internal/reporttemplate` -- `internal/module` -- `internal/briefing` -- `internal/promptinput` - -Implementation: - -- Add typed hourly GeneratedText: - - ```go - type Hourly struct { - Summary string `json:"summary"` - Timing string `json:"timing"` - Impacts string `json:"impacts"` - Confidence string `json:"confidence,omitempty"` - } - ``` - -- Add validation: - - malformed JSON fails; - - empty required fields fail after trimming whitespace; - - unknown fields fail by using `json.Decoder.DisallowUnknownFields`; - - validated output is normalized to stable JSON. -- Do not add a Go JSON Schema dependency in this implementation. -- Add typed hourly render context. -- Build render context from: - - report metadata and location; - - selected module outputs from the module snapshot; - - validated hourly GeneratedText. -- Use `module.StanzaValue` or equivalent typed decoding rather than ad hoc map - traversal where practical. -- Keep render context curated; do not pass the full raw data package to the - template. - -Acceptance criteria: - -- valid GeneratedText passes and round-trips to stable JSON; -- missing required fields fail; -- unknown fields fail; -- render context has enough deterministic fields for the hourly template; -- render context construction fails clearly when required module stanzas are - missing. - -Suggested validation: - -```bash -go test ./internal/generatedtext ./internal/reporttemplate ./internal/module -``` - -This stage is small enough for one implementation prompt. - -## Stage 6: State Artifacts For GeneratedText Reports - -Goal: add explicit managed paths and save/load helpers for generated-text -artifacts. - -Files to inspect: - -- `internal/state/filesystem.go` -- `internal/state/store.go` -- `internal/state/metadata.go` -- `internal/state/filesystem_test.go` -- `internal/fileutil` - -Implementation: - -- Extend `state.ArtifactPaths` with: - - `GeneratedTextRaw` - - `GeneratedTextResult` - - `GeneratedText` - - `RenderContext` -- Store all four under the existing snapshots tree: - - `snapshots///.generated_text.raw.json` - - `snapshots///.generated_text.run.json` - - `snapshots///.generated_text.json` - - `snapshots///.render_context.json` -- Do not add new workspace config directories. -- Extend `state.Metadata` with optional path fields for those artifacts. - Also record `GeneratedTextSchemaID` as metadata for auditability without - copying schema contents into every run. -- Add store methods as needed, for example: - - `SaveGeneratedTextRaw` - - `SaveGeneratedTextResult` - - `SaveGeneratedText` - - `SaveRenderContext` -- Use atomic writes through existing `fileutil` helpers. -- Keep existing metadata JSON shape for Markdown-path reports, with new fields - omitted when empty. - -Acceptance criteria: - -- path tests cover all new artifact paths; -- save helpers create parent directories and write atomically; -- metadata links generated-text paths for hourly reports; -- existing report metadata tests still pass. - -Suggested validation: - -```bash -go test ./internal/state ./internal/fileutil -``` - -This stage is small enough for one implementation prompt. - -## Stage 7: Scriptorium GeneratedText Run Adapter - -Goal: add a GeneratedText run helper behind the Scriptorium adapter while -preserving the existing `scriptorium run` command shape. - -Files to inspect: - -- `internal/adapters/scriptorium/runner.go` -- `internal/adapters/scriptorium/runner_test.go` -- `docs/integrations/scriptorium.md` after implementation - -Implementation: - -- Add weatherreporter-owned request/result types: - - ```go - type StructuredRunRequest struct { - PromptID string - DataPackagePath string - OutputPath string - } - ``` - -- Add `StructuredRun(ctx, req)` to the runner. -- Validate prompt ID, data package path, and output path before - subprocess execution. -- Preserve current command result capture behavior: - - full argv; - - stdout; - - stderr; - - truncation flags; - - exit code; - - output path. -- Do not change current `Render` or `Run` methods. -- Scriptorium selects the structured output schema from the backend prompt - configuration associated with `PromptID`; do not pass a schema path at - runtime. -- Use the same `scriptorium run` command form as Markdown report generation. - The only material difference is that `--out` points to the raw GeneratedText - JSON artifact path rather than a Markdown report path. -- Use this argv shape: - - ```bash - scriptorium run \ - --prompt \ - --input data_package= \ - --out - ``` - -- Do not add `--format json` or any schema flag. Structured output is selected - by Scriptorium's internal prompt configuration. -- If local Scriptorium documentation needs updating after implementation, - update `docs/integrations/scriptorium.md` to describe this contract. - -Acceptance criteria: - -- argv construction tests prove `scriptorium run` is invoked without - `--format` or schema flags and with `--out `; -- nonzero exits return captured result plus error; -- missing required fields fail before subprocess execution; -- current Scriptorium render/run tests still pass; -- tokens/secrets are not added to argv or errors. - -Suggested validation: - -```bash -go test ./internal/adapters/scriptorium -``` - -This stage is small enough for one implementation prompt. - -## Stage 8: GeneratedText App Workflow Success Path - -Goal: wire the successful generated-text/template path into app orchestration -for hourly while preserving the existing path for all current reports. - -Files to inspect: - -- `internal/app/app.go` -- `internal/app/app_test.go` -- `internal/adapters/scriptorium` -- `internal/reporttemplate` -- `internal/generatedtext` -- `internal/state` -- `internal/promptinput` - -Implementation: - -- Extend the app `Renderer` interface, or introduce a second narrow interface, - so tests can fake structured generation. -- Branch report generation by `req.Resolved.Definition.GenerationMode`. -- For `GenerationModeScriptoriumMarkdown`, keep the existing flow unchanged. -- For the successful `GenerationModeGeneratedTextTemplate` path: - 1. run existing preflight and save preflight artifact; - 2. call Scriptorium structured generation with raw output path - `GeneratedTextRaw`; - 3. save structured Scriptorium command result to `GeneratedTextResult`; - 4. validate raw JSON into typed GeneratedText; - 5. save normalized GeneratedText to `GeneratedText`; - 6. build hourly render context from metadata, module snapshot, and - GeneratedText; - 7. save render context JSON; - 8. render Markdown via embedded template to managed report path; - 9. save final metadata with all generated-text paths and the schema ID; -- Defer distributor notification and optional output copy regression coverage - to Stage 10. -- Keep failure-specific persistence tests minimal in this stage; Stage 9 owns - failure artifact behavior. -- Keep Recent Changes empty for hourly. - -Acceptance criteria: - -- hourly generation reaches Scriptorium structured generation, validates - GeneratedText, renders Markdown, and persists final metadata; -- daily/tomorrow/three-day/weekend/storm generation still uses existing - Markdown Scriptorium path; -- generated-text artifacts are linked from metadata for successful hourly runs; -- app tests cover the successful hourly workflow with fake structured - Scriptorium output. - -Suggested validation: - -```bash -go test ./internal/app ./internal/state ./internal/reporttemplate ./internal/generatedtext -``` - -This stage is small enough for one implementation prompt if failure behavior is -kept to the later stages. - -## Stage 9: GeneratedText Failure Artifact Persistence - -Goal: make generated-text failures inspectable without changing successful -behavior from Stage 8. - -Files to inspect: - -- `internal/app/app.go` -- `internal/app/app_test.go` -- `internal/adapters/scriptorium` -- `internal/generatedtext` -- `internal/reporttemplate` -- `internal/state` - -Implementation: - -- Add focused app tests for failures at each generated-text step: - - preflight failure; - - structured Scriptorium generation failure; - - GeneratedText validation failure; - - render context construction failure; - - Markdown template rendering failure. -- Ensure failures preserve available artifacts: - - failed preflight saves preflight output when possible; - - failed structured generation saves command result when available; - - validation failures preserve raw GeneratedText output when available; - - render-context failures preserve validated GeneratedText when already - available; - - template failures preserve GeneratedText and render context when already - built. -- Return actionable errors that include report ID, RunID, and operation context - without exposing data package contents or secrets. -- Do not run distributor notification after any generated-text failure. -- Do not create optional `--out` copies after any generated-text failure. - -Acceptance criteria: - -- every generated-text failure point has an app-level regression test; -- partial artifacts are persisted where available and linked from metadata when - metadata can be safely written; -- failed hourly runs do not notify distributor; -- existing Markdown-generation failure behavior is unchanged. - -Suggested validation: - -```bash -go test ./internal/app ./internal/state ./internal/generatedtext ./internal/reporttemplate -``` - -This stage is small enough for one implementation prompt. - -## Stage 10: GeneratedText Output Copy And Notification Regression Coverage - -Goal: preserve existing user-facing output behavior and distributor boundaries -for generated-text reports. - -Files to inspect: - -- `internal/app/app.go` -- `internal/app/app_test.go` -- `internal/adapters/distributor` -- `internal/state` - -Implementation: - -- Keep `--out` copy behavior unchanged after the managed Markdown report - exists. -- Keep `--out-dir` behavior unchanged for batch paths if hourly is ever run - through a batch helper during tests. -- Run distributor notification, if enabled, only after: - - structured generation succeeds; - - GeneratedText validates; - - Markdown template rendering succeeds; - - final metadata is saved. -- Use the managed Markdown report path as the distributor source. -- Never use optional `--out` or `--out-dir` copies as distributor source files. -- Keep distributor request construction template-driven; do not add - hourly-specific path branching. -- Keep Recent Changes empty for hourly in this implementation. - -Acceptance criteria: - -- `--out` copies the managed generated-text Markdown report after success; -- distributor notification receives the managed Markdown report path; -- distributor notification is not called on any failure from Stage 9; -- enabled distributor failure makes hourly generation fail with the same - semantics as existing generated reports; -- existing daily/tomorrow/three-day/weekend/storm distributor behavior is - unchanged. - -Suggested validation: - -```bash -go test ./internal/app ./internal/adapters/distributor ./internal/state -``` - -This stage is small enough for one implementation prompt. - -## Stage 11: End-To-End CLI, Config, And Prompt Package Coverage - -Goal: prove the public hourly workflow and configured module overrides work -through the CLI/app boundary. - -Files to inspect: - -- `internal/cli/root.go` -- `internal/cli/root_test.go` -- `internal/config/reports.go` -- `internal/config/config_test.go` -- `internal/app/app_test.go` -- `examples/config.yml` - -Implementation: - -- Add CLI command tests for `generate hourly`. -- Reject the retired rolling-report command name. -- Reject hourly `--date`, `--start`, `--end`, and duration-like flags. -- Add config override tests for `reports.hourly.deterministic_modules`. -- Ensure retired rolling-report config keys are rejected. -- Add app/CLI workflow tests using fake Scriptorium structured output. -- Update examples only if they enumerate report module overrides. - -Acceptance criteria: - -- `weatherreporter --help` lists `generate hourly`; -- the retired rolling-report command name is not accepted; -- hourly config overrides validate compatible modules; -- incompatible daily/daypart modules fail clearly for hourly; -- examples load successfully. - -Suggested validation: - -```bash -go test ./internal/cli ./internal/config ./internal/app -go run ./cmd/weatherreporter --help -``` - -This stage is small enough for one implementation prompt. - -## Stage 12: Implemented Documentation - -Goal: document implemented behavior after the code exists. - -Files to inspect and update: - -- `docs/cli.md` -- `docs/operations.md` -- `docs/config.md` -- `docs/internal/app-orchestration.md` -- `docs/internal/report-registry.md` -- `docs/internal/briefing.md` -- `docs/internal/prompt-input.md` -- `docs/internal/scriptorium-adapter.md` -- `docs/internal/state.md` -- new `docs/internal/reporttemplate.md` -- `docs/integrations/scriptorium.md` -- `docs/roadmap/future.md` -- the superseded roadmap file for the retired rolling-report name -- `examples/config.yml`, only if examples changed - -Documentation requirements: - -- Non-roadmap docs describe only implemented hourly and GeneratedText behavior. -- `docs/cli.md` documents `generate hourly`, not the retired command name. -- `docs/config.md` documents `reports.hourly` only if override support is - implemented. -- Internal docs distinguish: - - CollectedFacts; - - DerivedFacts; - - ModuleOutput; - - GeneratedText; - - RenderContext. -- Scriptorium integration docs include the structured-output command actually - used by the adapter. -- State docs list generated-text artifacts and metadata links. -- the superseded roadmap file for the retired rolling-report name is deleted - or marked superseded. -- Deferred migrations for other report types stay under roadmap docs only. - -Acceptance criteria: - -- No non-roadmap docs describe unimplemented generated-text migrations. -- No active docs describe retired rolling-report identifiers as the target - public report. -- Maintained examples remain valid. - -Suggested validation: - -```bash -go test ./internal/config -rg -n 'near[_-]term|Near''Term' docs internal examples -git diff --check -``` - -This stage is small enough for one implementation prompt. - -## Stage 13: Final Validation - -Goal: run full validation and perform targeted manual checks. - -Commands: - -```bash -go test ./... -go run ./cmd/weatherreporter --help -git diff --check -``` - -Manual checks: - -- `weatherreporter --help` lists `generate hourly`. -- `weatherreporter --help` does not list the retired rolling-report command. -- generated hourly Markdown has deterministic headings and section order. -- GeneratedText JSON is persisted separately from deterministic facts. -- render context JSON is persisted and readable. -- embedded hourly template is readable and easy to edit. -- embedded hourly schema is readable and easy to edit. -- existing Markdown-generation reports still run through the previous - Scriptorium `run --out ` path. -- distributor notification, when enabled, uploads the managed Markdown report. - -This stage is small enough for one implementation prompt. - -## Deferred Work - -Do not include these in the first implementation: - -- migrating daily, tomorrow, three-day, weekend, or storm to GeneratedText; -- user-configurable template or local schema source overrides; -- user-defined templates or schemas; -- a generic workflow engine; -- template hot reloading; -- dynamic module selection for templates; -- prompt/schema generation from Go structs; -- scheduled hourly batch membership; -- configurable hourly report duration; -- rolling-window Recent Changes for hourly; -- distributor-specific generated-text behavior. - -## Open Questions - -None block implementation. diff --git a/docs/templates.md b/docs/templates.md new file mode 100644 index 0000000..b9a36eb --- /dev/null +++ b/docs/templates.md @@ -0,0 +1,232 @@ +# Report Templates + +## Purpose + +This guide describes the implemented Markdown report template surface for +`weatherreporter`. It is for maintainers editing embedded report templates, +especially the hourly report template. + +Templates are Go `text/template` files. The current implemented template is: + +- `internal/reporttemplate/templates/hourly.md.tmpl` + +The hourly template is rendered from a curated `HourlyRenderContext`, not from +the raw prompt data package. Weather data selection, derivation, module +execution, LLM generation, and artifact paths are handled before template +rendering. + +## Editing Rules + +- Use Go `text/template` syntax. +- Keep templates focused on Markdown layout, headings, ordering, and simple + conditional display. +- Do not put weather derivation, source selection, or path construction logic + in templates. +- Missing template keys are errors. A misspelled variable will fail rendering. +- No custom template functions are currently registered. +- Optional strings can be guarded with `{{ with .Field }}...{{ end }}`. +- Slices can be rendered with `{{ range .Items }}...{{ else }}...{{ end }}`. + +## Hourly Template Variables + +These are the complete variables currently available to +`internal/reporttemplate/templates/hourly.md.tmpl`. + +### Report Metadata + +| Variable | Type | Description | +| --- | --- | --- | +| `.ReportTitle` | string | Display title for the report. Currently `Hourly Report`. | +| `.LocationName` | string | Prompt/report location label, such as `Brentwood, MO`. Falls back to source location if configured location metadata is unavailable. | +| `.ValidPeriod` | string | Friendly local valid period label, such as `2026-05-29 at 8:30 AM to 2026-05-29 at 2:30 PM`. | +| `.GeneratedAt` | string | Friendly local generation time label. | + +### GeneratedText + +These fields are written by Scriptorium as structured JSON, validated by +weatherreporter, and then inserted into the render context. + +| Variable | Type | Description | +| --- | --- | --- | +| `.GeneratedText.Summary` | string | Required short prose summary. | +| `.GeneratedText.Timing` | string | Required prose about timing of notable weather changes or hazards. | +| `.GeneratedText.Impacts` | string | Required prose about practical near-term impacts. | +| `.GeneratedText.Confidence` | string | Optional confidence or uncertainty note. Empty when omitted by the LLM. | + +Example: + +```gotemplate +## Summary + +{{ .GeneratedText.Summary }} +{{ with .GeneratedText.Confidence }} + +## Confidence + +{{ . }} +{{ end }} +``` + +### Current Conditions + +| Variable | Type | Description | +| --- | --- | --- | +| `.CurrentConditions` | string | Deterministic one-line current conditions summary. May include condition text, temperature, apparent temperature, humidity, and wind. | + +Example value: + +```text +Partly cloudy; 74 F; feels like 76 F; humidity 71%; wind S 8 mph. +``` + +### Hourly Forecast + +| Variable | Type | Description | +| --- | --- | --- | +| `.HourlyForecast` | []HourlyForecastRow | Ordered rows for the hourly report valid period. | +| `.HourlyForecast[].Time` | string | Friendly local period start time, or the source period name if no start label is available. | +| `.HourlyForecast[].Summary` | string | Hourly text description, falling back to the period name. | +| `.HourlyForecast[].Temperature` | string | Rounded temperature label such as `75 F`, or empty. | +| `.HourlyForecast[].Precipitation` | string | Rounded precipitation probability label such as `70% precipitation`, or empty. | +| `.HourlyForecast[].Wind` | string | Wind label such as `wind S 10 mph, gusts 18 mph`, or empty. | + +Example: + +```gotemplate +## Hourly Forecast +{{ range .HourlyForecast }} +- {{ .Time }}: {{ .Summary }}{{ with .Temperature }}; {{ . }}{{ end }}{{ with .Precipitation }}; {{ . }}{{ end }}{{ with .Wind }}; {{ . }}{{ end }} +{{ else }} +- No hourly forecast rows available. +{{ end }} +``` + +### Precipitation Timing + +| Variable | Type | Description | +| --- | --- | --- | +| `.PrecipitationTiming` | string | Deterministic precipitation timing summary. May include peak probability, precipitation windows, and thunder mention. | + +Example value: + +```text +Peak precipitation probability 70% at 10 AM; 2026-05-29 at 10:00 AM to 2026-05-29 at 12:00 PM (max 70% at 10 AM); Thunder is mentioned in the forecast. +``` + +### Alerts + +| Variable | Type | Description | +| --- | --- | --- | +| `.Alerts` | []string | Alert labels for active alerts overlapping the report period. Empty when there are no relevant alert overlaps. | + +Example: + +```gotemplate +## Alerts +{{ range .Alerts }} +- {{ . }} +{{ else }} +- No active alert overlaps for this report period. +{{ end }} +``` + +### SPC Outlooks + +| Variable | Type | Description | +| --- | --- | --- | +| `.SPCOutlooks` | []string | SPC convective outlook labels overlapping the report period. Empty when there are no overlapping outlooks. | + +Example value: + +```text +Slight Risk from 2026-05-29 at 7:00 AM to 2026-05-29 at 3:00 PM +``` + +### Forecast Discussion + +| Variable | Type | Description | +| --- | --- | --- | +| `.ForecastDiscussion.KeyMessages` | []string | AFD key messages included for the hourly report. | +| `.ForecastDiscussion.ShortTerm` | string | AFD short-term discussion text, or empty if unavailable. | + +Example: + +```gotemplate +## Forecast Discussion +{{ range .ForecastDiscussion.KeyMessages }} +- {{ . }} +{{ end }}{{ with .ForecastDiscussion.ShortTerm }} + +{{ . }} +{{ end }} +``` + +### SPC Discussion + +| Variable | Type | Description | +| --- | --- | --- | +| `.SPCDiscussions` | []string | SPC discussion labels retained for overlapping qualifying outlook periods. Empty when there is no relevant SPC discussion. | + +Example: + +```gotemplate +## SPC Discussion +{{ range .SPCDiscussions }} +- {{ . }} +{{ else }} +- No overlapping SPC discussion. +{{ end }} +``` + +### Weather Story + +| Variable | Type | Description | +| --- | --- | --- | +| `.WeatherStory` | string | Weather story title and description, or `No weather story available.` | + +## Common Patterns + +Use `with` for optional strings: + +```gotemplate +{{ with .GeneratedText.Confidence }} +## Confidence + +{{ . }} +{{ end }} +``` + +Use `range` with `else` for optional lists: + +```gotemplate +{{ range .SPCOutlooks }} +- {{ . }} +{{ else }} +- No overlapping SPC outlooks. +{{ end }} +``` + +Keep punctuation outside optional blocks when possible: + +```gotemplate +- {{ .Time }}: {{ .Summary }}{{ with .Temperature }}; {{ . }}{{ end }} +``` + +## Validation + +After editing a template, run: + +```bash +go test ./internal/reporttemplate ./internal/generatedtext ./internal/app +``` + +For a full check, run: + +```bash +go test ./... +go run ./cmd/weatherreporter --help +git diff --check +``` + +Template render tests currently exercise the hourly template through +`internal/generatedtext/render_context_test.go`. diff --git a/internal/reporttemplate/prompts/hourly.generated_text.md b/internal/reporttemplate/prompts/hourly.generated_text.md new file mode 100644 index 0000000..9d2b548 --- /dev/null +++ b/internal/reporttemplate/prompts/hourly.generated_text.md @@ -0,0 +1,42 @@ +You are writing structured text slots for a short-term weather report. + +The calling application will render the final Markdown report. Your job is not +to write the full report. Return only a JSON object matching the configured +schema. + +Use only the supplied `data_package`. Do not invent weather details, times, +hazards, probabilities, or impacts that are not supported by the data. + +The report focuses on the valid period in `report.valid_period`, typically the +next several hours for the configured location. + +Write for a general local audience. Be clear, practical, and concise. + +Return these fields: + +- `summary`: 1-2 sentences summarizing the main weather story for the valid + period. +- `timing`: 1-3 sentences explaining when the most important changes or hazards + are expected during the valid period. +- `impacts`: 1-3 sentences explaining practical impacts for people planning + travel, outdoor activity, errands, commuting, or similar near-term decisions. +- `confidence`: optional. Include only if uncertainty, timing spread, or + conflicting signals materially affect how the reader should interpret the + forecast. + +Guidance: + +- Prefer location-applicable risk products, active alerts, and overlapping SPC + products for hazard wording. +- Use the hourly forecast and precipitation timing modules for timing details. +- Use current conditions only for immediate context; do not let them override + the forecast. +- Use AFD key messages and short-term discussion for context, but keep regional + or broad discussion tied back to the configured location and valid period. +- Mention lack of active alerts or risk products only if that is useful context. +- Do not repeat deterministic tables or lists verbatim. +- Do not include Markdown headings, bullets, or code fences. +- Do not include fields outside the schema. +- If a field cannot be supported by the data, keep it brief and conservative. + +Return JSON only.