Add data-aware batch planning

This commit is contained in:
2026-06-17 15:59:13 +00:00
parent c04e3c5599
commit 6f9255105d
5 changed files with 264 additions and 51 deletions

View File

@@ -2237,7 +2237,7 @@ func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyWorkspaceConfig(t, server)
renderer := &selectiveRenderer{
failRenderPrompt: "weather.three_day_outlook",
failRenderPrompt: "weather.tomorrow_generated_text",
runBody: "# Batch Report\n",
}
@@ -2251,23 +2251,23 @@ func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
t.Fatalf("RunBatchDetailed() error = %v", err)
}
if result.Total != 3 || result.Succeeded != 2 || result.Failed != 1 {
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 3/2/1", result.Total, result.Succeeded, result.Failed)
if result.Total != 2 || result.Succeeded != 1 || result.Failed != 1 {
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 2/1/1", result.Total, result.Succeeded, result.Failed)
}
if renderer.runCalls != 1 || renderer.structuredRunCalls != 1 {
if renderer.runCalls != 0 || renderer.structuredRunCalls != 1 {
t.Fatalf("renderer calls run=%d structured=%d, want successful reports to continue", renderer.runCalls, renderer.structuredRunCalls)
}
var failedThreeDay bool
var failedTomorrow bool
for _, item := range result.Reports {
if item.ReportID == report.ThreeDay && item.Status == "failed" && strings.Contains(item.Error, "render failed") {
failedThreeDay = true
if item.ReportID == report.Tomorrow && item.Status == "failed" && strings.Contains(item.Error, "render failed") {
failedTomorrow = true
}
if item.ReportID != report.ThreeDay && item.Status != "succeeded" {
if item.ReportID != report.Tomorrow && item.Status != "succeeded" {
t.Fatalf("report %s status = %s, want succeeded", item.ReportID, item.Status)
}
}
if !failedThreeDay {
t.Fatalf("reports = %#v, want failed 3-day item", result.Reports)
if !failedTomorrow {
t.Fatalf("reports = %#v, want failed Tomorrow item", result.Reports)
}
}
@@ -2276,7 +2276,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
cfg := dailyNotificationConfig(t, server)
notifier := &recordingNotifier{
errByReport: map[report.ID]error{
report.ThreeDay: errors.New("distributor unavailable"),
report.Tomorrow: errors.New("distributor unavailable"),
},
}
@@ -2291,26 +2291,26 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
t.Fatalf("RunBatchDetailed() error = %v", err)
}
if result.Total != 3 || result.Succeeded != 2 || result.Failed != 1 {
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 3/2/1", result.Total, result.Succeeded, result.Failed)
if result.Total != 2 || result.Succeeded != 1 || result.Failed != 1 {
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 2/1/1", result.Total, result.Succeeded, result.Failed)
}
if len(notifier.requests) != 3 {
if len(notifier.requests) != 2 {
t.Fatalf("notification requests = %d, want one per generated report", len(notifier.requests))
}
var failedThreeDay bool
var failedTomorrow bool
for _, item := range result.Reports {
if item.ReportID == report.ThreeDay {
if item.ReportID == report.Tomorrow {
if item.Status == "failed" && strings.Contains(item.Error, "notify report") && strings.Contains(item.Error, "distributor unavailable") {
failedThreeDay = true
failedTomorrow = true
}
if item.NotificationStatus != "failed" {
t.Fatalf("3-day notification status = %q, want failed", item.NotificationStatus)
t.Fatalf("Tomorrow notification status = %q, want failed", item.NotificationStatus)
}
if item.NotificationPipelineID != "weatherreporter.three-day" {
t.Fatalf("3-day notification pipeline = %q, want weatherreporter.three-day", item.NotificationPipelineID)
if item.NotificationPipelineID != "weatherreporter.tomorrow" {
t.Fatalf("Tomorrow notification pipeline = %q, want weatherreporter.tomorrow", item.NotificationPipelineID)
}
if !strings.Contains(item.NotificationError, "distributor unavailable") {
t.Fatalf("3-day notification error = %q, want distributor unavailable", item.NotificationError)
t.Fatalf("Tomorrow notification error = %q, want distributor unavailable", item.NotificationError)
}
continue
}
@@ -2324,8 +2324,8 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
t.Fatalf("report %s notification pipeline is empty", item.ReportID)
}
}
if !failedThreeDay {
t.Fatalf("reports = %#v, want notification failure on 3-day item", result.Reports)
if !failedTomorrow {
t.Fatalf("reports = %#v, want notification failure on Tomorrow item", result.Reports)
}
cfg.Workspace.Root = t.TempDir()
@@ -2338,7 +2338,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
},
Notifier: &recordingNotifier{
errByReport: map[report.ID]error{
report.ThreeDay: errors.New("distributor unavailable"),
report.Tomorrow: errors.New("distributor unavailable"),
},
},
})
@@ -2346,7 +2346,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
if !errors.As(err, &batchErr) {
t.Fatalf("RunBatch() error = %T %v, want BatchError", err, err)
}
if batchErr.Result == nil || batchErr.Result.Failed != 1 || batchErr.Result.Succeeded != 2 {
if batchErr.Result == nil || batchErr.Result.Failed != 1 || batchErr.Result.Succeeded != 1 {
t.Fatalf("RunBatch() result = %#v, want notification failure aggregate", batchErr.Result)
}
}
@@ -2404,7 +2404,7 @@ func TestRunBatchMorningUsesTodayOutputName(t *testing.T) {
if err != nil {
t.Fatalf("RunBatchDetailed() error = %v", err)
}
if result.Failed != 0 || len(result.Reports) != 3 {
if result.Failed != 0 || len(result.Reports) != 2 {
t.Fatalf("summary = %#v, want successful morning batch", result)
}
var todayItem *BatchReportResult
@@ -2448,26 +2448,18 @@ func TestRunBatchDetailedUsesProvidedCollector(t *testing.T) {
collector := &recordingCollector{err: errors.New("batch collector failed")}
renderer := &recordingRenderer{}
result, err := RunBatchDetailed(context.Background(), BatchRequest{
_, err := RunBatchDetailed(context.Background(), BatchRequest{
Config: cfg,
Batch: BatchMorning,
Now: mustParse("2026-05-29T05:00:00-05:00"),
Collector: collector,
Renderer: renderer,
})
if err != nil {
t.Fatalf("RunBatchDetailed() error = %v", err)
if err == nil || !strings.Contains(err.Error(), "batch collector failed") {
t.Fatalf("RunBatchDetailed() error = %v, want fake collector error", err)
}
if result.Total == 0 || result.Failed != result.Total {
t.Fatalf("batch result = %#v, want every report failed by collector", result)
}
if len(collector.requests) != result.Total {
t.Fatalf("collector requests = %d, want %d", len(collector.requests), result.Total)
}
for _, reportResult := range result.Reports {
if !strings.Contains(reportResult.Error, "batch collector failed") {
t.Fatalf("report error = %q, want fake collector error", reportResult.Error)
}
if len(collector.requests) != 1 {
t.Fatalf("collector requests = %d, want one planning collection", len(collector.requests))
}
if renderer.renderCalls != 0 || renderer.runCalls != 0 || renderer.structuredRunCalls != 0 {
t.Fatalf("renderer calls render=%d run=%d structured=%d, want none after collection failure", renderer.renderCalls, renderer.runCalls, renderer.structuredRunCalls)