Update the today report template
This commit is contained in:
@@ -1,424 +0,0 @@
|
|||||||
# Today Report Roadmap
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This roadmap defines the target state and implementation work needed to add a
|
|
||||||
new independent generated-text-template `today` report.
|
|
||||||
|
|
||||||
The report is not implemented yet. Current report behavior remains documented
|
|
||||||
outside `docs/roadmap/`.
|
|
||||||
|
|
||||||
## Intent
|
|
||||||
|
|
||||||
Today should be an independent generated-text-template report focused on the
|
|
||||||
current local civil day. It should mirror the implemented Tomorrow Report
|
|
||||||
structure, but its valid period, identity, prompt, template, schema, workspace
|
|
||||||
paths, distributor identity, and deterministic planning module should use
|
|
||||||
`today`.
|
|
||||||
|
|
||||||
This is a clean, separate report addition:
|
|
||||||
|
|
||||||
- `today` becomes a new canonical current-day generated-text report ID.
|
|
||||||
- The existing `daily_today` report remains independent for now.
|
|
||||||
- The existing public `daily` command remains independent for now.
|
|
||||||
- `weatherreporter generate today` and `weatherreporter generate daily` must
|
|
||||||
not be aliases for each other.
|
|
||||||
- Morning batch should use `today`, not `daily_today`.
|
|
||||||
- New Today artifacts use the `today` artifact group and report ID.
|
|
||||||
- Existing historical `daily_today` workspace artifacts do not need migration.
|
|
||||||
|
|
||||||
The next planned feature will rewrite the `daily` command/report in the same
|
|
||||||
style as Today and Tomorrow. Do not pre-solve that future work in this roadmap.
|
|
||||||
|
|
||||||
## Locked Decisions
|
|
||||||
|
|
||||||
- `today` is independent of `daily_today`.
|
|
||||||
- `generate today` is independent of `generate daily`.
|
|
||||||
- No command compatibility aliases between `today` and `daily`.
|
|
||||||
- No config-key compatibility aliases between `reports.today` and
|
|
||||||
`reports.daily` / `reports.daily_today`.
|
|
||||||
- Morning batch includes `today`.
|
|
||||||
- Morning batch should not include `daily_today` once Today is implemented.
|
|
||||||
- `today` needs its own deterministic planning module analogous to
|
|
||||||
`TomorrowPlanning`.
|
|
||||||
- Add a new public `generate today` command.
|
|
||||||
- Do not reuse the Tomorrow prompt, template, schema, report ID, artifact group,
|
|
||||||
or planning stanza for Today.
|
|
||||||
- Do not attempt to migrate old workspace artifacts.
|
|
||||||
|
|
||||||
## Target Report Shape
|
|
||||||
|
|
||||||
Today should render from deterministic current-day facts plus structured prose
|
|
||||||
from Scriptorium:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Today's Weather
|
|
||||||
|
|
||||||
**Forecast date:** Friday, May 29, 2026
|
|
||||||
**Updated:** Friday, May 29, 2026 at 8:30 AM
|
|
||||||
|
|
||||||
<GeneratedText summary>
|
|
||||||
|
|
||||||
## Daypart Forecast
|
|
||||||
|
|
||||||
- **Morning:** <deterministic daypart line>
|
|
||||||
- **Midday:** <deterministic daypart line>
|
|
||||||
- **Afternoon:** <deterministic daypart line>
|
|
||||||
- **Evening:** <deterministic daypart line>
|
|
||||||
|
|
||||||
## Precipitation Timing
|
|
||||||
|
|
||||||
- **10:00 AM** to **1:00 PM**: Precipitation is expected during this period.
|
|
||||||
The peak precipitation chance is 70% at 11:00 AM.
|
|
||||||
- <optional GeneratedText precipitation_timing>
|
|
||||||
|
|
||||||
## Forecast Discussion
|
|
||||||
|
|
||||||
<GeneratedText forecast_discussion paragraphs>
|
|
||||||
```
|
|
||||||
|
|
||||||
The precipitation section should render only when precipitation windows exist
|
|
||||||
for the current-day valid period. The deterministic daypart forecast should
|
|
||||||
follow the same module-driven presentation approach used by Tomorrow.
|
|
||||||
|
|
||||||
The title should be stable and explicit: `Today's Weather`.
|
|
||||||
|
|
||||||
## Report Identity
|
|
||||||
|
|
||||||
Add a report definition with:
|
|
||||||
|
|
||||||
- report ID: `today`
|
|
||||||
- public generate command: `today`
|
|
||||||
- prompt ID: `weather.today_generated_text`
|
|
||||||
- generation mode: `generated_text_template`
|
|
||||||
- template ID: `today`
|
|
||||||
- generated-text schema ID: `today`
|
|
||||||
- artifact group: `today`
|
|
||||||
- batch output name: `today.md`
|
|
||||||
- prior compatibility: Today only
|
|
||||||
- comparison strategy: same valid local date
|
|
||||||
- valid period: current local civil day in the effective report timezone,
|
|
||||||
`[00:00, next 00:00)`
|
|
||||||
|
|
||||||
Keep the existing `daily_today` report definition independent unless the future
|
|
||||||
Daily rewrite roadmap removes or replaces it.
|
|
||||||
|
|
||||||
Command and config resolution must remain distinct:
|
|
||||||
|
|
||||||
- `generate today` resolves to report ID `today`.
|
|
||||||
- `generate daily` resolves to the existing daily report ID.
|
|
||||||
- `reports.today` config overrides apply only to report ID `today`.
|
|
||||||
- `reports.daily` and `reports.daily_today` config overrides continue to apply
|
|
||||||
only to the existing daily report while that report exists.
|
|
||||||
|
|
||||||
## Batch Behavior
|
|
||||||
|
|
||||||
Morning batch should include Today in the position currently occupied by Daily
|
|
||||||
Today.
|
|
||||||
|
|
||||||
Target morning order:
|
|
||||||
|
|
||||||
1. `today`
|
|
||||||
2. `three_day`
|
|
||||||
3. `weekend`, when existing weekend rules include it
|
|
||||||
|
|
||||||
Existing Sunday behavior should remain equivalent except with Today replacing
|
|
||||||
Daily Today in the batch: scheduled Sunday morning should include `today` and
|
|
||||||
`three_day`, and should skip `weekend`.
|
|
||||||
|
|
||||||
Evening batch behavior is unchanged and should continue to include `tomorrow`.
|
|
||||||
Hourly remains excluded from scheduled batches unless a later roadmap changes
|
|
||||||
that.
|
|
||||||
|
|
||||||
The existing `daily` command/report may remain available for manual generation,
|
|
||||||
but it is no longer the morning batch current-day product once Today is
|
|
||||||
implemented.
|
|
||||||
|
|
||||||
## GeneratedText Contract
|
|
||||||
|
|
||||||
Today should use the same structured prose shape as Tomorrow:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"summary": "string",
|
|
||||||
"forecast_discussion": ["string"],
|
|
||||||
"precipitation_timing": "string",
|
|
||||||
"confidence": "string"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Required:
|
|
||||||
|
|
||||||
- `summary`
|
|
||||||
- `forecast_discussion`
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
|
|
||||||
- `precipitation_timing`
|
|
||||||
- `confidence`
|
|
||||||
|
|
||||||
Validation should match Tomorrow semantics:
|
|
||||||
|
|
||||||
- reject malformed JSON and unknown fields
|
|
||||||
- reject trailing JSON values
|
|
||||||
- trim `summary`, `precipitation_timing`, and `confidence`
|
|
||||||
- trim each `forecast_discussion` paragraph
|
|
||||||
- drop blank discussion paragraphs
|
|
||||||
- require at least one nonblank discussion paragraph
|
|
||||||
- return canonical normalized JSON with the same public field names
|
|
||||||
|
|
||||||
Add a dedicated prompt asset for the source prompt text if prompt assets are
|
|
||||||
maintained locally:
|
|
||||||
|
|
||||||
- `internal/reporttemplate/prompts/today.generated_text.md`
|
|
||||||
|
|
||||||
Scriptorium registration remains out of band. Weatherreporter should invoke the
|
|
||||||
Today prompt by prompt ID and pass the data package as it does for other
|
|
||||||
generated-text reports.
|
|
||||||
|
|
||||||
## Template Context
|
|
||||||
|
|
||||||
Add dedicated Today types under `internal/generatedtext`, rather than reusing
|
|
||||||
Tomorrow types directly. The shape should mirror Tomorrow so template behavior
|
|
||||||
stays explicit and testable:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type TodayRenderContext struct {
|
|
||||||
Report TodayReportContext
|
|
||||||
GeneratedText Today
|
|
||||||
Modules TodayTemplateModules
|
|
||||||
Collected facts.CollectedFacts
|
|
||||||
Derived facts.DerivedFacts
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`TodayReportContext` should include:
|
|
||||||
|
|
||||||
- `Title`, exactly `Today's Weather`
|
|
||||||
- `ForecastDate`
|
|
||||||
- `ForecastDateLabel`, for example `Friday, May 29, 2026`
|
|
||||||
- `ForecastDayName`, for example `Friday`
|
|
||||||
- `GeneratedAt`
|
|
||||||
- `GeneratedAtLabel`
|
|
||||||
- `ValidPeriod`
|
|
||||||
- `Timezone`
|
|
||||||
|
|
||||||
`TodayTemplateModules` should expose the categories the Today template needs:
|
|
||||||
|
|
||||||
- `Metadata`
|
|
||||||
- `CurrentConditions`
|
|
||||||
- `HourlyForecast`
|
|
||||||
- `DerivedDailySummary`
|
|
||||||
- `DerivedDaypartSummaries`
|
|
||||||
- ordered daypart rows
|
|
||||||
- `PrecipTiming`
|
|
||||||
- `AlertDigest`
|
|
||||||
- `SPCConvectiveOutlooks`
|
|
||||||
- `AreaForecastDiscussion`
|
|
||||||
- `SPCConvectiveDiscussion`
|
|
||||||
- `WeatherStory`
|
|
||||||
- `TodayPlanning`
|
|
||||||
|
|
||||||
Today may share private helper functions with Tomorrow render-context
|
|
||||||
construction when the helper represents identical mechanics, such as snapshot
|
|
||||||
lookup or daypart row ordering. Do not expose Tomorrow-specific types through
|
|
||||||
the Today template context.
|
|
||||||
|
|
||||||
## Module Composition
|
|
||||||
|
|
||||||
The default module composition should mirror Tomorrow where the same facts are
|
|
||||||
useful for the current-day report, with a Today-specific planning module:
|
|
||||||
|
|
||||||
- `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`
|
|
||||||
|
|
||||||
Keep module outputs deterministic and prompt-facing. Do not add prose-only Go
|
|
||||||
fields unless the template needs a structured fact that cannot be expressed from
|
|
||||||
existing module data.
|
|
||||||
|
|
||||||
## Today Planning Module
|
|
||||||
|
|
||||||
Add a Today-specific deterministic planning module:
|
|
||||||
|
|
||||||
- module ID: `today_planning`
|
|
||||||
- stanza name: `today_planning`
|
|
||||||
- options type: `TodayPlanningOptions`
|
|
||||||
- output type: `TodayPlanningModule`
|
|
||||||
- supported report: `today`
|
|
||||||
|
|
||||||
The module should be analogous to `TomorrowPlanning`, but current-day oriented.
|
|
||||||
It should not reuse the public `TomorrowPlanningModule` type or
|
|
||||||
`tomorrow_planning` stanza.
|
|
||||||
|
|
||||||
Recommended initial fields:
|
|
||||||
|
|
||||||
- `morning_readiness`
|
|
||||||
- `commute_school_workday_concerns`
|
|
||||||
- `outdoor_planning`
|
|
||||||
- `late_day_change_watch`
|
|
||||||
|
|
||||||
The exact field names may be adjusted during implementation if tests and docs
|
|
||||||
make a better shape clear, but the module must remain deterministic and
|
|
||||||
current-day specific. Private helper functions may be shared with Tomorrow
|
|
||||||
planning when the underlying logic is truly identical.
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
|
|
||||||
1. Add Today report identity.
|
|
||||||
- Add `report.Today`.
|
|
||||||
- Keep the existing `report.DailyToday` active for now.
|
|
||||||
- Add command-name support for `today`.
|
|
||||||
- Do not change the `daily` command mapping in this roadmap.
|
|
||||||
- Add config-key support for `today`.
|
|
||||||
- Do not change `reports.daily` or `reports.daily_today` mapping in this
|
|
||||||
roadmap.
|
|
||||||
- Add a report definition with generated-text-template identity fields.
|
|
||||||
- Add current-local-civil-day valid-period resolution.
|
|
||||||
- Update report tests for ID, prompt ID, artifact group, command mapping,
|
|
||||||
config key mapping, batch order, and valid period.
|
|
||||||
|
|
||||||
2. Add Today planning module.
|
|
||||||
- Add `module.TodayPlanning`.
|
|
||||||
- Add `TodayPlanningOptions`.
|
|
||||||
- Add `TodayPlanningModule` and builder under `internal/briefing`.
|
|
||||||
- Register it in the default module registry for `report.Today`.
|
|
||||||
- Add tests for output shape, supported report, unsupported reports, and
|
|
||||||
empty/fallback behavior.
|
|
||||||
|
|
||||||
3. Add Today generated-text validation and schema.
|
|
||||||
- Add `generatedtext.Today`.
|
|
||||||
- Add `ValidateToday`.
|
|
||||||
- Add `today.generated_text.schema.json`.
|
|
||||||
- Add generated-text validation tests matching Tomorrow coverage.
|
|
||||||
|
|
||||||
4. Add Today template and render context.
|
|
||||||
- Add `today.md.tmpl`.
|
|
||||||
- Add `TodayRenderContext`, `TodayReportContext`, and
|
|
||||||
`TodayTemplateModules`.
|
|
||||||
- Add `BuildTodayRenderContext`.
|
|
||||||
- Use the internal snapshot lookup helper for module extraction.
|
|
||||||
- Add render-context and template tests for populated and omitted optional
|
|
||||||
modules.
|
|
||||||
|
|
||||||
5. Register Today in generated-text and embedded asset catalogs.
|
|
||||||
- Add a generated-text catalog entry.
|
|
||||||
- Add reporttemplate template/schema map entries if the current asset
|
|
||||||
lookup still uses explicit maps.
|
|
||||||
- Extend catalog completeness tests.
|
|
||||||
- Add unsupported schema/template combination coverage if needed.
|
|
||||||
|
|
||||||
6. Integrate app, CLI, config, batches, and distributor workflows.
|
|
||||||
- Ensure `weatherreporter generate today` resolves and runs through the
|
|
||||||
generated-text-template workflow.
|
|
||||||
- Ensure `weatherreporter generate daily` remains the existing daily
|
|
||||||
workflow and does not resolve to Today.
|
|
||||||
- Ensure morning batch uses `today` instead of `daily_today`.
|
|
||||||
- Persist raw generated text, structured run result, normalized generated
|
|
||||||
text, render context, Markdown report, metadata, and optional output copy.
|
|
||||||
- Ensure distributor notification uses the managed Today Markdown report
|
|
||||||
path and Today template values.
|
|
||||||
- Ensure report metadata, RunID, artifact paths, data-package paths, batch
|
|
||||||
output names, and distributor template variables use `today`.
|
|
||||||
|
|
||||||
7. Update implemented documentation after code changes.
|
|
||||||
- Update `docs/cli.md`, `docs/config.md`, `docs/operations.md`,
|
|
||||||
`docs/templates.md`, `docs/internal/report-registry.md`,
|
|
||||||
`docs/internal/generatedtext.md`, `docs/internal/reporttemplate.md`,
|
|
||||||
`docs/internal/module.md`, `docs/internal/app-orchestration.md`, and
|
|
||||||
maintained examples as needed.
|
|
||||||
- Non-roadmap docs must describe only the implemented Today behavior after
|
|
||||||
the code changes land.
|
|
||||||
- Keep `daily` and `today` documented as distinct commands/reports.
|
|
||||||
|
|
||||||
## Test Plan
|
|
||||||
|
|
||||||
Add or update tests for:
|
|
||||||
|
|
||||||
- report registry membership for both `today` and existing `daily_today`
|
|
||||||
- `today` command-name resolution to `today`
|
|
||||||
- `daily` command-name resolution remaining independent from `today`
|
|
||||||
- config-key resolution for `reports.today`
|
|
||||||
- config-key resolution for `reports.daily` and `reports.daily_today`
|
|
||||||
remaining independent from `reports.today`
|
|
||||||
- morning batch order with `today`, `three_day`, and conditional `weekend`
|
|
||||||
- Sunday morning batch skip behavior with `today`
|
|
||||||
- current-day valid period in the configured timezone
|
|
||||||
- generated-text unknown fields, trailing JSON, missing required fields, blank
|
|
||||||
fields, paragraph trimming, and canonical output
|
|
||||||
- generated-text catalog completeness
|
|
||||||
- template/schema asset lookup
|
|
||||||
- render context labels, module extraction, nil optional modules, Today
|
|
||||||
planning extraction, and daypart ordering
|
|
||||||
- Today planning module output and supported-report validation
|
|
||||||
- app workflow artifacts for `generate today`
|
|
||||||
- `generate daily` remains separate from `generate today`
|
|
||||||
- optional `--out` copy behavior
|
|
||||||
- distributor template values and managed report notification source
|
|
||||||
- CLI parser support for `generate today`
|
|
||||||
- config report module overrides for `reports.today`
|
|
||||||
|
|
||||||
Suggested validation:
|
|
||||||
|
|
||||||
```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 ./...
|
|
||||||
go run ./cmd/weatherreporter --help
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation Updates After Implementation
|
|
||||||
|
|
||||||
Update implemented docs only after the code exists:
|
|
||||||
|
|
||||||
- `docs/cli.md`: document `generate today` and keep `generate daily`
|
|
||||||
documented as a separate existing command.
|
|
||||||
- `docs/config.md`: document `reports.today` module overrides separately from
|
|
||||||
existing daily report overrides.
|
|
||||||
- `docs/operations.md`: document Today artifact paths, morning batch behavior,
|
|
||||||
and distributor upload identity.
|
|
||||||
- `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 behavior.
|
|
||||||
- `docs/internal/reporttemplate.md`: document Today template assets.
|
|
||||||
- `docs/internal/module.md`: document `today_planning`.
|
|
||||||
- `docs/internal/app-orchestration.md`: document Today workflow only if it
|
|
||||||
differs from the generic generated-text-template flow.
|
|
||||||
|
|
||||||
## Ambiguities Addressed
|
|
||||||
|
|
||||||
- Independence: `today` is separate from `daily_today`; it does not replace the
|
|
||||||
existing report definition in this roadmap.
|
|
||||||
- Command behavior: `generate today` and `generate daily` are not aliases.
|
|
||||||
- Config behavior: `reports.today` is not an alias for `reports.daily` or
|
|
||||||
`reports.daily_today`.
|
|
||||||
- Batch behavior: morning batch includes `today` instead of `daily_today`.
|
|
||||||
- Planning module: Today has its own deterministic planning module.
|
|
||||||
- Artifact identity: new Today artifacts and distributor values use `today`.
|
|
||||||
- Historical artifacts: old `daily_today` workspace files are not migrated.
|
|
||||||
|
|
||||||
## Open Decisions
|
|
||||||
|
|
||||||
No open decisions remain that block implementation.
|
|
||||||
|
|
||||||
Future decisions that should not be resolved in this roadmap:
|
|
||||||
|
|
||||||
- How the future rewritten `daily` command/report should relate to `today`.
|
|
||||||
- Whether the future Daily rewrite removes or retires `daily_today`.
|
|
||||||
- Whether old `daily_today` workspace artifacts should ever receive a migration
|
|
||||||
or inspection compatibility layer.
|
|
||||||
@@ -203,14 +203,12 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
|||||||
"# Today's Weather",
|
"# Today's Weather",
|
||||||
"**Forecast date:** Monday, June 15, 2026",
|
"**Forecast date:** Monday, June 15, 2026",
|
||||||
"Today starts quiet, then showers become more likely later in the day.",
|
"Today starts quiet, then showers become more likely later in the day.",
|
||||||
"Currently, it is 58°F and clear.",
|
"Currently, it is 58°F and clear. It feels like 57°F, with a relative humidity of 61% and winds from the northwest at 9 mph.",
|
||||||
"- **Morning:** Partly cloudy, with temperatures in the low 60s.",
|
"- **Morning:** Partly cloudy, with temperatures in the low 60s.",
|
||||||
"- **Afternoon:** Showers, with temperatures in the mid 70s. Chance of precipitation is 70%.",
|
"- **Afternoon:** Showers, with temperatures in the mid 70s. Chance of precipitation is 70%.",
|
||||||
"## Precipitation Timing",
|
"## Precipitation Timing",
|
||||||
"- **3:00 PM** to **6:00 PM**: Precipitation is expected during this period. The peak precipitation chance is 70% at 3:00 PM.",
|
"- **3:00 PM** to **6:00 PM**: Precipitation is expected during this period. The peak precipitation chance is 70% at 3:00 PM.",
|
||||||
"The most likely rain window is from midafternoon into early evening.",
|
"The most likely rain window is from midafternoon into early evening.",
|
||||||
"## Planning Notes",
|
|
||||||
"- Take sunglasses early.",
|
|
||||||
"Morning conditions should stay mostly dry.",
|
"Morning conditions should stay mostly dry.",
|
||||||
"Rain chances increase during the afternoon as deeper moisture arrives.",
|
"Rain chances increase during the afternoon as deeper moisture arrives.",
|
||||||
} {
|
} {
|
||||||
@@ -225,9 +223,11 @@ func TestBuildTodayRenderContext(t *testing.T) {
|
|||||||
"- **Morning:**",
|
"- **Morning:**",
|
||||||
"- **Afternoon:**",
|
"- **Afternoon:**",
|
||||||
"## Precipitation Timing",
|
"## Precipitation Timing",
|
||||||
"## Planning Notes",
|
|
||||||
"## Forecast Discussion",
|
"## Forecast Discussion",
|
||||||
})
|
})
|
||||||
|
if strings.Contains(text, "## Planning Notes") || strings.Contains(text, "- Take sunglasses early.") || strings.Contains(text, "Forecast details are limited") {
|
||||||
|
t.Fatalf("rendered template included removed Today template content:\n%s", text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildTodayRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
func TestBuildTodayRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
||||||
@@ -250,7 +250,7 @@ func TestBuildTodayRenderContextAllowsOmittedOptionalModules(t *testing.T) {
|
|||||||
t.Fatalf("Render() error = %v", err)
|
t.Fatalf("Render() error = %v", err)
|
||||||
}
|
}
|
||||||
text := string(rendered)
|
text := string(rendered)
|
||||||
for _, unwanted := range []string{"## Current Conditions", "## Precipitation Timing", "## Planning Notes"} {
|
for _, unwanted := range []string{"## Current Conditions", "## Precipitation Timing", "## Planning Notes", "Forecast details are limited"} {
|
||||||
if strings.Contains(text, unwanted) {
|
if strings.Contains(text, unwanted) {
|
||||||
t.Fatalf("rendered template included %q without module data:\n%s", unwanted, text)
|
t.Fatalf("rendered template included %q without module data:\n%s", unwanted, text)
|
||||||
}
|
}
|
||||||
@@ -493,8 +493,12 @@ func testTodaySnapshot(t *testing.T) module.Snapshot {
|
|||||||
ID: module.CurrentConditions,
|
ID: module.CurrentConditions,
|
||||||
StanzaName: string(module.CurrentConditions),
|
StanzaName: string(module.CurrentConditions),
|
||||||
Value: briefing.CurrentConditionsModule{
|
Value: briefing.CurrentConditionsModule{
|
||||||
ConditionTextLower: "clear",
|
ConditionTextLower: "clear",
|
||||||
TemperatureF: intPtr(58),
|
TemperatureF: intPtr(58),
|
||||||
|
ApparentTemperatureF: intPtr(57),
|
||||||
|
RelativeHumidityPercent: intPtr(61),
|
||||||
|
WindSpeedMph: intPtr(9),
|
||||||
|
WindDirectionText: "northwest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,11 +35,14 @@ func TestTodayTemplateLookup(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Template() error = %v", err)
|
t.Fatalf("Template() error = %v", err)
|
||||||
}
|
}
|
||||||
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", "## Current Conditions", "## Daypart Forecast", "## Planning Notes", "## Forecast Discussion"} {
|
for _, want := range []string{"# {{ .Report.Title }}", "**Forecast date:**", "## Current Conditions", "## Daypart Forecast", "## Forecast Discussion"} {
|
||||||
if !strings.Contains(source, want) {
|
if !strings.Contains(source, want) {
|
||||||
t.Fatalf("template missing %q:\n%s", want, source)
|
t.Fatalf("template missing %q:\n%s", want, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.Contains(source, "## Planning Notes") {
|
||||||
|
t.Fatalf("template includes Planning Notes section:\n%s", source)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchemaLookup(t *testing.T) {
|
func TestSchemaLookup(t *testing.T) {
|
||||||
@@ -365,8 +368,12 @@ func TestRenderToday(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Modules: testTodayModules{
|
Modules: testTodayModules{
|
||||||
CurrentConditions: &testCurrentConditions{
|
CurrentConditions: &testCurrentConditions{
|
||||||
ConditionTextLower: "clear",
|
ConditionTextLower: "clear",
|
||||||
TemperatureF: intPtr(58),
|
TemperatureF: intPtr(58),
|
||||||
|
ApparentTemperatureF: intPtr(57),
|
||||||
|
RelativeHumidityPercent: intPtr(61),
|
||||||
|
WindDirectionText: "northwest",
|
||||||
|
WindSpeedMph: intPtr(9),
|
||||||
},
|
},
|
||||||
Dayparts: []testTomorrowDaypart{
|
Dayparts: []testTomorrowDaypart{
|
||||||
{
|
{
|
||||||
@@ -393,6 +400,12 @@ func TestRenderToday(t *testing.T) {
|
|||||||
MentionPrecipitation: true,
|
MentionPrecipitation: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Key: "evening",
|
||||||
|
Summary: testDaypartSummary{
|
||||||
|
DisplayName: "Evening",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
PrecipTiming: &testPrecipTiming{
|
PrecipTiming: &testPrecipTiming{
|
||||||
PrecipitationWindows: []testPrecipWindow{
|
PrecipitationWindows: []testPrecipWindow{
|
||||||
@@ -414,14 +427,11 @@ func TestRenderToday(t *testing.T) {
|
|||||||
"**Forecast date:** Monday, June 15, 2026",
|
"**Forecast date:** Monday, June 15, 2026",
|
||||||
"**Updated:** Monday, June 15, 2026 at 7:14 AM",
|
"**Updated:** Monday, June 15, 2026 at 7:14 AM",
|
||||||
"Today starts dry before showers return later in the day.",
|
"Today starts dry before showers return later in the day.",
|
||||||
"Currently, it is 58°F and clear.",
|
"Currently, it is 58°F and clear. It feels like 57°F, with a relative humidity of 61% and winds from the northwest at 9 mph.",
|
||||||
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
"- **Morning:** Sunny, with temperatures rising from the upper 50s to the upper 60s.",
|
||||||
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
"- **Afternoon:** Showers, with temperatures in the upper 70s. Chance of precipitation is 70%.",
|
||||||
"- **3:00 PM** to **6:00 PM**: Precipitation is expected during this period. The peak precipitation chance is 70% at 3:00 PM.",
|
"- **3:00 PM** to **6:00 PM**: Precipitation is expected during this period. The peak precipitation chance is 70% at 3:00 PM.",
|
||||||
"A few showers may linger into early evening.",
|
"A few showers may linger into early evening.",
|
||||||
"## Planning Notes",
|
|
||||||
"- Morning weather looks routine.",
|
|
||||||
"- Watch late-day shower timing.",
|
|
||||||
"Clouds increase after sunrise.",
|
"Clouds increase after sunrise.",
|
||||||
"Rain chances peak during the afternoon.",
|
"Rain chances peak during the afternoon.",
|
||||||
} {
|
} {
|
||||||
@@ -436,9 +446,13 @@ func TestRenderToday(t *testing.T) {
|
|||||||
"- **Morning:**",
|
"- **Morning:**",
|
||||||
"- **Afternoon:**",
|
"- **Afternoon:**",
|
||||||
"## Precipitation Timing",
|
"## Precipitation Timing",
|
||||||
"## Planning Notes",
|
|
||||||
"## Forecast Discussion",
|
"## Forecast Discussion",
|
||||||
})
|
})
|
||||||
|
for _, unwanted := range []string{"## Planning Notes", "Morning weather looks routine.", "Watch late-day shower timing.", "- **Evening:**", "Forecast details are limited"} {
|
||||||
|
if strings.Contains(text, unwanted) {
|
||||||
|
t.Fatalf("rendered template included %q:\n%s", unwanted, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderTomorrowOmitsPrecipitationTimingWithoutWindows(t *testing.T) {
|
func TestRenderTomorrowOmitsPrecipitationTimingWithoutWindows(t *testing.T) {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
|
|
||||||
{{ .GeneratedText.Summary }}
|
{{ .GeneratedText.Summary }}
|
||||||
|
|
||||||
{{ with .Modules.CurrentConditions }}{{ $current := . }}## Current Conditions
|
{{ with .Modules.CurrentConditions }}
|
||||||
Currently, {{ with .TemperatureF }}it is {{ . }}°F{{ with $current.ConditionTextLower }} and {{ . }}{{ end }}{{ else }}{{ with .ConditionTextLower }}conditions are {{ . }}{{ else }}latest current conditions are available{{ end }}{{ end }}.
|
## Current Conditions
|
||||||
|
{{ with .TemperatureF }}Currently, it is {{ . }}°F{{ with $.Modules.CurrentConditions.ConditionTextLower }} and {{ . }}{{ end }}.{{ else }}{{ with .ConditionText }}Currently, it is {{ . }}.{{ else }}Current conditions are unavailable.{{ end }}{{ end }}{{ with .ApparentTemperatureF }} It feels like {{ . }}°F{{ with $.Modules.CurrentConditions.RelativeHumidityPercent }}, with a relative humidity of {{ . }}%{{ end }}{{ with $.Modules.CurrentConditions.WindDirectionText }} and winds from the {{ . }}{{ with $.Modules.CurrentConditions.WindSpeedMph }} at {{ . }} mph{{ end }}{{ end }}.{{ else }}{{ with .RelativeHumidityPercent }} Relative humidity is {{ . }}%.{{ end }}{{ with .WindDirectionText }} Winds are from the {{ . }}{{ with $.Modules.CurrentConditions.WindSpeedMph }} at {{ . }} mph{{ end }}.{{ end }}{{ end }}
|
||||||
|
|
||||||
{{ end }}## Daypart Forecast
|
{{ end }}
|
||||||
{{ with .Modules.Dayparts }}{{ range . }}{{ $daypart := . }}- **{{ if .Summary.DisplayName }}{{ .Summary.DisplayName }}{{ else }}{{ .Key }}{{ end }}:** {{ with .Summary.DominantConditionDisplay }}{{ . }}{{ else }}{{ with .Summary.DominantCondition }}{{ . }}{{ else }}Forecast details are limited{{ end }}{{ end }}{{ if eq .Summary.TemperatureTrend "rising" }}{{ with .Summary.TemperatureStartPhraseF }}, with temperatures rising from the {{ . }}{{ with $daypart.Summary.TemperatureEndPhraseF }} to the {{ . }}{{ end }}{{ end }}{{ else if eq .Summary.TemperatureTrend "falling" }}{{ with .Summary.TemperatureStartPhraseF }}, with temperatures falling from the {{ . }}{{ with $daypart.Summary.TemperatureEndPhraseF }} to the {{ . }}{{ end }}{{ end }}{{ else if eq .Summary.TemperatureTrend "peaking" }}{{ with .Summary.TemperaturePeakPhraseF }}, with temperatures peaking in the {{ . }}{{ end }}{{ else }}{{ with .Summary.TemperatureSteadyPhraseF }}, with temperatures in the {{ . }}{{ else }}{{ with .Summary.TemperaturePhraseF }}, with temperatures in the {{ . }}{{ end }}{{ end }}{{ end }}.{{ if .Summary.MentionPrecipitation }}{{ with .Summary.MaxPopPercent }} Chance of precipitation is {{ . }}%.{{ end }}{{ end }}
|
## Daypart Forecast
|
||||||
{{ end }}{{ else }}- No daypart forecast details are available.
|
{{ with .Modules.Dayparts }}{{ range . }}{{ $daypart := . }}{{ if or .Summary.DominantConditionDisplay .Summary.DominantCondition }}- **{{ if .Summary.DisplayName }}{{ .Summary.DisplayName }}{{ else }}{{ .Key }}{{ end }}:** {{ with .Summary.DominantConditionDisplay }}{{ . }}{{ else }}{{ with .Summary.DominantCondition }}{{ . }}{{ end }}{{ end }}{{ if eq .Summary.TemperatureTrend "rising" }}{{ with .Summary.TemperatureStartPhraseF }}, with temperatures rising from the {{ . }}{{ with $daypart.Summary.TemperatureEndPhraseF }} to the {{ . }}{{ end }}{{ end }}{{ else if eq .Summary.TemperatureTrend "falling" }}{{ with .Summary.TemperatureStartPhraseF }}, with temperatures falling from the {{ . }}{{ with $daypart.Summary.TemperatureEndPhraseF }} to the {{ . }}{{ end }}{{ end }}{{ else if eq .Summary.TemperatureTrend "peaking" }}{{ with .Summary.TemperaturePeakPhraseF }}, with temperatures peaking in the {{ . }}{{ end }}{{ else }}{{ with .Summary.TemperatureSteadyPhraseF }}, with temperatures in the {{ . }}{{ else }}{{ with .Summary.TemperaturePhraseF }}, with temperatures in the {{ . }}{{ end }}{{ end }}{{ end }}.{{ if .Summary.MentionPrecipitation }}{{ with .Summary.MaxPopPercent }} Chance of precipitation is {{ . }}%.{{ end }}{{ end }}
|
||||||
|
{{ end }}{{ end }}{{ else }}- No daypart forecast details are available.
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ with .Modules.PrecipTiming }}{{ with .PrecipitationWindows }}
|
{{ with .Modules.PrecipTiming }}{{ with .PrecipitationWindows }}
|
||||||
@@ -20,13 +22,7 @@ Currently, {{ with .TemperatureF }}it is {{ . }}°F{{ with $current.ConditionTex
|
|||||||
{{ end }}{{ with $.GeneratedText.PrecipitationTiming }}
|
{{ end }}{{ with $.GeneratedText.PrecipitationTiming }}
|
||||||
{{ . }}
|
{{ . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}{{ end }}{{ with .Modules.TodayPlanning }}{{ if or .MorningReadiness .CommuteSchoolWorkdayConcerns .OutdoorPlanning .LateDayChangeWatch }}
|
{{ end }}{{ end }}
|
||||||
## Planning Notes
|
|
||||||
{{ with .MorningReadiness }}{{ range . }}- {{ . }}
|
|
||||||
{{ end }}{{ end }}{{ with .CommuteSchoolWorkdayConcerns }}{{ range . }}- {{ . }}
|
|
||||||
{{ end }}{{ end }}{{ with .OutdoorPlanning }}{{ range . }}- {{ . }}
|
|
||||||
{{ end }}{{ end }}{{ with .LateDayChangeWatch }}{{ range . }}- {{ . }}
|
|
||||||
{{ end }}{{ end }}{{ end }}{{ end }}
|
|
||||||
## Forecast Discussion
|
## Forecast Discussion
|
||||||
{{ range .GeneratedText.ForecastDiscussion }}
|
{{ range .GeneratedText.ForecastDiscussion }}
|
||||||
{{ . }}
|
{{ . }}
|
||||||
|
|||||||
Reference in New Issue
Block a user