Skip to content

Commit

Permalink
Merge branch 'feat/qualifier-progression' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
CourtHive committed Dec 13, 2024
2 parents 3276a96 + 946174e commit 4b5be8f
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 0 deletions.
17 changes: 17 additions & 0 deletions documentation/docs/governors/draws-governor.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,23 @@ engine.qualifierDrawPositionAssignment({

---

## qualifiersProgression

Randomly replaces existing qualifier drawPosition assignments in the MAIN draw with qualified participants from the QUALIFYING draw.

Fills as many qualifier drawPositions as possible with qualified participants, e.g. if there are 4 qualifier drawPositions and 2 currently qualified participants, only 2 will be assigned.

```js
engine.qualifierProgression({
drawId,
eventId,
targetRoundNumber, // optional - defaults to 1
tournamentId,
});
```

---

## removeDrawDefinitionExtension

```js
Expand Down
1 change: 1 addition & 0 deletions src/assemblies/governors/drawsGovernor/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { removeDrawPositionAssignment } from '@Mutate/drawDefinitions/removeDraw
export { automatedPlayoffPositioning } from '@Mutate/drawDefinitions/automatedPlayoffPositioning';
export { modifySeedAssignment } from '@Mutate/drawDefinitions/entryGovernor/modifySeedAssignment';
export { qualifierDrawPositionAssignment } from '@Mutate/matchUps/drawPositions/positionQualifier';
export { qualifierProgression } from '@Mutate/drawDefinitions/positionGovernor/qualifierProgression';
export { setStructureOrder } from '@Mutate/drawDefinitions/structureGovernor/setStructureOrder';
export { attachQualifyingStructure } from '@Mutate/drawDefinitions/attachQualifyingStructure';
export { renameStructures } from '@Mutate/drawDefinitions/structureGovernor/renameStructures';
Expand Down
10 changes: 10 additions & 0 deletions src/constants/errorConditionConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ export const MISSING_STRUCTURE = {
message: 'Missing structure',
code: 'ERR_MISSING_STRUCTURE',
};
export const MISSING_MAIN_STRUCTURE = {
message: 'Missing MAIN structure',
code: 'ERR_MISSING_MAIN_STRUCTURE',
};
export const UNLINKED_STRUCTURES = {
message: 'drawDefinition contains unlinked structures',
code: 'ERR_MISSING_STRUCTURE_LINKS',
Expand Down Expand Up @@ -503,6 +507,10 @@ export const MISSING_PARTICIPANT_ID = {
message: 'Missing participantId',
code: 'ERR_MISSING_PARTICIPANT_ID',
};
export const MISSING_QUALIFIED_PARTICIPANTS = {
message: 'Missing qualified participants',
code: 'ERR_MISSING_QUALIFIED_PARTICIPANTS',
};
export const PARTICIPANT_NOT_FOUND = {
message: 'Participant Not Found',
code: 'ERR_NOT_FOUND_PARTICIPANT',
Expand Down Expand Up @@ -894,6 +902,7 @@ export const errorConditionConstants = {
MISSING_DRAW_SIZE,
MISSING_ENTRIES,
MISSING_EVENT,
MISSING_QUALIFIED_PARTICIPANTS,
MISSING_MATCHUP_FORMAT,
MISSING_MATCHUP_ID,
MISSING_MATCHUP_IDS,
Expand Down Expand Up @@ -924,6 +933,7 @@ export const errorConditionConstants = {
MISSING_STAGE,
MISSING_STRUCTURE_ID,
MISSING_STRUCTURE,
MISSING_MAIN_STRUCTURE,
MISSING_STRUCTURES,
MISSING_TARGET_LINK,
MISSING_TIE_FORMAT,
Expand Down
192 changes: 192 additions & 0 deletions src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { findExtension } from '@Acquire/findExtension';
import { qualifierDrawPositionAssignment } from '@Assemblies/governors/drawsGovernor';
import { DRAW_DEFINITION, EVENT, TOURNAMENT_RECORD } from '@Constants/attributeConstants';
import { MAIN, POSITION, QUALIFYING, WINNER } from '@Constants/drawDefinitionConstants';
import {
MISSING_MAIN_STRUCTURE,
MISSING_QUALIFIED_PARTICIPANTS,
NO_DRAW_POSITIONS_AVAILABLE_FOR_QUALIFIERS,
} from '@Constants/errorConditionConstants';
import { TALLY } from '@Constants/extensionConstants';
import { BYE } from '@Constants/matchUpStatusConstants';
import { POLICY_TYPE_POSITION_ACTIONS } from '@Constants/policyConstants';
import { SUCCESS } from '@Constants/resultConstants';
import { decorateResult } from '@Functions/global/decorateResult';
import { checkRequiredParameters } from '@Helpers/parameters/checkRequiredParameters';
import { getPositionAssignments, structureAssignedDrawPositions } from '@Query/drawDefinition/positionsGetter';
import { isCompletedStructure } from '@Query/drawDefinition/structureActions';
import { getAppliedPolicies } from '@Query/extensions/getAppliedPolicies';
import { getAllStructureMatchUps } from '@Query/matchUps/getAllStructureMatchUps';
import { getSourceStructureIdsAndRelevantLinks } from '@Query/structure/getSourceStructureIdsAndRelevantLinks';
import { randomPop } from '@Tools/arrays';
import { definedAttributes } from '@Tools/definedAttributes';
import { ResultType } from '@Types/factoryTypes';
import { DrawDefinition, Event, Tournament } from '@Types/tournamentTypes';

interface QualifierProgressionArgs {
drawDefinition: DrawDefinition;
event: Event;
targetRoundNumber?: number;
tournamentRecord: Tournament;
}

export function qualifierProgression({
drawDefinition,
event,
targetRoundNumber = 1,
tournamentRecord,
}: QualifierProgressionArgs): ResultType {
const paramsCheck = checkRequiredParameters(
{
drawDefinition,
event,
tournamentRecord,
},
[{ [DRAW_DEFINITION]: true, [EVENT]: true, [TOURNAMENT_RECORD]: true }],
);
if (paramsCheck.error) return paramsCheck;

const qualifyingParticipantIds: string[] = [];
const assignedParticipants: {
participantId: string;
drawPosition: number;
}[] = [];

const mainStructure = drawDefinition.structures?.find(
(structure) => structure.stage === MAIN && structure.stageSequence === 1,
);

if (!mainStructure) return decorateResult({ result: { error: MISSING_MAIN_STRUCTURE } });

const appliedPolicies =
getAppliedPolicies({
tournamentRecord,
drawDefinition,
structure: mainStructure,
event,
}).appliedPolicies ?? {};

const policy = appliedPolicies[POLICY_TYPE_POSITION_ACTIONS];
const requireCompletedStructures = policy?.requireCompletedStructures;

const { qualifierPositions, positionAssignments } = structureAssignedDrawPositions({ structure: mainStructure });

if (!qualifierPositions.length)
return decorateResult({ result: { error: NO_DRAW_POSITIONS_AVAILABLE_FOR_QUALIFIERS } });

const assignedParticipantIds = positionAssignments.map((assignment) => assignment.participantId).filter(Boolean);

const { relevantLinks: eliminationSourceLinks } =
getSourceStructureIdsAndRelevantLinks({
targetRoundNumber,
linkType: WINNER, // WINNER of qualifying structures will traverse link
drawDefinition,
structureId: mainStructure.structureId,
}) || {};

const { relevantLinks: roundRobinSourceLinks } =
getSourceStructureIdsAndRelevantLinks({
targetRoundNumber,
linkType: POSITION, // link will define how many finishingPositions traverse the link
drawDefinition,
structureId: mainStructure.structureId,
}) || {};

for (const sourceLink of eliminationSourceLinks) {
const structure = drawDefinition.structures?.find(
(structure) => structure.structureId === sourceLink.source.structureId,
);
if (structure?.stage !== QUALIFYING) continue;

const structureCompleted = isCompletedStructure({
structureId: sourceLink.source.structureId,
drawDefinition,
});

if (!requireCompletedStructures || structureCompleted) {
const qualifyingRoundNumber = structure.qualifyingRoundNumber;
const { matchUps } = getAllStructureMatchUps({
matchUpFilters: {
...(qualifyingRoundNumber && {
roundNumbers: [qualifyingRoundNumber],
}),
hasWinningSide: true,
},
afterRecoveryTimes: false,
inContext: true,
structure,
});

for (const matchUp of matchUps) {
const winningSide = matchUp.sides.find((side) => side?.sideNumber === matchUp.winningSide);
const relevantSide = matchUp.matchUpStatus === BYE && matchUp.sides?.find(({ participantId }) => participantId);

if (winningSide || relevantSide) {
const { participantId } = winningSide || relevantSide || {};
if (participantId && !assignedParticipantIds.includes(participantId)) {
qualifyingParticipantIds.push(participantId);
}
}
}
}
}

for (const sourceLink of roundRobinSourceLinks) {
const structure = drawDefinition?.structures?.find(
(structure) => structure.structureId === sourceLink.source.structureId,
);
if (structure?.stage !== QUALIFYING) continue;

const structureCompleted = isCompletedStructure({
structureId: sourceLink.source.structureId,
drawDefinition,
});

if (structureCompleted) {
const { positionAssignments } = getPositionAssignments({ structure });
const relevantParticipantIds: any =
positionAssignments
?.map((assignment) => {
const participantId = assignment.participantId;
const results = findExtension({
element: assignment,
name: TALLY,
}).extension?.value;

return results ? { participantId, groupOrder: results?.groupOrder } : {};
})
.filter(
({ groupOrder, participantId }) => groupOrder === 1 && !assignedParticipantIds.includes(participantId),
)
.map(({ participantId }) => participantId) ?? [];

if (relevantParticipantIds) qualifyingParticipantIds.push(...relevantParticipantIds);
}
}

if (!qualifyingParticipantIds.length) return decorateResult({ result: { error: MISSING_QUALIFIED_PARTICIPANTS } });

qualifierPositions.forEach((position) => {
const randomParticipantId = randomPop(qualifyingParticipantIds);

if (randomParticipantId) {
const positionAssignmentResult: ResultType = qualifierDrawPositionAssignment({
qualifyingParticipantId: randomParticipantId,
tournamentRecord,
drawDefinition,
drawPosition: position.drawPosition,
structureId: mainStructure.structureId,
});

positionAssignmentResult?.success &&
assignedParticipants.push({ participantId: randomParticipantId, drawPosition: position.drawPosition });
}
});

return decorateResult({
result: definedAttributes({
...SUCCESS,
assignedParticipants,
}),
});
}
Loading

0 comments on commit 4b5be8f

Please sign in to comment.