# Today Report Implementation Roadmap ## Purpose This roadmap defines the staged implementation plan for `docs/roadmap/today.md`. It is written for an LLM coding agent that will implement each stage in order. This is a planning document only. It may describe unimplemented behavior because it lives under `docs/roadmap/`. ## Source Roadmap `docs/roadmap/today.md` is the feature roadmap and target-state authority for the Today report. This implementation plan provides the concrete sequence for reaching that target. If this implementation plan and `today.md` conflict, update the feature roadmap first so it remains the conceptual source of truth, then update this file. ## Locked Decisions - `today` is a new independent generated-text-template report. - `today` does not replace the existing `daily_today` report in this roadmap. - `weatherreporter generate today` is a new public command. - `weatherreporter generate daily` remains the existing daily command and must not alias to Today. - `reports.today` is a new independent config override key. - `reports.daily` and `reports.daily_today` remain mapped to the existing daily report while that report exists. - Morning batch includes `today` in the position currently occupied by `daily_today`. - Morning batch should not include both `today` and `daily_today`. - Evening batch remains `tomorrow`. - New Today runtime identity uses report ID `today`, artifact group `today`, batch output `today.md`, template ID `today`, generated-text schema ID `today`, and prompt ID `weather.today_generated_text`. - Historical `daily_today` workspace artifacts are not migrated. - Today uses its own deterministic `today_planning` module. - Today does not reuse Tomorrow's prompt, template, schema, report ID, artifact group, or public planning stanza. - Scriptorium registration remains out of band. Weatherreporter invokes Scriptorium by prompt ID and passes the data package as it does for existing generated-text reports. ## Implementation Principles - Keep report identity and public name resolution in `internal/report`. - Keep CLI parsing in `internal/cli`; do not move domain policy into CLI. - Keep generated-text validation and render-context construction in `internal/generatedtext`. - Keep embedded schema/template assets in `internal/reporttemplate`. - Keep module IDs in `internal/module` and module builders in `internal/briefing`. - Keep the app orchestration path shared with other generated-text-template reports. - Do not introduce a new workflow engine, CLI framework, plugin system, or compatibility migration layer for old workspace artifacts. - Do not make `daily` and `today` compatibility aliases. - Preserve existing managed artifact layout conventions. ## Stage 1: Today ID Scaffold And Planning Module Goal: add the internal Today report ID scaffold and Today planning module without making Today public or changing batch behavior yet. This stage should compile and pass focused module/report tests while the existing active `daily_today` report remains unchanged. ### Implementation - In `internal/report/definition.go`, add `Today ID = "today"`. - Do not remove or alter `DailyToday` in this stage. - In `internal/module/module.go`, add: - `TodayPlanning ID = "today_planning"`; - `TodayPlanningOptions struct{}`. - Add `internal/briefing/today_planning_module.go`. - Add `TodayPlanningModule` with initial prompt-facing fields: - `morning_readiness`; - `commute_school_workday_concerns`; - `outdoor_planning`; - `late_day_change_watch`. - Add a private deterministic helper, such as `buildTodayPlanning`, near the existing planning summary helpers. - Reuse private helper functions from Tomorrow planning only when the underlying logic is identical. Do not expose or reuse `TomorrowPlanningModule` or the `tomorrow_planning` stanza. - Register `TodayPlanning` in `internal/briefing/modules.go` with: - stanza name `today_planning`; - supported reports `[]report.ID{report.Today}`; - required derived facts matching the daily-summary/daypart facts it uses; - `MissingDataEmpty` unless implementation discovers a stricter requirement is needed. - Add `report.Today` to module support lists where Today should be eligible: - all-report modules; - daypart modules; - narrative forecast; - hourly forecast; - derived daily summary; - derived daypart summaries. - Keep existing `report.DailyToday` support unchanged. ### Files To Inspect - `internal/report/definition.go` - `internal/module/module.go` - `internal/briefing/modules.go` - `internal/briefing/tomorrow_planning_module.go` - `internal/briefing/summary_helpers.go` - `internal/briefing/derived_modules_test.go` - `internal/briefing/modules_test.go` ### Acceptance Criteria - `module.TodayPlanning` is registered and buildable for `report.Today`. - `today_planning` is rejected for unsupported reports. - The new module emits deterministic snake_case JSON fields. - Existing Tomorrow planning behavior is unchanged. - Existing Daily Today behavior is unchanged. ### Tests Add or update tests for: - Today planning output shape. - Today planning supported report validation. - Unsupported report validation, including Tomorrow and Daily Today. - Empty/fallback behavior when useful planning facts are missing. Run: ```sh go test ./internal/briefing ./internal/module ./internal/report go test ./... git diff --check ``` Stage size: suitable for one implementation prompt. ## Stage 2: Today GeneratedText, Schema, Prompt, Template, And Render Context Goal: add Today generated-text assets and render-context support while keeping the report inactive until Stage 3. ### Implementation - Add `internal/generatedtext/today.go`. - Add `generatedtext.Today` with fields: - `Summary string`; - `ForecastDiscussion []string`; - `PrecipitationTiming string`; - `Confidence string`. - Add `ValidateToday` with Tomorrow-equivalent semantics: - strict JSON; - reject unknown fields; - reject trailing JSON values; - trim string fields; - trim and drop blank discussion paragraphs; - require nonblank `summary`; - require at least one nonblank `forecast_discussion` paragraph; - return canonical normalized JSON using the same public field names. - Add `internal/reporttemplate/schemas/today.generated_text.schema.json`. - Add `internal/reporttemplate/templates/today.md.tmpl`. - Add `internal/reporttemplate/prompts/today.generated_text.md`. - Add `TodayRenderContext`, `TodayReportContext`, `TodayTemplateModules`, and `BuildTodayRenderContext` in `internal/generatedtext`. - Today report context should include: - `Title` exactly `Today's Weather`; - `ForecastDate`; - `ForecastDateLabel`; - `ForecastDayName`; - `GeneratedAt`; - `GeneratedAtLabel`; - `ValidPeriod`; - `Timezone`. - Today template modules should include at least: - `Metadata`; - `CurrentConditions`; - `HourlyForecast`; - `DerivedDailySummary`; - `DerivedDaypartSummaries`; - ordered daypart rows; - `PrecipTiming`; - `AlertDigest`; - `SPCConvectiveOutlooks`; - `AreaForecastDiscussion`; - `SPCConvectiveDiscussion`; - `WeatherStory`; - `TodayPlanning`. - Share private render-context helper mechanics with Tomorrow where appropriate, such as snapshot lookup and daypart row ordering. - Do not expose Tomorrow-specific types through the Today context. - Register Today in the generated-text catalog with schema ID `today` and template ID `today`. - Add Today schema/template entries to `internal/reporttemplate` if explicit asset maps are still used. - The Today prompt asset should instruct the LLM to produce only the structured JSON fields expected by the Today schema, using the data package as the only weather source. ### Files To Inspect - `internal/generatedtext/tomorrow.go` - `internal/generatedtext/render_context.go` - `internal/generatedtext/catalog.go` - `internal/generatedtext/*_test.go` - `internal/reporttemplate/reporttemplate.go` - `internal/reporttemplate/templates/tomorrow.md.tmpl` - `internal/reporttemplate/schemas/tomorrow.generated_text.schema.json` - `internal/reporttemplate/prompts/tomorrow.generated_text.md` ### Acceptance Criteria - Today generated-text validation mirrors Tomorrow behavior. - Today schema accepts exactly the intended public fields. - Today template renders from structured module data and generated text. - Today render context uses Today-specific public types. - Today generated-text catalog lookup works once a report definition references schema/template ID `today`. - Existing Hourly and Tomorrow generated-text behavior is unchanged. ### Tests Add or update tests for: - `ValidateToday` success, missing required fields, unknown fields, trailing JSON, malformed JSON, blank strings, paragraph trimming, and canonical JSON. - Today JSON schema lookup. - Today template lookup. - Today render context labels, daypart ordering, populated modules, and nil optional modules. - Generated-text catalog completeness support for Today once the report definition is active. If the report is not active until Stage 3, add this assertion in Stage 3. Run: ```sh go test ./internal/generatedtext ./internal/reporttemplate ./internal/briefing go test ./... git diff --check ``` Stage size: suitable for one implementation prompt. ## Stage 3: Today Report Definition And Batch Integration Goal: add Today as an active report and switch morning batch to Today without aliasing or replacing the existing `daily` command/report. ### Implementation - Add a Today definition, preferably in a new `internal/report/today_report.go` file. - The Today definition must use: - `ID: report.Today`; - `Name: "Today Report"` or equivalent user-facing report name; - `PromptID: "weather.today_generated_text"`; - `GenerationMode: report.GenerationModeGeneratedTextTemplate`; - `TemplateID: "today"`; - `GeneratedTextSchemaID: "today"`; - `ComparisonStrategy: report.CompareSameValidDate`; - `ArtifactGroup: "today"`; - `BatchOutputName: "today.md"`; - `Generated: true`; - `CompatiblePriorIDs: []report.ID{report.Today}`; - `Morning: true`; - current-local-civil-day resolver. - Keep the existing Daily Today definition and report ID unchanged. - Today default modules must include, in this order unless implementation tests justify a more useful order: - `metadata`; - `current_conditions`; - `narrative_forecast`; - `derived_daily_summary`; - `derived_daypart_summaries`; - `precip_timing`; - `alert_digest`; - `spc_convective_outlooks`; - `area_forecast_discussion`; - `spc_convective_discussion`; - `weather_story`; - `outdoor_windows`; - `hourly_forecast`; - `today_planning`. - Update `internal/report/registry.go`: - default registry includes both Daily Today and Today; - `All()` includes both reports in a stable order, with Today near the other current-day report. - Update `internal/report/period.go`: - morning batch resolves Today first; - morning batch does not include Daily Today; - morning Sunday skip behavior for Weekend is unchanged; - evening batch remains Tomorrow. - Update report name helpers in `internal/report/names.go`: - add `CommandNameToday = "today"`; - `IDForCommandName("today") == report.Today`; - `IDForCommandName("daily")` remains the existing Daily Today report; - `CommandNames()` includes both `today` and `daily` as distinct commands. - Update config-key resolution: - `IDForConfigKey("today") == report.Today`; - `IDForConfigKey("daily")` remains the existing Daily Today report; - `IDForConfigKey("daily_today")` remains the existing Daily Today report. - Update structured Recent Changes dispatch so Today uses the daily comparison logic currently used by Daily Today and Tomorrow. - Update briefing/package variant logic so Today produces variant `today`. ### Files To Inspect - `internal/report/definition.go` - `internal/report/daily_report.go` - `internal/report/registry.go` - `internal/report/period.go` - `internal/report/names.go` - `internal/report/period_test.go` - `internal/briefing/modules.go` - `internal/briefing/package.go` - `internal/app/app.go` - `internal/config/reports.go` - `internal/config/config_test.go` - `internal/changes` ### Acceptance Criteria - `report.DefaultRegistry()` has active definitions for both `today` and the existing daily report. - Morning batch report IDs are `today,three_day,weekend` when Weekend is included. - Sunday morning batch report IDs are `today,three_day`. - `daily` and `today` command names resolve to different report IDs. - `reports.today` and `reports.daily` / `reports.daily_today` resolve to different report IDs. - Today report metadata, RunID, artifact group, batch output name, data-package path, report path, and distributor template values use `today`. - Existing manual Daily, Tomorrow, Hourly, Three-Day, Weekend, and Storm behavior is unchanged. ### Tests Add or update tests for: - report registry membership and `All()` order; - `IDForCommandName` for `today` and `daily`; - `CommandNames()` stable order; - `IDForConfigKey` for `today`, `daily`, and `daily_today`; - proof that Today and Daily resolve to different report IDs; - Today valid period with default current local date; - Today valid period with explicit `--date` / request date; - morning and Sunday morning batch order; - Recent Changes dispatch for Today; - module registry support for Today modules. Run: ```sh go test ./internal/report ./internal/config ./internal/briefing ./internal/changes go test ./internal/app ./internal/cli go test ./... go run ./cmd/weatherreporter --help git diff --check ``` Stage size: this is a broad but coherent report/batch integration stage. It is suitable for one implementation prompt if the agent keeps the scope to report identity and tests. ## Stage 4: App, CLI, Config, And Workflow Integration Goal: make `generate today`, batches, artifacts, and distributor notification work end-to-end through the existing app flow while keeping `generate daily` separate. ### Implementation - Add `app.ReportToday` if app keeps report-kind constants. - Keep `app.ReportDaily` mapped to the existing daily command/report. - Update CLI help text to show `generate today` as a distinct command. - Keep `generate daily` help text separate. - Parse `--date YYYY-MM-DD` for Today. - If omitted, use current local date in the configured timezone. - Keep existing `daily --date` behavior unchanged. - Ensure app generation resolves: - `today` to `report.Today`; - `daily` to the existing daily report. - Ensure Today generated-text-template flow persists: - raw generated text; - structured Scriptorium run result; - normalized generated text; - render context; - rendered Markdown report; - metadata; - optional output copy. - Ensure Today prompt invocation uses `weather.today_generated_text`. - Ensure Scriptorium output path for Today generated text is a JSON artifact, matching existing generated-text-template reports. - Ensure state and metadata inspection work for Today run IDs. - Ensure distributor notification uses the managed Today Markdown report path, not optional output copies. - Ensure distributor template variables resolve with: - `report_id=today`; - `artifact_group=today`; - `batch_output_name=today.md`; - valid-period variables based on the Today valid period. - Update fake Scriptorium scripts in app/CLI tests to return valid Today JSON when prompt ID is `weather.today_generated_text`. ### Files To Inspect - `internal/app/app.go` - `internal/app/app_test.go` - `internal/cli/root.go` - `internal/cli/root_test.go` - `internal/state` - `internal/adapters/scriptorium` - `internal/adapters/distributor` - `internal/config` - `examples/config.yml` ### Acceptance Criteria - `weatherreporter generate today` succeeds in tests through the generated-text template path. - `weatherreporter generate daily` still succeeds through the existing daily path and does not produce Today identity artifacts. - `weatherreporter generate today --date YYYY-MM-DD` resolves that local civil day. - Morning batch uses Today artifacts and `today.md` output copy names. - Notification debug artifacts record Today report ID, pipeline ID, bundle ID, and bundle paths. - Optional `--out` and batch `--out-dir` behavior remains unchanged except for the new Today output name in morning batch. - No secret values appear in CLI output, metadata, or notification artifacts. ### Tests Add or update tests for: - CLI parser support for `generate today`. - CLI parser proof that `generate daily` remains separate. - CLI `--date` support for Today. - App workflow artifacts for Today. - App output copy behavior for Today. - App workflow proof that Daily still produces existing daily identity. - Batch output directory using `today.md`. - Distributor request values for Today. - Inspect commands loading Today run metadata/modules/data package/sources. Run: ```sh go test ./internal/app ./internal/cli ./internal/state go test ./internal/adapters/scriptorium ./internal/adapters/distributor go test ./... go run ./cmd/weatherreporter --help git diff --check ``` Stage size: suitable for one implementation prompt. ## Stage 5: Documentation And Examples Goal: update implemented docs and maintained examples after the code exists. ### Implementation Update non-roadmap docs to describe implemented behavior only: - `docs/cli.md`: - add `generate today`; - keep `generate daily` documented as a separate command; - document `--date` for Today generation; - keep Daily docs accurate for the existing command. - `docs/config.md`: - document `reports.today`; - keep `reports.daily` / `reports.daily_today` documentation separate while the existing daily report remains implemented. - `docs/operations.md`: - update morning batch behavior; - document Today artifact paths; - update distributor identity examples if present. - `docs/templates.md`: - document Today template variables. - `docs/internal/report-registry.md`: - document Today report identity and its independence from Daily Today. - `docs/internal/generatedtext.md`: - document Today generated-text schema and validation. - `docs/internal/reporttemplate.md`: - document Today template/schema/prompt assets. - `docs/internal/module.md`: - document `today_planning`. - `docs/internal/app-orchestration.md`: - document Today only where the generic generated-text-template workflow does not already cover it. - `examples/config.yml`: - add Today module override examples only if useful; - keep existing daily examples accurate if they remain; - keep no raw secrets. Roadmap docs: - Keep future Daily rewrite work only under `docs/roadmap/`. - Do not document the future Daily rewrite outside roadmap docs. ### Acceptance Criteria - Non-roadmap docs describe Today as implemented and distinct from Daily. - Non-roadmap docs do not imply `today` and `daily` are aliases. - Maintained examples load successfully. - CLI examples match actual `--help` output and parser behavior. - Documentation follows `docs/policy/documentation.md`. ### Tests And Checks Run: ```sh go test ./internal/config ./internal/cli ./internal/app go test ./... go run ./cmd/weatherreporter --help git diff --check ``` Manual checks: ```sh rg -n "today.*alias|daily.*alias|daily_today.*retired|replace.*daily_today" README.md docs examples internal ``` Expected remaining matches should be limited to roadmap discussion that clearly states there is no aliasing in this feature. Stage size: suitable for one implementation prompt. ## Stage 6: Final Validation And Stale-Symbol Sweep Goal: confirm Today is fully implemented as a separate report and no accidental Daily/Today aliasing remains. ### Required Validation Run: ```sh go test ./... go run ./cmd/weatherreporter --help git diff --check ``` Run focused package checks: ```sh go test ./internal/report ./internal/generatedtext ./internal/reporttemplate go test ./internal/briefing ./internal/module go test ./internal/app ./internal/cli ./internal/config go test ./internal/state ./internal/promptinput ./internal/changes go test ./internal/adapters/distributor ./internal/adapters/scriptorium ./internal/adapters/weatherapi ``` Run stale/alias checks: ```sh rg -n "IDForCommandName\\(\"daily\"\\).*Today|IDForConfigKey\\(\"daily\"\\).*Today" internal rg -n "weather.today_generated_text|today.md|report.Today" internal docs examples ``` Manual review: - `generate today` is listed in help and docs. - `generate daily` remains listed separately. - `generate daily` does not resolve to report ID `today`. - `reports.daily` and `reports.today` are distinct config override keys. - Morning batch output includes `today.md`. - Today data package and metadata use report ID `today`. - Distributor request values use `today`. - Existing manual Daily behavior remains available. - Existing Tomorrow behavior is unchanged. Stage size: suitable for one implementation prompt. ## Deferred Work Do not include these in the Today implementation: - Rewriting the `daily` command/report in Today/Tomorrow style. - Removing or retiring the existing `daily_today` report. - Making `daily` an alias for `today`. - Making `today` an alias for `daily`. - Migrating historical `daily_today` workspace artifacts. - Adding an inspection compatibility layer that rewrites old metadata IDs. - Changing Tomorrow template/schema behavior except where shared helpers require tests to be adjusted. - Changing Distributor adapter behavior beyond Today template values. ## Open Questions No open questions block implementation. The future relationship between `daily`, `daily_today`, and `today` is explicitly deferred to the next Daily rewrite roadmap. For this implementation, the required behavior is that Today and Daily remain separate commands and separate report IDs.