feat: gate mentor auto-assign on CONFIRMED finalist status

mentor.autoAssignBulkForRound now skips any project whose finalist
confirmation isn't CONFIRMED — there's no point assigning a mentor to
a team that won't be at the grand finale. Other eligibility rules
(wantsMentorship, admin_selected, already-assigned) are preserved.

Updated existing requested_only and skip-already-assigned tests to seed
CONFIRMED confirmations so they continue to isolate their target gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-28 18:57:18 +02:00
parent 903ec2401f
commit ff355ee10e
2 changed files with 146 additions and 0 deletions

View File

@@ -717,6 +717,10 @@ export const mentorRouter = router({
roundId: input.roundId, roundId: input.roundId,
project: { project: {
mentorAssignment: null, mentorAssignment: null,
// Only assign mentors to projects whose team has confirmed they will
// attend the grand finale. This skips PENDING/DECLINED/EXPIRED/SUPERSEDED
// confirmations and any project without a confirmation row at all.
finalistConfirmation: { status: 'CONFIRMED' },
...(eligibility === 'requested_only' ? { wantsMentorship: true } : {}), ...(eligibility === 'requested_only' ? { wantsMentorship: true } : {}),
}, },
}, },

View File

@@ -182,6 +182,29 @@ describe('mentor.autoAssignBulkForRound', () => {
data: { projectId: projWithoutRequest.id, roundId: round.id, state: 'PENDING' }, data: { projectId: projWithoutRequest.id, roundId: round.id, state: 'PENDING' },
}) })
// Both projects must have CONFIRMED FinalistConfirmation to qualify for
// mentor assignment — the wantsMentorship gate is what we're isolating here.
await prisma.finalistConfirmation.createMany({
data: [
{
projectId: projWithRequest.id,
category: 'STARTUP',
status: 'CONFIRMED',
deadline: new Date(Date.now() + 86_400_000),
token: `tok_${uid()}`,
confirmedAt: new Date(),
},
{
projectId: projWithoutRequest.id,
category: 'STARTUP',
status: 'CONFIRMED',
deadline: new Date(Date.now() + 86_400_000),
token: `tok_${uid()}`,
confirmedAt: new Date(),
},
],
})
const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] }) const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
userIds.push(mentor.id) userIds.push(mentor.id)
@@ -230,6 +253,29 @@ describe('mentor.autoAssignBulkForRound', () => {
data: { projectId: projUnassigned.id, roundId: round.id, state: 'PENDING' }, data: { projectId: projUnassigned.id, roundId: round.id, state: 'PENDING' },
}) })
// Both projects must have CONFIRMED FinalistConfirmation to qualify for
// mentor assignment — the existing-assignment gate is what we're isolating here.
await prisma.finalistConfirmation.createMany({
data: [
{
projectId: projAlreadyAssigned.id,
category: 'STARTUP',
status: 'CONFIRMED',
deadline: new Date(Date.now() + 86_400_000),
token: `tok_${uid()}`,
confirmedAt: new Date(),
},
{
projectId: projUnassigned.id,
category: 'STARTUP',
status: 'CONFIRMED',
deadline: new Date(Date.now() + 86_400_000),
token: `tok_${uid()}`,
confirmedAt: new Date(),
},
],
})
await prisma.mentorAssignment.create({ await prisma.mentorAssignment.create({
data: { data: {
projectId: projAlreadyAssigned.id, projectId: projAlreadyAssigned.id,
@@ -251,6 +297,102 @@ describe('mentor.autoAssignBulkForRound', () => {
expect(stillExisting?.mentorId).toBe(existingMentor.id) // unchanged expect(stillExisting?.mentorId).toBe(existingMentor.id) // unchanged
}) })
it('only assigns mentors to projects with CONFIRMED FinalistConfirmation', async () => {
const admin = await createTestUser('SUPER_ADMIN')
userIds.push(admin.id)
const program = await createTestProgram({ name: `bulk-finalist-${uid()}` })
programIds.push(program.id)
const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
const round = await createTestRound(competition.id, {
roundType: 'MENTORING',
configJson: { eligibility: 'all_advancing' },
})
const projConfirmed = await prisma.project.create({
data: {
id: uid('proj'),
title: 'Confirmed Finalist',
programId: program.id,
tags: ['x'],
wantsMentorship: true,
},
})
const projPending = await prisma.project.create({
data: {
id: uid('proj'),
title: 'Pending Finalist',
programId: program.id,
tags: ['x'],
wantsMentorship: true,
},
})
const projNoConfirmation = await prisma.project.create({
data: {
id: uid('proj'),
title: 'No Confirmation',
programId: program.id,
tags: ['x'],
wantsMentorship: true,
},
})
await prisma.projectRoundState.createMany({
data: [
{ projectId: projConfirmed.id, roundId: round.id, state: 'PENDING' },
{ projectId: projPending.id, roundId: round.id, state: 'PENDING' },
{ projectId: projNoConfirmation.id, roundId: round.id, state: 'PENDING' },
],
})
await prisma.finalistConfirmation.create({
data: {
projectId: projConfirmed.id,
category: 'STARTUP',
status: 'CONFIRMED',
deadline: new Date(Date.now() + 86_400_000),
token: `tok_${uid()}`,
confirmedAt: new Date(),
},
})
await prisma.finalistConfirmation.create({
data: {
projectId: projPending.id,
category: 'STARTUP',
status: 'PENDING',
deadline: new Date(Date.now() + 86_400_000),
token: `tok_${uid()}`,
},
})
const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
userIds.push(mentor.id)
const caller = createCaller(mentorRouter, {
id: admin.id,
email: admin.email,
role: 'SUPER_ADMIN',
})
const result = await caller.autoAssignBulkForRound({ roundId: round.id, useAI: false })
expect(result.assigned).toBe(1)
const confirmedAssigned = await prisma.mentorAssignment.findUnique({
where: { projectId: projConfirmed.id },
})
expect(confirmedAssigned).not.toBeNull()
const pendingAssigned = await prisma.mentorAssignment.findUnique({
where: { projectId: projPending.id },
})
expect(pendingAssigned).toBeNull()
const noConfAssigned = await prisma.mentorAssignment.findUnique({
where: { projectId: projNoConfirmation.id },
})
expect(noConfAssigned).toBeNull()
})
it('refuses with admin_selected eligibility (admin must pick manually)', async () => { it('refuses with admin_selected eligibility (admin must pick manually)', async () => {
const admin = await createTestUser('SUPER_ADMIN') const admin = await createTestUser('SUPER_ADMIN')
userIds.push(admin.id) userIds.push(admin.id)