Skip to content

Commit 0b1be5a

Browse files
committed
fix buchholu caluclation
1 parent f92cc70 commit 0b1be5a

6 files changed

Lines changed: 110 additions & 16 deletions

File tree

api/src/event/event.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
IsNull,
1818
LessThanOrEqual,
1919
Repository,
20+
type FindOptionsRelations,
2021
UpdateResult,
2122
} from "typeorm";
2223
import {
@@ -26,7 +27,6 @@ import {
2627
import * as CryptoJS from "crypto-js";
2728
import { ConfigService } from "@nestjs/config";
2829
import { TeamService } from "../team/team.service";
29-
import { FindOptionsRelations } from "typeorm/find-options/FindOptionsRelations";
3030
import { Cron, CronExpression } from "@nestjs/schedule";
3131
import { EventVersionDto } from "./dtos/eventVersionDto";
3232
import { LockKeys } from "../constants";

api/src/match/match.service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import { InjectRepository } from "@nestjs/typeorm";
44
import { MatchEntity, MatchPhase, MatchState } from "./entites/match.entity";
55
import { MatchStatsEntity } from "./entites/matchStats.entity";
66
import { getCreditWagerRefund } from "./match-credit-wager";
7-
import { DataSource, In, Not, Repository } from "typeorm";
7+
import {
8+
DataSource,
9+
In,
10+
Not,
11+
Repository,
12+
type FindOptionsRelations,
13+
type FindOptionsSelect,
14+
} from "typeorm";
815
import { Swiss } from "tournament-pairings";
916
import { EventService } from "../event/event.service";
1017
import type { Player } from "tournament-pairings/interfaces";
@@ -14,8 +21,6 @@ import { getRabbitmqConfig } from "../main";
1421
import { ConfigService } from "@nestjs/config";
1522
import { TeamEntity } from "../team/entities/team.entity";
1623
import { GithubApiService } from "../github-api/github-api.service";
17-
import { FindOptionsRelations } from "typeorm/find-options/FindOptionsRelations";
18-
import { FindOptionsSelect } from "typeorm/find-options/FindOptionsSelect";
1924
import { MatchTeamResultEntity } from "./entites/match.team.result.entity";
2025
import { GamblingService } from "../gambling/gambling.service";
2126
import { canViewTeamMatchReplay } from "./team-match-visibility";

api/src/team/team-search.spec.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
jest.mock("./entities/team.entity", () => ({
2+
TeamEntity: class TeamEntity {},
3+
}));
4+
jest.mock("../event/entities/event.entity", () => ({
5+
EventEntity: class EventEntity {},
6+
}));
7+
jest.mock("../event/event.service", () => ({
8+
EventService: class EventService {},
9+
}));
10+
jest.mock("../github-api/github-api.service", () => ({
11+
GithubApiService: class GithubApiService {},
12+
}));
13+
jest.mock("../match/match.service", () => ({
14+
MatchService: class MatchService {},
15+
}));
16+
jest.mock("../user/user.service", () => ({
17+
UserService: class UserService {},
18+
}));
19+
20+
import type { TeamEntity } from "./entities/team.entity";
21+
import { TeamService } from "./team.service";
22+
23+
describe("TeamService.getSearchedTeamsForEvent", () => {
24+
it("returns the persisted Buchholz score for the admin reveal view", async () => {
25+
const team = {
26+
id: "team-1",
27+
name: "Team One",
28+
locked: false,
29+
score: 4,
30+
buchholzPoints: 12,
31+
hadBye: false,
32+
credits: 0,
33+
createdAt: new Date("2026-01-01T00:00:00.000Z"),
34+
updatedAt: new Date("2026-01-01T00:00:00.000Z"),
35+
} as TeamEntity;
36+
37+
const queryBuilder = {
38+
innerJoin: jest.fn().mockReturnThis(),
39+
leftJoin: jest.fn().mockReturnThis(),
40+
where: jest.fn().mockReturnThis(),
41+
andWhere: jest.fn().mockReturnThis(),
42+
select: jest.fn().mockReturnThis(),
43+
addSelect: jest.fn().mockReturnThis(),
44+
groupBy: jest.fn().mockReturnThis(),
45+
getRawAndEntities: jest.fn().mockResolvedValue({
46+
entities: [team],
47+
raw: [{ user_count: "2" }],
48+
}),
49+
};
50+
const calculateBuchholzPointsForTeams = jest
51+
.fn()
52+
.mockResolvedValue(new Map([["team-1", 8]]));
53+
const service = Object.create(TeamService.prototype) as TeamService;
54+
55+
Reflect.set(service, "teamRepository", {
56+
createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
57+
});
58+
Reflect.set(service, "eventService", {
59+
isEventAdmin: jest.fn().mockResolvedValue(true),
60+
});
61+
Reflect.set(service, "matchService", {
62+
calculateBuchholzPointsForTeams,
63+
});
64+
jest
65+
.spyOn(service, "getLocationTagsForTeams")
66+
.mockResolvedValue(new Map([["team-1", []]]));
67+
68+
const result = await service.getSearchedTeamsForEvent(
69+
"event-1",
70+
undefined,
71+
undefined,
72+
undefined,
73+
"admin-1",
74+
true,
75+
);
76+
77+
expect(result[0].buchholzPoints).toBe(12);
78+
expect(calculateBuchholzPointsForTeams).not.toHaveBeenCalled();
79+
});
80+
});

api/src/team/team.service.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import {
1616
LessThanOrEqual,
1717
Not,
1818
Repository,
19+
type FindOptionsRelations,
1920
} from "typeorm";
2021
import { GithubApiService } from "../github-api/github-api.service";
2122
import { EventService } from "../event/event.service";
2223
import { UserService } from "../user/user.service";
23-
import { FindOptionsRelations } from "typeorm/find-options/FindOptionsRelations";
2424
import { MatchService } from "../match/match.service";
2525
import { Cron, CronExpression } from "@nestjs/schedule";
2626
import { LockKeys } from "../constants";
@@ -57,7 +57,11 @@ export class TeamService {
5757
private readonly matchService: MatchService,
5858
@InjectDataSource()
5959
private readonly dataSource: DataSource,
60-
) {}
60+
) {
61+
this.getSortedTeamsForTournament("2c10eecb-ab9b-48e4-a840-d54febb25854").then(results => {
62+
console.log(results)
63+
})
64+
}
6165

6266
logger = new Logger("TeamService");
6367

@@ -617,13 +621,17 @@ export class TeamService {
617621

618622
const result = await query.getRawAndEntities();
619623

620-
// Batch calculate Buchholz points for all teams in the result
621624
const teamIds = result.entities.map((t) => t.id);
622-
const buchholzMap = await this.matchService.calculateBuchholzPointsForTeams(
623-
teamIds,
624-
eventId,
625-
!revealAll,
626-
);
625+
// Public standings are calculated from revealed matches. The admin view must
626+
// keep the persisted value selected above instead of overwriting it with a
627+
// different on-the-fly calculation.
628+
const revealedBuchholzMap = revealAll
629+
? undefined
630+
: await this.matchService.calculateBuchholzPointsForTeams(
631+
teamIds,
632+
eventId,
633+
true,
634+
);
627635
const tagsByTeam = await this.getLocationTagsForTeams(teamIds);
628636

629637
// Map properties from raw if entity is missing them due to partial select
@@ -655,8 +663,9 @@ export class TeamService {
655663
(team.hadBye ? 1 : 0);
656664
}
657665

658-
// Use the batch-calculated Buchholz points
659-
mappedTeam.buchholzPoints = buchholzMap.get(team.id) || 0;
666+
mappedTeam.buchholzPoints = revealAll
667+
? (team.buchholzPoints ?? 0)
668+
: (revealedBuchholzMap?.get(team.id) ?? 0);
660669

661670
return mappedTeam;
662671
}),

frontend/src/components/tournament/admin-bracket-stepper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export function AdminBracketStepper({
167167

168168
<div className="h-1 overflow-hidden rounded-full bg-muted">
169169
<motion.div
170-
className="h-full rounded-full bg-gradient-to-r from-primary/70 via-primary to-emerald-500"
170+
className="h-full rounded-full bg-linear-to-r from-primary/70 via-primary to-emerald-500"
171171
initial={false}
172172
animate={{
173173
width: `${((safeIndex + 1) / orderedMatches.length) * 100}%`,

frontend/src/components/tournament/group-phase-graph-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function GroupPhaseGraphView({
103103

104104
if (matches.length === 0) {
105105
return (
106-
<div className="flex min-h-[360px] items-center justify-center rounded-xl border bg-card/50 px-6 text-center text-sm text-muted-foreground">
106+
<div className="flex min-h-90 items-center justify-center rounded-xl border bg-card/50 px-6 text-center text-sm text-muted-foreground">
107107
Matches will appear here once the group phase starts.
108108
</div>
109109
)

0 commit comments

Comments
 (0)