Skip to content

Commit 19f35bb

Browse files
committed
feat: ✨ support groupTotalGamesPlayed in tally policies
1 parent 2a29283 commit 19f35bb

File tree

6 files changed

+85
-19
lines changed

6 files changed

+85
-19
lines changed

src/fixtures/policies/POLICY_ROUND_ROBIN_TALLY_DEFAULT.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const POLICY_ROUND_ROBIN_TALLY_DEFAULT = {
44
[POLICY_TYPE_ROUND_ROBIN_TALLY]: {
55
policyName: 'Default Round Robin Tally',
66
groupOrderKey: 'matchUpsWon', // possible to group by tieMatchUpsWon, tieSinglesWon, tieDoublesWon, matchUpsWon, pointsWon, gamesWon, setsWon, gamesPct, setsPct, pointsPct, matchUpsPct
7+
groupTotalGamesPlayed: false, // optional - when true will calculate % of games won based on total group games played rather than participant games played
78
groupTotalSetsPlayed: false, // optional - when true will calculate % of sets won based on total group sets played rather than participant sets played
89
headToHead: { disabled: false },
910
tallyDirectives: [
@@ -12,17 +13,17 @@ export const POLICY_ROUND_ROBIN_TALLY_DEFAULT = {
1213
// with { idsFilter: false } the ratio is calculated from all group matchUps
1314
// with { idsFilter: true } the ratio is calculated from matchUps including tied participants
1415
// any attribute/idsFilter combination can be selectively disabled for Head to Head calculations
15-
{ attribute: 'matchUpsPct', idsFilter: false },
16+
{ attribute: 'matchUpsPct', idsFilter: false, groupTotals: false },
1617
{ attribute: 'allDefaults', reversed: true, idsFilter: false }, // reversed: true => reverses default which is greatest to least
1718
{ attribute: 'defaults', reversed: true, idsFilter: false },
1819
{ attribute: 'walkovers', reversed: true, idsFilter: false },
1920
{ attribute: 'retirements', reversed: true, idsFilter: false },
20-
{ attribute: 'setsPct', idsFilter: false },
21-
{ attribute: 'gamesPct', idsFilter: false },
21+
{ attribute: 'setsPct', idsFilter: false, groupTotals: false },
22+
{ attribute: 'gamesPct', idsFilter: false, groupTotals: false },
2223
{ attribute: 'pointsPct', idsFilter: false },
23-
{ attribute: 'matchUpsPct', idsFilter: true },
24-
{ attribute: 'setsPct', idsFilter: true },
25-
{ attribute: 'gamesPct', idsFilter: true },
24+
{ attribute: 'matchUpsPct', idsFilter: true, groupTotals: false },
25+
{ attribute: 'setsPct', idsFilter: true, groupTotals: false },
26+
{ attribute: 'gamesPct', idsFilter: true, groupTotals: false },
2627
{ attribute: 'pointsPct', idsFilter: true },
2728
],
2829
disqualifyDefaults: true, // disqualified participants are pushed to the bottom of the group order

src/query/matchUps/roundRobinTally/calculatePercentages.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { parse } from '@Helpers/matchUpFormatCode/parse';
22

3-
export function calculatePercentages({ participantResults, matchUpFormat, tallyPolicy, perPlayer, totalSets }) {
3+
export function calculatePercentages({
4+
participantResults,
5+
groupingTotal,
6+
matchUpFormat,
7+
tallyPolicy,
8+
totalGames,
9+
perPlayer,
10+
totalSets,
11+
}) {
412
const parsedGroupMatchUpFormat = (matchUpFormat && parse(matchUpFormat)) || {};
513
const bestOfGames = parsedGroupMatchUpFormat.bestOf;
614
const bracketSetsToWin = (bestOfGames && Math.ceil(bestOfGames / 2)) || 1;
@@ -10,9 +18,10 @@ export function calculatePercentages({ participantResults, matchUpFormat, tallyP
1018
Object.keys(participantResults).forEach((participantId) => {
1119
const setsWon = participantResults[participantId].setsWon;
1220
const setsLost = participantResults[participantId].setsLost;
13-
const setsTotal = tallyPolicy?.groupTotalSetsPlayed
14-
? totalSets
15-
: perPlayer * (bracketSetsToWin || 0) || setsWon + setsLost;
21+
const setsTotal =
22+
tallyPolicy?.groupTotalSetsPlayed || groupingTotal === 'setsPct'
23+
? totalSets
24+
: perPlayer * (bracketSetsToWin || 0) || setsWon + setsLost;
1625
let setsPct = Math.round((setsWon / setsTotal) * precision) / precision;
1726
if (setsPct === Infinity || isNaN(setsPct)) setsPct = setsTotal;
1827

@@ -31,7 +40,10 @@ export function calculatePercentages({ participantResults, matchUpFormat, tallyP
3140
const gamesWon = participantResults[participantId].gamesWon || 0;
3241
const gamesLost = participantResults[participantId].gamesLost || 0;
3342
const minimumExpectedGames = (perPlayer || 0) * (bracketSetsToWin || 0) * (bracketGamesForSet || 0);
34-
const gamesTotal = Math.max(minimumExpectedGames, gamesWon + gamesLost);
43+
const gamesTotal =
44+
tallyPolicy?.groupTotalGamesPlayed || groupingTotal === 'gamesPct'
45+
? totalGames
46+
: Math.max(minimumExpectedGames, gamesWon + gamesLost);
3547
let gamesPct = Math.round((gamesWon / gamesTotal) * precision) / precision;
3648
if (gamesPct === Infinity || isNaN(gamesPct)) gamesPct = 0;
3749

src/query/matchUps/roundRobinTally/getGroupOrder.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ function processAttribute({
205205
disableHeadToHead,
206206
participantIds,
207207
matchUpFormat,
208+
groupTotals,
208209
tallyPolicy,
209210
attribute,
210211
idsFilter,
@@ -213,6 +214,7 @@ function processAttribute({
213214
}) {
214215
const { participantResults } = getParticipantResults({
215216
participantIds: idsFilter && participantIds,
217+
groupingTotal: groupTotals && attribute,
216218
matchUpFormat,
217219
tallyPolicy,
218220
matchUps,
@@ -290,11 +292,12 @@ function groupSubSort({ participantResults, disableHeadToHead, participantIds, m
290292

291293
if (excludedDirectives.length) report.push({ excludedDirectives, participantIds });
292294

293-
filteredDirectives.every(({ attribute, reversed, idsFilter, disableHeadToHead }) => {
295+
filteredDirectives.every(({ attribute, reversed, idsFilter, groupTotals, disableHeadToHead }) => {
294296
result = processAttribute({
295297
disableHeadToHead,
296298
participantIds,
297299
matchUpFormat,
300+
groupTotals,
298301
tallyPolicy,
299302
attribute,
300303
idsFilter,

src/query/matchUps/roundRobinTally/getParticipantResults.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type GetParticipantResultsArgs = {
1414
matchUps: HydratedMatchUp[];
1515
participantIds?: string[];
1616
pressureRating?: string;
17+
groupingTotal?: string; // attribute being processed for group totals
1718
matchUpFormat?: string;
1819
perPlayer?: number;
1920
tallyPolicy?: any;
@@ -22,6 +23,7 @@ type GetParticipantResultsArgs = {
2223
export function getParticipantResults({
2324
participantIds,
2425
pressureRating,
26+
groupingTotal,
2527
matchUpFormat,
2628
tallyPolicy,
2729
perPlayer,
@@ -42,14 +44,25 @@ export function getParticipantResults({
4244
);
4345
});
4446

45-
const allSets = filteredMatchUps?.flatMap(({ score, tieMatchUps }) =>
47+
const allSetsCount = filteredMatchUps?.flatMap(({ score, tieMatchUps }) =>
4648
tieMatchUps
4749
? tieMatchUps
4850
.filter(({ matchUpStatus }) => !excludeMatchUpStatuses.includes(matchUpStatus))
4951
.flatMap(({ score }) => score?.sets?.length ?? 0)
5052
: (score?.sets?.length ?? 0),
5153
);
52-
const totalSets = allSets?.reduce((a, b) => a + b, 0);
54+
const totalSets = allSetsCount?.reduce((a, b) => a + b, 0);
55+
56+
const getGames = (score) =>
57+
score?.sets?.reduce((total, set) => total + (set?.side1Score ?? 0) + (set?.side2Score ?? 0), 0) ?? 0;
58+
const allGamesCount = filteredMatchUps?.flatMap(({ score, tieMatchUps }) =>
59+
tieMatchUps
60+
? tieMatchUps
61+
.filter(({ matchUpStatus }) => !excludeMatchUpStatuses.includes(matchUpStatus))
62+
.flatMap(({ score }) => getGames(score))
63+
: getGames(score),
64+
);
65+
const totalGames = allGamesCount?.reduce((a, b) => a + b, 0);
5366

5467
for (const matchUp of filteredMatchUps ?? []) {
5568
const { matchUpStatus, tieMatchUps, tieFormat, score, winningSide, sides } = matchUp;
@@ -200,10 +213,10 @@ export function getParticipantResults({
200213
}
201214
}
202215

203-
const gamesWonSide1 = score?.sets?.reduce((total, set) => total + (set?.side1Score ?? 0), 0);
204-
const gamesWonSide2 = score?.sets?.reduce((total, set) => total + (set.side2Score ?? 0), 0);
205-
206216
if (manualGamesOverride) {
217+
const gamesWonSide1 = score?.sets?.reduce((total, set) => total + (set?.side1Score ?? 0), 0);
218+
const gamesWonSide2 = score?.sets?.reduce((total, set) => total + (set.side2Score ?? 0), 0);
219+
207220
const side1participantId = sides?.find(({ sideNumber }) => sideNumber === 1)?.participantId;
208221
const side2participantId = sides?.find(({ sideNumber }) => sideNumber === 2)?.participantId;
209222

@@ -224,8 +237,10 @@ export function getParticipantResults({
224237

225238
calculatePercentages({
226239
participantResults,
240+
groupingTotal,
227241
matchUpFormat,
228242
tallyPolicy,
243+
totalGames,
229244
perPlayer,
230245
totalSets,
231246
});

src/query/matchUps/roundRobinTally/tallyParticipantResults.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ export function tallyParticipantResults({
8989
participantResults,
9090
participantsCount,
9191
matchUpFormat,
92-
tallyPolicy,
9392
subOrderMap,
93+
tallyPolicy,
9494
});
9595

9696
if (pressureRating) addPressureOrder({ participantResults });

src/tests/mutations/drawDefinitions/drawTypeSpecific/tallyPolicy.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import mocksEngine from '@Assemblies/engines/mock';
33
import tournamentEngine from '@Engines/syncEngine';
44
import { expect, test } from 'vitest';
55

6+
// Constants
7+
import { ROUND_ROBIN } from '@Constants/drawDefinitionConstants';
8+
69
test('roundRobinTally policy can specify tally by games only', () => {
710
// prettier-ignore
811
const mockProfile: any = {
@@ -24,8 +27,8 @@ test('roundRobinTally policy can specify tally by games only', () => {
2427

2528
drawProfiles: [
2629
{
27-
drawType: 'ROUND_ROBIN',
2830
matchUpFormat: 'SET1-S:T20',
31+
drawType: ROUND_ROBIN,
2932
drawSize: 4,
3033
},
3134
],
@@ -53,3 +56,35 @@ test('roundRobinTally policy can specify tally by games only', () => {
5356
expect([expectation - 1, expectation].includes(parseInt(GEMscore?.toString()?.slice(0, 2)))).toEqual(true);
5457
});
5558
});
59+
60+
test('roundRobinTally policy can specify groupTotalGamesPlayed and groupTotalSetsPlayed', () => {
61+
const mockProfile: any = {
62+
policyDefinitions: { roundRobinTally: { groupTotalGamesPlayed: true, groupTotalSetsPlayed: true } },
63+
drawProfiles: [{ drawType: ROUND_ROBIN, drawSize: 4 }],
64+
tournamentName: 'group total games and sets',
65+
};
66+
67+
mocksEngine.generateTournamentRecord({
68+
completeAllMatchUps: true,
69+
setState: true,
70+
...mockProfile,
71+
});
72+
73+
const { matchUps } = tournamentEngine.allTournamentMatchUps();
74+
75+
const { participantResults } = tallyParticipantResults({
76+
policyDefinitions: mockProfile.policyDefinitions,
77+
matchUpFormat: mockProfile.matchUpFormat,
78+
matchUps,
79+
});
80+
81+
let gamesPctTotal = 0;
82+
let setsPctTotal = 0;
83+
Object.values(participantResults).forEach((result: any) => {
84+
const { setsPct, gamesPct = 0 } = result;
85+
gamesPctTotal += gamesPct;
86+
setsPctTotal += setsPct;
87+
});
88+
expect(Math.round(gamesPctTotal)).toEqual(1);
89+
expect(Math.round(setsPctTotal)).toEqual(1);
90+
});

0 commit comments

Comments
 (0)