Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
import { z } from 'zod'
|
|
|
|
|
import { router, adminProcedure } from '../trpc'
|
|
|
|
|
import { resolveMemberContext } from '@/server/services/competition-context'
|
|
|
|
|
import { evaluateAssignmentPolicy } from '@/server/services/assignment-policy'
|
|
|
|
|
|
|
|
|
|
export const assignmentPolicyRouter = router({
|
|
|
|
|
/**
|
|
|
|
|
* Get the fully-resolved assignment policy for a specific member in a round.
|
|
|
|
|
* Returns cap, cap mode, buffer, category bias — all with provenance.
|
|
|
|
|
*/
|
|
|
|
|
getMemberPolicy: adminProcedure
|
|
|
|
|
.input(z.object({ roundId: z.string(), userId: z.string() }))
|
|
|
|
|
.query(async ({ input }) => {
|
|
|
|
|
const ctx = await resolveMemberContext(input.roundId, input.userId)
|
|
|
|
|
return evaluateAssignmentPolicy(ctx)
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get policy summary for all members in a jury group for a given round.
|
|
|
|
|
* Useful for admin dashboards showing cap compliance across the group.
|
|
|
|
|
*/
|
|
|
|
|
getGroupPolicySummary: adminProcedure
|
|
|
|
|
.input(z.object({ juryGroupId: z.string(), roundId: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const members = await ctx.prisma.juryGroupMember.findMany({
|
|
|
|
|
where: { juryGroupId: input.juryGroupId },
|
|
|
|
|
include: {
|
|
|
|
|
user: { select: { id: true, name: true, email: true } },
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const results = await Promise.all(
|
|
|
|
|
members.map(async (member) => {
|
|
|
|
|
try {
|
|
|
|
|
const memberCtx = await resolveMemberContext(input.roundId, member.userId)
|
|
|
|
|
const policy = evaluateAssignmentPolicy(memberCtx)
|
|
|
|
|
return {
|
|
|
|
|
userId: member.userId,
|
|
|
|
|
userName: member.user.name,
|
|
|
|
|
userEmail: member.user.email,
|
|
|
|
|
role: member.role,
|
|
|
|
|
policy,
|
|
|
|
|
}
|
2026-03-07 16:18:24 +01:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[AssignmentPolicy] Failed to resolve member context:', err)
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return results.filter(Boolean)
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get cap compliance report for a round.
|
|
|
|
|
* Groups members into overCap, atCap, belowCap, and noCap buckets.
|
|
|
|
|
*/
|
|
|
|
|
getCapComplianceReport: adminProcedure
|
|
|
|
|
.input(z.object({ roundId: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const round = await ctx.prisma.round.findUniqueOrThrow({
|
|
|
|
|
where: { id: input.roundId },
|
|
|
|
|
select: { juryGroupId: true },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!round.juryGroupId) {
|
|
|
|
|
return { overCap: [], atCap: [], belowCap: [], noCap: [] }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const members = await ctx.prisma.juryGroupMember.findMany({
|
|
|
|
|
where: { juryGroupId: round.juryGroupId },
|
|
|
|
|
include: {
|
|
|
|
|
user: { select: { id: true, name: true, email: true } },
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const report: {
|
|
|
|
|
overCap: Array<{ userId: string; userName: string | null; overCapBy: number }>
|
|
|
|
|
atCap: Array<{ userId: string; userName: string | null }>
|
|
|
|
|
belowCap: Array<{ userId: string; userName: string | null; remaining: number }>
|
|
|
|
|
noCap: Array<{ userId: string; userName: string | null }>
|
|
|
|
|
} = { overCap: [], atCap: [], belowCap: [], noCap: [] }
|
|
|
|
|
|
|
|
|
|
for (const member of members) {
|
|
|
|
|
try {
|
|
|
|
|
const memberCtx = await resolveMemberContext(input.roundId, member.userId)
|
|
|
|
|
const policy = evaluateAssignmentPolicy(memberCtx)
|
|
|
|
|
|
|
|
|
|
if (policy.effectiveCapMode.value === 'NONE') {
|
|
|
|
|
report.noCap.push({ userId: member.userId, userName: member.user.name })
|
|
|
|
|
} else if (policy.isOverCap) {
|
|
|
|
|
report.overCap.push({
|
|
|
|
|
userId: member.userId,
|
|
|
|
|
userName: member.user.name,
|
|
|
|
|
overCapBy: policy.overCapBy,
|
|
|
|
|
})
|
|
|
|
|
} else if (policy.remainingCapacity === 0) {
|
|
|
|
|
report.atCap.push({ userId: member.userId, userName: member.user.name })
|
|
|
|
|
} else {
|
|
|
|
|
report.belowCap.push({
|
|
|
|
|
userId: member.userId,
|
|
|
|
|
userName: member.user.name,
|
|
|
|
|
remaining: policy.remainingCapacity,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-07 16:18:24 +01:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[AssignmentPolicy] Failed to evaluate policy for member:', err)
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return report
|
|
|
|
|
}),
|
|
|
|
|
})
|