16 KiB
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_templateworkflow 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_tomorrowtotomorrow.
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.DailyTomorrowwithreport.Tomorrowwhose value is"tomorrow". - Rename report-definition helpers and resolvers around the new identity:
dailyTomorrowDefinitiontotomorrowDefinition,dailyTomorrowModulestotomorrowModules, andresolveDailyTomorrowtoresolveTomorrow. - Update
report.DefaultRegistry,Registry.All, batch resolution, and tests so the built-in report order containstomorrowinstead ofdaily_tomorrow. - Update
internal/appsoapp.ReportTomorrowresolves toreport.Tomorrow. - Keep the valid period as the next local civil day.
- Keep evening batch behavior:
run eveningshould still generate the Tomorrow report. - Change Tomorrow definition identity fields to:
ID: report.TomorrowName: "Tomorrow Report"ArtifactGroup: "tomorrow"BatchOutputName: "tomorrow.md"Generated: trueCompatiblePriorIDs: []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_tomorrowpaths, metadata, RunIDs, prior compatibility, or registry IDs.
Acceptance criteria:
- No production-code references to
report.DailyTomorrowor report IDdaily_tomorrowremain. weatherreporter generate tomorrowstill parses and resolves successfully.- Evening batch contains
tomorrow. - Managed workspace artifact paths and distributor identity values now use
tomorrow.
Suggested validation:
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.gowith:
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 eachForecastDiscussionparagraph. - 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_discussionas an array of strings with at least one item; - allow optional
precipitation_timingandconfidence; - reject additional properties.
- require
- Add
internal/reporttemplate/prompts/tomorrow.generated_text.mdas 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/reporttemplateschema lookup. - Update app generated-text validation dispatch so it can return either
generatedtext.Hourlyorgeneratedtext.Tomorrow. Prefer a small generic dispatch shape such as:
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_timingandconfidenceare trimmed and omitted from normalized JSON when empty. - Hourly generated text behavior is unchanged.
Suggested validation:
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_summariesmodule output with presentation helpers that are facts, not complete sentences:display_name, for exampleMorning;dominant_condition_lower, for inline template text;temperature_phrase_f, such aslow 70s,upper 60s, orupper 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 as8:00 AMwhen 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_linestring. - 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:
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:
type TomorrowRenderContext struct {
Report TomorrowReportContext
GeneratedText Tomorrow
Modules TomorrowTemplateModules
Collected facts.CollectedFacts
Derived facts.DerivedFacts
}
- Add
TomorrowReportContextwith:Title, for exampleSunday's Weather;ForecastDate;ForecastDateLabel, for exampleSunday, June 15, 2026;ForecastDayName, for exampleSunday;GeneratedAt;GeneratedAtLabel, for exampleSaturday, June 14, 2026 at 9:14 AM;ValidPeriod;Timezone.
- Construct
Titlein Go, not in the template. - Add
TomorrowTemplateModuleswith 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 exampleDayparts []TomorrowDaypartContext. - Add
BuildTomorrowRenderContext. - Add
internal/reporttemplate/templates/tomorrow.md.tmpl. - Add Tomorrow to
internal/reporttemplatetemplate lookup. - Template shape:
- title;
- forecast date;
- generated timestamp;
GeneratedText.Summary;- deterministic
Daypart Forecastbullets in configured order; - conditional
Precipitation Timingonly when precipitation windows exist; - deterministic precipitation window bullets before optional
GeneratedText.PrecipitationTiming; Forecast Discussionwith one paragraph perGeneratedText.ForecastDiscussionitem.
- 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:
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.GenerationModeGeneratedTextTemplateTemplateID: "tomorrow"GeneratedTextSchemaID: "tomorrow"
- Update
internal/app.buildRenderContextdispatch:- hourly template requires
generatedtext.Hourly; - tomorrow template requires
generatedtext.Tomorrow; - unsupported type/template combinations return actionable errors.
- hourly template requires
- Ensure Scriptorium
runwrites raw generated text to a.jsonpath 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
tomorrowand RunID context.
Acceptance criteria:
weatherreporter generate tomorrowno 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:
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.
- evening batch emits report ID
- Update Recent Changes tests:
- Tomorrow compares only against prior
tomorrowsnapshots; - Daily Today no longer treats Tomorrow as a compatible prior unless the implementation explicitly keeps that relationship for Daily Today only.
- Tomorrow compares only against prior
- 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 acceptreports.daily_tomorrowunless 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:
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.ymlif it references Tomorrow modules orreports.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_tomorrowremain outside historical roadmap context.
Suggested validation:
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:
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:
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_tomorrowsymbols 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_tomorrowcompatibility aliases are intentionally not preserved.
Global Validation Checklist
go test ./...go run ./cmd/weatherreporter --helpgit diff --checkrg -n "DailyTomorrow|dailyTomorrow|daily_tomorrow" internal docs examples- Confirm
weatherreporter generate tomorrowuses structured JSON from Scriptorium and renders final Markdown locally. - Confirm distributor notification context uses
report_id=tomorrowandartifact_group=tomorrow. - Confirm examples contain no unimplemented fields and no secrets.