Competition/Round architecture: full platform rewrite (Phases 1-9)
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:
2026-02-15 23:04:15 +01:00
parent 9ab4717f96
commit 6ca39c976b
349 changed files with 69938 additions and 28767 deletions

View File

@@ -0,0 +1,117 @@
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure, protectedProcedure, juryProcedure } from '../trpc'
import {
previewRoundAssignment,
executeRoundAssignment,
getRoundCoverageReport,
getUnassignedQueue,
} from '../services/round-assignment'
export const roundAssignmentRouter = router({
/**
* Preview round assignments without committing
*/
preview: adminProcedure
.input(
z.object({
roundId: z.string(),
honorIntents: z.boolean().default(true),
requiredReviews: z.number().int().min(1).max(20).default(3),
})
)
.query(async ({ ctx, input }) => {
return previewRoundAssignment(
input.roundId,
{
honorIntents: input.honorIntents,
requiredReviews: input.requiredReviews,
},
ctx.prisma,
)
}),
/**
* Execute round assignments (create Assignment records)
*/
execute: adminProcedure
.input(
z.object({
roundId: z.string(),
assignments: z.array(
z.object({
userId: z.string(),
projectId: z.string(),
})
).min(1),
})
)
.mutation(async ({ ctx, input }) => {
const result = await executeRoundAssignment(
input.roundId,
input.assignments,
ctx.user.id,
ctx.prisma,
)
if (result.errors.length > 0 && result.created === 0) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: result.errors.join('; '),
})
}
return result
}),
/**
* Get coverage report for a round
*/
coverageReport: protectedProcedure
.input(
z.object({
roundId: z.string(),
requiredReviews: z.number().int().min(1).max(20).default(3),
})
)
.query(async ({ ctx, input }) => {
return getRoundCoverageReport(input.roundId, input.requiredReviews, ctx.prisma)
}),
/**
* Get projects below required reviews threshold
*/
unassignedQueue: protectedProcedure
.input(
z.object({
roundId: z.string(),
requiredReviews: z.number().int().min(1).max(20).default(3),
})
)
.query(async ({ ctx, input }) => {
return getUnassignedQueue(input.roundId, input.requiredReviews, ctx.prisma)
}),
/**
* Get assignments for the current jury member in a specific round
*/
getMyAssignments: juryProcedure
.input(z.object({ roundId: z.string() }))
.query(async ({ ctx, input }) => {
return ctx.prisma.assignment.findMany({
where: {
roundId: input.roundId,
userId: ctx.user.id,
},
include: {
project: {
select: { id: true, title: true, competitionCategory: true },
},
evaluation: {
select: { id: true, status: true, globalScore: true },
},
},
orderBy: { createdAt: 'asc' },
})
}),
})