From 0680a5d6019be35173decd81a9335b2f1cae5f4e Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 27 Apr 2026 13:19:15 +0200 Subject: [PATCH] feat: add useBalancedRanking flag to round config schema Defaults to true so existing rounds preserve current behavior; toggled per-round from the ranking dashboard side panel. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/types/competition-configs.ts | 5 +++ .../unit/round-config-balance-toggle.test.ts | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/unit/round-config-balance-toggle.test.ts diff --git a/src/types/competition-configs.ts b/src/types/competition-configs.ts index b250934..cdf0318 100644 --- a/src/types/competition-configs.ts +++ b/src/types/competition-configs.ts @@ -142,6 +142,11 @@ export const EvaluationConfigSchema = z.object({ }) .optional(), + // Whether the ranking dashboard ranks projects by juror-balanced (z-normalized) + // average. Defaulting to true preserves existing behavior. Toggled per-round + // from the dashboard side panel. + useBalancedRanking: z.boolean().default(true), + // Ranking (Phase 1) rankingEnabled: z.boolean().default(false), rankingCriteria: z.string().optional(), diff --git a/tests/unit/round-config-balance-toggle.test.ts b/tests/unit/round-config-balance-toggle.test.ts new file mode 100644 index 0000000..55001c2 --- /dev/null +++ b/tests/unit/round-config-balance-toggle.test.ts @@ -0,0 +1,37 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { prisma, createCaller } from '../setup' +import { + createTestUser, createTestProgram, createTestCompetition, createTestRound, + cleanupTestData, uid, +} from '../helpers' +import { roundRouter } from '../../src/server/routers/round' + +describe('Round.configJson.useBalancedRanking', () => { + let programId: string + let admin: { id: string; email: string; role: 'SUPER_ADMIN' } + const userIds: string[] = [] + + beforeAll(async () => { + const program = await createTestProgram({ name: `bal-toggle-${uid()}` }) + programId = program.id + const adminUser = await createTestUser('SUPER_ADMIN') + userIds.push(adminUser.id) + admin = { id: adminUser.id, email: adminUser.email, role: 'SUPER_ADMIN' } + }) + + afterAll(async () => { + await cleanupTestData(programId, userIds) + }) + + it('persists useBalancedRanking via round.update', async () => { + const competition = await createTestCompetition(programId) + const round = await createTestRound(competition.id) + const caller = createCaller(roundRouter, admin) + await caller.update({ + id: round.id, + configJson: { useBalancedRanking: false }, + }) + const reloaded = await prisma.round.findUniqueOrThrow({ where: { id: round.id } }) + expect((reloaded.configJson as Record).useBalancedRanking).toBe(false) + }) +})