Split Tomorrow report identity
This commit is contained in:
@@ -47,7 +47,7 @@ embedded hourly template.
|
|||||||
`--end`.
|
`--end`.
|
||||||
|
|
||||||
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
|
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
|
||||||
except on Sunday. `run evening` generates the Tomorrow Planning Brief. Batch
|
except on Sunday. `run evening` generates the Tomorrow Report. Batch
|
||||||
runs continue independent reports after a failure, print a JSON summary to
|
runs continue independent reports after a failure, print a JSON summary to
|
||||||
stdout, write compact status lines to stderr, and return nonzero when any report
|
stdout, write compact status lines to stderr, and return nonzero when any report
|
||||||
failed. `--out-dir` writes extra Markdown copies for the operator; distributor
|
failed. `--out-dir` writes extra Markdown copies for the operator; distributor
|
||||||
|
|||||||
@@ -182,8 +182,8 @@ snapshot exists and a threshold is crossed.
|
|||||||
report definitions. Omit a report entry to use its default module order.
|
report definitions. Omit a report entry to use its default module order.
|
||||||
|
|
||||||
Supported report keys are `daily`, `tomorrow`, `hourly`, `three_day`,
|
Supported report keys are `daily`, `tomorrow`, `hourly`, `three_day`,
|
||||||
`weekend`, and `storm`. Canonical report IDs such as `daily_today` and
|
`weekend`, and `storm`. Canonical report IDs such as `daily_today` are also
|
||||||
`daily_tomorrow` are also accepted.
|
accepted.
|
||||||
|
|
||||||
Each report entry supports:
|
Each report entry supports:
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ failure context.
|
|||||||
## Batch Workflow
|
## Batch Workflow
|
||||||
|
|
||||||
`run morning` resolves Daily Today, 3-Day Outlook, and Weekend Outlook except
|
`run morning` resolves Daily Today, 3-Day Outlook, and Weekend Outlook except
|
||||||
on Sunday. `run evening` resolves Daily Tomorrow. Batch output copy names come
|
on Sunday. `run evening` resolves Tomorrow Report. Batch output copy names come
|
||||||
from report definitions. Batch generation continues independent reports after a
|
from report definitions. Batch generation continues independent reports after a
|
||||||
failure, records each result, writes compact status lines to stderr, emits a
|
failure, records each result, writes compact status lines to stderr, emits a
|
||||||
JSON summary to stdout, and returns an aggregate error when any report failed.
|
JSON summary to stdout, and returns an aggregate error when any report failed.
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ the embedded hourly template.
|
|||||||
|
|
||||||
| Report | ID | Prompt | Generation mode | Artifact group | Batch copy | Prior compatibility |
|
| Report | ID | Prompt | Generation mode | Artifact group | Batch copy | Prior compatibility |
|
||||||
| --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
| Daily Today | `daily_today` | `weather.daily_report` | `scriptorium_markdown` | `daily` | `daily.md` | Daily Today, Daily Tomorrow |
|
| Daily Today | `daily_today` | `weather.daily_report` | `scriptorium_markdown` | `daily` | `daily.md` | Daily Today |
|
||||||
| Daily Tomorrow | `daily_tomorrow` | `weather.daily_report` | `scriptorium_markdown` | `daily` | `tomorrow.md` | Daily Today, Daily Tomorrow |
|
| Tomorrow Report | `tomorrow` | `weather.daily_report` | `scriptorium_markdown` | `tomorrow` | `tomorrow.md` | Tomorrow Report |
|
||||||
| Hourly Report | `hourly` | `weather.hourly_generated_text` | `generated_text_template` | `hourly` | `hourly.md` | Hourly Report |
|
| Hourly Report | `hourly` | `weather.hourly_generated_text` | `generated_text_template` | `hourly` | `hourly.md` | Hourly Report |
|
||||||
| 3-Day Outlook | `three_day` | `weather.three_day_outlook` | `scriptorium_markdown` | `three-day` | `three-day.md` | 3-Day Outlook |
|
| 3-Day Outlook | `three_day` | `weather.three_day_outlook` | `scriptorium_markdown` | `three-day` | `three-day.md` | 3-Day Outlook |
|
||||||
| Weekend Outlook | `weekend` | `weather.weekend_outlook` | `scriptorium_markdown` | `weekend` | `weekend.md` | Weekend Outlook |
|
| Weekend Outlook | `weekend` | `weather.weekend_outlook` | `scriptorium_markdown` | `weekend` | `weekend.md` | Weekend Outlook |
|
||||||
@@ -49,7 +49,7 @@ All report definitions are eligible for generation.
|
|||||||
|
|
||||||
- Daily Today covers the selected local civil day, or the current local civil
|
- Daily Today covers the selected local civil day, or the current local civil
|
||||||
day when no date override is supplied.
|
day when no date override is supplied.
|
||||||
- Daily Tomorrow covers the next local civil day from generation time.
|
- Tomorrow Report covers the next local civil day from generation time.
|
||||||
- Hourly Report covers the half-open six-hour period from generation time in
|
- Hourly Report covers the half-open six-hour period from generation time in
|
||||||
the effective report timezone. The duration is an internal report constant,
|
the effective report timezone. The duration is an internal report constant,
|
||||||
not a configuration field.
|
not a configuration field.
|
||||||
@@ -81,7 +81,7 @@ module overrides can use the `hourly` key for Hourly Report.
|
|||||||
## Batch Membership
|
## Batch Membership
|
||||||
|
|
||||||
Morning batches include Daily Today, 3-Day Outlook, and Weekend Outlook except
|
Morning batches include Daily Today, 3-Day Outlook, and Weekend Outlook except
|
||||||
on Sunday. Evening batches include Daily Tomorrow. Hourly Report is not part
|
on Sunday. Evening batches include Tomorrow Report. Hourly Report is not part
|
||||||
of a scheduled batch.
|
of a scheduled batch.
|
||||||
|
|
||||||
## State And App Usage
|
## State And App Usage
|
||||||
@@ -110,7 +110,7 @@ Inspect:
|
|||||||
## Invariants
|
## Invariants
|
||||||
|
|
||||||
- Report selection goes through the registry.
|
- Report selection goes through the registry.
|
||||||
- Daily Today and Daily Tomorrow both use `weather.daily_report`.
|
- Daily Today and Tomorrow Report both use `weather.daily_report`.
|
||||||
- Valid periods are half-open intervals independent of rendered report text.
|
- Valid periods are half-open intervals independent of rendered report text.
|
||||||
- Artifact grouping, batch output filenames, generated-report eligibility,
|
- Artifact grouping, batch output filenames, generated-report eligibility,
|
||||||
default module composition, comparison compatibility, and comparison strategy
|
default module composition, comparison compatibility, and comparison strategy
|
||||||
|
|||||||
@@ -91,7 +91,9 @@ Prior snapshot lookup reads stored metadata through the shared lookup path and
|
|||||||
selects the latest earlier snapshot whose report ID is compatible with the
|
selects the latest earlier snapshot whose report ID is compatible with the
|
||||||
current report definition.
|
current report definition.
|
||||||
|
|
||||||
- Daily Today and Daily Tomorrow are compatible with each other for the same
|
- Daily Today compares with prior Daily Today snapshots for the same valid
|
||||||
|
local date.
|
||||||
|
- Tomorrow Report compares with prior Tomorrow Report snapshots for the same
|
||||||
valid local date.
|
valid local date.
|
||||||
- 3-Day Outlook compares with prior 3-Day snapshots for the same valid local
|
- 3-Day Outlook compares with prior 3-Day snapshots for the same valid local
|
||||||
date.
|
date.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ weatherreporter run evening
|
|||||||
```
|
```
|
||||||
|
|
||||||
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
|
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
|
||||||
except on Sunday. `run evening` generates the Tomorrow Planning Brief. Batch
|
except on Sunday. `run evening` generates the Tomorrow Report. Batch
|
||||||
commands print a JSON summary to stdout, write compact per-report status lines
|
commands print a JSON summary to stdout, write compact per-report status lines
|
||||||
to stderr, continue independent reports after one report fails, and return
|
to stderr, continue independent reports after one report fails, and return
|
||||||
nonzero when any report failed. When notification is configured, the summary and
|
nonzero when any report failed. When notification is configured, the summary and
|
||||||
@@ -246,7 +246,8 @@ source provenance and warnings without dumping full weather payloads.
|
|||||||
Recent Changes are computed from structured module snapshots, not rendered
|
Recent Changes are computed from structured module snapshots, not rendered
|
||||||
Markdown or YAML text.
|
Markdown or YAML text.
|
||||||
|
|
||||||
Daily Today and Daily Tomorrow can compare with each other when they cover the
|
Daily Today compares with prior Daily Today snapshots for the same valid local
|
||||||
|
date. Tomorrow Report compares with prior Tomorrow Report snapshots for the
|
||||||
same valid local date. 3-Day Outlook compares with prior compatible 3-Day
|
same valid local date. 3-Day Outlook compares with prior compatible 3-Day
|
||||||
snapshots for the same valid local date. Weekend Outlook compares with prior
|
snapshots for the same valid local date. Weekend Outlook compares with prior
|
||||||
compatible Weekend snapshots for the same weekend window. Hourly Report and
|
compatible Weekend snapshots for the same weekend window. Hourly Report and
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ func reportIDForCommand(kind ReportKind) (report.ID, error) {
|
|||||||
case ReportDaily:
|
case ReportDaily:
|
||||||
return report.DailyToday, nil
|
return report.DailyToday, nil
|
||||||
case ReportTomorrow:
|
case ReportTomorrow:
|
||||||
return report.DailyTomorrow, nil
|
return report.Tomorrow, nil
|
||||||
case ReportHourly:
|
case ReportHourly:
|
||||||
return report.Hourly, nil
|
return report.Hourly, nil
|
||||||
case ReportThreeDay:
|
case ReportThreeDay:
|
||||||
@@ -1085,7 +1085,7 @@ func recentChanges(ctx context.Context, store state.Store, priorSnapshot *state.
|
|||||||
PrecipTimingShiftMinutes: cfg.PrecipTimingShiftMinutes,
|
PrecipTimingShiftMinutes: cfg.PrecipTimingShiftMinutes,
|
||||||
}
|
}
|
||||||
switch reportID {
|
switch reportID {
|
||||||
case report.DailyToday, report.DailyTomorrow:
|
case report.DailyToday, report.Tomorrow:
|
||||||
return changes.CompareDaily(previous, current, thresholds)
|
return changes.CompareDaily(previous, current, thresholds)
|
||||||
case report.ThreeDay:
|
case report.ThreeDay:
|
||||||
return changes.CompareThreeDay(previous, current, thresholds)
|
return changes.CompareThreeDay(previous, current, thresholds)
|
||||||
|
|||||||
@@ -1240,7 +1240,7 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
|
|||||||
t.Fatalf("GenerateReport() error = %v", err)
|
t.Fatalf("GenerateReport() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Metadata.ReportID != report.DailyTomorrow || result.Metadata.Variant != "tomorrow" {
|
if result.Metadata.ReportID != report.Tomorrow || result.Metadata.Variant != "tomorrow" {
|
||||||
t.Fatalf("metadata report/variant = %q/%q, want tomorrow", result.Metadata.ReportID, result.Metadata.Variant)
|
t.Fatalf("metadata report/variant = %q/%q, want tomorrow", result.Metadata.ReportID, result.Metadata.Variant)
|
||||||
}
|
}
|
||||||
dailySummary, ok, err := module.StanzaValue[map[string]any](result.ModuleSnapshot, "derived_daily_summary")
|
dailySummary, ok, err := module.StanzaValue[map[string]any](result.ModuleSnapshot, "derived_daily_summary")
|
||||||
@@ -1253,12 +1253,12 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
|
|||||||
if _, ok := result.ModuleSnapshot.LookupStanza("tomorrow_planning"); !ok {
|
if _, ok := result.ModuleSnapshot.LookupStanza("tomorrow_planning"); !ok {
|
||||||
t.Fatal("tomorrow_planning stanza missing")
|
t.Fatal("tomorrow_planning stanza missing")
|
||||||
}
|
}
|
||||||
if !strings.Contains(filepath.Base(result.ReportPath), "daily_tomorrow") {
|
if !strings.Contains(filepath.Base(result.ReportPath), "tomorrow") {
|
||||||
t.Fatalf("ReportPath = %q, want managed tomorrow report path", result.ReportPath)
|
t.Fatalf("ReportPath = %q, want managed tomorrow report path", result.ReportPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTomorrowReportCanCompareAgainstPriorDailySnapshot(t *testing.T) {
|
func TestTomorrowReportCanCompareAgainstPriorTomorrowSnapshot(t *testing.T) {
|
||||||
server := dailyBundleServer(t)
|
server := dailyBundleServer(t)
|
||||||
cfg := config.Defaults()
|
cfg := config.Defaults()
|
||||||
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
||||||
@@ -1270,8 +1270,7 @@ func TestTomorrowReportCanCompareAgainstPriorDailySnapshot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
priorResolved, err := ResolveGenerate(GenerateRequest{
|
priorResolved, err := ResolveGenerate(GenerateRequest{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
Report: ReportDaily,
|
Report: ReportTomorrow,
|
||||||
Date: mustParse("2026-05-30T12:00:00-05:00"),
|
|
||||||
}, mustParse("2026-05-29T17:00:00-05:00"))
|
}, mustParse("2026-05-29T17:00:00-05:00"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ResolveGenerate(prior) error = %v", err)
|
t.Fatalf("ResolveGenerate(prior) error = %v", err)
|
||||||
@@ -1301,10 +1300,10 @@ func TestTomorrowReportCanCompareAgainstPriorDailySnapshot(t *testing.T) {
|
|||||||
t.Fatalf("GenerateReport() error = %v", err)
|
t.Fatalf("GenerateReport() error = %v", err)
|
||||||
}
|
}
|
||||||
if result.PriorSnapshot == nil {
|
if result.PriorSnapshot == nil {
|
||||||
t.Fatal("PriorSnapshot = nil, want compatible prior daily snapshot")
|
t.Fatal("PriorSnapshot = nil, want compatible prior tomorrow snapshot")
|
||||||
}
|
}
|
||||||
if len(result.RecentChanges) == 0 {
|
if len(result.RecentChanges) == 0 {
|
||||||
t.Fatal("RecentChanges length = 0, want changes from compatible prior daily snapshot")
|
t.Fatal("RecentChanges length = 0, want changes from compatible prior tomorrow snapshot")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1622,7 +1621,7 @@ func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "tomorrow",
|
name: "tomorrow",
|
||||||
kind: ReportTomorrow,
|
kind: ReportTomorrow,
|
||||||
wantID: report.DailyTomorrow,
|
wantID: report.Tomorrow,
|
||||||
wantPrompt: "weather.daily_report",
|
wantPrompt: "weather.daily_report",
|
||||||
wantStart: "2026-05-30T00:00:00-05:00",
|
wantStart: "2026-05-30T00:00:00-05:00",
|
||||||
wantEnd: "2026-05-31T00:00:00-05:00",
|
wantEnd: "2026-05-31T00:00:00-05:00",
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func TestDerivedDaypartSummariesExposeConfiguredKeysAndHazards(t *testing.T) {
|
|||||||
|
|
||||||
func TestOutdoorWindowsAndTomorrowPlanningModulesPreserveDailyContent(t *testing.T) {
|
func TestOutdoorWindowsAndTomorrowPlanningModulesPreserveDailyContent(t *testing.T) {
|
||||||
registry := MustDefaultModuleRegistry()
|
registry := MustDefaultModuleRegistry()
|
||||||
ctx := derivedModuleContext(report.DailyTomorrow)
|
ctx := derivedModuleContext(report.Tomorrow)
|
||||||
|
|
||||||
outdoorOutput, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.OutdoorWindows})
|
outdoorOutput, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.OutdoorWindows})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -249,8 +249,8 @@ func (d ModuleDefinition) ValidateOptions(options any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultModuleDefinitions() []ModuleDefinition {
|
func defaultModuleDefinitions() []ModuleDefinition {
|
||||||
allReports := []report.ID{report.DailyToday, report.DailyTomorrow, report.Hourly, report.ThreeDay, report.Weekend, report.Storm}
|
allReports := []report.ID{report.DailyToday, report.Tomorrow, report.Hourly, report.ThreeDay, report.Weekend, report.Storm}
|
||||||
daypartReports := []report.ID{report.DailyToday, report.DailyTomorrow, report.ThreeDay, report.Weekend}
|
daypartReports := []report.ID{report.DailyToday, report.Tomorrow, report.ThreeDay, report.Weekend}
|
||||||
return []ModuleDefinition{
|
return []ModuleDefinition{
|
||||||
{
|
{
|
||||||
ID: module.Metadata,
|
ID: module.Metadata,
|
||||||
@@ -276,7 +276,7 @@ func defaultModuleDefinitions() []ModuleDefinition {
|
|||||||
DefaultOptions: module.NarrativeForecastOptions{},
|
DefaultOptions: module.NarrativeForecastOptions{},
|
||||||
RequiredCollected: []module.FactRequirement{module.CollectedNarrativeForecast},
|
RequiredCollected: []module.FactRequirement{module.CollectedNarrativeForecast},
|
||||||
RequiredDerived: []module.FactRequirement{module.RequiresDerivedNarrativePeriods},
|
RequiredDerived: []module.FactRequirement{module.RequiresDerivedNarrativePeriods},
|
||||||
SupportedReports: []report.ID{report.DailyToday, report.DailyTomorrow},
|
SupportedReports: []report.ID{report.DailyToday, report.Tomorrow},
|
||||||
MissingData: module.MissingDataOmit,
|
MissingData: module.MissingDataOmit,
|
||||||
Builder: buildNarrativeForecastModule,
|
Builder: buildNarrativeForecastModule,
|
||||||
},
|
},
|
||||||
@@ -286,7 +286,7 @@ func defaultModuleDefinitions() []ModuleDefinition {
|
|||||||
DefaultOptions: module.HourlyForecastOptions{},
|
DefaultOptions: module.HourlyForecastOptions{},
|
||||||
RequiredCollected: []module.FactRequirement{module.CollectedHourlyForecast},
|
RequiredCollected: []module.FactRequirement{module.CollectedHourlyForecast},
|
||||||
RequiredDerived: []module.FactRequirement{module.RequiresDerivedHourlyPeriods},
|
RequiredDerived: []module.FactRequirement{module.RequiresDerivedHourlyPeriods},
|
||||||
SupportedReports: []report.ID{report.DailyToday, report.DailyTomorrow, report.Hourly},
|
SupportedReports: []report.ID{report.DailyToday, report.Tomorrow, report.Hourly},
|
||||||
MissingData: module.MissingDataOmit,
|
MissingData: module.MissingDataOmit,
|
||||||
Builder: buildHourlyForecastModule,
|
Builder: buildHourlyForecastModule,
|
||||||
},
|
},
|
||||||
@@ -295,7 +295,7 @@ func defaultModuleDefinitions() []ModuleDefinition {
|
|||||||
StanzaName: "derived_daily_summary",
|
StanzaName: "derived_daily_summary",
|
||||||
DefaultOptions: module.DerivedDailySummaryOptions{},
|
DefaultOptions: module.DerivedDailySummaryOptions{},
|
||||||
RequiredDerived: []module.FactRequirement{module.RequiresDerivedDailySummaries, module.RequiresDerivedPrecipTiming},
|
RequiredDerived: []module.FactRequirement{module.RequiresDerivedDailySummaries, module.RequiresDerivedPrecipTiming},
|
||||||
SupportedReports: []report.ID{report.DailyToday, report.DailyTomorrow},
|
SupportedReports: []report.ID{report.DailyToday, report.Tomorrow},
|
||||||
MissingData: module.MissingDataError,
|
MissingData: module.MissingDataError,
|
||||||
Builder: buildDerivedDailySummaryModule,
|
Builder: buildDerivedDailySummaryModule,
|
||||||
},
|
},
|
||||||
@@ -379,7 +379,7 @@ func defaultModuleDefinitions() []ModuleDefinition {
|
|||||||
StanzaName: "tomorrow_planning",
|
StanzaName: "tomorrow_planning",
|
||||||
DefaultOptions: module.TomorrowPlanningOptions{},
|
DefaultOptions: module.TomorrowPlanningOptions{},
|
||||||
RequiredDerived: []module.FactRequirement{module.RequiresDerivedDailySummaries},
|
RequiredDerived: []module.FactRequirement{module.RequiresDerivedDailySummaries},
|
||||||
SupportedReports: []report.ID{report.DailyTomorrow},
|
SupportedReports: []report.ID{report.Tomorrow},
|
||||||
MissingData: module.MissingDataEmpty,
|
MissingData: module.MissingDataEmpty,
|
||||||
Builder: buildTomorrowPlanningModule,
|
Builder: buildTomorrowPlanningModule,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func variantForReport(id report.ID) string {
|
|||||||
switch id {
|
switch id {
|
||||||
case report.DailyToday:
|
case report.DailyToday:
|
||||||
return "today"
|
return "today"
|
||||||
case report.DailyTomorrow:
|
case report.Tomorrow:
|
||||||
return "tomorrow"
|
return "tomorrow"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -133,19 +133,19 @@ func TestRunGenerateTomorrowWritesMarkdownReport(t *testing.T) {
|
|||||||
if !strings.Contains(string(report), "# Daily Report") {
|
if !strings.Contains(string(report), "# Daily Report") {
|
||||||
t.Fatalf("report output missing markdown:\n%s", string(report))
|
t.Fatalf("report output missing markdown:\n%s", string(report))
|
||||||
}
|
}
|
||||||
dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "daily", "2026-05-30", "*.data_package.yaml")
|
dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "tomorrow", "2026-05-30", "*.data_package.yaml")
|
||||||
data, err := os.ReadFile(dataPackagePath)
|
data, err := os.ReadFile(dataPackagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read managed data package: %v", err)
|
t.Fatalf("read managed data package: %v", err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(string(data), "id: daily_tomorrow") || !strings.Contains(string(data), "tomorrow_planning:") {
|
if !strings.Contains(string(data), "id: tomorrow") || !strings.Contains(string(data), "tomorrow_planning:") {
|
||||||
t.Fatalf("data package output missing tomorrow content:\n%s", string(data))
|
t.Fatalf("data package output missing tomorrow content:\n%s", string(data))
|
||||||
}
|
}
|
||||||
reportMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "reports", "daily", "*.md"))
|
reportMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "reports", "tomorrow", "*.md"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("glob managed report: %v", err)
|
t.Fatalf("glob managed report: %v", err)
|
||||||
}
|
}
|
||||||
if len(reportMatches) != 1 || !strings.Contains(filepath.Base(reportMatches[0]), "daily_tomorrow") {
|
if len(reportMatches) != 1 || !strings.Contains(filepath.Base(reportMatches[0]), "tomorrow") {
|
||||||
t.Fatalf("managed reports = %#v, want tomorrow report", reportMatches)
|
t.Fatalf("managed reports = %#v, want tomorrow report", reportMatches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,18 +171,18 @@ func TestRunEveningGeneratesTomorrowReport(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Run() error = %v", err)
|
t.Fatalf("Run() error = %v", err)
|
||||||
}
|
}
|
||||||
dataPackageMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "daily", "2026-05-30", "*.data_package.yaml"))
|
dataPackageMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "tomorrow", "2026-05-30", "*.data_package.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("glob data package: %v", err)
|
t.Fatalf("glob data package: %v", err)
|
||||||
}
|
}
|
||||||
if len(dataPackageMatches) != 1 {
|
if len(dataPackageMatches) != 1 {
|
||||||
t.Fatalf("data package files = %#v, want one", dataPackageMatches)
|
t.Fatalf("data package files = %#v, want one", dataPackageMatches)
|
||||||
}
|
}
|
||||||
reportMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "reports", "daily", "*.md"))
|
reportMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "reports", "tomorrow", "*.md"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("glob managed report: %v", err)
|
t.Fatalf("glob managed report: %v", err)
|
||||||
}
|
}
|
||||||
if len(reportMatches) != 1 || !strings.Contains(filepath.Base(reportMatches[0]), "daily_tomorrow") {
|
if len(reportMatches) != 1 || !strings.Contains(filepath.Base(reportMatches[0]), "tomorrow") {
|
||||||
t.Fatalf("managed reports = %#v, want only tomorrow report", reportMatches)
|
t.Fatalf("managed reports = %#v, want only tomorrow report", reportMatches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,10 +482,10 @@ func TestRunEveningReportsNotificationSuccess(t *testing.T) {
|
|||||||
distributorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
distributorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/runs/distributor-run-1" {
|
if r.URL.Path == "/runs/distributor-run-1" {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, _ = w.Write([]byte(`{"run_id":"distributor-run-1","pipeline_id":"weatherreporter.daily","status":"succeeded","report":{"actions":[{"action":"replace_older"}]}}`))
|
_, _ = w.Write([]byte(`{"run_id":"distributor-run-1","pipeline_id":"weatherreporter.tomorrow","status":"succeeded","report":{"actions":[{"action":"replace_older"}]}}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.URL.Path != "/v1/pipelines/weatherreporter.daily/upload" {
|
if r.URL.Path != "/v1/pipelines/weatherreporter.tomorrow/upload" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -521,7 +521,7 @@ func TestRunEveningReportsNotificationSuccess(t *testing.T) {
|
|||||||
if len(summary.Reports) != 1 {
|
if len(summary.Reports) != 1 {
|
||||||
t.Fatalf("reports = %#v, want one report", summary.Reports)
|
t.Fatalf("reports = %#v, want one report", summary.Reports)
|
||||||
}
|
}
|
||||||
if summary.Reports[0].NotificationStatus != "succeeded" || summary.Reports[0].NotificationRunID != "distributor-run-1" || summary.Reports[0].NotificationPipelineID != "weatherreporter.daily" {
|
if summary.Reports[0].NotificationStatus != "succeeded" || summary.Reports[0].NotificationRunID != "distributor-run-1" || summary.Reports[0].NotificationPipelineID != "weatherreporter.tomorrow" {
|
||||||
t.Fatalf("notification fields = %#v", summary.Reports[0])
|
t.Fatalf("notification fields = %#v", summary.Reports[0])
|
||||||
}
|
}
|
||||||
if !strings.Contains(stderr.String(), `notificationStatus="succeeded"`) || !strings.Contains(stderr.String(), `notificationRunId="distributor-run-1"`) {
|
if !strings.Contains(stderr.String(), `notificationStatus="succeeded"`) || !strings.Contains(stderr.String(), `notificationRunId="distributor-run-1"`) {
|
||||||
|
|||||||
@@ -111,8 +111,8 @@ func reportIDForConfigKey(key string) (report.ID, error) {
|
|||||||
switch normalized {
|
switch normalized {
|
||||||
case "daily", "daily_today":
|
case "daily", "daily_today":
|
||||||
return report.DailyToday, nil
|
return report.DailyToday, nil
|
||||||
case "tomorrow", "daily_tomorrow":
|
case "tomorrow":
|
||||||
return report.DailyTomorrow, nil
|
return report.Tomorrow, nil
|
||||||
case "hourly":
|
case "hourly":
|
||||||
return report.Hourly, nil
|
return report.Hourly, nil
|
||||||
case "three_day", "three_day_outlook":
|
case "three_day", "three_day_outlook":
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func BuildDerived(req BuildDerivedRequest) (DerivedFacts, error) {
|
|||||||
|
|
||||||
switch req.Resolved.Definition.ID {
|
switch req.Resolved.Definition.ID {
|
||||||
case report.Hourly:
|
case report.Hourly:
|
||||||
case report.DailyToday, report.DailyTomorrow:
|
case report.DailyToday, report.Tomorrow:
|
||||||
summary, err := forecast.BuildDailySummary(bundle, period.Start, location, req.Dayparts)
|
summary, err := forecast.BuildDailySummary(bundle, period.Start, location, req.Dayparts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DerivedFacts{}, err
|
return DerivedFacts{}, err
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ func TestBuildDerivedOutlookBuildsPartialDaySummariesWithMissingOptionalSources(
|
|||||||
|
|
||||||
func TestBuildDerivedWeekendAndTomorrow(t *testing.T) {
|
func TestBuildDerivedWeekendAndTomorrow(t *testing.T) {
|
||||||
location := testLocation()
|
location := testLocation()
|
||||||
for _, id := range []report.ID{report.DailyTomorrow, report.Weekend} {
|
for _, id := range []report.ID{report.Tomorrow, report.Weekend} {
|
||||||
resolved := resolveForTest(t, id, mustParse("2026-05-29T08:00:00-05:00"), location)
|
resolved := resolveForTest(t, id, mustParse("2026-05-29T08:00:00-05:00"), location)
|
||||||
derived, err := BuildDerived(BuildDerivedRequest{
|
derived, err := BuildDerived(BuildDerivedRequest{
|
||||||
Resolved: resolved,
|
Resolved: resolved,
|
||||||
@@ -259,7 +259,7 @@ func TestBuildDerivedSelectsSPCConvectiveOutlooksByValidPeriod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "daily tomorrow",
|
name: "daily tomorrow",
|
||||||
resolved: resolveForTest(t, report.DailyTomorrow, now, location),
|
resolved: resolveForTest(t, report.Tomorrow, now, location),
|
||||||
wantOutlookIDs: []string{"sat-enhanced"},
|
wantOutlookIDs: []string{"sat-enhanced"},
|
||||||
wantDiscussion: []string{"day2"},
|
wantDiscussion: []string{"day2"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,27 +15,27 @@ func dailyTodayDefinition() Definition {
|
|||||||
ArtifactGroup: "daily",
|
ArtifactGroup: "daily",
|
||||||
BatchOutputName: "daily.md",
|
BatchOutputName: "daily.md",
|
||||||
Generated: true,
|
Generated: true,
|
||||||
CompatiblePriorIDs: []ID{DailyToday, DailyTomorrow},
|
CompatiblePriorIDs: []ID{DailyToday},
|
||||||
Modules: dailyTodayModules(),
|
Modules: dailyTodayModules(),
|
||||||
Morning: true,
|
Morning: true,
|
||||||
resolve: resolveDailyToday,
|
resolve: resolveDailyToday,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dailyTomorrowDefinition() Definition {
|
func tomorrowDefinition() Definition {
|
||||||
return Definition{
|
return Definition{
|
||||||
ID: DailyTomorrow,
|
ID: Tomorrow,
|
||||||
Name: "Tomorrow Planning Brief",
|
Name: "Tomorrow Report",
|
||||||
PromptID: "weather.daily_report",
|
PromptID: "weather.daily_report",
|
||||||
GenerationMode: GenerationModeScriptoriumMarkdown,
|
GenerationMode: GenerationModeScriptoriumMarkdown,
|
||||||
ComparisonStrategy: CompareSameValidDate,
|
ComparisonStrategy: CompareSameValidDate,
|
||||||
ArtifactGroup: "daily",
|
ArtifactGroup: "tomorrow",
|
||||||
BatchOutputName: "tomorrow.md",
|
BatchOutputName: "tomorrow.md",
|
||||||
Generated: true,
|
Generated: true,
|
||||||
CompatiblePriorIDs: []ID{DailyToday, DailyTomorrow},
|
CompatiblePriorIDs: []ID{Tomorrow},
|
||||||
Modules: dailyTomorrowModules(),
|
Modules: tomorrowModules(),
|
||||||
Evening: true,
|
Evening: true,
|
||||||
resolve: resolveDailyTomorrow,
|
resolve: resolveTomorrow,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ func dailyTodayModules() []module.ConfigItem {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dailyTomorrowModules() []module.ConfigItem {
|
func tomorrowModules() []module.ConfigItem {
|
||||||
return moduleItems(
|
return moduleItems(
|
||||||
module.Metadata,
|
module.Metadata,
|
||||||
module.CurrentConditions,
|
module.CurrentConditions,
|
||||||
@@ -83,6 +83,6 @@ func resolveDailyToday(req ResolveRequest) (timeutil.Period, error) {
|
|||||||
return timeutil.CivilDay(req.Now, req.Location), nil
|
return timeutil.CivilDay(req.Now, req.Location), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveDailyTomorrow(req ResolveRequest) (timeutil.Period, error) {
|
func resolveTomorrow(req ResolveRequest) (timeutil.Period, error) {
|
||||||
return timeutil.CivilDay(req.Now.In(req.Location).AddDate(0, 0, 1), req.Location), nil
|
return timeutil.CivilDay(req.Now.In(req.Location).AddDate(0, 0, 1), req.Location), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type ID string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DailyToday ID = "daily_today"
|
DailyToday ID = "daily_today"
|
||||||
DailyTomorrow ID = "daily_tomorrow"
|
Tomorrow ID = "tomorrow"
|
||||||
Hourly ID = "hourly"
|
Hourly ID = "hourly"
|
||||||
ThreeDay ID = "three_day"
|
ThreeDay ID = "three_day"
|
||||||
Weekend ID = "weekend"
|
Weekend ID = "weekend"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (r Registry) BatchReports(batch Batch, req ResolveRequest) ([]Resolved, err
|
|||||||
}
|
}
|
||||||
return r.resolveIDs(ids, req)
|
return r.resolveIDs(ids, req)
|
||||||
case Evening:
|
case Evening:
|
||||||
return r.resolveIDs([]ID{DailyTomorrow}, req)
|
return r.resolveIDs([]ID{Tomorrow}, req)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown batch %q", batch)
|
return nil, fmt.Errorf("unknown batch %q", batch)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func TestTomorrowValidPeriodFromEveningGeneration(t *testing.T) {
|
|||||||
location := mustLoadLocation(t)
|
location := mustLoadLocation(t)
|
||||||
now := mustParse("2026-05-29T20:00:00-05:00")
|
now := mustParse("2026-05-29T20:00:00-05:00")
|
||||||
|
|
||||||
resolved, err := Resolve(DailyTomorrow, ResolveRequest{Now: now, Location: location})
|
resolved, err := Resolve(Tomorrow, ResolveRequest{Now: now, Location: location})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Resolve() error = %v", err)
|
t.Fatalf("Resolve() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -207,8 +207,8 @@ func TestEveningBatchIncludesTomorrow(t *testing.T) {
|
|||||||
t.Fatalf("BatchReports() error = %v", err)
|
t.Fatalf("BatchReports() error = %v", err)
|
||||||
}
|
}
|
||||||
ids := resolvedIDs(resolved)
|
ids := resolvedIDs(resolved)
|
||||||
if strings.Join(ids, ",") != "daily_tomorrow" {
|
if strings.Join(ids, ",") != "tomorrow" {
|
||||||
t.Fatalf("ids = %v, want daily_tomorrow", ids)
|
t.Fatalf("ids = %v, want tomorrow", ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ func TestRegistryLookupErrorIsActionable(t *testing.T) {
|
|||||||
|
|
||||||
func TestRegistryAllIncludesHourlyInStableOrder(t *testing.T) {
|
func TestRegistryAllIncludesHourlyInStableOrder(t *testing.T) {
|
||||||
ids := resolvedDefinitionIDs(DefaultRegistry().All())
|
ids := resolvedDefinitionIDs(DefaultRegistry().All())
|
||||||
want := []string{"daily_today", "daily_tomorrow", "hourly", "three_day", "weekend", "storm"}
|
want := []string{"daily_today", "tomorrow", "hourly", "three_day", "weekend", "storm"}
|
||||||
if strings.Join(ids, ",") != strings.Join(want, ",") {
|
if strings.Join(ids, ",") != strings.Join(want, ",") {
|
||||||
t.Fatalf("All() ids = %#v, want %#v", ids, want)
|
t.Fatalf("All() ids = %#v, want %#v", ids, want)
|
||||||
}
|
}
|
||||||
@@ -306,15 +306,15 @@ func TestRegistryDefinitionsDeclarePathAndCompatibilityPolicy(t *testing.T) {
|
|||||||
artifactGroup: "daily",
|
artifactGroup: "daily",
|
||||||
batchOutputName: "daily.md",
|
batchOutputName: "daily.md",
|
||||||
generated: true,
|
generated: true,
|
||||||
compatiblePriorIDs: []ID{DailyToday, DailyTomorrow},
|
compatiblePriorIDs: []ID{DailyToday},
|
||||||
comparisonStrategy: CompareSameValidDate,
|
comparisonStrategy: CompareSameValidDate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: DailyTomorrow,
|
id: Tomorrow,
|
||||||
artifactGroup: "daily",
|
artifactGroup: "tomorrow",
|
||||||
batchOutputName: "tomorrow.md",
|
batchOutputName: "tomorrow.md",
|
||||||
generated: true,
|
generated: true,
|
||||||
compatiblePriorIDs: []ID{DailyToday, DailyTomorrow},
|
compatiblePriorIDs: []ID{Tomorrow},
|
||||||
comparisonStrategy: CompareSameValidDate,
|
comparisonStrategy: CompareSameValidDate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -406,7 +406,7 @@ func TestRegistryDefinitionsDeclareDefaultModules(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: DailyTomorrow,
|
id: Tomorrow,
|
||||||
want: []module.ID{
|
want: []module.ID{
|
||||||
module.Metadata,
|
module.Metadata,
|
||||||
module.CurrentConditions,
|
module.CurrentConditions,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type Registry struct {
|
|||||||
func DefaultRegistry() Registry {
|
func DefaultRegistry() Registry {
|
||||||
definitions := []Definition{
|
definitions := []Definition{
|
||||||
dailyTodayDefinition(),
|
dailyTodayDefinition(),
|
||||||
dailyTomorrowDefinition(),
|
tomorrowDefinition(),
|
||||||
hourlyDefinition(),
|
hourlyDefinition(),
|
||||||
threeDayDefinition(),
|
threeDayDefinition(),
|
||||||
weekendDefinition(),
|
weekendDefinition(),
|
||||||
@@ -74,7 +74,7 @@ func (r Registry) MustLookup(id ID) Definition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r Registry) All() []Definition {
|
func (r Registry) All() []Definition {
|
||||||
ids := []ID{DailyToday, DailyTomorrow, Hourly, ThreeDay, Weekend, Storm}
|
ids := []ID{DailyToday, Tomorrow, Hourly, ThreeDay, Weekend, Storm}
|
||||||
out := make([]Definition, 0, len(ids))
|
out := make([]Definition, 0, len(ids))
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if definition, ok := r.definitions[id]; ok {
|
if definition, ok := r.definitions[id]; ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user