AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table - Jury invite flow: create user + add to group + send invitation from dialog - Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount - Moved notify-on-advance from competition-level to per-round setting - AI filtering: round-tagged files with newest-first sorting, optional file content extraction - File content extractor service (pdf-parse for PDF, utf-8 for text files) - AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT) - generateAIRecommendations tRPC endpoint with per-round config integration - AI recommendations UI: trigger button, confirmation dialog, per-category results display - Category-aware advance dialog: select/deselect projects by category with target caps - STAGE_ACTIVE bug fix in assignment router Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { Prisma } from '@prisma/client'
|
||||
import { router, adminProcedure, protectedProcedure } from '../trpc'
|
||||
import { logAudit } from '@/server/utils/audit'
|
||||
import { validateRoundConfig, defaultRoundConfig } from '@/types/competition-configs'
|
||||
import { generateShortlist } from '../services/ai-shortlist'
|
||||
import {
|
||||
openWindow,
|
||||
closeWindow,
|
||||
@@ -358,6 +359,74 @@ export const roundRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
// =========================================================================
|
||||
// AI Shortlist Recommendations
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Generate AI-powered shortlist recommendations for a round.
|
||||
* Runs independently for STARTUP and BUSINESS_CONCEPT categories.
|
||||
* Uses per-round config for advancement targets and file parsing.
|
||||
*/
|
||||
generateAIRecommendations: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
roundId: z.string(),
|
||||
rubric: z.string().optional(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const round = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
competitionId: true,
|
||||
configJson: true,
|
||||
},
|
||||
})
|
||||
|
||||
const config = (round.configJson as Record<string, unknown>) ?? {}
|
||||
const startupTopN = (config.startupAdvanceCount as number) || 10
|
||||
const conceptTopN = (config.conceptAdvanceCount as number) || 10
|
||||
const aiParseFiles = !!config.aiParseFiles
|
||||
|
||||
const result = await generateShortlist(
|
||||
{
|
||||
roundId: input.roundId,
|
||||
competitionId: round.competitionId,
|
||||
startupTopN,
|
||||
conceptTopN,
|
||||
rubric: input.rubric,
|
||||
aiParseFiles,
|
||||
},
|
||||
ctx.prisma,
|
||||
)
|
||||
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
action: 'AI_SHORTLIST',
|
||||
entityType: 'Round',
|
||||
entityId: input.roundId,
|
||||
detailsJson: {
|
||||
roundName: round.name,
|
||||
startupTopN,
|
||||
conceptTopN,
|
||||
aiParseFiles,
|
||||
success: result.success,
|
||||
startupCount: result.recommendations.STARTUP.length,
|
||||
conceptCount: result.recommendations.BUSINESS_CONCEPT.length,
|
||||
tokensUsed: result.tokensUsed,
|
||||
errors: result.errors,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return result
|
||||
}),
|
||||
|
||||
// =========================================================================
|
||||
// Submission Window Management
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user