# Tomorrow Report Implementation Roadmap ## Purpose This roadmap defines the staged implementation plan for `docs/roadmap/tomorrow.md`. It is written for an LLM coding agent that will implement each stage in order. The conceptual target state, user intent, and locked product decisions live in `docs/roadmap/tomorrow.md`; this file defines the concrete implementation sequence. This is future-work planning. Do not treat the behavior described here as implemented until the corresponding code, tests, examples, and non-roadmap docs are updated. ## Implementation Guardrails - Preserve public CLI syntax: `weatherreporter generate tomorrow`. - Make a clean pre-release break from report ID `daily_tomorrow`; do not add compatibility aliases. - Keep report identity, prompt IDs, template IDs, artifact groups, output names, and comparison policy centralized in `internal/report`. - Reuse the existing `generated_text_template` workflow implemented for Hourly. - Keep Scriptorium details behind the existing adapter boundary. - Keep Go responsible for deterministic facts, valid periods, module snapshots, structured generated-text validation, and final Markdown template rendering. - Keep templates responsible for wording and layout. - Keep generated JSON schemas and Markdown templates as embedded asset files, not inline Go strings. - Preserve current managed artifact behavior except where report identity intentionally changes from `daily_tomorrow` to `tomorrow`. ## Stage 1: Report Identity Split Goal: make Tomorrow an independent report ID and artifact identity while preserving the public `generate tomorrow` command. Implementation: - Replace `report.DailyTomorrow` with `report.Tomorrow` whose value is `"tomorrow"`. - Rename report-definition helpers and resolvers around the new identity: `dailyTomorrowDefinition` to `tomorrowDefinition`, `dailyTomorrowModules` to `tomorrowModules`, and `resolveDailyTomorrow` to `resolveTomorrow`. - Update `report.DefaultRegistry`, `Registry.All`, batch resolution, and tests so the built-in report order contains `tomorrow` instead of `daily_tomorrow`. - Update `internal/app` so `app.ReportTomorrow` resolves to `report.Tomorrow`. - Keep the valid period as the next local civil day. - Keep evening batch behavior: `run evening` should still generate the Tomorrow report. - Change Tomorrow definition identity fields to: - `ID: report.Tomorrow` - `Name: "Tomorrow Report"` - `ArtifactGroup: "tomorrow"` - `BatchOutputName: "tomorrow.md"` - `Generated: true` - `CompatiblePriorIDs: []report.ID{report.Tomorrow}` - `ComparisonStrategy: report.CompareSameValidDate` - Keep the current Tomorrow module composition initially, renamed to `tomorrowModules`, so the prompt data package continues to include the daypart, precipitation, alert, SPC, AFD, weather story, tomorrow planning, and hourly facts already available. - Update all code and tests that assert `daily_tomorrow` paths, metadata, RunIDs, prior compatibility, or registry IDs. Acceptance criteria: - No production-code references to `report.DailyTomorrow` or report ID `daily_tomorrow` remain. - `weatherreporter generate tomorrow` still parses and resolves successfully. - Evening batch contains `tomorrow`. - Managed workspace artifact paths and distributor identity values now use `tomorrow`. Suggested validation: ```bash go test ./internal/report ./internal/app ./internal/cli ./internal/state ``` This stage is suitable for one implementation prompt. ## Stage 2: Tomorrow GeneratedText Contract Goal: add structured Tomorrow LLM output validation and schema assets. Implementation: - Add `internal/generatedtext/tomorrow.go` with: ```go type Tomorrow struct { Summary string `json:"summary"` ForecastDiscussion []string `json:"forecast_discussion"` PrecipitationTiming string `json:"precipitation_timing,omitempty"` Confidence string `json:"confidence,omitempty"` } ``` - Add `generatedtext.ValidateTomorrow`. - Use `json.Decoder.DisallowUnknownFields`. - Reject multiple JSON values. - Trim `Summary`, `PrecipitationTiming`, `Confidence`, and each `ForecastDiscussion` paragraph. - Drop blank discussion paragraphs after trimming, then require at least one remaining paragraph. - Reject blank `Summary`. - Return normalized JSON with the same public field names. - Add `internal/reporttemplate/schemas/tomorrow.generated_text.schema.json`. The schema should: - require `summary`; - require `forecast_discussion`; - define `forecast_discussion` as an array of strings with at least one item; - allow optional `precipitation_timing` and `confidence`; - reject additional properties. - Add `internal/reporttemplate/prompts/tomorrow.generated_text.md` as the maintained Scriptorium prompt source asset. This file is a source contract for out-of-band Scriptorium prompt registration; weatherreporter does not need to load prompt Markdown at runtime. - Add Tomorrow to `internal/reporttemplate` schema lookup. - Update app generated-text validation dispatch so it can return either `generatedtext.Hourly` or `generatedtext.Tomorrow`. Prefer a small generic dispatch shape such as: ```go func validateGeneratedText(def report.Definition, data []byte) (any, []byte, error) ``` Then type-check the returned value in render-context dispatch. Acceptance criteria: - Tomorrow generated text rejects unknown fields, missing required fields, blank summary, and no usable forecast discussion paragraphs. - Optional `precipitation_timing` and `confidence` are trimmed and omitted from normalized JSON when empty. - Hourly generated text behavior is unchanged. Suggested validation: ```bash go test ./internal/generatedtext ./internal/reporttemplate ./internal/app ``` This stage is suitable for one implementation prompt. ## Stage 3: Daypart Presentation Fields Goal: add only the daypart fields needed to keep the Tomorrow template composable without moving prose construction into Go. Implementation: - Extend the `derived_daypart_summaries` module output with presentation helpers that are facts, not complete sentences: - `display_name`, for example `Morning`; - `dominant_condition_lower`, for inline template text; - `temperature_phrase_f`, such as `low 70s`, `upper 60s`, or `upper 60s to mid-70s`; - `mention_precipitation`, true when max PoP is at or above the existing hourly forecast precipitation mention threshold, currently 20%; - `max_pop_time_label`, using friendly local hour format such as `8:00 AM` when max PoP time exists. - Reuse the existing hourly forecast precipitation mention threshold constant rather than adding a user config field in this change. - Keep existing structured numeric fields in the module output. - Do not add a prewritten `daypart_line` string. - Do not add wind prose in this stage unless tests show the initial template needs a specific structured wind fact. If wind wording is needed, add a small structured wind field, not a full sentence. Acceptance criteria: - Daypart module output has enough structured fields for a readable Tomorrow template. - The output remains useful for YAML prompt packages. - No generated prose sentence is hard-coded into the module. Suggested validation: ```bash go test ./internal/briefing ./internal/promptinput ``` This stage is suitable for one implementation prompt. ## Stage 4: Tomorrow Render Context And Template Goal: render Tomorrow Markdown from structured generated text, module outputs, and report metadata. Implementation: - Add a dedicated Tomorrow render context under `internal/generatedtext`, for example: ```go type TomorrowRenderContext struct { Report TomorrowReportContext GeneratedText Tomorrow Modules TomorrowTemplateModules Collected facts.CollectedFacts Derived facts.DerivedFacts } ``` - Add `TomorrowReportContext` with: - `Title`, for example `Sunday's Weather`; - `ForecastDate`; - `ForecastDateLabel`, for example `Sunday, June 15, 2026`; - `ForecastDayName`, for example `Sunday`; - `GeneratedAt`; - `GeneratedAtLabel`, for example `Saturday, June 14, 2026 at 9:14 AM`; - `ValidPeriod`; - `Timezone`. - Construct `Title` in Go, not in the template. - Add `TomorrowTemplateModules` with pointer fields for the module outputs used by the template: - `Metadata`; - `DerivedDailySummary`; - `DerivedDaypartSummaries`; - `PrecipTiming`; - `AlertDigest`; - `SPCConvectiveOutlooks`; - `AreaForecastDiscussion`; - `SPCConvectiveDiscussion`; - `WeatherStory`; - `TomorrowPlanning`. - Add an ordered daypart slice for the template, derived from the configured daypart order rather than ranging directly over a map. This can live in `TomorrowTemplateModules`, for example `Dayparts []TomorrowDaypartContext`. - Add `BuildTomorrowRenderContext`. - Add `internal/reporttemplate/templates/tomorrow.md.tmpl`. - Add Tomorrow to `internal/reporttemplate` template lookup. - Template shape: - title; - forecast date; - generated timestamp; - `GeneratedText.Summary`; - deterministic `Daypart Forecast` bullets in configured order; - conditional `Precipitation Timing` only when precipitation windows exist; - deterministic precipitation window bullets before optional `GeneratedText.PrecipitationTiming`; - `Forecast Discussion` with one paragraph per `GeneratedText.ForecastDiscussion` item. - Keep current conditions and hourly forecast available through the data package and render context, but do not render them in the initial Tomorrow template unless the template explicitly uses them. Acceptance criteria: - The template renders without map-order nondeterminism. - Precipitation Timing is omitted when no precipitation windows exist. - Forecast Discussion supports multiple paragraphs. - Missing optional modules produce clean omission or fallback behavior, not template execution errors. Suggested validation: ```bash go test ./internal/generatedtext ./internal/reporttemplate ./internal/briefing ``` This stage is suitable for one implementation prompt. ## Stage 5: App Workflow Integration Goal: route Tomorrow through the generated-text-template workflow end to end. Implementation: - Change Tomorrow report definition to: - `PromptID: "weather.tomorrow_generated_text"` - `GenerationMode: report.GenerationModeGeneratedTextTemplate` - `TemplateID: "tomorrow"` - `GeneratedTextSchemaID: "tomorrow"` - Update `internal/app.buildRenderContext` dispatch: - hourly template requires `generatedtext.Hourly`; - tomorrow template requires `generatedtext.Tomorrow`; - unsupported type/template combinations return actionable errors. - Ensure Scriptorium `run` writes raw generated text to a `.json` path for Tomorrow, matching the existing generated-text-template workflow. - Ensure normalized generated text, render context JSON, generated Markdown, metadata, and final report artifacts are persisted through existing state helpers. - Ensure app errors include report ID `tomorrow` and RunID context. Acceptance criteria: - `weatherreporter generate tomorrow` no longer invokes Scriptorium for full Markdown. - The workflow validates Scriptorium JSON output, builds a Tomorrow render context, and renders Markdown locally. - Hourly generated-text-template behavior remains unchanged. Suggested validation: ```bash go test ./internal/app ./internal/state ./internal/adapters/scriptorium ``` This stage is suitable for one implementation prompt. ## Stage 6: CLI, State, Distributor, And Batch Behavior Goal: update cross-package behavior affected by the report ID clean break. Implementation: - Update CLI tests and command-output expectations for `generate tomorrow`. - Update state/path tests so managed artifacts, snapshots, data packages, generated-text artifacts, render contexts, and report files use artifact group `tomorrow`. - Update batch tests: - evening batch emits report ID `tomorrow`; - batch output copy name remains `tomorrow.md`; - partial-failure behavior is unchanged. - Update Recent Changes tests: - Tomorrow compares only against prior `tomorrow` snapshots; - Daily Today no longer treats Tomorrow as a compatible prior unless the implementation explicitly keeps that relationship for Daily Today only. - Update distributor notification tests so rendered template variables use: - `report_id=tomorrow`; - `artifact_group=tomorrow`; - `batch_output_name=tomorrow.md`. - Update config tests so report module overrides use `reports.tomorrow`. Do not accept `reports.daily_tomorrow` unless a future explicit compatibility decision reverses the clean break. Acceptance criteria: - Public CLI syntax is stable. - Persisted artifacts and distributor request context use the new report ID. - Batch behavior is unchanged except for the new ID. - No tests rely on `daily_tomorrow`. Suggested validation: ```bash go test ./internal/cli ./internal/app ./internal/state ./internal/config ./internal/report ``` This stage is suitable for one implementation prompt. ## Stage 7: Documentation And Examples Goal: align implemented docs and maintained examples after the code change. Implementation: - Update non-roadmap docs only after the behavior is implemented. - Inspect and update: - `docs/cli.md`; - `docs/config.md`; - `docs/operations.md`; - `docs/troubleshooting.md`, if new failure modes are introduced; - `docs/internal/report-registry.md`; - `docs/internal/generatedtext.md`; - `docs/internal/reporttemplate.md`; - `docs/internal/state.md`; - `docs/templates.md`; - relevant Scriptorium and distributor integration docs only if their weatherreporter-facing contract changed. - Update `examples/config.yml` if it references Tomorrow modules or `reports.daily_tomorrow`. - Keep future Today/Daily split language only under `docs/roadmap/`. - Do not document unimplemented Today or generic Daily products as current behavior. Acceptance criteria: - Non-roadmap docs describe implemented behavior only. - Docs use report ID `tomorrow`. - Examples load under current config validation. - No stale user-facing references to `daily_tomorrow` remain outside historical roadmap context. Suggested validation: ```bash rg -n "daily_tomorrow|Daily Tomorrow|weather.daily_report" README.md docs examples internal git diff --check ``` This stage is suitable for one implementation prompt. ## Stage 8: Final Validation Goal: verify the completed cutover as a coherent behavior change. Run: ```bash go test ./internal/report ./internal/generatedtext ./internal/reporttemplate ./internal/briefing ./internal/app ./internal/cli ./internal/state go test ./... go run ./cmd/weatherreporter --help git diff --check ``` Also run targeted stale-symbol checks: ```bash rg -n "DailyTomorrow|dailyTomorrow|daily_tomorrow" internal docs examples rg -n "weather.tomorrow_generated_text|TemplateID:.*tomorrow|GeneratedTextSchemaID:.*tomorrow" internal docs ``` Acceptance criteria: - Full test suite passes. - Help output still shows `generate tomorrow`. - No production-code stale `daily_tomorrow` symbols remain. - Generated-text-template artifacts for Tomorrow are persisted in the same categories as Hourly. - Existing Hourly behavior still passes tests. This stage is suitable for one implementation prompt. ## Open Questions None block implementation. The required decisions are locked by `docs/roadmap/tomorrow.md` and this implementation plan: - Tomorrow uses report ID `tomorrow`. - Tomorrow uses prompt ID `weather.tomorrow_generated_text`. - Tomorrow uses template ID and generated-text schema ID `tomorrow`. - Tomorrow forecast discussion is an array of paragraph strings. - `daily_tomorrow` compatibility aliases are intentionally not preserved. ## Global Validation Checklist - `go test ./...` - `go run ./cmd/weatherreporter --help` - `git diff --check` - `rg -n "DailyTomorrow|dailyTomorrow|daily_tomorrow" internal docs examples` - Confirm `weatherreporter generate tomorrow` uses structured JSON from Scriptorium and renders final Markdown locally. - Confirm distributor notification context uses `report_id=tomorrow` and `artifact_group=tomorrow`. - Confirm examples contain no unimplemented fields and no secrets.