Files
weatherreporter/docs/roadmap/implementation.md

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_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:

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:
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:
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:

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:

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 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:

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:

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:

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:

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_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.