tods-competition-factory 7.3.1 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import { checkConnectedStructures } from './checkConnectedStructures.mjs';
8
8
  import { attemptToSetWinningSide } from './attemptToSetWinningSide.mjs';
9
9
  import { decorateResult } from '../../../functions/global/decorateResult.mjs';
10
10
  import { attemptToModifyScore } from './attemptToModifyScore.mjs';
11
+ import { unwindDualDoubleExit } from './unwindDualDoubleExit.mjs';
11
12
  import { pushGlobalLog } from '../../../functions/global/globalLog.mjs';
12
13
  import { isExit, isDoubleExit } from '../../../validators/isExit.mjs';
13
14
  import { removeDoubleExit } from './removeDoubleExit.mjs';
@@ -25,6 +26,9 @@ function noDownstreamDependencies(params) {
25
26
  if (result.error)
26
27
  return decorateResult({ result, stack });
27
28
  }
29
+ const dualUnwound = unwindDualDoubleExit(params);
30
+ if (dualUnwound.error)
31
+ return decorateResult({ result: dualUnwound, stack });
28
32
  const doubleExit = isDoubleExit(matchUpStatus);
29
33
  const preserveScoreStatuses = [IN_PROGRESS, SUSPENDED, CANCELLED, ABANDONED, INCOMPLETE];
30
34
  const scoreWithNoWinningSide = checkScoreHasValue({ score }) &&
@@ -1,18 +1,17 @@
1
- import { withdrawByeClaim, withdrawByeClaimsFrom, byeClaimSurvives, getSideExitProvenance, retainPolicyCodes, setSideExitProvenance, retainForeignProvenance, deriveExitStateFromProvenance } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
2
1
  import { removeDirectedBye, removeDirectedWinner } from '../../matchUps/drawPositions/removeDirectedParticipants.mjs';
3
2
  import { propagatesByeOnDoubleExit } from '../../matchUps/drawPositions/propagatesByeOnDoubleExit.mjs';
4
3
  import { getPairedPreviousMatchUp } from '../../../query/matchUps/getPairedPreviousMatchup.mjs';
5
4
  import { modifyMatchUpScore } from '../../matchUps/score/modifyMatchUpScore.mjs';
6
5
  import { decorateResult } from '../../../functions/global/decorateResult.mjs';
7
- import { chunkArray, intersection, overlap } from '../../../tools/arrays.mjs';
8
6
  import { positionTargets } from '../../../query/matchUp/positionTargets.mjs';
9
7
  import { pushGlobalLog } from '../../../functions/global/globalLog.mjs';
10
8
  import { findStructure } from '../../../acquire/findStructure.mjs';
9
+ import { intersection, overlap } from '../../../tools/arrays.mjs';
11
10
  import { isDoubleExit } from '../../../validators/isExit.mjs';
12
- import { FIRST_MATCHUP } from '../../../constants/drawDefinitionConstants.mjs';
11
+ import { withdrawByeClaim, withdrawByeClaimsFrom, byeClaimSurvives, getSideExitProvenance, retainPolicyCodes, setSideExitProvenance, retainForeignProvenance, deriveExitStateFromProvenance } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
13
12
  import { SUCCESS } from '../../../constants/resultConstants.mjs';
14
13
  import { completedMatchUpStatuses } from '../../../constants/matchUpStatusConstants.mjs';
15
- import { BYE, DOUBLE_DEFAULT, DOUBLE_WALKOVER, DEFAULTED, WALKOVER, TO_BE_PLAYED } from '../../../constants/matchUpStatusValues.mjs';
14
+ import { BYE, DOUBLE_WALKOVER, DOUBLE_DEFAULT, DEFAULTED, WALKOVER, TO_BE_PLAYED } from '../../../constants/matchUpStatusValues.mjs';
16
15
 
