From fe249d7892913ee96564ff21ee1e3136dd95533c Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Tue, 18 Nov 2025 02:10:14 -0800 Subject: [PATCH 01/16] Get the judge to team pairings from the previous round --- .../judgeToTeam/getJudgeToTeamPairings.ts | 26 +++++++++++++++++++ .../_utils/matching/judgesToTeamsAlgorithm.ts | 13 +++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts diff --git a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts new file mode 100644 index 00000000..d70c1f83 --- /dev/null +++ b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts @@ -0,0 +1,26 @@ +import JudgeToTeam from '@typeDefs/judgeToTeam'; +import { getDatabase } from '@utils/mongodb/mongoClient.mjs'; +import parseAndReplace from '@utils/request/parseAndReplace'; +import { HttpError } from '@utils/response/Errors'; +import { ObjectId } from 'mongodb'; +import Submission from '@typeDefs/submission'; + +export const GetJudgeToTeamPairings = async () => { + try { + const db = await getDatabase(); + const submissions = await db + .collection('submissions') + .find() + .toArray(); + + const pairings = submissions.map((submission: Submission) => ({ + judge_id: submission.judge_id, + team_id: submission.team_id + })); + // erm im not sure if i did this right?? + + return { ok: true, body: pairings as JudgeToTeam[], error: null}; + const error = e as HttpError; + return { ok: false, body: null, error: error.message }; + } +} \ No newline at end of file diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index ddad3c8f..31ecc236 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -6,6 +6,7 @@ import { optedHDTracks, nonHDTracks } from '@data/tracks'; import { GetManyUsers } from '@datalib/users/getUser'; import { GetManyTeams } from '@datalib/teams/getTeam'; +import { GetJudgeToTeamPairings } from '@datalib/judgeToTeam/getJudgeToTeamPairings'; interface Judge { user: User; @@ -66,7 +67,7 @@ export default async function matchAllTeams(options?: { alpha?: number }) { const teamMatchQualities: { [teamId: string]: number[] } = {}; const teamJudgeDomainTypes: { [teamId: string]: string[] } = {}; - const rounds = 3; + const rounds = 2; const ALPHA = options?.alpha ?? 4; // Fetch all checked in judges. const judgesResponse = await GetManyUsers({ @@ -157,6 +158,16 @@ export default async function matchAllTeams(options?: { alpha?: number }) { .filter((team) => team.tracks.length < rounds) .map((team) => [team._id ?? '', rounds - team.tracks.length]) ); + + // Get previous pairings + const previousPairings = await GetJudgeToTeamPairings(); + if (previousPairings.ok) { + const isSecondRound = previousPairings.body.length !== 0 ? true : false; + } else { + console.log(previousPairings.error); + const isSecondRound = false; + } + // Main loop: process each team for each round. for (let domainIndex = 0; domainIndex < rounds; domainIndex++) { for (const team of modifiedTeams) { From ff8dfd8495f7c56eab6f96f83d2924b4699115c6 Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Tue, 18 Nov 2025 02:33:16 -0800 Subject: [PATCH 02/16] Oppsie i accidentiy deleted catch --- app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts index d70c1f83..cf8288a1 100644 --- a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts +++ b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts @@ -19,8 +19,10 @@ export const GetJudgeToTeamPairings = async () => { })); // erm im not sure if i did this right?? - return { ok: true, body: pairings as JudgeToTeam[], error: null}; + return { ok: true, body: pairings as JudgeToTeam[], error: null}; // um not sure if the format in the db is the exact same as the judgetoteam?? + } catch (e) { const error = e as HttpError; return { ok: false, body: null, error: error.message }; } -} \ No newline at end of file +} + From e4307c64321db3e7cf4568b571ed6113ca6e297b Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Tue, 18 Nov 2025 02:35:04 -0800 Subject: [PATCH 03/16] Ignores judge to team pair if they were paired before (hopefully) --- .../_utils/matching/judgesToTeamsAlgorithm.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index 31ecc236..74a8cabf 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -159,13 +159,14 @@ export default async function matchAllTeams(options?: { alpha?: number }) { .map((team) => [team._id ?? '', rounds - team.tracks.length]) ); - // Get previous pairings + // Get previous pairings and push it to the judgeToTeam array (so that it triggers duplicate exists??) const previousPairings = await GetJudgeToTeamPairings(); - if (previousPairings.ok) { - const isSecondRound = previousPairings.body.length !== 0 ? true : false; + if (previousPairings.body) { + //const isSecondRound = previousPairings.body.length !== 0 ? true : false; // theres a red line?? + judgeToTeam.push(...previousPairings.body); } else { console.log(previousPairings.error); - const isSecondRound = false; + //const isSecondRound = false; } // Main loop: process each team for each round. @@ -223,6 +224,11 @@ export default async function matchAllTeams(options?: { alpha?: number }) { shuffleArray(modifiedTeams); } + // Remove the previous pairings?? + if (previousPairings.body) { + judgeToTeam.splice(0, previousPairings.body.length); + } + console.log('No. of judgeToTeam:', judgeToTeam.length); const judgeAssignments = judgesQueue.map((judge) => judge.teamsAssigned); From be0cd95215ede70cfb8914585ad75c1564ade9f3 Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:53:24 -0800 Subject: [PATCH 04/16] Fixed bugs: current number of rounds did not match validation error checking --- app/(api)/_actions/logic/checkMatches.ts | 4 ++-- app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/(api)/_actions/logic/checkMatches.ts b/app/(api)/_actions/logic/checkMatches.ts index 54fe18f8..6068cc87 100644 --- a/app/(api)/_actions/logic/checkMatches.ts +++ b/app/(api)/_actions/logic/checkMatches.ts @@ -4,7 +4,7 @@ export default function checkMatches( matches: Submission[], teamsLength: number ) { - if (matches.length < 3 * teamsLength) return false; + if (matches.length < 2 * teamsLength) return false; let valid = true; const mp: Map = new Map(); @@ -18,7 +18,7 @@ export default function checkMatches( } mp.forEach((count) => { - if (count !== 3) valid = false; + if (count !== 2) valid = false; }); return valid; diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index 74a8cabf..eca8a26f 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -161,7 +161,7 @@ export default async function matchAllTeams(options?: { alpha?: number }) { // Get previous pairings and push it to the judgeToTeam array (so that it triggers duplicate exists??) const previousPairings = await GetJudgeToTeamPairings(); - if (previousPairings.body) { + if (previousPairings.ok && previousPairings.body) { //const isSecondRound = previousPairings.body.length !== 0 ? true : false; // theres a red line?? judgeToTeam.push(...previousPairings.body); } else { From 713ea5ec6e3eab0dea9d2b8f4810608d633d285e Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:36:34 -0800 Subject: [PATCH 05/16] Commented out submissions validation error check (because current algorithm checks for that) --- app/(api)/_actions/logic/matchTeams.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/(api)/_actions/logic/matchTeams.ts b/app/(api)/_actions/logic/matchTeams.ts index 157245ce..4c5bf3ba 100644 --- a/app/(api)/_actions/logic/matchTeams.ts +++ b/app/(api)/_actions/logic/matchTeams.ts @@ -11,7 +11,7 @@ import checkMatches from '@actions/logic/checkMatches'; export default async function matchTeams( options: { alpha: number } = { alpha: 4 } ) { - const submissionsResponse = await GetManySubmissions(); + /*const submissionsResponse = await GetManySubmissions(); if ( submissionsResponse.ok && submissionsResponse.body && @@ -23,7 +23,7 @@ export default async function matchTeams( error: 'Submissions collection is not empty. Please clear submissions before matching teams.', }; - } + }*/ // Generate submissions based on judge-team assignments. const teamsRes = await GetManyTeams(); @@ -46,10 +46,11 @@ export default async function matchTeams( } const res = await CreateManySubmissions(parsedJudgeToTeam); if (!res.ok) { + console.log(`${res.error}`); return { ok: false, body: null, - error: 'Invalid submissions.', + error: 'Invalid submissions.1', }; } // for (const submission of parsedJudgeToTeam) { @@ -65,7 +66,7 @@ export default async function matchTeams( return { ok: false, body: null, - error: 'Invalid submissions.', + error: 'Invalid submissions.2', }; } return { From b961928c3d285e232a2daa4ae6cafbf4e945f265 Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:38:24 -0800 Subject: [PATCH 06/16] Fixed bug: type mismatch with JudgeToTeam previous pairings and the current matches --- app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts | 4 ++-- app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts index cf8288a1..88354db3 100644 --- a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts +++ b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts @@ -14,8 +14,8 @@ export const GetJudgeToTeamPairings = async () => { .toArray(); const pairings = submissions.map((submission: Submission) => ({ - judge_id: submission.judge_id, - team_id: submission.team_id + judge_id: String(submission.judge_id), + team_id: String(submission.team_id) })); // erm im not sure if i did this right?? diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index eca8a26f..c1fdcabe 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -182,8 +182,8 @@ export default async function matchAllTeams(options?: { alpha?: number }) { for (const judge of judgesQueue) { const duplicateExists = judgeToTeam.some( (entry) => - entry.judge_id === judge.user._id?.toString() && - entry.team_id === team._id?.toString() + String(entry.judge_id) === judge.user._id?.toString() && + String(entry.team_id) === team._id?.toString() ); if (!duplicateExists) { selectedJudge = judge; From 0d1e82b4104749e034a7f90043a5df7f12af64c8 Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:42:37 -0800 Subject: [PATCH 07/16] Fixed comments --- app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts | 3 +-- app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts index 88354db3..fbfc7500 100644 --- a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts +++ b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts @@ -17,9 +17,8 @@ export const GetJudgeToTeamPairings = async () => { judge_id: String(submission.judge_id), team_id: String(submission.team_id) })); - // erm im not sure if i did this right?? - return { ok: true, body: pairings as JudgeToTeam[], error: null}; // um not sure if the format in the db is the exact same as the judgetoteam?? + return { ok: true, body: pairings as JudgeToTeam[], error: null}; } catch (e) { const error = e as HttpError; return { ok: false, body: null, error: error.message }; diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index c1fdcabe..a10ffea9 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -159,14 +159,12 @@ export default async function matchAllTeams(options?: { alpha?: number }) { .map((team) => [team._id ?? '', rounds - team.tracks.length]) ); - // Get previous pairings and push it to the judgeToTeam array (so that it triggers duplicate exists??) + // Get previous pairings and push it to the judgeToTeam array (so that !duplicateExists is true) const previousPairings = await GetJudgeToTeamPairings(); if (previousPairings.ok && previousPairings.body) { - //const isSecondRound = previousPairings.body.length !== 0 ? true : false; // theres a red line?? judgeToTeam.push(...previousPairings.body); } else { console.log(previousPairings.error); - //const isSecondRound = false; } // Main loop: process each team for each round. @@ -224,7 +222,7 @@ export default async function matchAllTeams(options?: { alpha?: number }) { shuffleArray(modifiedTeams); } - // Remove the previous pairings?? + // Remove the previous pairings if (previousPairings.body) { judgeToTeam.splice(0, previousPairings.body.length); } From d96eda6af7937cade10689266f65204e5a650913 Mon Sep 17 00:00:00 2001 From: phlangiee <174984579+phlangiee@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:52:25 -0800 Subject: [PATCH 08/16] Formatting code (for eslint { - try { - const db = await getDatabase(); - const submissions = await db - .collection('submissions') - .find() - .toArray(); - - const pairings = submissions.map((submission: Submission) => ({ - judge_id: String(submission.judge_id), - team_id: String(submission.team_id) - })); - - return { ok: true, body: pairings as JudgeToTeam[], error: null}; - } catch (e) { - const error = e as HttpError; - return { ok: false, body: null, error: error.message }; - } -} - + try { + const db = await getDatabase(); + const submissions = await db.collection('submissions').find().toArray(); + const pairings = submissions.map((submission: Submission) => ({ + judge_id: String(submission.judge_id), + team_id: String(submission.team_id), + })); + return { ok: true, body: pairings as JudgeToTeam[], error: null }; + } catch (e) { + const error = e as HttpError; + return { ok: false, body: null, error: error.message }; + } +}; From d13b25dcf748c40e10c719334e8d9555d6197440 Mon Sep 17 00:00:00 2001 From: Sandeep Reehal <126985041+ReehalS@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:53:31 -0800 Subject: [PATCH 09/16] Update app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../_datalib/judgeToTeam/getJudgeToTeamPairings.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts index e000ac65..989b43b7 100644 --- a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts +++ b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts @@ -2,12 +2,21 @@ import JudgeToTeam from '@typeDefs/judgeToTeam'; import { getDatabase } from '@utils/mongodb/mongoClient.mjs'; import { HttpError } from '@utils/response/Errors'; import Submission from '@typeDefs/submission'; +import { ObjectId } from 'mongodb'; + +type MongoSubmission = Omit & { + judge_id: ObjectId; + team_id: ObjectId; +}; export const GetJudgeToTeamPairings = async () => { try { const db = await getDatabase(); - const submissions = await db.collection('submissions').find().toArray(); - const pairings = submissions.map((submission: Submission) => ({ + const submissions = await db + .collection('submissions') + .find() + .toArray(); + const pairings = submissions.map((submission) => ({ judge_id: String(submission.judge_id), team_id: String(submission.team_id), })); From aa1cf177e51d0dc027bf282ebe569ea1020306fe Mon Sep 17 00:00:00 2001 From: Sandeep Reehal <126985041+ReehalS@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:53:54 -0800 Subject: [PATCH 10/16] Force fail if getJudgeToTeamPairings fails Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index a10ffea9..ce7c2e12 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -161,11 +161,14 @@ export default async function matchAllTeams(options?: { alpha?: number }) { // Get previous pairings and push it to the judgeToTeam array (so that !duplicateExists is true) const previousPairings = await GetJudgeToTeamPairings(); - if (previousPairings.ok && previousPairings.body) { - judgeToTeam.push(...previousPairings.body); - } else { - console.log(previousPairings.error); + if (!previousPairings.ok || !previousPairings.body) { + throw new Error( + `Failed to load existing judge-to-team pairings: ${ + previousPairings.error ?? 'Unknown error' + }` + ); } + judgeToTeam.push(...previousPairings.body); // Main loop: process each team for each round. for (let domainIndex = 0; domainIndex < rounds; domainIndex++) { From 66522199ab79f00da4ee9e2c358f12a2557c2c07 Mon Sep 17 00:00:00 2001 From: Sandeep Reehal <126985041+ReehalS@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:54:59 -0800 Subject: [PATCH 11/16] Defensive check for previousPairings.ok before body check Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index ce7c2e12..4b387787 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -226,7 +226,7 @@ export default async function matchAllTeams(options?: { alpha?: number }) { } // Remove the previous pairings - if (previousPairings.body) { + if (previousPairings.ok && previousPairings.body) { judgeToTeam.splice(0, previousPairings.body.length); } From 000482a382fffa72a5905e6dc22287d374b72e9d Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 29 Jan 2026 17:55:33 -0800 Subject: [PATCH 12/16] Update getJudgeToTeamPairings.ts --- app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts index 989b43b7..86bb5fc5 100644 --- a/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts +++ b/app/(api)/_datalib/judgeToTeam/getJudgeToTeamPairings.ts @@ -2,7 +2,7 @@ import JudgeToTeam from '@typeDefs/judgeToTeam'; import { getDatabase } from '@utils/mongodb/mongoClient.mjs'; import { HttpError } from '@utils/response/Errors'; import Submission from '@typeDefs/submission'; -import { ObjectId } from 'mongodb'; +import { ObjectId, Db } from 'mongodb'; type MongoSubmission = Omit & { judge_id: ObjectId; @@ -11,7 +11,7 @@ type MongoSubmission = Omit & { export const GetJudgeToTeamPairings = async () => { try { - const db = await getDatabase(); + const db = (await getDatabase()) as Db; const submissions = await db .collection('submissions') .find() From c79a9a4d2d693fae910ab08136f1a3bd17fad954 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 29 Jan 2026 18:50:50 -0800 Subject: [PATCH 13/16] Update page to allow multiple rounds now. --- .../_actions/logic/applyDiagnosticAlpha.ts | 35 ++++++++++++++----- .../JudgeTeamGrouping/JudgeTeamGrouping.tsx | 13 ++++--- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/app/(api)/_actions/logic/applyDiagnosticAlpha.ts b/app/(api)/_actions/logic/applyDiagnosticAlpha.ts index cb26d70b..e52815e3 100644 --- a/app/(api)/_actions/logic/applyDiagnosticAlpha.ts +++ b/app/(api)/_actions/logic/applyDiagnosticAlpha.ts @@ -14,25 +14,35 @@ import checkMatches from '@actions/logic/checkMatches'; export default async function applyDiagnosticAlpha(options: { alpha: number; judgeToTeam: JudgeToTeam[]; -}): Promise<{ ok: boolean; body: JudgeToTeam[] | null; error: string | null }> { +}): Promise<{ + ok: boolean; + body: JudgeToTeam[] | null; + error: string | null; + message?: string; +}> { const existing = await GetManySubmissions(); - if (existing.ok && existing.body && existing.body.length > 0) { + const teamsRes = await GetManyTeams(); + if (!teamsRes.ok) { return { ok: false, body: null, - error: - 'Submissions collection is not empty. Please clear before applying diagnostics.', + error: `GetManyTeams error: ${teamsRes.error}`, }; } - const teamsRes = await GetManyTeams(); - if (!teamsRes.ok) { + const teams = teamsRes.body; + const existingCount = existing.ok && existing.body ? existing.body.length : 0; + const maxRounds = 4; + const maxSubmissions = teams.length * maxRounds; + const isSecondRound = existingCount > 0; + + if (existingCount >= maxSubmissions) { return { ok: false, body: null, - error: `GetManyTeams error: ${teamsRes.error}`, + error: + 'Two rounds have already been completed. Clear submissions to rerun.', }; } - const teams = teamsRes.body; const parsedSubmissions = await parseAndReplace(options.judgeToTeam); if (!checkMatches(parsedSubmissions, teams.length)) { return { @@ -62,5 +72,12 @@ export default async function applyDiagnosticAlpha(options: { // return { ok: false, body: null, error: res.error }; // } - return { ok: true, body: options.judgeToTeam, error: null }; + return { + ok: true, + body: options.judgeToTeam, + error: null, + message: isSecondRound + ? 'Second round detected: new pairings were added on top of existing submissions.' + : undefined, + }; } diff --git a/app/(pages)/admin/_components/JudgeTeamGrouping/JudgeTeamGrouping.tsx b/app/(pages)/admin/_components/JudgeTeamGrouping/JudgeTeamGrouping.tsx index aabb46a0..18c021ab 100644 --- a/app/(pages)/admin/_components/JudgeTeamGrouping/JudgeTeamGrouping.tsx +++ b/app/(pages)/admin/_components/JudgeTeamGrouping/JudgeTeamGrouping.tsx @@ -130,6 +130,9 @@ export default function JudgeTeamGrouping() { setShowSubmissions(true); setApplyAlphaSuccess(true); setError(''); + if (res.message) { + alert(res.message); + } } else { setError(res.error!); setShowMatching(false); @@ -326,14 +329,14 @@ export default function JudgeTeamGrouping() { return (
-
-

Error

- {error && ( + {error && ( +
+

Error

{error}
- )} -
+
+ )} {/* Diagnostics inputs */}
From 98e31fcbc50a5724dc57881c9765cb40ad746081 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 5 Feb 2026 20:33:11 -0800 Subject: [PATCH 14/16] Add tests for judgeToTeam --- __tests__/datalib/judgeToTeam.ts | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 __tests__/datalib/judgeToTeam.ts diff --git a/__tests__/datalib/judgeToTeam.ts b/__tests__/datalib/judgeToTeam.ts new file mode 100644 index 00000000..f8e26999 --- /dev/null +++ b/__tests__/datalib/judgeToTeam.ts @@ -0,0 +1,77 @@ +import { GetJudgeToTeamPairings } from '@datalib/judgeToTeam/getJudgeToTeamPairings'; +import { setupDatabase, teardownDatabase } from './setup'; + +describe('Judge To Team Pairings', () => { + beforeAll(async () => { + await setupDatabase(); + }); + + afterAll(async () => { + await teardownDatabase(); + }); + + it('should return empty array when no submissions exist', async () => { + const res = await GetJudgeToTeamPairings(); + + expect(res.ok).toBe(true); + expect(res.body).toEqual([]); + expect(res.error).toBeNull(); + }); + + it('should return all judge-to-team pairings with correct structure', async () => { + const db = await (await import('@utils/mongodb/mongoClient.mjs')).getDatabase(); + + // Insert test submissions + const testSubmissions = [ + { + judge_id: db.collection('judges').findOne({}).then((j: any) => j?._id), + team_id: db.collection('teams').findOne({}).then((t: any) => t?._id), + average_score: 85, + round: 1, + }, + ]; + + // For now, just test that the function returns in the correct format + const res = await GetJudgeToTeamPairings(); + + expect(res.ok).toBe(true); + expect(Array.isArray(res.body)).toBe(true); + + if (res.body && res.body.length > 0) { + // Verify structure of returned pairings + const pairing = res.body[0]; + expect(pairing).toHaveProperty('judge_id'); + expect(pairing).toHaveProperty('team_id'); + expect(typeof pairing.judge_id).toBe('string'); + expect(typeof pairing.team_id).toBe('string'); + } + }); + + it('should convert ObjectIds to strings in returned pairings', async () => { + const res = await GetJudgeToTeamPairings(); + + expect(res.ok).toBe(true); + + if (res.body && res.body.length > 0) { + // Verify that all IDs are strings, not ObjectIds + for (const pairing of res.body) { + expect(typeof pairing.judge_id).toBe('string'); + expect(typeof pairing.team_id).toBe('string'); + // ObjectIds have 24 character hex strings or similar patterns + // Strings should be valid + expect(pairing.judge_id).toBeTruthy(); + expect(pairing.team_id).toBeTruthy(); + } + } + }); + + it('should handle database errors gracefully', async () => { + // Note: This test would need a mock or a way to trigger DB errors + // For now, we just verify the function handles the happy path + const res = await GetJudgeToTeamPairings(); + + expect(res).toHaveProperty('ok'); + expect(res).toHaveProperty('body'); + expect(res).toHaveProperty('error'); + }); +}); From 2b9832dc41d8f654ff16c7dc90e3ebbe92664e4d Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 5 Feb 2026 20:39:54 -0800 Subject: [PATCH 15/16] Update test for judgeToTeam Also add a comment so Copilot Code Review doesn't complain again. --- __tests__/datalib/judgeToTeam.ts | 171 +++++++++++------- .../_utils/matching/judgesToTeamsAlgorithm.ts | 4 + 2 files changed, 113 insertions(+), 62 deletions(-) diff --git a/__tests__/datalib/judgeToTeam.ts b/__tests__/datalib/judgeToTeam.ts index f8e26999..6a339046 100644 --- a/__tests__/datalib/judgeToTeam.ts +++ b/__tests__/datalib/judgeToTeam.ts @@ -1,77 +1,124 @@ +import { db } from '../../jest.setup'; import { GetJudgeToTeamPairings } from '@datalib/judgeToTeam/getJudgeToTeamPairings'; -import { setupDatabase, teardownDatabase } from './setup'; +import { ObjectId } from 'mongodb'; +import JudgeToTeam from '@typeDefs/judgeToTeam'; -describe('Judge To Team Pairings', () => { - beforeAll(async () => { - await setupDatabase(); - }); +beforeEach(async () => { + await db.collection('submissions').deleteMany({}); +}); - afterAll(async () => { - await teardownDatabase(); +// Helper to create valid submission documents +function createSubmission(judgeId: ObjectId, teamId: ObjectId) { + return { + judge_id: judgeId, + team_id: teamId, + social_good: null, + creativity: null, + presentation: null, + scores: [], + is_scored: false, + queuePosition: null, + }; +} + +describe('GetJudgeToTeamPairings', () => { + it('should return an empty array when no submissions exist', async () => { + const result = await GetJudgeToTeamPairings(); + expect(result.ok).toBe(true); + expect(result.body).toEqual([]); + expect(result.error).toBe(null); }); - it('should return empty array when no submissions exist', async () => { - const res = await GetJudgeToTeamPairings(); + it('should return pairings with string IDs converted from ObjectIds', async () => { + const judgeId = new ObjectId(); + const teamId = new ObjectId(); + + await db.collection('submissions').insertOne(createSubmission(judgeId, teamId)); - expect(res.ok).toBe(true); - expect(res.body).toEqual([]); - expect(res.error).toBeNull(); + const result = await GetJudgeToTeamPairings(); + expect(result.ok).toBe(true); + expect(result.body).toHaveLength(1); + expect(result.error).toBe(null); + + const pairing = result.body?.[0]; + expect(pairing).toEqual({ + judge_id: judgeId.toString(), + team_id: teamId.toString(), + }); }); - it('should return all judge-to-team pairings with correct structure', async () => { - const db = await (await import('@utils/mongodb/mongoClient.mjs')).getDatabase(); - - // Insert test submissions - const testSubmissions = [ - { - judge_id: db.collection('judges').findOne({}).then((j: any) => j?._id), - team_id: db.collection('teams').findOne({}).then((t: any) => t?._id), - average_score: 85, - round: 1, - }, - ]; - - // For now, just test that the function returns in the correct format - const res = await GetJudgeToTeamPairings(); - - expect(res.ok).toBe(true); - expect(Array.isArray(res.body)).toBe(true); - - if (res.body && res.body.length > 0) { - // Verify structure of returned pairings - const pairing = res.body[0]; - expect(pairing).toHaveProperty('judge_id'); - expect(pairing).toHaveProperty('team_id'); - expect(typeof pairing.judge_id).toBe('string'); - expect(typeof pairing.team_id).toBe('string'); - } + it('should return multiple pairings correctly', async () => { + const judgeId1 = new ObjectId(); + const judgeId2 = new ObjectId(); + const teamId1 = new ObjectId(); + const teamId2 = new ObjectId(); + const teamId3 = new ObjectId(); + + await db.collection('submissions').insertMany([ + createSubmission(judgeId1, teamId1), + createSubmission(judgeId1, teamId2), + createSubmission(judgeId2, teamId3), + ]); + + const result = await GetJudgeToTeamPairings(); + expect(result.ok).toBe(true); + expect(result.body).toHaveLength(3); + expect(result.error).toBe(null); + + const pairings = result.body as JudgeToTeam[]; + expect(pairings[0]).toEqual({ + judge_id: judgeId1.toString(), + team_id: teamId1.toString(), + }); + expect(pairings[1]).toEqual({ + judge_id: judgeId1.toString(), + team_id: teamId2.toString(), + }); + expect(pairings[2]).toEqual({ + judge_id: judgeId2.toString(), + team_id: teamId3.toString(), + }); }); - it('should convert ObjectIds to strings in returned pairings', async () => { - const res = await GetJudgeToTeamPairings(); - - expect(res.ok).toBe(true); - - if (res.body && res.body.length > 0) { - // Verify that all IDs are strings, not ObjectIds - for (const pairing of res.body) { - expect(typeof pairing.judge_id).toBe('string'); - expect(typeof pairing.team_id).toBe('string'); - // ObjectIds have 24 character hex strings or similar patterns - // Strings should be valid - expect(pairing.judge_id).toBeTruthy(); - expect(pairing.team_id).toBeTruthy(); - } - } + it('should handle duplicate judge-team pairings', async () => { + const judgeId = new ObjectId(); + const teamId = new ObjectId(); + + await db.collection('submissions').insertMany([ + createSubmission(judgeId, teamId), + createSubmission(judgeId, teamId), + ]); + + const result = await GetJudgeToTeamPairings(); + expect(result.ok).toBe(true); + expect(result.body).toHaveLength(2); + + const pairings = result.body as JudgeToTeam[]; + expect(pairings[0]).toEqual(pairings[1]); }); - it('should handle database errors gracefully', async () => { - // Note: This test would need a mock or a way to trigger DB errors - // For now, we just verify the function handles the happy path - const res = await GetJudgeToTeamPairings(); + it('should convert ObjectIds to strings for duplicate prevention comparison', async () => { + const judgeId = new ObjectId(); + const teamId = new ObjectId(); + const judgeIdString = judgeId.toString(); + const teamIdString = teamId.toString(); + + await db.collection('submissions').insertOne(createSubmission(judgeId, teamId)); + + const result = await GetJudgeToTeamPairings(); + const pairings = result.body as JudgeToTeam[]; + + // This test verifies that String() conversion is necessary for comparison + // in algorithms like judgesToTeamsAlgorithm.ts + expect(String(pairings[0].judge_id)).toBe(judgeIdString); + expect(String(pairings[0].team_id)).toBe(teamIdString); - expect(res).toHaveProperty('ok'); - expect(res).toHaveProperty('body'); - expect(res).toHaveProperty('error'); + // Simulate the duplicate check from judgesToTeamsAlgorithm.ts (lines 183-189) + const duplicateExists = pairings.some( + (entry) => + String(entry.judge_id) === judgeIdString && + String(entry.team_id) === teamIdString + ); + expect(duplicateExists).toBe(true); }); }); diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index 4b387787..64409175 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -181,6 +181,10 @@ export default async function matchAllTeams(options?: { alpha?: number }) { let selectedJudge: Judge | undefined = undefined; for (const judge of judgesQueue) { + // String() conversion is necessary because: + // - previousPairings from GetJudgeToTeamPairings converts ObjectIds to strings + // - judge.user._id and team._id are ObjectIds that need .toString() + // - Comparing without String() would cause false negatives in duplicate detection const duplicateExists = judgeToTeam.some( (entry) => String(entry.judge_id) === judge.user._id?.toString() && From 8e0fb4950626f4548bc5b83ec9c24bae52b69412 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 5 Feb 2026 21:01:12 -0800 Subject: [PATCH 16/16] Address copilot comments --- __tests__/datalib/judgeToTeam.ts | 30 ++++++++++++------- .../_actions/logic/applyDiagnosticAlpha.ts | 6 ++-- app/(api)/_actions/logic/matchTeams.ts | 6 ++-- .../_utils/matching/judgesToTeamsAlgorithm.ts | 23 +++++++++++--- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/__tests__/datalib/judgeToTeam.ts b/__tests__/datalib/judgeToTeam.ts index 6a339046..fb685794 100644 --- a/__tests__/datalib/judgeToTeam.ts +++ b/__tests__/datalib/judgeToTeam.ts @@ -33,7 +33,9 @@ describe('GetJudgeToTeamPairings', () => { const judgeId = new ObjectId(); const teamId = new ObjectId(); - await db.collection('submissions').insertOne(createSubmission(judgeId, teamId)); + await db + .collection('submissions') + .insertOne(createSubmission(judgeId, teamId)); const result = await GetJudgeToTeamPairings(); expect(result.ok).toBe(true); @@ -54,11 +56,13 @@ describe('GetJudgeToTeamPairings', () => { const teamId2 = new ObjectId(); const teamId3 = new ObjectId(); - await db.collection('submissions').insertMany([ - createSubmission(judgeId1, teamId1), - createSubmission(judgeId1, teamId2), - createSubmission(judgeId2, teamId3), - ]); + await db + .collection('submissions') + .insertMany([ + createSubmission(judgeId1, teamId1), + createSubmission(judgeId1, teamId2), + createSubmission(judgeId2, teamId3), + ]); const result = await GetJudgeToTeamPairings(); expect(result.ok).toBe(true); @@ -84,10 +88,12 @@ describe('GetJudgeToTeamPairings', () => { const judgeId = new ObjectId(); const teamId = new ObjectId(); - await db.collection('submissions').insertMany([ - createSubmission(judgeId, teamId), - createSubmission(judgeId, teamId), - ]); + await db + .collection('submissions') + .insertMany([ + createSubmission(judgeId, teamId), + createSubmission(judgeId, teamId), + ]); const result = await GetJudgeToTeamPairings(); expect(result.ok).toBe(true); @@ -103,7 +109,9 @@ describe('GetJudgeToTeamPairings', () => { const judgeIdString = judgeId.toString(); const teamIdString = teamId.toString(); - await db.collection('submissions').insertOne(createSubmission(judgeId, teamId)); + await db + .collection('submissions') + .insertOne(createSubmission(judgeId, teamId)); const result = await GetJudgeToTeamPairings(); const pairings = result.body as JudgeToTeam[]; diff --git a/app/(api)/_actions/logic/applyDiagnosticAlpha.ts b/app/(api)/_actions/logic/applyDiagnosticAlpha.ts index e52815e3..b77aa4f8 100644 --- a/app/(api)/_actions/logic/applyDiagnosticAlpha.ts +++ b/app/(api)/_actions/logic/applyDiagnosticAlpha.ts @@ -31,8 +31,8 @@ export default async function applyDiagnosticAlpha(options: { } const teams = teamsRes.body; const existingCount = existing.ok && existing.body ? existing.body.length : 0; - const maxRounds = 4; - const maxSubmissions = teams.length * maxRounds; + const maxTotalAssignmentsPerTeam = 4; + const maxSubmissions = teams.length * maxTotalAssignmentsPerTeam; const isSecondRound = existingCount > 0; if (existingCount >= maxSubmissions) { @@ -40,7 +40,7 @@ export default async function applyDiagnosticAlpha(options: { ok: false, body: null, error: - 'Two rounds have already been completed. Clear submissions to rerun.', + 'Maximum judge assignments reached (4 judges per team). Clear submissions to rerun.', }; } const parsedSubmissions = await parseAndReplace(options.judgeToTeam); diff --git a/app/(api)/_actions/logic/matchTeams.ts b/app/(api)/_actions/logic/matchTeams.ts index 290d4212..b58fa5a5 100644 --- a/app/(api)/_actions/logic/matchTeams.ts +++ b/app/(api)/_actions/logic/matchTeams.ts @@ -50,7 +50,9 @@ export default async function matchTeams( return { ok: false, body: null, - error: 'Invalid submissions.1', + error: `Failed to create submissions in database: ${ + res.error ?? 'Unknown error' + }`, }; } // for (const submission of parsedJudgeToTeam) { @@ -66,7 +68,7 @@ export default async function matchTeams( return { ok: false, body: null, - error: 'Invalid submissions.2', + error: 'Invalid submissions: assignment validation failed.', }; } return { diff --git a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts index 64409175..9d029252 100644 --- a/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts +++ b/app/(api)/_utils/matching/judgesToTeamsAlgorithm.ts @@ -168,7 +168,8 @@ export default async function matchAllTeams(options?: { alpha?: number }) { }` ); } - judgeToTeam.push(...previousPairings.body); + const previousPairingsBody = previousPairings.body; + judgeToTeam.push(...previousPairingsBody); // Main loop: process each team for each round. for (let domainIndex = 0; domainIndex < rounds; domainIndex++) { @@ -229,9 +230,23 @@ export default async function matchAllTeams(options?: { alpha?: number }) { shuffleArray(modifiedTeams); } - // Remove the previous pairings - if (previousPairings.ok && previousPairings.body) { - judgeToTeam.splice(0, previousPairings.body.length); + // Remove the previous pairings without relying on array insertion order. + if (previousPairingsBody.length > 0) { + const previousPairingKeySet = new Set( + previousPairingsBody.map((pairing) => { + const judgeId = String(pairing.judge_id); + const teamId = String(pairing.team_id); + return `${judgeId}::${teamId}`; + }) + ); + const filteredJudgeToTeam = judgeToTeam.filter((entry) => { + const judgeId = String(entry.judge_id); + const teamId = String(entry.team_id); + const key = `${judgeId}::${teamId}`; + return !previousPairingKeySet.has(key); + }); + judgeToTeam.length = 0; + judgeToTeam.push(...filteredJudgeToTeam); } console.log('No. of judgeToTeam:', judgeToTeam.length);