From 08506dde30191520e88d27ea9368beeb535a2a6b Mon Sep 17 00:00:00 2001 From: mgallagher-reid Date: Tue, 10 Dec 2024 19:37:01 +0000 Subject: [PATCH 1/4] feat: add function for qualifier progression --- .../governors/drawsGovernor/mutate.ts | 1 + .../positionGovernor/qualifierProgression.ts | 163 ++++++++++++++++++ .../getSourceStructureIdsAndRelevantLinks.ts | 2 +- 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts diff --git a/src/assemblies/governors/drawsGovernor/mutate.ts b/src/assemblies/governors/drawsGovernor/mutate.ts index 0cf8ee66f5..a26efc27fa 100644 --- a/src/assemblies/governors/drawsGovernor/mutate.ts +++ b/src/assemblies/governors/drawsGovernor/mutate.ts @@ -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'; diff --git a/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts b/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts new file mode 100644 index 0000000000..6d3aa2644d --- /dev/null +++ b/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts @@ -0,0 +1,163 @@ +import { findDrawDefinition } from '@Acquire/findDrawDefinition'; +import { findEvent } from '@Acquire/findEvent'; +import { findExtension } from '@Acquire/findExtension'; +import { findStructure } from '@Acquire/findStructure'; +import { qualifierDrawPositionAssignment } from '@Assemblies/governors/drawsGovernor'; +import { POSITION, QUALIFYING, WINNER } from '@Constants/drawDefinitionConstants'; +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 { 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, Structure, Tournament } from '@Types/tournamentTypes'; + +interface QualifierProgressionArgs { + drawId: string; + eventId: string; + mainStructureId: string; + tournamentRecord: Tournament; +} + +export function qualifierProgression({ + drawId, + eventId, + mainStructureId, + tournamentRecord, +}: QualifierProgressionArgs): ResultType { + const qualifyingParticipantIds: string[] = []; + + const drawDefinition = findDrawDefinition({ tournamentRecord, drawId })?.drawDefinition ?? ({} as DrawDefinition); + const event = findEvent({ tournamentRecord, eventId })?.event ?? ({} as Event); + const structure = findStructure({ drawDefinition, structureId: mainStructureId })?.structure ?? ({} as Structure); + + const appliedPolicies = + getAppliedPolicies({ + tournamentRecord, + drawDefinition, + structure, + event, + }).appliedPolicies ?? {}; + + const policy = appliedPolicies[POLICY_TYPE_POSITION_ACTIONS]; + const requireCompletedStructures = policy?.requireCompletedStructures; + + const { qualifierPositions, positionAssignments } = structureAssignedDrawPositions({ structure }); + + if (!qualifierPositions.length) return decorateResult({ result: { error: 'NO_QUALIFIER_POSITIONS' } }); // update with error constant + + const assignedParticipantIds = positionAssignments.map((assignment) => assignment.participantId).filter(Boolean); + + const { relevantLinks: eliminationSourceLinks } = + getSourceStructureIdsAndRelevantLinks({ + linkType: WINNER, // WINNER of qualifying structures will traverse link + drawDefinition, + structureId: structure.structureId, + }) || {}; + + const { relevantLinks: roundRobinSourceLinks } = + getSourceStructureIdsAndRelevantLinks({ + linkType: POSITION, // link will define how many finishingPositions traverse the link + drawDefinition, + structureId: structure.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 (!requireCompletedStructures || 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: 'NO_QUALIFIERS_FOUND' } }); // update with error constant + + qualifierPositions.forEach((position) => { + const randomParticipantId = randomPop(qualifyingParticipantIds); + randomParticipantId && + qualifierDrawPositionAssignment({ + qualifyingParticipantId: randomParticipantId, + tournamentRecord, + drawDefinition, + drawPosition: position.drawPosition, + structureId: structure.structureId, + }); + }); + + return decorateResult({ + result: definedAttributes({ + ...SUCCESS, + }), + }); +} diff --git a/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts b/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts index 7e798d7460..a05a28f52f 100644 --- a/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts +++ b/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts @@ -5,7 +5,7 @@ import { findStructure } from '@Acquire/findStructure'; type GetSourceStructureDetailArgs = { drawDefinition: DrawDefinition; finishingPosition?: string; - targetRoundNumber: number; + targetRoundNumber?: number; structureId: string; linkType: string; }; From b9c78e6eeb8664d083f000d1736290800fbf8695 Mon Sep 17 00:00:00 2001 From: mgallagher-reid Date: Wed, 11 Dec 2024 17:07:48 +0000 Subject: [PATCH 2/4] test: add test to cover qualifierProgression --- src/constants/errorConditionConstants.ts | 5 + .../positionGovernor/qualifierProgression.ts | 22 +-- .../qualifying/qualifierProgression.test.ts | 142 ++++++++++++++++++ 3 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts diff --git a/src/constants/errorConditionConstants.ts b/src/constants/errorConditionConstants.ts index b1ba3502ce..68e792bf87 100644 --- a/src/constants/errorConditionConstants.ts +++ b/src/constants/errorConditionConstants.ts @@ -503,6 +503,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', @@ -894,6 +898,7 @@ export const errorConditionConstants = { MISSING_DRAW_SIZE, MISSING_ENTRIES, MISSING_EVENT, + MISSING_QUALIFIED_PARTICIPANTS, MISSING_MATCHUP_FORMAT, MISSING_MATCHUP_ID, MISSING_MATCHUP_IDS, diff --git a/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts b/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts index 6d3aa2644d..4cb66e1dff 100644 --- a/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts +++ b/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts @@ -1,9 +1,11 @@ -import { findDrawDefinition } from '@Acquire/findDrawDefinition'; -import { findEvent } from '@Acquire/findEvent'; import { findExtension } from '@Acquire/findExtension'; import { findStructure } from '@Acquire/findStructure'; import { qualifierDrawPositionAssignment } from '@Assemblies/governors/drawsGovernor'; import { POSITION, QUALIFYING, WINNER } from '@Constants/drawDefinitionConstants'; +import { + 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'; @@ -20,24 +22,21 @@ import { ResultType } from '@Types/factoryTypes'; import { DrawDefinition, Event, Structure, Tournament } from '@Types/tournamentTypes'; interface QualifierProgressionArgs { - drawId: string; - eventId: string; + drawDefinition: DrawDefinition; + event: Event; mainStructureId: string; tournamentRecord: Tournament; } export function qualifierProgression({ - drawId, - eventId, + drawDefinition, + event, mainStructureId, tournamentRecord, }: QualifierProgressionArgs): ResultType { const qualifyingParticipantIds: string[] = []; - const drawDefinition = findDrawDefinition({ tournamentRecord, drawId })?.drawDefinition ?? ({} as DrawDefinition); - const event = findEvent({ tournamentRecord, eventId })?.event ?? ({} as Event); const structure = findStructure({ drawDefinition, structureId: mainStructureId })?.structure ?? ({} as Structure); - const appliedPolicies = getAppliedPolicies({ tournamentRecord, @@ -51,7 +50,8 @@ export function qualifierProgression({ const { qualifierPositions, positionAssignments } = structureAssignedDrawPositions({ structure }); - if (!qualifierPositions.length) return decorateResult({ result: { error: 'NO_QUALIFIER_POSITIONS' } }); // update with error constant + if (!qualifierPositions.length) + return decorateResult({ result: { error: NO_DRAW_POSITIONS_AVAILABLE_FOR_QUALIFIERS } }); const assignedParticipantIds = positionAssignments.map((assignment) => assignment.participantId).filter(Boolean); @@ -141,7 +141,7 @@ export function qualifierProgression({ } } - if (!qualifyingParticipantIds.length) return decorateResult({ result: { error: 'NO_QUALIFIERS_FOUND' } }); // update with error constant + if (!qualifyingParticipantIds.length) return decorateResult({ result: { error: MISSING_QUALIFIED_PARTICIPANTS } }); qualifierPositions.forEach((position) => { const randomParticipantId = randomPop(qualifyingParticipantIds); diff --git a/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts b/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts new file mode 100644 index 0000000000..085b563fa0 --- /dev/null +++ b/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts @@ -0,0 +1,142 @@ +import tournamentEngine from '@Engines/syncEngine'; +import mocksEngine from '@Assemblies/engines/mock'; +import { expect, it } from 'vitest'; + +import { MAIN, QUALIFYING } from '@Constants/drawDefinitionConstants'; +import { INDIVIDUAL } from '@Constants/participantConstants'; + +import { COMPLETED } from '@Constants/matchUpStatusConstants'; +import { + MISSING_QUALIFIED_PARTICIPANTS, + NO_DRAW_POSITIONS_AVAILABLE_FOR_QUALIFIERS, +} from '@Constants/errorConditionConstants'; + +it('can assign all available qualified participants to the main structure qualifying draw positions', () => { + const { + tournamentRecord, + eventIds: [eventId], + } = mocksEngine.generateTournamentRecord({ + participantsProfile: { participantsCount: 100 }, + eventProfiles: [{ eventName: 'test' }], + }); + expect(tournamentRecord.participants.length).toEqual(100); + + tournamentEngine.setState(tournamentRecord); + + const { participants } = tournamentEngine.getParticipants({ + participantFilters: { participantTypes: [INDIVIDUAL] }, + }); + const participantIds = participants.map((p) => p.participantId); + const mainParticipantIds = participantIds.slice(0, 12); + const qualifyingParticipantIds = participantIds.slice(12, 28); + + let result = tournamentEngine.addEventEntries({ + participantIds: mainParticipantIds, + eventId, + }); + expect(result.success).toEqual(true); + result = tournamentEngine.addEventEntries({ + participantIds: qualifyingParticipantIds, + entryStage: QUALIFYING, + eventId, + }); + expect(result.success).toEqual(true); + + const { drawDefinition: qualifyingDrawDefinition } = tournamentEngine.generateDrawDefinition({ + qualifyingProfiles: [ + { + structureProfiles: [ + { + qualifyingPositions: 4, + drawSize: 16, + }, + ], + }, + ], + qualifyingOnly: true, + eventId, + }); + + // assert QUALIFYING structure is populated and MAIN structure is empty + const mainStructure = qualifyingDrawDefinition.structures.find(({ stage }) => stage === MAIN); + const qualifyingStructure = qualifyingDrawDefinition.structures.find(({ stage }) => stage === QUALIFYING); + expect(qualifyingStructure.matchUps.length).toEqual(12); + expect(mainStructure.matchUps.length).toEqual(0); + + const addDrawDefinitionResult = tournamentEngine.addDrawDefinition({ + activeTournamentId: tournamentRecord.tournamentId, + drawDefinition: qualifyingDrawDefinition, + allowReplacement: true, + eventId, + }); + + expect(addDrawDefinitionResult.success).toEqual(true); + + // assert no MAIN draw qualifying positions are available + const noMainProgressionResult = tournamentEngine.qualifierProgression({ + drawId: qualifyingDrawDefinition.drawId, + mainStructureId: mainStructure.structureId, + tournamentId: tournamentRecord.tournamentId, + eventId: eventId, + }); + expect(noMainProgressionResult.error).toEqual(NO_DRAW_POSITIONS_AVAILABLE_FOR_QUALIFIERS); + + const { drawDefinition } = tournamentEngine.generateDrawDefinition({ + qualifyingProfiles: [ + { + structureProfiles: [{ seedsCount: 4, drawSize: 16, qualifyingPositions: 4 }], + }, + ], + eventId, + }); + + // assert MAIN and QUALIFYING structures are populated + const populatedMainStructure = drawDefinition.structures.find(({ stage }) => stage === MAIN); + const newQualifyingStructure = drawDefinition.structures.find(({ stage }) => stage === QUALIFYING); + expect(populatedMainStructure.matchUps.length).toEqual(15); + expect(newQualifyingStructure.matchUps.length).toEqual(12); + + const addMainDrawDefinitionResult = tournamentEngine.addDrawDefinition({ + activeTournamentId: tournamentRecord.tournamentId, + drawDefinition, + allowReplacement: true, + eventId, + }); + + expect(addMainDrawDefinitionResult.success).toEqual(true); + + // assert no qualified participants are available + const noQualifiersProgressionResult = tournamentEngine.qualifierProgression({ + drawId: drawDefinition.drawId, + mainStructureId: populatedMainStructure.structureId, + tournamentId: tournamentRecord.tournamentId, + eventId: eventId, + }); + + expect(noQualifiersProgressionResult.error).toEqual(MISSING_QUALIFIED_PARTICIPANTS); + + newQualifyingStructure.matchUps.forEach(({ matchUpId }) => + tournamentEngine.setMatchUpStatus({ + tournamentId: tournamentRecord.tournamentId, + drawId: drawDefinition.drawId, + matchUpId, + matchUpStatus: COMPLETED, + outcome: { winningSide: 1 }, + }), + ); + + const progressQualifiersResult = tournamentEngine.qualifierProgression({ + drawId: drawDefinition.drawId, + mainStructureId: populatedMainStructure.structureId, + tournamentId: tournamentRecord.tournamentId, + eventId: eventId, + }); + + expect(progressQualifiersResult.success).toEqual(true); + + // assert qualified participants have been assigned to the main draw positions + const mainDrawPositionAssignments = populatedMainStructure.positionAssignments; + expect(mainDrawPositionAssignments.length).toEqual(16); + expect(mainDrawPositionAssignments.filter((p) => p.qualifier && p.participantId).length).toEqual(4); + expect(mainDrawPositionAssignments.filter((p) => p.qualifier && !p.participantId).length).toEqual(0); +}); From 1c29daf38571402b966b118708ebd4e9d5a91061 Mon Sep 17 00:00:00 2001 From: mgallagher-reid Date: Wed, 11 Dec 2024 17:08:41 +0000 Subject: [PATCH 3/4] docs: add documentation for qualifierProgression --- documentation/docs/governors/draws-governor.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/documentation/docs/governors/draws-governor.md b/documentation/docs/governors/draws-governor.md index fb9dba6ba8..6197290347 100644 --- a/documentation/docs/governors/draws-governor.md +++ b/documentation/docs/governors/draws-governor.md @@ -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, + mainStructureId, + tournamentId, +}); +``` + +--- + ## removeDrawDefinitionExtension ```js From 946174ef277974c6b05f97c20253a7cd6b8ccf73 Mon Sep 17 00:00:00 2001 From: mgallagher-reid Date: Thu, 12 Dec 2024 13:14:55 +0000 Subject: [PATCH 4/4] fix: update based on PR comments --- .../docs/governors/draws-governor.md | 2 +- src/constants/errorConditionConstants.ts | 5 ++ .../positionGovernor/qualifierProgression.ts | 57 ++++++++++++++----- .../getSourceStructureIdsAndRelevantLinks.ts | 2 +- .../qualifying/qualifierProgression.test.ts | 7 ++- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/documentation/docs/governors/draws-governor.md b/documentation/docs/governors/draws-governor.md index 6197290347..39b9032a28 100644 --- a/documentation/docs/governors/draws-governor.md +++ b/documentation/docs/governors/draws-governor.md @@ -328,7 +328,7 @@ Fills as many qualifier drawPositions as possible with qualified participants, e engine.qualifierProgression({ drawId, eventId, - mainStructureId, + targetRoundNumber, // optional - defaults to 1 tournamentId, }); ``` diff --git a/src/constants/errorConditionConstants.ts b/src/constants/errorConditionConstants.ts index 68e792bf87..5c0e5b5336 100644 --- a/src/constants/errorConditionConstants.ts +++ b/src/constants/errorConditionConstants.ts @@ -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', @@ -929,6 +933,7 @@ export const errorConditionConstants = { MISSING_STAGE, MISSING_STRUCTURE_ID, MISSING_STRUCTURE, + MISSING_MAIN_STRUCTURE, MISSING_STRUCTURES, MISSING_TARGET_LINK, MISSING_TIE_FORMAT, diff --git a/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts b/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts index 4cb66e1dff..64b95fcf1d 100644 --- a/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts +++ b/src/mutate/drawDefinitions/positionGovernor/qualifierProgression.ts @@ -1,8 +1,9 @@ import { findExtension } from '@Acquire/findExtension'; -import { findStructure } from '@Acquire/findStructure'; import { qualifierDrawPositionAssignment } from '@Assemblies/governors/drawsGovernor'; -import { POSITION, QUALIFYING, WINNER } from '@Constants/drawDefinitionConstants'; +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'; @@ -11,6 +12,7 @@ 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'; @@ -19,36 +21,55 @@ import { getSourceStructureIdsAndRelevantLinks } from '@Query/structure/getSourc import { randomPop } from '@Tools/arrays'; import { definedAttributes } from '@Tools/definedAttributes'; import { ResultType } from '@Types/factoryTypes'; -import { DrawDefinition, Event, Structure, Tournament } from '@Types/tournamentTypes'; +import { DrawDefinition, Event, Tournament } from '@Types/tournamentTypes'; interface QualifierProgressionArgs { drawDefinition: DrawDefinition; event: Event; - mainStructureId: string; + targetRoundNumber?: number; tournamentRecord: Tournament; } export function qualifierProgression({ drawDefinition, event, - mainStructureId, + 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 structure = findStructure({ drawDefinition, structureId: mainStructureId })?.structure ?? ({} as Structure); const appliedPolicies = getAppliedPolicies({ tournamentRecord, drawDefinition, - structure, + structure: mainStructure, event, }).appliedPolicies ?? {}; const policy = appliedPolicies[POLICY_TYPE_POSITION_ACTIONS]; const requireCompletedStructures = policy?.requireCompletedStructures; - const { qualifierPositions, positionAssignments } = structureAssignedDrawPositions({ structure }); + const { qualifierPositions, positionAssignments } = structureAssignedDrawPositions({ structure: mainStructure }); if (!qualifierPositions.length) return decorateResult({ result: { error: NO_DRAW_POSITIONS_AVAILABLE_FOR_QUALIFIERS } }); @@ -57,16 +78,18 @@ export function qualifierProgression({ const { relevantLinks: eliminationSourceLinks } = getSourceStructureIdsAndRelevantLinks({ + targetRoundNumber, linkType: WINNER, // WINNER of qualifying structures will traverse link drawDefinition, - structureId: structure.structureId, + structureId: mainStructure.structureId, }) || {}; const { relevantLinks: roundRobinSourceLinks } = getSourceStructureIdsAndRelevantLinks({ + targetRoundNumber, linkType: POSITION, // link will define how many finishingPositions traverse the link drawDefinition, - structureId: structure.structureId, + structureId: mainStructure.structureId, }) || {}; for (const sourceLink of eliminationSourceLinks) { @@ -119,7 +142,7 @@ export function qualifierProgression({ drawDefinition, }); - if (!requireCompletedStructures || structureCompleted) { + if (structureCompleted) { const { positionAssignments } = getPositionAssignments({ structure }); const relevantParticipantIds: any = positionAssignments @@ -145,19 +168,25 @@ export function qualifierProgression({ qualifierPositions.forEach((position) => { const randomParticipantId = randomPop(qualifyingParticipantIds); - randomParticipantId && - qualifierDrawPositionAssignment({ + + if (randomParticipantId) { + const positionAssignmentResult: ResultType = qualifierDrawPositionAssignment({ qualifyingParticipantId: randomParticipantId, tournamentRecord, drawDefinition, drawPosition: position.drawPosition, - structureId: structure.structureId, + structureId: mainStructure.structureId, }); + + positionAssignmentResult?.success && + assignedParticipants.push({ participantId: randomParticipantId, drawPosition: position.drawPosition }); + } }); return decorateResult({ result: definedAttributes({ ...SUCCESS, + assignedParticipants, }), }); } diff --git a/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts b/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts index a05a28f52f..7e798d7460 100644 --- a/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts +++ b/src/query/structure/getSourceStructureIdsAndRelevantLinks.ts @@ -5,7 +5,7 @@ import { findStructure } from '@Acquire/findStructure'; type GetSourceStructureDetailArgs = { drawDefinition: DrawDefinition; finishingPosition?: string; - targetRoundNumber?: number; + targetRoundNumber: number; structureId: string; linkType: string; }; diff --git a/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts b/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts index 085b563fa0..c46974c367 100644 --- a/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts +++ b/src/tests/mutations/drawDefinitions/structures/qualifying/qualifierProgression.test.ts @@ -75,7 +75,7 @@ it('can assign all available qualified participants to the main structure qualif // assert no MAIN draw qualifying positions are available const noMainProgressionResult = tournamentEngine.qualifierProgression({ drawId: qualifyingDrawDefinition.drawId, - mainStructureId: mainStructure.structureId, + targetRoundNumber: 1, tournamentId: tournamentRecord.tournamentId, eventId: eventId, }); @@ -108,7 +108,7 @@ it('can assign all available qualified participants to the main structure qualif // assert no qualified participants are available const noQualifiersProgressionResult = tournamentEngine.qualifierProgression({ drawId: drawDefinition.drawId, - mainStructureId: populatedMainStructure.structureId, + targetRoundNumber: 1, tournamentId: tournamentRecord.tournamentId, eventId: eventId, }); @@ -127,11 +127,12 @@ it('can assign all available qualified participants to the main structure qualif const progressQualifiersResult = tournamentEngine.qualifierProgression({ drawId: drawDefinition.drawId, - mainStructureId: populatedMainStructure.structureId, + targetRoundNumber: 1, tournamentId: tournamentRecord.tournamentId, eventId: eventId, }); + expect(progressQualifiersResult.assignedParticipants.length).toEqual(4); expect(progressQualifiersResult.success).toEqual(true); // assert qualified participants have been assigned to the main draw positions