Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,22 +17,23 @@ import {
|
||||
} from '../services/in-app-notification'
|
||||
import { logAudit } from '@/server/utils/audit'
|
||||
|
||||
async function runAIAssignmentJob(jobId: string, stageId: string, userId: string) {
|
||||
async function runAIAssignmentJob(jobId: string, roundId: string, userId: string) {
|
||||
try {
|
||||
await prisma.assignmentJob.update({
|
||||
where: { id: jobId },
|
||||
data: { status: 'RUNNING', startedAt: new Date() },
|
||||
})
|
||||
|
||||
const stage = await prisma.stage.findUniqueOrThrow({
|
||||
where: { id: stageId },
|
||||
const round = await prisma.round.findUniqueOrThrow({
|
||||
where: { id: roundId },
|
||||
select: {
|
||||
name: true,
|
||||
configJson: true,
|
||||
competitionId: true,
|
||||
},
|
||||
})
|
||||
|
||||
const config = (stage.configJson ?? {}) as Record<string, unknown>
|
||||
const config = (round.configJson ?? {}) as Record<string, unknown>
|
||||
const requiredReviews = (config.requiredReviews as number) ?? 3
|
||||
const minAssignmentsPerJuror =
|
||||
(config.minLoadPerJuror as number) ??
|
||||
@@ -53,17 +54,17 @@ async function runAIAssignmentJob(jobId: string, stageId: string, userId: string
|
||||
maxAssignments: true,
|
||||
_count: {
|
||||
select: {
|
||||
assignments: { where: { stageId } },
|
||||
assignments: { where: { roundId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const projectStageStates = await prisma.projectStageState.findMany({
|
||||
where: { stageId },
|
||||
const projectRoundStates = await prisma.projectRoundState.findMany({
|
||||
where: { roundId },
|
||||
select: { projectId: true },
|
||||
})
|
||||
const projectIds = projectStageStates.map((pss) => pss.projectId)
|
||||
const projectIds = projectRoundStates.map((prs) => prs.projectId)
|
||||
|
||||
const projects = await prisma.project.findMany({
|
||||
where: { id: { in: projectIds } },
|
||||
@@ -73,12 +74,12 @@ async function runAIAssignmentJob(jobId: string, stageId: string, userId: string
|
||||
description: true,
|
||||
tags: true,
|
||||
teamName: true,
|
||||
_count: { select: { assignments: { where: { stageId } } } },
|
||||
_count: { select: { assignments: { where: { roundId } } } },
|
||||
},
|
||||
})
|
||||
|
||||
const existingAssignments = await prisma.assignment.findMany({
|
||||
where: { stageId },
|
||||
where: { roundId },
|
||||
select: { userId: true, projectId: true },
|
||||
})
|
||||
|
||||
@@ -126,7 +127,7 @@ async function runAIAssignmentJob(jobId: string, stageId: string, userId: string
|
||||
projects,
|
||||
constraints,
|
||||
userId,
|
||||
stageId,
|
||||
roundId,
|
||||
onProgress
|
||||
)
|
||||
|
||||
@@ -157,12 +158,12 @@ async function runAIAssignmentJob(jobId: string, stageId: string, userId: string
|
||||
await notifyAdmins({
|
||||
type: NotificationTypes.AI_SUGGESTIONS_READY,
|
||||
title: 'AI Assignment Suggestions Ready',
|
||||
message: `AI generated ${result.suggestions.length} assignment suggestions for ${stage.name || 'stage'}${result.fallbackUsed ? ' (using fallback algorithm)' : ''}.`,
|
||||
linkUrl: `/admin/rounds/pipeline/stages/${stageId}/assignments`,
|
||||
message: `AI generated ${result.suggestions.length} assignment suggestions for ${round.name || 'round'}${result.fallbackUsed ? ' (using fallback algorithm)' : ''}.`,
|
||||
linkUrl: `/admin/competitions/${round.competitionId}/assignments`,
|
||||
linkLabel: 'View Suggestions',
|
||||
priority: 'high',
|
||||
metadata: {
|
||||
stageId,
|
||||
roundId,
|
||||
jobId,
|
||||
projectCount: projects.length,
|
||||
suggestionsCount: result.suggestions.length,
|
||||
@@ -187,10 +188,10 @@ async function runAIAssignmentJob(jobId: string, stageId: string, userId: string
|
||||
|
||||
export const assignmentRouter = router({
|
||||
listByStage: adminProcedure
|
||||
.input(z.object({ stageId: z.string() }))
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
return ctx.prisma.assignment.findMany({
|
||||
where: { stageId: input.stageId },
|
||||
where: { roundId: input.roundId },
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true, expertiseTags: true } },
|
||||
project: { select: { id: true, title: true, tags: true } },
|
||||
@@ -233,18 +234,18 @@ export const assignmentRouter = router({
|
||||
myAssignments: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
stageId: z.string().optional(),
|
||||
roundId: z.string().optional(),
|
||||
status: z.enum(['all', 'pending', 'completed']).default('all'),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const where: Record<string, unknown> = {
|
||||
userId: ctx.user.id,
|
||||
stage: { status: 'STAGE_ACTIVE' },
|
||||
round: { status: 'STAGE_ACTIVE' },
|
||||
}
|
||||
|
||||
if (input.stageId) {
|
||||
where.stageId = input.stageId
|
||||
if (input.roundId) {
|
||||
where.roundId = input.roundId
|
||||
}
|
||||
|
||||
if (input.status === 'pending') {
|
||||
@@ -259,7 +260,7 @@ export const assignmentRouter = router({
|
||||
project: {
|
||||
include: { files: true },
|
||||
},
|
||||
stage: true,
|
||||
round: true,
|
||||
evaluation: true,
|
||||
},
|
||||
orderBy: [{ isCompleted: 'asc' }, { createdAt: 'asc' }],
|
||||
@@ -277,7 +278,7 @@ export const assignmentRouter = router({
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true } },
|
||||
project: { include: { files: true } },
|
||||
stage: { include: { evaluationForms: { where: { isActive: true } } } },
|
||||
round: { include: { evaluationForms: { where: { isActive: true } } } },
|
||||
evaluation: true,
|
||||
},
|
||||
})
|
||||
@@ -304,7 +305,7 @@ export const assignmentRouter = router({
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
projectId: z.string(),
|
||||
stageId: z.string(),
|
||||
roundId: z.string(),
|
||||
isRequired: z.boolean().default(true),
|
||||
forceOverride: z.boolean().default(false),
|
||||
})
|
||||
@@ -312,10 +313,10 @@ export const assignmentRouter = router({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const existing = await ctx.prisma.assignment.findUnique({
|
||||
where: {
|
||||
userId_projectId_stageId: {
|
||||
userId_projectId_roundId: {
|
||||
userId: input.userId,
|
||||
projectId: input.projectId,
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -328,8 +329,8 @@ export const assignmentRouter = router({
|
||||
}
|
||||
|
||||
const [stage, user] = await Promise.all([
|
||||
ctx.prisma.stage.findUniqueOrThrow({
|
||||
where: { id: input.stageId },
|
||||
ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { configJson: true },
|
||||
}),
|
||||
ctx.prisma.user.findUniqueOrThrow({
|
||||
@@ -346,7 +347,7 @@ export const assignmentRouter = router({
|
||||
const effectiveMax = user.maxAssignments ?? maxAssignmentsPerJuror
|
||||
|
||||
const currentCount = await ctx.prisma.assignment.count({
|
||||
where: { userId: input.userId, stageId: input.stageId },
|
||||
where: { userId: input.userId, roundId: input.roundId },
|
||||
})
|
||||
|
||||
// Check if at or over limit
|
||||
@@ -387,8 +388,8 @@ export const assignmentRouter = router({
|
||||
where: { id: input.projectId },
|
||||
select: { title: true },
|
||||
}),
|
||||
ctx.prisma.stage.findUnique({
|
||||
where: { id: input.stageId },
|
||||
ctx.prisma.round.findUnique({
|
||||
where: { id: input.roundId },
|
||||
select: { name: true, windowCloseAt: true },
|
||||
}),
|
||||
])
|
||||
@@ -408,7 +409,7 @@ export const assignmentRouter = router({
|
||||
type: NotificationTypes.ASSIGNED_TO_PROJECT,
|
||||
title: 'New Project Assignment',
|
||||
message: `You have been assigned to evaluate "${project.title}" for ${stageInfo.name}.`,
|
||||
linkUrl: `/jury/stages`,
|
||||
linkUrl: `/jury/competitions`,
|
||||
linkLabel: 'View Assignment',
|
||||
metadata: {
|
||||
projectName: project.title,
|
||||
@@ -428,7 +429,7 @@ export const assignmentRouter = router({
|
||||
bulkCreate: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
stageId: z.string(),
|
||||
roundId: z.string(),
|
||||
assignments: z.array(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
@@ -448,7 +449,7 @@ export const assignmentRouter = router({
|
||||
maxAssignments: true,
|
||||
_count: {
|
||||
select: {
|
||||
assignments: { where: { stageId: input.stageId } },
|
||||
assignments: { where: { roundId: input.roundId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -456,8 +457,8 @@ export const assignmentRouter = router({
|
||||
const userMap = new Map(users.map((u) => [u.id, u]))
|
||||
|
||||
// Get stage default max
|
||||
const stage = await ctx.prisma.stage.findUniqueOrThrow({
|
||||
where: { id: input.stageId },
|
||||
const stage = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { configJson: true, name: true, windowCloseAt: true },
|
||||
})
|
||||
const config = (stage.configJson ?? {}) as Record<string, unknown>
|
||||
@@ -494,7 +495,7 @@ export const assignmentRouter = router({
|
||||
const result = await ctx.prisma.assignment.createMany({
|
||||
data: allowedAssignments.map((a) => ({
|
||||
...a,
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
method: 'BULK',
|
||||
createdBy: ctx.user.id,
|
||||
})),
|
||||
@@ -550,7 +551,7 @@ export const assignmentRouter = router({
|
||||
type: NotificationTypes.BATCH_ASSIGNED,
|
||||
title: `${projectCount} Projects Assigned`,
|
||||
message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${stage?.name || 'this stage'}.`,
|
||||
linkUrl: `/jury/stages`,
|
||||
linkUrl: `/jury/competitions`,
|
||||
linkLabel: 'View Assignments',
|
||||
metadata: {
|
||||
projectCount,
|
||||
@@ -601,20 +602,20 @@ export const assignmentRouter = router({
|
||||
* Get assignment statistics for a round
|
||||
*/
|
||||
getStats: adminProcedure
|
||||
.input(z.object({ stageId: z.string() }))
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const stage = await ctx.prisma.stage.findUniqueOrThrow({
|
||||
where: { id: input.stageId },
|
||||
const stage = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { configJson: true },
|
||||
})
|
||||
const config = (stage.configJson ?? {}) as Record<string, unknown>
|
||||
const requiredReviews = (config.requiredReviews as number) ?? 3
|
||||
|
||||
const projectStageStates = await ctx.prisma.projectStageState.findMany({
|
||||
where: { stageId: input.stageId },
|
||||
const projectRoundStates = await ctx.prisma.projectRoundState.findMany({
|
||||
where: { roundId: input.roundId },
|
||||
select: { projectId: true },
|
||||
})
|
||||
const projectIds = projectStageStates.map((pss) => pss.projectId)
|
||||
const projectIds = projectRoundStates.map((pss) => pss.projectId)
|
||||
|
||||
const [
|
||||
totalAssignments,
|
||||
@@ -622,13 +623,13 @@ export const assignmentRouter = router({
|
||||
assignmentsByUser,
|
||||
projectCoverage,
|
||||
] = await Promise.all([
|
||||
ctx.prisma.assignment.count({ where: { stageId: input.stageId } }),
|
||||
ctx.prisma.assignment.count({ where: { roundId: input.roundId } }),
|
||||
ctx.prisma.assignment.count({
|
||||
where: { stageId: input.stageId, isCompleted: true },
|
||||
where: { roundId: input.roundId, isCompleted: true },
|
||||
}),
|
||||
ctx.prisma.assignment.groupBy({
|
||||
by: ['userId'],
|
||||
where: { stageId: input.stageId },
|
||||
where: { roundId: input.roundId },
|
||||
_count: true,
|
||||
}),
|
||||
ctx.prisma.project.findMany({
|
||||
@@ -636,7 +637,7 @@ export const assignmentRouter = router({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
_count: { select: { assignments: { where: { stageId: input.stageId } } } },
|
||||
_count: { select: { assignments: { where: { roundId: input.roundId } } } },
|
||||
},
|
||||
}),
|
||||
])
|
||||
@@ -670,12 +671,12 @@ export const assignmentRouter = router({
|
||||
getSuggestions: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
stageId: z.string(),
|
||||
roundId: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const stage = await ctx.prisma.stage.findUniqueOrThrow({
|
||||
where: { id: input.stageId },
|
||||
const stage = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { configJson: true },
|
||||
})
|
||||
const config = (stage.configJson ?? {}) as Record<string, unknown>
|
||||
@@ -705,17 +706,17 @@ export const assignmentRouter = router({
|
||||
maxAssignments: true,
|
||||
_count: {
|
||||
select: {
|
||||
assignments: { where: { stageId: input.stageId } },
|
||||
assignments: { where: { roundId: input.roundId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const projectStageStates = await ctx.prisma.projectStageState.findMany({
|
||||
where: { stageId: input.stageId },
|
||||
const projectRoundStates = await ctx.prisma.projectRoundState.findMany({
|
||||
where: { roundId: input.roundId },
|
||||
select: { projectId: true },
|
||||
})
|
||||
const projectIds = projectStageStates.map((pss) => pss.projectId)
|
||||
const projectIds = projectRoundStates.map((pss) => pss.projectId)
|
||||
|
||||
const projects = await ctx.prisma.project.findMany({
|
||||
where: { id: { in: projectIds } },
|
||||
@@ -727,12 +728,12 @@ export const assignmentRouter = router({
|
||||
projectTags: {
|
||||
include: { tag: { select: { name: true } } },
|
||||
},
|
||||
_count: { select: { assignments: { where: { stageId: input.stageId } } } },
|
||||
_count: { select: { assignments: { where: { roundId: input.roundId } } } },
|
||||
},
|
||||
})
|
||||
|
||||
const existingAssignments = await ctx.prisma.assignment.findMany({
|
||||
where: { stageId: input.stageId },
|
||||
where: { roundId: input.roundId },
|
||||
select: { userId: true, projectId: true },
|
||||
})
|
||||
const assignmentSet = new Set(
|
||||
@@ -743,7 +744,7 @@ export const assignmentRouter = router({
|
||||
const jurorCategoryDistribution = new Map<string, Record<string, number>>()
|
||||
if (categoryQuotas) {
|
||||
const assignmentsWithCategory = await ctx.prisma.assignment.findMany({
|
||||
where: { stageId: input.stageId },
|
||||
where: { roundId: input.roundId },
|
||||
select: {
|
||||
userId: true,
|
||||
project: { select: { competitionCategory: true } },
|
||||
@@ -884,14 +885,14 @@ export const assignmentRouter = router({
|
||||
getAISuggestions: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
stageId: z.string(),
|
||||
roundId: z.string(),
|
||||
useAI: z.boolean().default(true),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const completedJob = await ctx.prisma.assignmentJob.findFirst({
|
||||
where: {
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
status: 'COMPLETED',
|
||||
},
|
||||
orderBy: { completedAt: 'desc' },
|
||||
@@ -914,7 +915,7 @@ export const assignmentRouter = router({
|
||||
}>
|
||||
|
||||
const existingAssignments = await ctx.prisma.assignment.findMany({
|
||||
where: { stageId: input.stageId },
|
||||
where: { roundId: input.roundId },
|
||||
select: { userId: true, projectId: true },
|
||||
})
|
||||
const assignmentSet = new Set(
|
||||
@@ -949,7 +950,7 @@ export const assignmentRouter = router({
|
||||
applyAISuggestions: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
stageId: z.string(),
|
||||
roundId: z.string(),
|
||||
assignments: z.array(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
@@ -977,15 +978,15 @@ export const assignmentRouter = router({
|
||||
maxAssignments: true,
|
||||
_count: {
|
||||
select: {
|
||||
assignments: { where: { stageId: input.stageId } },
|
||||
assignments: { where: { roundId: input.roundId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
const userMap = new Map(users.map((u) => [u.id, u]))
|
||||
|
||||
const stageData = await ctx.prisma.stage.findUniqueOrThrow({
|
||||
where: { id: input.stageId },
|
||||
const stageData = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { configJson: true },
|
||||
})
|
||||
const config = (stageData.configJson ?? {}) as Record<string, unknown>
|
||||
@@ -1020,7 +1021,7 @@ export const assignmentRouter = router({
|
||||
data: assignmentsToCreate.map((a) => ({
|
||||
userId: a.userId,
|
||||
projectId: a.projectId,
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
method: input.usedAI ? 'AI_SUGGESTED' : 'ALGORITHM',
|
||||
aiConfidenceScore: a.confidenceScore,
|
||||
expertiseMatchScore: a.expertiseMatchScore,
|
||||
@@ -1036,7 +1037,7 @@ export const assignmentRouter = router({
|
||||
action: input.usedAI ? 'APPLY_AI_SUGGESTIONS' : 'APPLY_SUGGESTIONS',
|
||||
entityType: 'Assignment',
|
||||
detailsJson: {
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
count: created.count,
|
||||
usedAI: input.usedAI,
|
||||
forceOverride: input.forceOverride,
|
||||
@@ -1055,8 +1056,8 @@ export const assignmentRouter = router({
|
||||
{} as Record<string, number>
|
||||
)
|
||||
|
||||
const stage = await ctx.prisma.stage.findUnique({
|
||||
where: { id: input.stageId },
|
||||
const stage = await ctx.prisma.round.findUnique({
|
||||
where: { id: input.roundId },
|
||||
select: { name: true, windowCloseAt: true },
|
||||
})
|
||||
|
||||
@@ -1083,7 +1084,7 @@ export const assignmentRouter = router({
|
||||
type: NotificationTypes.BATCH_ASSIGNED,
|
||||
title: `${projectCount} Projects Assigned`,
|
||||
message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${stage?.name || 'this stage'}.`,
|
||||
linkUrl: `/jury/stages`,
|
||||
linkUrl: `/jury/competitions`,
|
||||
linkLabel: 'View Assignments',
|
||||
metadata: {
|
||||
projectCount,
|
||||
@@ -1107,7 +1108,7 @@ export const assignmentRouter = router({
|
||||
applySuggestions: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
stageId: z.string(),
|
||||
roundId: z.string(),
|
||||
assignments: z.array(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
@@ -1132,15 +1133,15 @@ export const assignmentRouter = router({
|
||||
maxAssignments: true,
|
||||
_count: {
|
||||
select: {
|
||||
assignments: { where: { stageId: input.stageId } },
|
||||
assignments: { where: { roundId: input.roundId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
const userMap = new Map(users.map((u) => [u.id, u]))
|
||||
|
||||
const stageData = await ctx.prisma.stage.findUniqueOrThrow({
|
||||
where: { id: input.stageId },
|
||||
const stageData = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { configJson: true },
|
||||
})
|
||||
const config = (stageData.configJson ?? {}) as Record<string, unknown>
|
||||
@@ -1175,7 +1176,7 @@ export const assignmentRouter = router({
|
||||
data: assignmentsToCreate.map((a) => ({
|
||||
userId: a.userId,
|
||||
projectId: a.projectId,
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
method: 'ALGORITHM',
|
||||
aiReasoning: a.reasoning,
|
||||
createdBy: ctx.user.id,
|
||||
@@ -1189,7 +1190,7 @@ export const assignmentRouter = router({
|
||||
action: 'APPLY_SUGGESTIONS',
|
||||
entityType: 'Assignment',
|
||||
detailsJson: {
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
count: created.count,
|
||||
forceOverride: input.forceOverride,
|
||||
skippedDueToCapacity,
|
||||
@@ -1207,8 +1208,8 @@ export const assignmentRouter = router({
|
||||
{} as Record<string, number>
|
||||
)
|
||||
|
||||
const stage = await ctx.prisma.stage.findUnique({
|
||||
where: { id: input.stageId },
|
||||
const stage = await ctx.prisma.round.findUnique({
|
||||
where: { id: input.roundId },
|
||||
select: { name: true, windowCloseAt: true },
|
||||
})
|
||||
|
||||
@@ -1235,7 +1236,7 @@ export const assignmentRouter = router({
|
||||
type: NotificationTypes.BATCH_ASSIGNED,
|
||||
title: `${projectCount} Projects Assigned`,
|
||||
message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${stage?.name || 'this stage'}.`,
|
||||
linkUrl: `/jury/stages`,
|
||||
linkUrl: `/jury/competitions`,
|
||||
linkLabel: 'View Assignments',
|
||||
metadata: {
|
||||
projectCount,
|
||||
@@ -1257,11 +1258,11 @@ export const assignmentRouter = router({
|
||||
* Start an AI assignment job (background processing)
|
||||
*/
|
||||
startAIAssignmentJob: adminProcedure
|
||||
.input(z.object({ stageId: z.string() }))
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const existingJob = await ctx.prisma.assignmentJob.findFirst({
|
||||
where: {
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
status: { in: ['PENDING', 'RUNNING'] },
|
||||
},
|
||||
})
|
||||
@@ -1282,12 +1283,12 @@ export const assignmentRouter = router({
|
||||
|
||||
const job = await ctx.prisma.assignmentJob.create({
|
||||
data: {
|
||||
stageId: input.stageId,
|
||||
roundId: input.roundId,
|
||||
status: 'PENDING',
|
||||
},
|
||||
})
|
||||
|
||||
runAIAssignmentJob(job.id, input.stageId, ctx.user.id).catch(console.error)
|
||||
runAIAssignmentJob(job.id, input.roundId, ctx.user.id).catch(console.error)
|
||||
|
||||
return { jobId: job.id }
|
||||
}),
|
||||
@@ -1321,10 +1322,10 @@ export const assignmentRouter = router({
|
||||
* Get the latest AI assignment job for a round
|
||||
*/
|
||||
getLatestAIAssignmentJob: adminProcedure
|
||||
.input(z.object({ stageId: z.string() }))
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const job = await ctx.prisma.assignmentJob.findFirst({
|
||||
where: { stageId: input.stageId },
|
||||
where: { roundId: input.roundId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user