17
16
  const keyColors = {
18
17
  drawPositionToRemove: 'green',
@@ -61,19 +60,6 @@ function removeDoubleExit(params) {
61
60
  const claimSideNumber = (rawLoserMatchUp?.drawPositions ?? []).indexOf(loserTargetDrawPosition) + 1 || 1;
62
61
  withdrawByeClaim({ matchUp: rawLoserMatchUp, sideNumber: claimSideNumber, claimantMatchUpId: matchUpId });
63
62
  withdrawByeClaimsFrom({ matchUps: matchUpsMap?.drawMatchUps, claimantMatchUpId: matchUpId });
64
- const isFMLC = targetData?.targetLinks?.loserTargetLink?.linkCondition === FIRST_MATCHUP;
65
- if (byePropagatedToLoserMatchUp && isFMLC) {
66
- const roundMatchUps = inContextDrawMatchUps.filter(({ roundNumber, structureId }) => structureId === matchUp.structureId && roundNumber === 1);
67
- const roundPositions = roundMatchUps.map(({ roundPosition }) => roundPosition);
68
- const pairedPositions = chunkArray(roundPositions.toSorted((a, b) => a - b), 2).find((chunk) => chunk.includes(matchUp.roundPosition));
69
- const pairedMatchUpStatuses = roundMatchUps
70
- .filter(({ roundPosition }) => pairedPositions.includes(roundPosition))
71
- ?.map(({ matchUpStatus }) => matchUpStatus);
72
- const pairedMatchUpIsDoubleExit = pairedMatchUpStatuses.every((matchUpStatus) => [DOUBLE_DEFAULT, DOUBLE_WALKOVER].includes(matchUpStatus));
73
- if (pairedMatchUpIsDoubleExit) {
74
- return decorateResult({ result: { ...SUCCESS }, stack });
75
- }
76
- }
77
63
  if (loserMatchUp && (loserMatchUp.matchUpStatus !== BYE || byePropagatedToLoserMatchUp)) {
78
64
  const inContextLoserMatchUp = inContextDrawMatchUps.find(({ matchUpId }) => matchUpId === loserMatchUp.matchUpId);
79
65
  const { structure: loserStructure } = findStructure({
@@ -0,0 +1,13 @@
1
+ import { removeDoubleExit } from './removeDoubleExit.mjs';
2
+ import { isDoubleExit } from '../../../validators/isExit.mjs';
3
+ import { SUCCESS } from '../../../constants/resultConstants.mjs';
4
+
5
+ function unwindDualDoubleExit(params) {
6
+ const { isCollectionMatchUp, dualMatchUp, matchUpsMap } = params;
7
+ if (!isCollectionMatchUp || !isDoubleExit(dualMatchUp?.matchUpStatus))
8
+ return { ...SUCCESS };
9
+ const rawDual = matchUpsMap?.drawMatchUps?.find((candidate) => candidate.matchUpId === dualMatchUp.matchUpId) ?? dualMatchUp;
10
+ return removeDoubleExit({ ...params, matchUp: rawDual });
11
+ }
12
+
13
+ export { unwindDualDoubleExit };
@@ -773,6 +773,13 @@ function getExitArrivalSideNumber({ inContextDrawMatchUps, nextWinnerMatchUp, so
773
773
  if (nextWinnerMatchUp.feedRound) {
774
774
  return sourceMatchUp.structureId === nextWinnerMatchUp.structureId ? 2 : 1;
775
775
  }
776
+ const sourcePositions = (sourceMatchUp.drawPositions ?? []).filter(Boolean);
777
+ const targetPositions = (nextWinnerMatchUp.drawPositions ?? []).filter(Boolean);
778
+ if (targetPositions.length === 2) {
779
+ const seated = nextWinnerMatchUp.sides?.find((side) => side?.sideNumber && side.drawPosition && sourcePositions.includes(side.drawPosition));
780
+ if (seated)
781
+ return seated.sideNumber;
782
+ }
776
783
  const feeders = inContextDrawMatchUps
777
784
  .filter(({ winnerMatchUpId, loserMatchUpId }) => winnerMatchUpId === nextWinnerMatchUp.matchUpId || loserMatchUpId === nextWinnerMatchUp.matchUpId)
778
785
  .sort((a, b) => a.roundPosition - b.roundPosition);
@@ -172,6 +172,22 @@ function assignDrawPositionBye({ byeFromPropagation, provisionalPositioning, pre
172
172
  if (result.error)
173
173
  return result;
174
174
  }
175
+ else if (matchUp &&
176
+ roundNumber &&
177
+ roundNumber > (getInitialRoundNumber({ drawPosition, matchUps }).initialRoundNumber ?? 0)) {
178
+ const result = assignByeToLoserTarget({
179
+ byeFromPropagation,
180
+ inContextDrawMatchUps,
181
+ preserveScheduling,
182
+ tournamentRecord,
183
+ drawDefinition,
184
+ matchUpsMap,
185
+ matchUp,
186
+ event,
187
+ });
188
+ if (result?.error)
189
+ return result;
190
+ }
175
191
  modifyPositionAssignmentsNotice({
176
192
  tournamentId: tournamentRecord?.tournamentId,
177
193
  drawDefinition,
@@ -422,11 +438,6 @@ function advanceWinner({ sourceRoundPosition, byeFromPropagation, drawPositionTo
422
438
  context: stack,
423
439
  drawDefinition,
424
440
  });
425
- const { targetLinks: { loserTargetLink }, targetMatchUps: { loserMatchUp, loserTargetDrawPosition }, } = positionTargets({
426
- matchUpId: winnerMatchUp.matchUpId,
427
- inContextDrawMatchUps,
428
- drawDefinition,
429
- });
430
441
  if (pairedDrawPositionIsBye || drawPositionIsBye) {
431
442
  const advancingDrawPosition = pairedDrawPositionIsBye ? drawPositionToAdvance : pairedDrawPosition;
432
443
  if (advancingDrawPosition) {
@@ -440,38 +451,55 @@ function advanceWinner({ sourceRoundPosition, byeFromPropagation, drawPositionTo
440
451
  matchUpsMap,
441
452
  });
442
453
  }
443
- else if (drawPositionIsBye && loserTargetLink && loserMatchUp) {
444
- if (loserMatchUp.feedRound) {
445
- assignFedDrawPositionBye({
446
- byeFromPropagation,
447
- loserTargetDrawPosition,
448
- preserveScheduling,
449
- tournamentRecord,
450
- loserTargetLink,
451
- drawDefinition,
452
- loserMatchUp,
453
- matchUpsMap,
454
- });
455
- }
456
- else {
457
- const sourceStructureRoundPosition = winnerMatchUp.roundPosition;
458
- const targetDrawPositionIndex = 1 - (sourceStructureRoundPosition % 2);
459
- const targetDrawPosition = loserMatchUp.drawPositions[targetDrawPositionIndex];
460
- const result = assignDrawPositionBye({
461
- byeFromPropagation,
462
- structureId: loserTargetLink.target.structureId,
463
- drawPosition: targetDrawPosition,
464
- preserveScheduling,
465
- tournamentRecord,
466
- drawDefinition,
467
- event,
468
- });
469
- if (result.error)
470
- return result;
471
- }
454
+ else if (drawPositionIsBye) {
455
+ const result = assignByeToLoserTarget({
456
+ matchUp: winnerMatchUp,
457
+ byeFromPropagation,
458
+ inContextDrawMatchUps,
459
+ preserveScheduling,
460
+ tournamentRecord,
461
+ drawDefinition,
462
+ matchUpsMap,
463
+ event,
464
+ });
465
+ if (result?.error)
466
+ return result;
472
467
  }
473
468
  }
474
469
  }
470
+ function assignByeToLoserTarget({ byeFromPropagation, inContextDrawMatchUps, preserveScheduling, tournamentRecord, drawDefinition, matchUpsMap, matchUp, event, }) {
471
+ const { targetLinks: { loserTargetLink }, targetMatchUps: { loserMatchUp, loserTargetDrawPosition }, } = positionTargets({
472
+ matchUpId: matchUp.matchUpId,
473
+ inContextDrawMatchUps,
474
+ drawDefinition,
475
+ });
476
+ if (!loserTargetLink || !loserMatchUp)
477
+ return { ...SUCCESS };
478
+ if (loserMatchUp.feedRound) {
479
+ return assignFedDrawPositionBye({
480
+ byeFromPropagation,
481
+ loserTargetDrawPosition,
482
+ preserveScheduling,
483
+ tournamentRecord,
484
+ loserTargetLink,
485
+ drawDefinition,
486
+ loserMatchUp,
487
+ matchUpsMap,
488
+ });
489
+ }
490
+ const sourceStructureRoundPosition = matchUp.roundPosition;
491
+ const targetDrawPositionIndex = 1 - (sourceStructureRoundPosition % 2);
492
+ const targetDrawPosition = loserMatchUp.drawPositions[targetDrawPositionIndex];
493
+ return assignDrawPositionBye({
494
+ byeFromPropagation,
495
+ structureId: loserTargetLink.target.structureId,
496
+ drawPosition: targetDrawPosition,
497
+ preserveScheduling,
498
+ tournamentRecord,
499
+ drawDefinition,
500
+ event,
501
+ });
502
+ }
475
503
  function arrivalIntoProvenanceOnlyExit({ existingDrawPositions, drawPositionToAdvance, pairedDrawPositionIsBye, sourceRoundPosition, drawPositionIsBye, holdsParticipant, inContextMatchUp, matchUp, ...context }) {
476
504
  const exitSides = Object.keys(matchUp.sideExitProvenance ?? {}).map(Number);
477
505
  const applies = isExit(matchUp.matchUpStatus) &&
@@ -1,7 +1,7 @@
1
- import { retainByeClaimsOnly, clearSideExitProvenance } from '../matchUpStatus/sideExitProvenance.mjs';
2
1
  import { modifyRoundRobinMatchUpsStatus } from '../matchUpStatus/modifyRoundRobinMatchUpsStatus.mjs';
3
2
  import { modifyPositionAssignmentsNotice, modifyMatchUpNotice } from '../../notifications/drawNotifications.mjs';
4
3
  import { structureAssignedDrawPositions, getPositionAssignments } from '../../../query/drawDefinition/positionsGetter.mjs';
4
+ import { releaseLinkedWinnerAdvancement } from './releaseLinkedWinnerAdvancement.mjs';
5
5
  import { getStructureDrawPositionProfiles } from '../../../query/structure/getStructureDrawPositionProfiles.mjs';
6
6
  import { normalizeDrawPositions } from './normalizeDrawPositions.mjs';
7
7
  import { getAllStructureMatchUps } from '../../../query/matchUps/getAllStructureMatchUps.mjs';
@@ -15,6 +15,7 @@ import { pushGlobalLog } from '../../../functions/global/globalLog.mjs';
15
15
  import { findStructure } from '../../../acquire/findStructure.mjs';
16
16
  import { ensureInt } from '../../../tools/ensureInt.mjs';
17
17
  import { overlap } from '../../../tools/arrays.mjs';
18
+ import { clearSideExitProvenance, deriveExitStateFromProvenance, retainByeClaimsOnly } from '../matchUpStatus/sideExitProvenance.mjs';
18
19
  import { BYE, TO_BE_PLAYED } from '../../../constants/matchUpStatusValues.mjs';
19
20
  import { CONTAINER, DRAW } from '../../../constants/drawDefinitionConstants.mjs';
20
21
  import { SUCCESS } from '../../../constants/resultConstants.mjs';
@@ -80,7 +81,6 @@ function drawPositionRemovals({ inContextDrawMatchUps, tournamentRecord, drawDef
80
81
  drawDefinition,
81
82
  structure,
82
83
  }).positionAssignments ?? [];
83
- const withdrawsPropagatedBye = positionAssignments.some((assignment) => assignment.drawPosition === drawPosition && assignment.bye && assignment.byeFromPropagation);
84
84
  const drawPositionCleared = positionAssignments.some((assignment) => {
85
85
  if (assignment.drawPosition === drawPosition) {
86
86
  delete assignment.participantId;
@@ -123,7 +123,6 @@ function drawPositionRemovals({ inContextDrawMatchUps, tournamentRecord, drawDef
123
123
  return;
124
124
  }
125
125
  removeSubsequentRoundsParticipant({
126
- withdrawsPropagatedBye,
127
126
  inContextDrawMatchUps,
128
127
  targetDrawPosition,
129
128
  tournamentRecord,
@@ -133,7 +132,6 @@ function drawPositionRemovals({ inContextDrawMatchUps, tournamentRecord, drawDef
133
132
  matchUpsMap,
134
133
  });
135
134
  removeDrawPosition({
136
- withdrawsPropagatedBye,
137
135
  inContextDrawMatchUps,
138
136
  positionAssignments,
139
137
  tournamentRecord,
@@ -147,7 +145,7 @@ function drawPositionRemovals({ inContextDrawMatchUps, tournamentRecord, drawDef
147
145
  });
148
146
  return { tasks, drawPositionCleared, positionAssignments };
149
147
  }
150
- function removeSubsequentRoundsParticipant({ withdrawsPropagatedBye, inContextDrawMatchUps, targetDrawPosition, tournamentRecord, drawDefinition, matchUpsMap, roundNumber, structureId, }) {
148
+ function removeSubsequentRoundsParticipant({ inContextDrawMatchUps, targetDrawPosition, tournamentRecord, drawDefinition, matchUpsMap, roundNumber, structureId, }) {
151
149
  const { structure } = findStructure({ drawDefinition, structureId });
152
150
  if (!structure)
153
151
  return { error: STRUCTURE_NOT_FOUND };
@@ -169,7 +167,6 @@ function removeSubsequentRoundsParticipant({ withdrawsPropagatedBye, inContextDr
169
167
  }).positionAssignments ?? [];
170
168
  relevantMatchUps?.forEach((matchUp) => removeDrawPosition({
171
169
  drawPosition: targetDrawPosition,
172
- withdrawsPropagatedBye,
173
170
  targetMatchUp: matchUp,
174
171
  inContextDrawMatchUps,
175
172
  positionAssignments,
@@ -180,7 +177,7 @@ function removeSubsequentRoundsParticipant({ withdrawsPropagatedBye, inContextDr
180
177
  }));
181
178
  return { ...SUCCESS };
182
179
  }
183
- function removeDrawPosition({ withdrawsPropagatedBye, inContextDrawMatchUps, positionAssignments, tournamentRecord, drawDefinition, targetMatchUp, drawPosition, matchUpsMap, structure, event, }) {
180
+ function removeDrawPosition({ inContextDrawMatchUps, positionAssignments, tournamentRecord, drawDefinition, targetMatchUp, drawPosition, matchUpsMap, structure, event, }) {
184
181
  const stack = 'removeDrawPosition';
185
182
  const initialDrawPositions = targetMatchUp.drawPositions?.slice();
186
183
  const initialMatchUpStatus = targetMatchUp.matchUpStatus;
@@ -192,13 +189,26 @@ function removeDrawPosition({ withdrawsPropagatedBye, inContextDrawMatchUps, pos
192
189
  drawPosition,
193
190
  matchUps,
194
191
  });
195
- const keepsByeAdvancement = !!withdrawsPropagatedBye &&
196
- advancedByOpponentsBye({ matchUps: matchUps ?? [], positionAssignments, targetMatchUp, drawPosition });
192
+ const keepsByeAdvancement = advancedByOpponentsBye({
193
+ matchUps: matchUps ?? [],
194
+ positionAssignments,
195
+ targetMatchUp,
196
+ drawPosition,
197
+ });
197
198
  if (!keepsByeAdvancement &&
198
199
  targetMatchUp.roundNumber &&
199
200
  initialRoundNumber &&
200
201
  targetMatchUp.roundNumber > initialRoundNumber) {
201
202
  targetMatchUp.drawPositions = normalizeDrawPositions((targetMatchUp.drawPositions ?? []).map((currentDrawPosition) => currentDrawPosition === drawPosition ? undefined : currentDrawPosition));
203
+ releaseLinkedWinnerAdvancement({
204
+ roundNumber: targetMatchUp.roundNumber,
205
+ structureId: structure.structureId,
206
+ tournamentRecord,
207
+ drawDefinition,
208
+ drawPosition,
209
+ matchUpsMap,
210
+ event,
211
+ });
202
212
  }
203
213
  handleTeamPositionRemoval({
204
214
  inContextDrawMatchUps,
@@ -313,11 +323,18 @@ function updateMatchUpStatusAfterRemoval({ initialDrawPositions, initialMatchUpS
313
323
  targetMatchUp.winningSide = undefined;
314
324
  if (targetMatchUp.matchUpStatusCodes?.length)
315
325
  targetMatchUp.matchUpStatusCodes = [];
316
- const retainedClaims = retainByeClaimsOnly(targetMatchUp.sideExitProvenance);
317
- clearSideExitProvenance(targetMatchUp);
318
- if (retainedClaims)
319
- targetMatchUp.sideExitProvenance = retainedClaims;
320
326
  const removedDrawPosition = initialDrawPositions?.find((position) => !targetMatchUp.drawPositions?.includes(position));
327
+ const clearedIndex = initialDrawPositions?.indexOf(drawPosition) ?? -1;
328
+ const clearedSideNumber = clearedIndex >= 0 ? clearedIndex + 1 : undefined;
329
+ const retained = retainProvenanceBesideRemoval(targetMatchUp.sideExitProvenance, clearedSideNumber);
330
+ clearSideExitProvenance(targetMatchUp);
331
+ if (retained)
332
+ targetMatchUp.sideExitProvenance = retained;
333
+ const rederived = !matchUpContainsBye && retained ? deriveExitStateFromProvenance(retained) : undefined;
334
+ if (rederived) {
335
+ targetMatchUp.matchUpStatus = rederived.matchUpStatus;
336
+ targetMatchUp.winningSide = rederived.winningSide;
337
+ }
321
338
  const noChange = initialDrawPositions?.includes(drawPosition) &&
322
339
  initialMatchUpStatus === targetMatchUp.matchUpStatus &&
323
340
  initialWinningSide === targetMatchUp.winningSide;
@@ -340,6 +357,23 @@ function updateMatchUpStatusAfterRemoval({ initialDrawPositions, initialMatchUpS
340
357
  }
341
358
  return matchUpContainsBye;
342
359
  }
360
+ function retainProvenanceBesideRemoval(provenance, clearedSideNumber) {
361
+ if (!provenance)
362
+ return undefined;
363
+ if (!clearedSideNumber)
364
+ return retainByeClaimsOnly(provenance);
365
+ const retained = {};
366
+ for (const sideNumber of [1, 2]) {
367
+ const entry = provenance[sideNumber];
368
+ if (!entry)
369
+ continue;
370
+ if (sideNumber !== clearedSideNumber)
371
+ retained[sideNumber] = { ...entry };
372
+ else if (entry.byeClaims?.length)
373
+ retained[sideNumber] = { byeClaims: [...entry.byeClaims] };
374
+ }
375
+ return Object.keys(retained).length ? retained : undefined;
376
+ }
343
377
  function handleLoserMatchUpRemoval({ loserMatchUpDrawPositionIndex, inContextDrawMatchUps, tournamentRecord, drawDefinition, loserMatchUp, matchUpsMap, event, stack, }) {
344
378
  const { drawPositions, roundNumber } = loserMatchUp;
345
379
  if (roundNumber === 1) {
@@ -4,7 +4,8 @@ import { getAllDrawMatchUps } from '../../../query/matchUps/drawMatchUps.mjs';
4
4
  import { positionTargets } from '../../../query/matchUp/positionTargets.mjs';
5
5
  import { getMatchUpsMap } from '../../../query/matchUps/getMatchUpsMap.mjs';
6
6
  import { findStructure } from '../../../acquire/findStructure.mjs';
7
- import { DEAD_RUBBER, TO_BE_PLAYED } from '../../../constants/matchUpStatusValues.mjs';
7
+ import { isAnyExit } from '../../../validators/isExit.mjs';
8
+ import { BYE, DEAD_RUBBER, TO_BE_PLAYED } from '../../../constants/matchUpStatusValues.mjs';
8
9
  import { WINNER, LOSER } from '../../../constants/drawDefinitionConstants.mjs';
9
10
  import { SUCCESS } from '../../../constants/resultConstants.mjs';
10
11
 
@@ -38,6 +39,8 @@ function reconcileDecider({ winningSideBefore, tournamentRecord, drawDefinition,
38
39
  const holdsAResult = !!decider.winningSide || !!decider.score?.sets?.length;
39
40
  if (!holdsAResult && decider.matchUpStatus === matchUpStatus)
40
41
  return { ...SUCCESS };
42
+ if (!holdsAResult && (decider.matchUpStatus === BYE || isAnyExit(decider.matchUpStatus)))
43
+ return { ...SUCCESS };
41
44
  return modifyMatchUpScore({
42
45
  matchUpId: decider.matchUpId,
43
46
  removeWinningSide: true,
@@ -1,13 +1,12 @@
1
- import { feedEligibilityChange } from './feedEligibilityGuard.mjs';
2
1
  import { noDownstreamDependencies } from '../../drawDefinitions/matchUpGovernor/noDownstreamDependencies.mjs';
3
2
  import { generateTieMatchUpScore } from '../../../assemblies/generators/tieMatchUpScore/generateTieMatchUpScore.mjs';
4
3
  import { isNonDirectingMatchUpStatus, isDirectingMatchUpStatus } from '../../../query/matchUp/checkStatusType.mjs';
5
4
  import { addMatchUpScheduleItems } from '../schedule/scheduleItems/scheduleItems.mjs';
6
5
  import { hasPropagatedExitDownstream } from '../../../query/drawDefinition/hasPropagatedExitDownstream.mjs';
6
+ import { feedEligibilityChange } from './feedEligibilityGuard.mjs';
7
7
  import { getProjectedDualWinningSide } from '../../../query/matchUp/getProjectedDualWinningSide.mjs';
8
- import { updateTieMatchUpScore } from '../score/updateTieMatchUpScore.mjs';
9
- import { getMatchUpStatusScopeViolation } from '../../../query/matchUps/getMatchUpStatusScopeViolation.mjs';
10
8
  import { setFirstClassOrExtension } from '../../extensions/setFirstClassOrExtension.mjs';
9
+ import { updateTieMatchUpScore } from '../score/updateTieMatchUpScore.mjs';
11
10
  import { isMatchUpEventType } from '../../../helpers/matchUpEventTypes/isMatchUpEventType.mjs';
12
11
  import { resolveTieFormat } from '../../../query/hierarchical/tieFormats/resolveTieFormat.mjs';
13
12
  import { swapWinnerLoser } from '../drawPositions/swapWinnerLoser.mjs';
@@ -29,6 +28,7 @@ import { isAdHoc } from '../../../query/drawDefinition/isAdHoc.mjs';
29
28
  import { findStructure } from '../../../acquire/findStructure.mjs';
30
29
  import { isDoubleExit } from '../../../validators/isExit.mjs';
31
30
  import { isObject } from '../../../tools/objects.mjs';
31
+ import { getMatchUpStatusScopeViolation } from '../../../query/matchUps/getMatchUpStatusScopeViolation.mjs';
32
32
  import { POLICY_TYPE_PROGRESSION, POLICY_TYPE_SCORING } from '../../../constants/policyConstants.mjs';
33
33
  import { DISABLE_AUTO_CALC } from '../../../constants/extensionConstants.mjs';
34
34
  import { QUALIFYING } from '../../../constants/drawDefinitionConstants.mjs';
@@ -388,6 +388,14 @@ function resolveAndApplyOutcome({ params, isTeam, dualWinningSideChange, activeD
388
388
  winningSide,
389
389
  });
390
390
  const hasPropagated = !!matchUp.winningSide || isDoubleExit(matchUp.matchUpStatus);
391
+ if (params.isCollectionMatchUp && isDoubleExit(params.dualMatchUp?.matchUpStatus) && activeDownstream) {
392
+ return decorateResult({
393
+ result: { error: CANNOT_CHANGE_OUTCOME },
394
+ info: 'the dual holds a double exit whose produced result has been played on',
395
+ context: { dualMatchUpId: params.dualMatchUp.matchUpId, dualMatchUpStatus: params.dualMatchUp.matchUpStatus },
396
+ stack,
397
+ });
398
+ }
391
399
  let result;
392
400
  if (!activeDownstream || !hasPropagated) {
393
401
  result = noDownstreamDependencies(params);
@@ -39,7 +39,7 @@ function courtModification({ tournamentRecord, disableNotice, venueMatchUps, mod
39
39
  return { error: INVALID_OBJECT };
40
40
  const result = findCourt({ tournamentRecord, courtId });
41
41
  if (result.error)
42
- return result;
42
+ return { error: result.error };
43
43
  const { venue, court } = result;
44
44
  const validAttributes = Object.keys(courtTemplate()).filter((attribute) => attribute !== 'courtId');
45
45
  const validModificationAttributes = Object.keys(modifications).filter((attribute) => validAttributes.includes(attribute));
@@ -1,3 +1,3 @@
1
- var version = "7.3.1";
1
+ var version = "7.4.0";
2
2
 
3
3
  export { version };
@@ -15,10 +15,9 @@ function checkSetIsComplete({ ignoreTiebreak = false, matchUpScoringFormat, matc
15
15
  const requiresTiebreak = isTiebreakSet ||
16
16
  (side1Score >= setTo && side2Score >= setTo) ||
17
17
  (tiebreakAt && tiebreakAt < setTo && (side1Score === tiebreakAt || side2Score === tiebreakAt));
18
- const tiebreakIsValid = ignoreTiebreak ||
19
- (requiresTiebreak &&
20
- ((leadingSide === 1 && set.side1TiebreakScore > set.side2TiebreakScore) ||
21
- (leadingSide === 2 && set.side2TiebreakScore > set.side1TiebreakScore)));
18
+ const leaderHoldsTiebreak = (leadingSide === 1 && set.side1TiebreakScore > set.side2TiebreakScore) ||
19
+ (leadingSide === 2 && set.side2TiebreakScore > set.side1TiebreakScore);
20
+ const tiebreakIsValid = ignoreTiebreak || (requiresTiebreak && leaderHoldsTiebreak && tiebreakReachesTarget(set, setFormat, isTiebreakSet));
22
21
  const declaredWinBy = setFormat.winBy;
23
22
  const winMargin = (!requiresTiebreak && setFormat.NoAD) || requiresTiebreak || (isTiebreakSet && setFormat.tiebreakFormat?.NoAD)
24
23
  ? 1
@@ -28,6 +27,16 @@ function checkSetIsComplete({ ignoreTiebreak = false, matchUpScoringFormat, matc
28
27
  return !!(((isTimedSet && hasScore) || validNormalSetScore || isTiebreakSet) &&
29
28
  (!requiresTiebreak || tiebreakIsValid));
30
29
  }
30
+ function tiebreakReachesTarget(set, setFormat, isTiebreakSet) {
31
+ const tiebreakFormat = isTiebreakSet ? setFormat?.tiebreakSet : setFormat?.tiebreakFormat;
32
+ const tiebreakTo = tiebreakFormat?.tiebreakTo;
33
+ if (typeof tiebreakTo !== 'number')
34
+ return true;
35
+ const high = Math.max(set.side1TiebreakScore ?? 0, set.side2TiebreakScore ?? 0);
36
+ const low = Math.min(set.side1TiebreakScore ?? 0, set.side2TiebreakScore ?? 0);
37
+ const margin = tiebreakFormat?.NoAD ? 1 : Math.min(2, tiebreakTo);
38
+ return high >= tiebreakTo && high - low >= margin;
39
+ }
31
40
  function getLeadingSide({ set }) {
32
41
  if (set.side1Score || set.side2Score) {
33
42
  if (set.side1Score > set.side2Score)
@@ -76,7 +76,7 @@ const getMaxSetScore = (params) => {
76
76
  if (tiebreakAt < setTo)
77
77
  return setTo;
78
78
  const ceiling = setTo + 1;
79
- if (opponentScore !== undefined && opponentScore < setTo)
79
+ if (opponentScore !== undefined && opponentScore < setTo - 1)
80
80
  return setTo;
81
81
  return ceiling;
82
82
  };
@@ -144,6 +144,17 @@ function parseSetScores(set, isTiebreakOnlyFormat, hasTiebreakScores) {
144
144
  const side2Score = isTiebreakOnlyFormat && hasTiebreakScores ? side2TiebreakScore : set.side2Score || set.side2 || 0;
145
145
  return { side1Score, side2Score, side1TiebreakScore, side2TiebreakScore };
146
146
  }
147
+ function validateTiebreakWinner(side1Score, side2Score, side1TiebreakScore, side2TiebreakScore) {
148
+ if (side1TiebreakScore === undefined || side2TiebreakScore === undefined)
149
+ return undefined;
150
+ if (side1Score === side2Score || side1TiebreakScore === side2TiebreakScore)
151
+ return undefined;
152
+ const gamesWinner = side1Score > side2Score ? 1 : 2;
153
+ const pointsWinner = side1TiebreakScore > side2TiebreakScore ? 1 : 2;
154
+ if (gamesWinner === pointsWinner)
155
+ return undefined;
156
+ return { isValid: false, error: `Set winner must win the tiebreak: side ${gamesWinner} won the set` };
157
+ }
147
158
  function validateTiebreakSet(winnerScore, loserScore, setTo, tiebreakAt, side1TiebreakScore, side2TiebreakScore, tiebreakFormat) {
148
159
  if (setTo && tiebreakAt) {
149
160
  const validation = validateTiebreakSetGames(winnerScore, loserScore, setTo, tiebreakAt);
@@ -226,6 +237,9 @@ function validateSetScore(set, matchUpFormat, isDecidingSet, allowIncomplete) {
226
237
  const isImplicitTiebreak = setTo && winnerScore === setTo + 1 && loserScore === setTo;
227
238
  const hasTiebreak = hasExplicitTiebreak || isImplicitTiebreak;
228
239
  if (hasTiebreak) {
240
+ const contradiction = validateTiebreakWinner(side1Score, side2Score, side1TiebreakScore, side2TiebreakScore);
241
+ if (contradiction)
242
+ return contradiction;
229
243
  return validateTiebreakSet(winnerScore, loserScore, setTo, tiebreakAt, side1TiebreakScore, side2TiebreakScore, tiebreakFormat);
230
244
  }
231
245
  return validateRegularSet({ side1: side1Score, side2: side2Score, winner: winnerScore, loser: loserScore, diff: scoreDiff }, { setTo, tiebreakAt, NoAD: setFormat.NoAD, winBy: setFormat.winBy }, allowIncomplete);