fix: 토너먼트 예선 8명 기록을 보존

This commit is contained in:
2026-08-17 15:28:29 +00:00
parent 50e7d894e4
commit 15c5c1c98f
6 changed files with 63 additions and 9 deletions
@@ -71,6 +71,13 @@ const zParticipant = z.object({
gl: z.number().int().optional(), gl: z.number().int().optional(),
seedRank: z.number().int().optional(), seedRank: z.number().int().optional(),
finalRank: z.number().int().optional(), finalRank: z.number().int().optional(),
preliminaryGroupId: z.number().int().min(0).max(7).optional(),
preliminaryGroupNo: z.number().int().min(0).max(7).optional(),
preliminaryRank: z.number().int().min(1).max(8).optional(),
preliminaryWin: z.number().int().min(0).optional(),
preliminaryDraw: z.number().int().min(0).optional(),
preliminaryLose: z.number().int().min(0).optional(),
preliminaryGl: z.number().int().optional(),
}); });
const zMatch = z.object({ const zMatch = z.object({
+7
View File
@@ -34,6 +34,13 @@ export interface TournamentParticipantEntry {
gl?: number; gl?: number;
seedRank?: number; seedRank?: number;
finalRank?: number; finalRank?: number;
preliminaryGroupId?: number;
preliminaryGroupNo?: number;
preliminaryRank?: number;
preliminaryWin?: number;
preliminaryDraw?: number;
preliminaryLose?: number;
preliminaryGl?: number;
} }
export interface TournamentMatchEntry { export interface TournamentMatchEntry {
+11 -2
View File
@@ -186,6 +186,8 @@ export const applyPreBattleStage = async (
gl: 0, gl: 0,
seedRank: 0, seedRank: 0,
finalRank: 0, finalRank: 0,
preliminaryGroupId: entry.groupId,
preliminaryGroupNo: entry.groupNo,
})); }));
await store.setParticipants(grouped); await store.setParticipants(grouped);
const nextState: TournamentState = { const nextState: TournamentState = {
@@ -244,10 +246,17 @@ export const applyPreBattleStage = async (
for (let groupId = 0; groupId < 8; groupId += 1) { for (let groupId = 0; groupId < 8; groupId += 1) {
const groupEntries = ranked.filter((entry) => entry.groupId === groupId); const groupEntries = ranked.filter((entry) => entry.groupId === groupId);
const ordered = sortByRanking(groupEntries); const ordered = sortByRanking(groupEntries);
ordered.slice(0, 4).forEach((entry, idx) => { ordered.forEach((entry, idx) => {
const target = ranked.find((item) => item.id === entry.id); const target = ranked.find((item) => item.id === entry.id);
if (target) { if (target) {
target.seedRank = idx + 1; target.preliminaryGroupId = groupId;
target.preliminaryGroupNo = entry.groupNo;
target.preliminaryRank = idx + 1;
target.preliminaryWin = entry.win ?? 0;
target.preliminaryDraw = entry.draw ?? 0;
target.preliminaryLose = entry.lose ?? 0;
target.preliminaryGl = entry.gl ?? 0;
target.seedRank = idx < 4 ? idx + 1 : 0;
} }
}); });
} }
+4 -1
View File
@@ -195,17 +195,20 @@ export const assignManualApplicantGroup = (options: {
extraSeed: `manual-group:${options.current.map((entry) => entry.id).join('-')}:${openGroupIds.join('-')}`, extraSeed: `manual-group:${options.current.map((entry) => entry.id).join('-')}:${openGroupIds.join('-')}`,
}); });
const groupId = rng.choice(openGroupIds); const groupId = rng.choice(openGroupIds);
const groupNo = groupCounts[groupId] ?? 0;
return { return {
...options.applicant, ...options.applicant,
groupId, groupId,
groupNo: groupCounts[groupId] ?? 0, groupNo,
win: 0, win: 0,
draw: 0, draw: 0,
lose: 0, lose: 0,
gl: 0, gl: 0,
seedRank: 0, seedRank: 0,
finalRank: 0, finalRank: 0,
preliminaryGroupId: groupId,
preliminaryGroupNo: groupNo,
}; };
}; };
@@ -249,6 +249,7 @@ describe('tournament router permissions and mutations', () => {
expect(results.filter((result) => result.status === 'rejected')).toHaveLength(1); expect(results.filter((result) => result.status === 'rejected')).toHaveLength(1);
const summary = await caller.tournament.getBettingSummary(); const summary = await caller.tournament.getBettingSummary();
expect(summary.myAmount).toBe(600); expect(summary.myAmount).toBe(600);
expect(Object.values(summary.myTotals)).toEqual([600]);
expect(summary.totalAmount).toBe(600); expect(summary.totalAmount).toBe(600);
expect(transport.gold.get(general.id)).toBe(2_400); expect(transport.gold.get(general.id)).toBe(2_400);
}); });
+33 -6
View File
@@ -246,8 +246,9 @@ describe('tournament worker schedule compatibility', () => {
groupNo, groupNo,
}; };
}); });
const groupCounts = Array.from({ length: 8 }, (_, groupId) => const groupCounts = Array.from(
current.filter((entry) => entry.groupId === groupId).length { length: 8 },
(_, groupId) => current.filter((entry) => entry.groupId === groupId).length
); );
const openGroupId = groupCounts.findIndex((count) => count === 7); const openGroupId = groupCounts.findIndex((count) => count === 7);
expect(openGroupId).toBeGreaterThanOrEqual(0); expect(openGroupId).toBeGreaterThanOrEqual(0);
@@ -267,7 +268,16 @@ describe('tournament worker schedule compatibility', () => {
}, },
}); });
expect(applicant).toMatchObject({ groupId: openGroupId, groupNo: 7, win: 0, draw: 0, lose: 0, gl: 0 }); expect(applicant).toMatchObject({
groupId: openGroupId,
groupNo: 7,
preliminaryGroupId: openGroupId,
preliminaryGroupNo: 7,
win: 0,
draw: 0,
lose: 0,
gl: 0,
});
}); });
it('catches up from the stored schedule instead of discarding elapsed legacy phases', () => { it('catches up from the stored schedule instead of discarding elapsed legacy phases', () => {
@@ -334,6 +344,7 @@ describe('tournament worker (in-memory)', () => {
expect(entries.map((entry) => entry.groupNo).sort((a, b) => Number(a) - Number(b))).toEqual([ expect(entries.map((entry) => entry.groupNo).sort((a, b) => Number(a) - Number(b))).toEqual([
0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7,
]); ]);
expect(entries.every((entry) => entry.preliminaryGroupId === groupId)).toBe(true);
} }
}); });
@@ -648,14 +659,30 @@ describe('tournament worker (in-memory)', () => {
expect(participants.find((entry) => entry.id === 1)).toMatchObject({ groupId: expect.any(Number) }); expect(participants.find((entry) => entry.id === 1)).toMatchObject({ groupId: expect.any(Number) });
expect(participants.find((entry) => entry.id === 1001)).toMatchObject({ groupId: expect.any(Number) }); expect(participants.find((entry) => entry.id === 1001)).toMatchObject({ groupId: expect.any(Number) });
expect( expect(
Array.from({ length: 8 }, (_, groupId) => Array.from({ length: 8 }, (_, groupId) => participants.filter((entry) => entry.groupId === groupId).length)
participants.filter((entry) => entry.groupId === groupId).length
)
).toEqual(Array.from({ length: 8 }, () => 8)); ).toEqual(Array.from({ length: 8 }, () => 8));
await store.setState(afterJoin); await store.setState(afterJoin);
const finalState = await runTournamentToCompletion({ store, prisma, baseSeed: 'seed' }); const finalState = await runTournamentToCompletion({ store, prisma, baseSeed: 'seed' });
const finalParticipants = await store.getParticipants();
expect(finalState.stage).toBe(0); expect(finalState.stage).toBe(0);
expect(finalState.winnerId).toBeDefined(); expect(finalState.winnerId).toBeDefined();
for (let groupId = 0; groupId < 8; groupId += 1) {
const preliminaryEntries = finalParticipants
.filter((entry) => entry.preliminaryGroupId === groupId)
.sort((lhs, rhs) => (lhs.preliminaryRank ?? 99) - (rhs.preliminaryRank ?? 99));
expect(preliminaryEntries).toHaveLength(8);
expect(preliminaryEntries.map((entry) => entry.preliminaryRank)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
expect(
preliminaryEntries.every(
(entry) =>
entry.preliminaryGroupNo !== undefined &&
entry.preliminaryWin !== undefined &&
entry.preliminaryDraw !== undefined &&
entry.preliminaryLose !== undefined &&
entry.preliminaryGl !== undefined
)
).toBe(true);
}
}); });
}); });