442 lines
16 KiB
Markdown
442 lines
16 KiB
Markdown
# 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.
|