Add Today report to morning batch

This commit is contained in:
2026-06-15 14:54:37 +00:00
parent 8ff5c44324
commit 3d5f71e72d
15 changed files with 431 additions and 32 deletions

View File

@@ -7,6 +7,7 @@ import (
const (
CommandNameDaily = "daily"
CommandNameToday = "today"
CommandNameTomorrow = "tomorrow"
CommandNameHourly = "hourly"
CommandNameThreeDay = "three-day"
@@ -21,6 +22,8 @@ func IDForCommandName(name string) (ID, error) {
switch name {
case CommandNameDaily:
return DailyToday, nil
case CommandNameToday:
return Today, nil
case CommandNameTomorrow:
return Tomorrow, nil
case CommandNameHourly:
@@ -39,6 +42,7 @@ func IDForCommandName(name string) (ID, error) {
func CommandNames() []string {
return []string{
CommandNameDaily,
CommandNameToday,
CommandNameTomorrow,
CommandNameHourly,
CommandNameThreeDay,
@@ -52,6 +56,8 @@ func IDForConfigKey(key string) (ID, error) {
switch normalized {
case "daily", "daily_today":
return DailyToday, nil
case "today":
return Today, nil
case "tomorrow":
return Tomorrow, nil
case "hourly":

View File

@@ -26,7 +26,7 @@ func (r Registry) BatchReports(batch Batch, req ResolveRequest) ([]Resolved, err
}
switch batch {
case Morning:
ids := []ID{DailyToday, ThreeDay}
ids := []ID{Today, ThreeDay}
if req.Now.In(req.Location).Weekday() != time.Sunday {
ids = append(ids, Weekend)
}

View File

@@ -33,6 +33,41 @@ func TestDailyValidPeriodCanUseExplicitDate(t *testing.T) {
assertPeriod(t, resolved.ValidPeriod, "2026-05-31T00:00:00-05:00", "2026-06-01T00:00:00-05:00")
}
func TestTodayValidPeriod(t *testing.T) {
location := mustLoadLocation(t)
now := mustParse("2026-05-29T17:45:00-05:00")
resolved, err := Resolve(Today, ResolveRequest{Now: now, Location: location})
if err != nil {
t.Fatalf("Resolve() error = %v", err)
}
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T00:00:00-05:00", "2026-05-30T00:00:00-05:00")
if resolved.Definition.PromptID != "weather.today_generated_text" {
t.Fatalf("PromptID = %q, want weather.today_generated_text", resolved.Definition.PromptID)
}
if resolved.Definition.GenerationMode != GenerationModeGeneratedTextTemplate {
t.Fatalf("GenerationMode = %q, want generated_text_template", resolved.Definition.GenerationMode)
}
if resolved.Definition.TemplateID != "today" {
t.Fatalf("TemplateID = %q, want today", resolved.Definition.TemplateID)
}
if resolved.Definition.GeneratedTextSchemaID != "today" {
t.Fatalf("GeneratedTextSchemaID = %q, want today", resolved.Definition.GeneratedTextSchemaID)
}
}
func TestTodayValidPeriodCanUseExplicitDate(t *testing.T) {
location := mustLoadLocation(t)
now := mustParse("2026-05-29T17:45:00-05:00")
date := mustParse("2026-05-31T12:00:00-05:00")
resolved, err := Resolve(Today, ResolveRequest{Now: now, Location: location, Date: date})
if err != nil {
t.Fatalf("Resolve() error = %v", err)
}
assertPeriod(t, resolved.ValidPeriod, "2026-05-31T00:00:00-05:00", "2026-06-01T00:00:00-05:00")
}
func TestTomorrowValidPeriodFromEveningGeneration(t *testing.T) {
location := mustLoadLocation(t)
now := mustParse("2026-05-29T20:00:00-05:00")
@@ -201,8 +236,8 @@ func TestMorningBatchSkipsWeekendOnSunday(t *testing.T) {
t.Fatalf("BatchReports() error = %v", err)
}
ids := resolvedIDs(resolved)
if strings.Join(ids, ",") != "daily_today,three_day" {
t.Fatalf("ids = %v, want daily_today and three_day", ids)
if strings.Join(ids, ",") != "today,three_day" {
t.Fatalf("ids = %v, want today and three_day", ids)
}
}
@@ -246,6 +281,7 @@ func TestIDForCommandName(t *testing.T) {
want ID
}{
{name: "daily", want: DailyToday},
{name: "today", want: Today},
{name: "tomorrow", want: Tomorrow},
{name: "hourly", want: Hourly},
{name: "three-day", want: ThreeDay},
@@ -263,9 +299,20 @@ func TestIDForCommandName(t *testing.T) {
}
})
}
if names := strings.Join(CommandNames(), ","); names != "daily,tomorrow,hourly,three-day,weekend,storm" {
if names := strings.Join(CommandNames(), ","); names != "daily,today,tomorrow,hourly,three-day,weekend,storm" {
t.Fatalf("CommandNames() = %s, want stable command names", names)
}
dailyID, err := IDForCommandName("daily")
if err != nil {
t.Fatalf("IDForCommandName(daily) error = %v", err)
}
todayID, err := IDForCommandName("today")
if err != nil {
t.Fatalf("IDForCommandName(today) error = %v", err)
}
if dailyID == todayID {
t.Fatalf("daily and today both resolve to %q, want distinct report IDs", dailyID)
}
if _, err := IDForCommandName("near-term"); err == nil || !strings.Contains(err.Error(), `unknown report command "near-term"`) {
t.Fatalf("IDForCommandName(near-term) error = %v, want unknown command", err)
}
@@ -278,6 +325,7 @@ func TestIDForConfigKey(t *testing.T) {
}{
{key: "daily", want: DailyToday},
{key: "daily_today", want: DailyToday},
{key: "today", want: Today},
{key: "tomorrow", want: Tomorrow},
{key: "hourly", want: Hourly},
{key: "three_day", want: ThreeDay},
@@ -303,6 +351,17 @@ func TestIDForConfigKey(t *testing.T) {
if _, err := IDForConfigKey("daily_tomorrow"); err == nil || !strings.Contains(err.Error(), `report config key "daily_tomorrow" is not a known report`) {
t.Fatalf("IDForConfigKey(daily_tomorrow) error = %v, want unknown key", err)
}
dailyID, err := IDForConfigKey("daily")
if err != nil {
t.Fatalf("IDForConfigKey(daily) error = %v", err)
}
todayID, err := IDForConfigKey("today")
if err != nil {
t.Fatalf("IDForConfigKey(today) error = %v", err)
}
if dailyID == todayID {
t.Fatalf("daily and today config keys both resolve to %q, want distinct report IDs", dailyID)
}
}
func TestBatchForCommandName(t *testing.T) {
@@ -342,7 +401,7 @@ func TestMorningBatchReportOrder(t *testing.T) {
t.Fatalf("BatchReports() error = %v", err)
}
ids := resolvedIDs(resolved)
if strings.Join(ids, ",") != "daily_today,three_day,weekend" {
if strings.Join(ids, ",") != "today,three_day,weekend" {
t.Fatalf("ids = %v, want morning report order", ids)
}
}
@@ -359,7 +418,7 @@ func TestRegistryLookupErrorIsActionable(t *testing.T) {
func TestRegistryAllIncludesHourlyInStableOrder(t *testing.T) {
ids := resolvedDefinitionIDs(DefaultRegistry().All())
want := []string{"daily_today", "tomorrow", "hourly", "three_day", "weekend", "storm"}
want := []string{"daily_today", "today", "tomorrow", "hourly", "three_day", "weekend", "storm"}
if strings.Join(ids, ",") != strings.Join(want, ",") {
t.Fatalf("All() ids = %#v, want %#v", ids, want)
}
@@ -384,7 +443,7 @@ func TestRegistryDefinitionsDeclareGenerationMetadata(t *testing.T) {
if !definition.Generated {
continue
}
if definition.ID == Hourly || definition.ID == Tomorrow {
if definition.ID == Hourly || definition.ID == Today || definition.ID == Tomorrow {
wantTemplate := string(definition.ID)
if definition.GenerationMode != GenerationModeGeneratedTextTemplate {
t.Fatalf("%s GenerationMode = %q, want %q", definition.ID, definition.GenerationMode, GenerationModeGeneratedTextTemplate)
@@ -426,6 +485,14 @@ func TestRegistryDefinitionsDeclarePathAndCompatibilityPolicy(t *testing.T) {
compatiblePriorIDs: []ID{DailyToday},
comparisonStrategy: CompareSameValidDate,
},
{
id: Today,
artifactGroup: "today",
batchOutputName: "today.md",
generated: true,
compatiblePriorIDs: []ID{Today},
comparisonStrategy: CompareSameValidDate,
},
{
id: Tomorrow,
artifactGroup: "tomorrow",
@@ -522,6 +589,25 @@ func TestRegistryDefinitionsDeclareDefaultModules(t *testing.T) {
module.HourlyForecast,
},
},
{
id: Today,
want: []module.ID{
module.Metadata,
module.CurrentConditions,
module.NarrativeForecast,
module.DerivedDailySummary,
module.DerivedDaypartSummaries,
module.PrecipTiming,
module.AlertDigest,
module.SPCConvectiveOutlooks,
module.AreaForecastDiscussion,
module.SPCConvectiveDiscussion,
module.WeatherStory,
module.OutdoorWindows,
module.HourlyForecast,
module.TodayPlanning,
},
},
{
id: Tomorrow,
want: []module.ID{

View File

@@ -13,6 +13,7 @@ type Registry struct {
func DefaultRegistry() Registry {
definitions := []Definition{
dailyTodayDefinition(),
todayDefinition(),
tomorrowDefinition(),
hourlyDefinition(),
threeDayDefinition(),
@@ -74,7 +75,7 @@ func (r Registry) MustLookup(id ID) Definition {
}
func (r Registry) All() []Definition {
ids := []ID{DailyToday, Tomorrow, Hourly, ThreeDay, Weekend, Storm}
ids := []ID{DailyToday, Today, Tomorrow, Hourly, ThreeDay, Weekend, Storm}
out := make([]Definition, 0, len(ids))
for _, id := range ids {
if definition, ok := r.definitions[id]; ok {

View File

@@ -0,0 +1,51 @@
package report
import (
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
)
func todayDefinition() Definition {
return Definition{
ID: Today,
Name: "Today Report",
PromptID: "weather.today_generated_text",
GenerationMode: GenerationModeGeneratedTextTemplate,
TemplateID: "today",
GeneratedTextSchemaID: "today",
ComparisonStrategy: CompareSameValidDate,
ArtifactGroup: "today",
BatchOutputName: "today.md",
Generated: true,
CompatiblePriorIDs: []ID{Today},
Modules: todayModules(),
Morning: true,
resolve: resolveToday,
}
}
func todayModules() []module.ConfigItem {
return moduleItems(
module.Metadata,
module.CurrentConditions,
module.NarrativeForecast,
module.DerivedDailySummary,
module.DerivedDaypartSummaries,
module.PrecipTiming,
module.AlertDigest,
module.SPCConvectiveOutlooks,
module.AreaForecastDiscussion,
module.SPCConvectiveDiscussion,
module.WeatherStory,
module.OutdoorWindows,
module.HourlyForecast,
module.TodayPlanning,
)
}
func resolveToday(req ResolveRequest) (timeutil.Period, error) {
if !req.Date.IsZero() {
return timeutil.CivilDay(req.Date, req.Location), nil
}
return timeutil.CivilDay(req.Now, req.Location), nil
}