Add Today generate command workflow

This commit is contained in:
2026-06-15 14:58:36 +00:00
parent 3d5f71e72d
commit 4a0238909b
5 changed files with 314 additions and 9 deletions

View File

@@ -18,6 +18,7 @@ const helpText = `weatherreporter prepares weather reports from normalized forec
Usage:
weatherreporter --help
weatherreporter generate daily [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate three-day [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
@@ -216,7 +217,7 @@ func (r Runner) resolveGenerate(args []string) (app.GenerateRequest, error) {
}
switch reportKind {
case app.ReportDaily:
case app.ReportDaily, app.ReportToday:
if opts.Date == "" {
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
} else {
@@ -278,7 +279,7 @@ func parseGenerateFlags(report app.ReportKind, args []string) (generateOptions,
fs.SetOutput(io.Discard)
opts := generateOptions{}
addCommonFlags(fs, &opts.commonOptions, true)
if report == app.ReportDaily {
if report == app.ReportDaily || report == app.ReportToday {
fs.StringVar(&opts.Date, "date", "", "report date in YYYY-MM-DD")
}
if report == app.ReportStorm {

View File

@@ -26,6 +26,9 @@ func TestRunHelpLongFlag(t *testing.T) {
if !strings.Contains(output.stdout, "generate daily") {
t.Fatalf("help output missing generate command:\n%s", output.stdout)
}
if !strings.Contains(output.stdout, "generate today") {
t.Fatalf("help output missing today generate command:\n%s", output.stdout)
}
if !strings.Contains(output.stdout, "weatherreporter generate hourly") {
t.Fatalf("help output missing hourly generate command:\n%s", output.stdout)
}
@@ -570,6 +573,66 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
}
}
func TestRunGenerateTodayWritesGeneratedTextReport(t *testing.T) {
server := dailyServer(t)
tempDir := t.TempDir()
scriptoriumPath := writeStructuredOutputScriptorium(t, tempDir)
workspaceRoot := filepath.Join(tempDir, "workspace")
configPath := writeTestConfig(t, server, scriptoriumPath, workspaceRoot)
outPath := filepath.Join(tempDir, "today.md")
var stdout bytes.Buffer
var stderr bytes.Buffer
runner := Runner{Clock: fixedClock()}
err := runner.Run(context.Background(), []string{
"generate", "today",
"--config", configPath,
"--date", "2026-05-29",
"--out", outPath,
}, &stdout, &stderr)
if err != nil {
t.Fatalf("Run() error = %v", err)
}
report, err := os.ReadFile(outPath)
if err != nil {
t.Fatalf("read report: %v", err)
}
for _, want := range []string{
"# Today's Weather",
"Today starts with showers before improving.",
"Morning showers should taper as drier air arrives.",
} {
if !strings.Contains(string(report), want) {
t.Fatalf("today report output missing %q:\n%s", want, string(report))
}
}
dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "today", "2026-05-29", "*.data_package.yaml")
dataPackage, err := os.ReadFile(dataPackagePath)
if err != nil {
t.Fatalf("read managed data package: %v", err)
}
if !strings.Contains(string(dataPackage), "id: today") ||
!strings.Contains(string(dataPackage), "prompt_id: weather.today_generated_text") ||
!strings.Contains(string(dataPackage), "today_planning:") {
t.Fatalf("data package output missing Today content:\n%s", string(dataPackage))
}
dailyPackages, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "daily", "2026-05-29", "*.data_package.yaml"))
if err != nil {
t.Fatalf("glob daily packages: %v", err)
}
if len(dailyPackages) != 0 {
t.Fatalf("daily packages = %#v, want generate today to stay separate", dailyPackages)
}
rawGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "today", "2026-05-29", "*.generated_text.raw.json")
validatedGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "today", "2026-05-29", "*.generated_text.json")
renderContextPath := oneArtifact(t, workspaceRoot, "snapshots", "today", "2026-05-29", "*.render_context.json")
managedReportPath := oneArtifact(t, workspaceRoot, "reports", "today", "*.md")
assertFileContains(t, rawGeneratedTextPath, `"summary": "Today starts with showers before improving."`)
assertFileContains(t, validatedGeneratedTextPath, `"summary":"Today starts with showers before improving."`)
assertFileContains(t, renderContextPath, `"Title": "Today's Weather"`)
assertFileContains(t, managedReportPath, "# Today's Weather")
}
func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
server := dailyServer(t)
tempDir := t.TempDir()
@@ -627,6 +690,65 @@ func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
assertFileContains(t, managedReportPath, "# Hourly Report")
}
func TestRunInspectTodayArtifacts(t *testing.T) {
server := dailyServer(t)
tempDir := t.TempDir()
scriptoriumPath := writeFakeScriptorium(t, tempDir)
configPath := filepath.Join(tempDir, "config.yml")
workspaceRoot := filepath.Join(tempDir, "workspace")
configBody := "weather_api:\n base_url: " + server.URL + "/\n timezone: America/Chicago\nscriptorium:\n binary: " + scriptoriumPath + "\nworkspace:\n root: " + workspaceRoot + "\n"
if err := os.WriteFile(configPath, []byte(configBody), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
runner := Runner{Clock: fixedClock()}
var stdout bytes.Buffer
var stderr bytes.Buffer
err := runner.Run(context.Background(), []string{
"generate", "today",
"--config", configPath,
"--date", "2026-05-29",
}, &stdout, &stderr)
if err != nil {
t.Fatalf("Run(generate) error = %v", err)
}
dataPackageMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "today", "2026-05-29", "*.data_package.yaml"))
if err != nil {
t.Fatalf("glob data package: %v", err)
}
if len(dataPackageMatches) != 1 {
t.Fatalf("data package files = %#v, want one", dataPackageMatches)
}
runID := strings.TrimSuffix(filepath.Base(dataPackageMatches[0]), ".data_package.yaml")
stdout.Reset()
err = runner.Run(context.Background(), []string{"inspect", "reports", "--config", configPath, "--limit", "1"}, &stdout, &stderr)
if err != nil {
t.Fatalf("Run(inspect reports) error = %v", err)
}
if !strings.Contains(stdout.String(), runID) || !strings.Contains(stdout.String(), `"reportId": "today"`) {
t.Fatalf("inspect reports output missing Today run:\n%s", stdout.String())
}
var sourcesOutput string
for _, command := range []string{"metadata", "modules", "data-package", "sources"} {
stdout.Reset()
err = runner.Run(context.Background(), []string{"inspect", command, "--config", configPath, runID}, &stdout, &stderr)
if err != nil {
t.Fatalf("Run(inspect %s) error = %v", command, err)
}
if !strings.Contains(stdout.String(), runID) || !strings.Contains(stdout.String(), "today") {
t.Fatalf("inspect %s output missing Today run id:\n%s", command, stdout.String())
}
if command == "sources" {
sourcesOutput = stdout.String()
}
}
if !strings.Contains(sourcesOutput, `"name": "narrative"`) || strings.Contains(sourcesOutput, `"name": "daily"`) {
t.Fatalf("inspect sources output missing narrative source or has unexpected daily source:\n%s", sourcesOutput)
}
}
func TestRunInspectGeneratedArtifacts(t *testing.T) {
server := dailyServer(t)
tempDir := t.TempDir()
@@ -753,6 +875,7 @@ func TestResolveGenerateCommands(t *testing.T) {
want app.ReportKind
}{
{name: "daily", args: []string{"daily", "--date", "2026-05-29"}, want: app.ReportDaily},
{name: "today", args: []string{"today", "--date", "2026-05-29"}, want: app.ReportToday},
{name: "tomorrow", args: []string{"tomorrow"}, want: app.ReportTomorrow},
{name: "hourly", args: []string{"hourly"}, want: app.ReportHourly},
{name: "three-day", args: []string{"three-day"}, want: app.ReportThreeDay},
@@ -778,7 +901,7 @@ func TestResolveGenerateSupportsEveryReportCommandName(t *testing.T) {
for _, name := range report.CommandNames() {
t.Run(name, func(t *testing.T) {
args := []string{name}
if name == report.CommandNameDaily {
if name == report.CommandNameDaily || name == report.CommandNameToday {
args = append(args, "--date", "2026-05-29")
}
if name == report.CommandNameStorm {
@@ -865,6 +988,39 @@ func TestResolveGenerateDailyDefaultsDateInConfiguredTimezone(t *testing.T) {
}
}
func TestResolveGenerateTodayDate(t *testing.T) {
runner := Runner{Clock: fixedClock()}
defaultReq, err := runner.resolveGenerate([]string{"today"})
if err != nil {
t.Fatalf("resolveGenerate(default) error = %v", err)
}
if defaultReq.Report != app.ReportToday {
t.Fatalf("Report = %q, want today", defaultReq.Report)
}
if got := defaultReq.Date.Format(timeutil.DateLayout); got != "2026-05-29" {
t.Fatalf("default Date = %s, want 2026-05-29", got)
}
explicitReq, err := runner.resolveGenerate([]string{"today", "--date", "2026-05-30", "--tz", "UTC"})
if err != nil {
t.Fatalf("resolveGenerate(explicit) error = %v", err)
}
if got := explicitReq.Date.Format(timeutil.DateLayout); got != "2026-05-30" {
t.Fatalf("explicit Date = %s, want 2026-05-30", got)
}
resolved, err := app.ResolveGenerate(explicitReq, explicitReq.Now)
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
if resolved.Definition.ID != report.Today {
t.Fatalf("resolved ID = %q, want today", resolved.Definition.ID)
}
if got := resolved.ValidPeriod.Start.Format(time.RFC3339); got != "2026-05-30T00:00:00Z" {
t.Fatalf("valid period start = %s, want explicit UTC date", got)
}
}
func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
runner := Runner{Clock: fixedClock()}