(config: BatchConfig)
| 327 | // --- Main Batch Loop --- |
| 328 | |
| 329 | async function processBatch(config: BatchConfig): Promise<BatchProgress> { |
| 330 | const progress: BatchProgress = { |
| 331 | total: 0, |
| 332 | completed: 0, |
| 333 | failed: 0, |
| 334 | skipped: 0, |
| 335 | tier0Count: 0, |
| 336 | tier1Count: 0, |
| 337 | totalCostCents: 0, |
| 338 | startTime: Date.now(), |
| 339 | }; |
| 340 | |
| 341 | // Ensure jobs exist in queue |
| 342 | await ensureJobsExist(config); |
| 343 | |
| 344 | // Count total pending |
| 345 | const countResult = await db |
| 346 | .select({ count: sql<number>`COUNT(*)::int` }) |
| 347 | .from(pipelineJobs) |
| 348 | .where( |
| 349 | and( |
| 350 | eq(pipelineJobs.jobType, "ai_analysis"), |
| 351 | eq(pipelineJobs.status, "pending"), |
| 352 | ) |
| 353 | ); |
| 354 | progress.total = countResult[0]?.count ?? 0; |
| 355 | |
| 356 | if (progress.total === 0) { |
| 357 | console.log("\nNo pending jobs to process."); |
| 358 | return progress; |
| 359 | } |
| 360 | |
| 361 | console.log(`\nTotal pending jobs: ${progress.total}`); |
| 362 | |
| 363 | // Check budget |
| 364 | const { totalCents: monthlySpent, docCount: monthlyDocs } = await getMonthlySpend(); |
| 365 | const budgetRemaining = config.monthlyCapCents - monthlySpent; |
| 366 | |
| 367 | console.log(`Monthly budget: ${formatCents(config.monthlyCapCents)} (spent: ${formatCents(monthlySpent)} on ${monthlyDocs} docs, remaining: ${formatCents(budgetRemaining)})`); |
| 368 | |
| 369 | if (budgetRemaining <= 0 && config.forceTier !== 0) { |
| 370 | console.log("Monthly budget exhausted. Tier 1 analysis disabled, falling back to Tier 0."); |
| 371 | } |
| 372 | |
| 373 | let processedInSession = 0; |
| 374 | const limit = config.limit ?? Infinity; |
| 375 | let currentBudgetRemaining = budgetRemaining; |
| 376 | |
| 377 | while (processedInSession < limit) { |
| 378 | const batch = await getNextBatch(Math.min(config.batchSize, limit - processedInSession)); |
| 379 | if (batch.length === 0) break; |
| 380 | |
| 381 | for (const job of batch) { |
| 382 | if (processedInSession >= limit) break; |
| 383 | |
| 384 | const meta = (job.metadata as any) ?? {}; |
| 385 | const dataSet = meta.dataSet ?? "unknown"; |
| 386 | const eftaNumber = meta.eftaNumber ?? ""; |
no test coverage detected