Add background filtering jobs, improved date picker, AI reasoning display
- Implement background job system for AI filtering to avoid HTTP timeouts - Add FilteringJob model to track progress of long-running filtering operations - Add real-time progress polling for filtering operations on round details page - Create custom DateTimePicker component with calendar popup (no year picker hassle) - Fix round date persistence bug (refetchOnWindowFocus was resetting form state) - Integrate filtering controls into round details page for filtering rounds - Display AI reasoning for flagged/filtered projects in results table - Add onboarding system scaffolding (schema, routes, basic UI) - Allow setting round dates in the past for manual overrides Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,16 @@ import {
|
||||
type AnonymizedProjectForAI,
|
||||
type ProjectAIMapping,
|
||||
} from './anonymization'
|
||||
import type { Prisma, FileType, SubmissionSource } from '@prisma/client'
|
||||
import type { Prisma, FileType, SubmissionSource, PrismaClient } from '@prisma/client'
|
||||
|
||||
// ─── Progress Callback Type ─────────────────────────────────────────────────
|
||||
|
||||
export type ProgressCallback = (progress: {
|
||||
currentBatch: number
|
||||
totalBatches: number
|
||||
processedCount: number
|
||||
tokensUsed?: number
|
||||
}) => Promise<void>
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -410,7 +419,8 @@ export async function executeAIScreening(
|
||||
config: AIScreeningConfig,
|
||||
projects: ProjectForFiltering[],
|
||||
userId?: string,
|
||||
entityId?: string
|
||||
entityId?: string,
|
||||
onProgress?: ProgressCallback
|
||||
): Promise<Map<string, AIScreeningResult>> {
|
||||
const results = new Map<string, AIScreeningResult>()
|
||||
|
||||
@@ -444,13 +454,15 @@ export async function executeAIScreening(
|
||||
}
|
||||
|
||||
let totalTokens = 0
|
||||
const totalBatches = Math.ceil(anonymized.length / BATCH_SIZE)
|
||||
|
||||
// Process in batches
|
||||
for (let i = 0; i < anonymized.length; i += BATCH_SIZE) {
|
||||
const batchAnon = anonymized.slice(i, i + BATCH_SIZE)
|
||||
const batchMappings = mappings.slice(i, i + BATCH_SIZE)
|
||||
const currentBatch = Math.floor(i / BATCH_SIZE) + 1
|
||||
|
||||
console.log(`[AI Filtering] Processing batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(anonymized.length / BATCH_SIZE)}`)
|
||||
console.log(`[AI Filtering] Processing batch ${currentBatch}/${totalBatches}`)
|
||||
|
||||
const { results: batchResults, tokensUsed } = await processAIBatch(
|
||||
openai,
|
||||
@@ -468,6 +480,16 @@ export async function executeAIScreening(
|
||||
for (const [id, result] of batchResults) {
|
||||
results.set(id, result)
|
||||
}
|
||||
|
||||
// Report progress
|
||||
if (onProgress) {
|
||||
await onProgress({
|
||||
currentBatch,
|
||||
totalBatches,
|
||||
processedCount: Math.min((currentBatch) * BATCH_SIZE, anonymized.length),
|
||||
tokensUsed: totalTokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[AI Filtering] Completed. Total tokens: ${totalTokens}`)
|
||||
@@ -513,7 +535,8 @@ export async function executeFilteringRules(
|
||||
rules: FilteringRuleInput[],
|
||||
projects: ProjectForFiltering[],
|
||||
userId?: string,
|
||||
roundId?: string
|
||||
roundId?: string,
|
||||
onProgress?: ProgressCallback
|
||||
): Promise<ProjectFilteringResult[]> {
|
||||
const activeRules = rules
|
||||
.filter((r) => r.isActive)
|
||||
@@ -528,7 +551,7 @@ export async function executeFilteringRules(
|
||||
|
||||
for (const aiRule of aiRules) {
|
||||
const config = aiRule.configJson as unknown as AIScreeningConfig
|
||||
const screeningResults = await executeAIScreening(config, projects, userId, roundId)
|
||||
const screeningResults = await executeAIScreening(config, projects, userId, roundId, onProgress)
|
||||
aiResults.set(aiRule.id, screeningResults)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user