tods-competition-factory 7.1.1 → 7.2.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.
@@ -150,6 +150,10 @@ function withdrawExitFromByeChain({ inContextDrawMatchUps, withdrawnSourceIds, d
150
150
  if (!noContextTargetMatchUp || !carriesWithdrawnOrigin(noContextTargetMatchUp, withdrawnSourceIds)) {
151
151
  return { ...SUCCESS };
152
152
  }
153
+ const priorWinningSide = noContextTargetMatchUp.winningSide;
154
+ const advancedDrawPosition = priorWinningSide
155
+ ? (noContextTargetMatchUp.drawPositions ?? [])[priorWinningSide - 1]
156
+ : undefined;
153
157
  const unwound = getUnwoundState({
154
158
  pairedPreviousDoubleExit: false,
155
159
  noContextTargetMatchUp,
@@ -191,6 +195,23 @@ function withdrawExitFromByeChain({ inContextDrawMatchUps, withdrawnSourceIds, d
191
195
  const nextWinnerMatchUp = targetMatchUps?.winnerMatchUp;
192
196
  if (!nextWinnerMatchUp?.matchUpId)
193
197
  return { ...SUCCESS };
198
+ if (advancedDrawPosition && nextWinnerMatchUp.structureId === fromMatchUp.structureId) {
199
+ pushGlobalLog({
200
+ method: 'withdrawExitFromByeChain',
201
+ decision: 'take_back_advanced_winner',
202
+ nextWinnerMatchUpId: nextWinnerMatchUp.matchUpId,
203
+ advancedDrawPosition,
204
+ color: 'brightcyan',
205
+ keyColors,
206
+ });
207
+ removeDirectedWinner({
208
+ winningDrawPosition: advancedDrawPosition,
209
+ winnerMatchUp: nextWinnerMatchUp,
210
+ inContextDrawMatchUps,
211
+ drawDefinition,
212
+ matchUpsMap,
213
+ });
214
+ }
194
215
  return withdrawExitFromByeChain({
195
216
  fromMatchUp: nextWinnerMatchUp,
196
217
  inContextDrawMatchUps,
@@ -1,3 +1,4 @@
1
+ import { propagateUnfillableLoserBye } from '../../matchUps/drawPositions/propagateUnfillableLoserBye.mjs';
1
2
  import { assignDrawPositionBye, advanceDrawPosition } from '../../matchUps/drawPositions/assignDrawPositionBye.mjs';
2
3
  import { getPairedPreviousMatchUpIsDoubleExit } from '../../../query/matchUps/getPairedPreviousMatchUpIsDoubleExit.mjs';
3
4
  import { assignMatchUpDrawPosition, directWinner } from '../../matchUps/drawPositions/drawPositionPlacement.mjs';
@@ -638,7 +639,12 @@ function advanceByeAdvancedDrawPosition({ nextWinnerMatchUpDrawPositions, nextWi
638
639
  const advancingParticipantId = inContextDrawMatchUps
639
640
  .find((candidate) => candidate.matchUpId === noContextNextWinnerMatchUp.matchUpId)
640
641
  ?.sides?.find((side) => side.drawPosition === nextDrawPositionToAdvance)?.participantId;
641
- const winningSide = advancingParticipantId || !occupiedSide ? occupiedSide : 3 - occupiedSide;
642
+ const arrivalSideNumber = getExitArrivalSideNumber({
643
+ inContextDrawMatchUps,
644
+ nextWinnerMatchUp,
645
+ sourceMatchUp: targetMatchUp,
646
+ });
647
+ const winningSide = advancingParticipantId || !occupiedSide ? occupiedSide : 3 - (arrivalSideNumber ?? occupiedSide);
642
648
  const exitingSideNumber = isExit(EXIT) && winningSide ? 3 - winningSide : undefined;
643
649
  const provenance = {
644
650
  ...getSideExitProvenance({ matchUp: noContextNextWinnerMatchUp }),
@@ -728,7 +734,9 @@ function opponentFeederCanDeliver({ inContextDrawMatchUps, nextWinnerMatchUp, so
728
734
  const holdsParticipant = feeder.sides?.some((side) => side.participantId);
729
735
  if (holdsParticipant)
730
736
  return true;
731
- return !isAnyExit(feeder.matchUpStatus) && feeder.matchUpStatus !== BYE;
737
+ if (feeder.matchUpStatus === BYE)
738
+ return !!feeder.sides?.some((side) => side && !side.participantId && !side.bye);
739
+ return !isAnyExit(feeder.matchUpStatus);
732
740
  });
733
741
  }
734
742
  function matchUpHoldsBye({ drawDefinition, matchUp }) {
@@ -760,7 +768,15 @@ function carryExitOnward({ inContextDrawMatchUps, drawDefinition, matchUpsMap, o
760
768
  });
761
769
  if (!arrivalSideNumber)
762
770
  return decorateResult({ result: { ...SUCCESS }, stack });
763
- if (nextWinnerMatchUp.sides?.some((side) => side.participantId) || isAnyExit(nextWinnerMatchUp.matchUpStatus)) {
771
+ const arrivingSideOccupied = !!nextWinnerMatchUp.sides?.some((side) => side?.sideNumber === arrivalSideNumber && side?.participantId);
772
+ if (arrivingSideOccupied || isAnyExit(nextWinnerMatchUp.matchUpStatus)) {
773
+ logAdvancement(stack, {
774
+ color: 'brightyellow',
775
+ decision: arrivingSideOccupied ? 'exit_not_carried_arriving_side_occupied' : 'exit_not_carried_convergence',
776
+ nextWinnerMatchUpId: nextWinnerMatchUp.matchUpId,
777
+ fromMatchUpId: fromMatchUp.matchUpId,
778
+ arrivalSideNumber,
779
+ });
764
780
  return decorateResult({ result: { ...SUCCESS }, stack });
765
781
  }
766
782
  const noContextNextWinnerMatchUp = matchUpsMap.drawMatchUps.find((matchUp) => matchUp.matchUpId === nextWinnerMatchUp.matchUpId);
@@ -777,6 +793,9 @@ function carryExitOnward({ inContextDrawMatchUps, drawDefinition, matchUpsMap, o
777
793
  });
778
794
  return decorateResult({ result: { ...SUCCESS }, stack });
779
795
  }
796
+ const opponentSideNumber = arrivalSideNumber === 1 ? 2 : 1;
797
+ const settledOpponentSide = nextWinnerMatchUp.sides?.find((side) => side?.sideNumber === opponentSideNumber && side?.participantId && !side?.bye);
798
+ const settledOpponentSideNumber = settledOpponentSide ? opponentSideNumber : undefined;
780
799
  const provenance = {
781
800
  ...getSideExitProvenance({ matchUp: noContextNextWinnerMatchUp }),
782
801
  ...buildCarriedExitProvenance({
@@ -800,7 +819,7 @@ function carryExitOnward({ inContextDrawMatchUps, drawDefinition, matchUpsMap, o
800
819
  matchUpId: noContextNextWinnerMatchUp.matchUpId,
801
820
  appliedPolicies: params.appliedPolicies,
802
821
  matchUp: noContextNextWinnerMatchUp,
803
- winningSide: undefined,
822
+ winningSide: !holdsBye ? settledOpponentSideNumber : undefined,
804
823
  matchUpStatus: holdsBye ? BYE : EXIT,
805
824
  removeScore: true,
806
825
  drawDefinition,
@@ -808,6 +827,36 @@ function carryExitOnward({ inContextDrawMatchUps, drawDefinition, matchUpsMap, o
808
827
  if (result.error)
809
828
  return decorateResult({ result, stack });
810
829
  mergeSideExitProvenance({ matchUp: noContextNextWinnerMatchUp, provenance });
830
+ if (!holdsBye && settledOpponentSideNumber && settledOpponentSide?.drawPosition) {
831
+ logAdvancement(stack, {
832
+ color: 'brightcyan',
833
+ keyColors: { decision: 'brightgreen' },
834
+ decision: 'carried_exit_advances_settled_opponent',
835
+ nextWinnerMatchUpId: nextWinnerMatchUp.matchUpId,
836
+ drawPositionToAdvance: settledOpponentSide.drawPosition,
837
+ winningSide: settledOpponentSideNumber,
838
+ });
839
+ const advanced = advanceDrawPosition({
840
+ inContextDrawMatchUps: getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [],
841
+ drawPositionToAdvance: settledOpponentSide.drawPosition,
842
+ matchUpId: noContextNextWinnerMatchUp.matchUpId,
843
+ tournamentRecord: params.tournamentRecord,
844
+ event: params.event,
845
+ drawDefinition,
846
+ matchUpsMap,
847
+ });
848
+ if (advanced?.error)
849
+ return decorateResult({ result: advanced, stack });
850
+ const unfillable = propagateUnfillableLoserBye({
851
+ matchUpId: noContextNextWinnerMatchUp.matchUpId,
852
+ tournamentRecord: params.tournamentRecord,
853
+ event: params.event,
854
+ drawDefinition,
855
+ matchUpsMap,
856
+ });
857
+ if (unfillable?.error)
858
+ return decorateResult({ result: unfillable, stack });
859
+ }
811
860
  if (!holdsBye)
812
861
  return decorateResult({ result: { ...SUCCESS }, stack });
813
862
  const refreshed = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? inContextDrawMatchUps;
@@ -1,5 +1,6 @@
1
1
  import { modifyDrawNotice, modifyMatchUpNotice } from '../notifications/drawNotifications.mjs';
2
2
  import { normalizeDrawPositions } from '../matchUps/drawPositions/normalizeDrawPositions.mjs';
3
+ import { clearSideExitProvenance } from '../matchUps/matchUpStatus/sideExitProvenance.mjs';
3
4
  import { getAllStructureMatchUps } from '../../query/matchUps/getAllStructureMatchUps.mjs';
4
5
  import { isLuckyBasedDraw } from '../../query/drawDefinition/isLuckyBasedDraw.mjs';
5
6
  import { removeExtension } from '../extensions/removeExtension.mjs';
@@ -144,7 +145,13 @@ function resetStructureMatchUps({ removeScheduling, tournamentRecord, removeAssi
144
145
  });
145
146
  }
146
147
  }
148
+ function clearExitResidue(matchUp) {
149
+ if (matchUp.matchUpStatusCodes?.length)
150
+ matchUp.matchUpStatusCodes = [];
151
+ clearSideExitProvenance(matchUp);
152
+ }
147
153
  function resetMatchUpScore({ matchUp, isLuckyDraw, removeAssignments, roundNumber, isRoundRobin, playedPositions }) {
154
+ clearExitResidue(matchUp);
148
155
  if (isLuckyDraw) {
149
156
  if (!removeAssignments && matchUp.matchUpStatus === BYE) ;
150
157
  else {
@@ -575,4 +575,4 @@ function assignFedDrawPositionBye({ byeFromPropagation, loserTargetDrawPosition,
575
575
  }
576
576
  }
577
577
 
578
- export { advanceDrawPosition, assignDrawPositionBye };
578
+ export { advanceDrawPosition, assignDrawPositionBye, assignFedDrawPositionBye };
@@ -6,7 +6,7 @@ import { getPairedPreviousMatchUpIsDoubleExit } from '../../../query/matchUps/ge
6
6
  import { getUpdatedDrawPositions } from '../../drawDefinitions/matchUpGovernor/getUpdatedDrawPositions.mjs';
7
7
  import { updateMatchUpStatusCodes } from '../../drawDefinitions/matchUpGovernor/matchUpStatusCodes.mjs';
8
8
  import { getStructureDrawPositionProfiles } from '../../../query/structure/getStructureDrawPositionProfiles.mjs';
9
- import { isPropagatedExit, clearResolvedSideExitProvenance } from '../matchUpStatus/sideExitProvenance.mjs';
9
+ import { isPropagatedExit, isProjectedExitCode, clearResolvedSideExitProvenance } from '../matchUpStatus/sideExitProvenance.mjs';
10
10
  import { getExitWinningSide } from '../../drawDefinitions/matchUpGovernor/getExitWinningSide.mjs';
11
11
  import { removeLineUpSubstitutions } from '../../drawDefinitions/removeLineUpSubstitutions.mjs';
12
12
  import { getMatchUpsMap, getMappedStructureMatchUps } from '../../../query/matchUps/getMatchUpsMap.mjs';
@@ -26,6 +26,7 @@ import { getRoundMatchUps } from '../../../query/matchUps/getRoundMatchUps.mjs';
26
26
  import { decorateResult } from '../../../functions/global/decorateResult.mjs';
27
27
  import { getAllDrawMatchUps } from '../../../query/matchUps/drawMatchUps.mjs';
28
28
  import { positionTargets } from '../../../query/matchUp/positionTargets.mjs';
29
+ import { propagateUnfillableLoserBye } from './propagateUnfillableLoserBye.mjs';
29
30
  import { assignDrawPositionBye } from './assignDrawPositionBye.mjs';
30
31
  import { getParticipantId } from '../../../functions/global/extractors.mjs';
31
32
  import { pushGlobalLog } from '../../../functions/global/globalLog.mjs';
@@ -106,7 +107,8 @@ function assignMatchUpDrawPosition({ inContextDrawMatchUps, sourceMatchUpStatus,
106
107
  !!matchUp?.roundNumber &&
107
108
  !!initialRoundNumber &&
108
109
  matchUp.roundNumber > initialRoundNumber;
109
- if (matchUp && positionAdded) {
110
+ const resolvesPendingExit = !positionAdded && isPropagatedExit$1 && slotNewlyOccupied;
111
+ if (matchUp && (positionAdded || resolvesPendingExit)) {
110
112
  applyPositionToMatchUp({
111
113
  updatedDrawPositions,
112
114
  holdsPropagatedExit,
@@ -189,6 +191,15 @@ function assignMatchUpDrawPosition({ inContextDrawMatchUps, sourceMatchUpStatus,
189
191
  });
190
192
  if (byeResult?.error)
191
193
  return byeResult;
194
+ const unfillableResult = propagateUnfillableLoserBye({
195
+ matchUpId: matchUp?.matchUpId,
196
+ tournamentRecord,
197
+ drawDefinition,
198
+ matchUpsMap,
199
+ event,
200
+ });
201
+ if (unfillableResult?.error)
202
+ return unfillableResult;
192
203
  return { ...SUCCESS };
193
204
  }
194
205
  function resolveMatchUpStatus({ isByeMatchUp, matchUpStatus, isDoubleExitExit, matchUp }) {
@@ -200,10 +211,12 @@ function resolveMatchUpStatus({ isByeMatchUp, matchUpStatus, isDoubleExitExit, m
200
211
  matchUp.matchUpStatus) ||
201
212
  TO_BE_PLAYED);
202
213
  }
203
- function exitCodeString(code) {
214
+ function policyCodeString(code) {
204
215
  if (typeof code === 'string')
205
216
  return code || undefined;
206
- return code?.matchUpStatusCode ?? code?.code ?? code?.matchUpStatus ?? undefined;
217
+ if (isProjectedExitCode(code))
218
+ return undefined;
219
+ return code?.matchUpStatusCode ?? code?.code ?? undefined;
207
220
  }
208
221
  function applyPositionToMatchUp({ updatedDrawPositions, sourceMatchUpStatus, holdsPropagatedExit, isPropagatedExit, isDoubleExitExit, tournamentRecord, inContextMatchUp, sourceMatchUpId, drawDefinition, matchUpStatus, drawPosition, matchUpsMap, matchUpId, matchUp, stack, event, }) {
209
222
  const refreshedMatchUps = getAllDrawMatchUps({
@@ -222,7 +235,10 @@ function applyPositionToMatchUp({ updatedDrawPositions, sourceMatchUpStatus, hol
222
235
  undefined;
223
236
  if (advancedExitWinningSide && !isDoubleExitExit) {
224
237
  const exitSideNumber = advancedExitWinningSide === 1 ? 2 : 1;
225
- const carriedCode = (matchUp.matchUpStatusCodes ?? []).map(exitCodeString).find(Boolean);
238
+ const carriedCode = (matchUp.matchUpStatusCodes ?? [])
239
+ .filter((code) => !isProjectedExitCode(code))
240
+ .map(policyCodeString)
241
+ .find(Boolean);
226
242
  const matchUpStatusCodes = [];
227
243
  if (carriedCode) {
228
244
  for (let i = 0; i < exitSideNumber - 1; i++)
@@ -0,0 +1,63 @@
1
+ import { getSideExitProvenance, carriedExitStatus, recordByeClaim } from '../matchUpStatus/sideExitProvenance.mjs';
2
+ import { getPositionAssignments } from '../../../query/drawDefinition/positionsGetter.mjs';
3
+ import { assignDrawPositionBye, assignFedDrawPositionBye } from './assignDrawPositionBye.mjs';
4
+ import { getAllDrawMatchUps } from '../../../query/matchUps/drawMatchUps.mjs';
5
+ import { positionTargets } from '../../../query/matchUp/positionTargets.mjs';
6
+ import { isExit } from '../../../validators/isExit.mjs';
7
+
8
+ function propagateUnfillableLoserBye({ tournamentRecord, drawDefinition, matchUpsMap, matchUpId, event, }) {
9
+ if (!matchUpId || !drawDefinition || !matchUpsMap)
10
+ return undefined;
11
+ const matchUp = matchUpsMap.drawMatchUps?.find((candidate) => candidate.matchUpId === matchUpId);
12
+ if (!matchUp?.winningSide || !isExit(matchUp.matchUpStatus))
13
+ return undefined;
14
+ const provenance = getSideExitProvenance({ matchUp }) ?? {};
15
+ const exitingSides = [1, 2].filter((sideNumber) => carriedExitStatus(provenance[sideNumber]));
16
+ if (exitingSides.length !== 1)
17
+ return undefined;
18
+ if (matchUp.winningSide === exitingSides[0])
19
+ return undefined;
20
+ const inContextDrawMatchUps = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
21
+ const inContextMatchUp = inContextDrawMatchUps.find((candidate) => candidate.matchUpId === matchUpId);
22
+ const { targetLinks: { loserTargetLink }, targetMatchUps: { loserMatchUp, loserTargetDrawPosition }, } = positionTargets({ inContextDrawMatchUps, drawDefinition, matchUpId });
23
+ if (!loserMatchUp || !loserTargetDrawPosition || !loserTargetLink)
24
+ return undefined;
25
+ if (loserMatchUp.structureId === inContextMatchUp?.structureId)
26
+ return undefined;
27
+ const { positionAssignments } = getPositionAssignments({ drawDefinition, structureId: loserMatchUp.structureId });
28
+ const targetAssignment = positionAssignments?.find((assignment) => assignment.drawPosition === loserTargetDrawPosition);
29
+ if (targetAssignment?.participantId || targetAssignment?.bye)
30
+ return undefined;
31
+ const result = loserMatchUp.roundNumber === 1
32
+ ? assignDrawPositionBye({
33
+ structureId: loserMatchUp.structureId,
34
+ drawPosition: loserTargetDrawPosition,
35
+ byeFromPropagation: true,
36
+ tournamentRecord,
37
+ drawDefinition,
38
+ matchUpsMap,
39
+ event,
40
+ })
41
+ : assignFedDrawPositionBye({
42
+ loserTargetDrawPosition,
43
+ byeFromPropagation: true,
44
+ loserTargetLink,
45
+ tournamentRecord,
46
+ drawDefinition,
47
+ loserMatchUp,
48
+ matchUpsMap,
49
+ event,
50
+ });
51
+ if (result?.error)
52
+ return result;
53
+ const refreshed = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
54
+ const inContextTarget = refreshed.find((candidate) => candidate.matchUpId === loserMatchUp.matchUpId);
55
+ const claimSideNumber = (inContextTarget?.sides ?? []).find((side) => side?.drawPosition === loserTargetDrawPosition)?.sideNumber;
56
+ if (claimSideNumber === 1 || claimSideNumber === 2) {
57
+ const rawLoserMatchUp = matchUpsMap.drawMatchUps?.find((candidate) => candidate.matchUpId === loserMatchUp.matchUpId);
58
+ recordByeClaim({ claimantMatchUpId: matchUpId, sideNumber: claimSideNumber, matchUp: rawLoserMatchUp });
59
+ }
60
+ return undefined;
61
+ }
62
+
63
+ export { propagateUnfillableLoserBye };
@@ -81,6 +81,17 @@ function clearSideExitProvenance(matchUp) {
81
81
  if (matchUp)
82
82
  delete matchUp.sideExitProvenance;
83
83
  }
84
+ function retainByeClaimsOnly(provenance) {
85
+ if (!provenance)
86
+ return undefined;
87
+ const retained = {};
88
+ for (const sideNumber of [1, 2]) {
89
+ const claims = provenance[sideNumber]?.byeClaims;
90
+ if (claims?.length)
91
+ retained[sideNumber] = { byeClaims: [...claims] };
92
+ }
93
+ return Object.keys(retained).length ? retained : undefined;
94
+ }
84
95
  function clearResolvedSideExitProvenance(matchUp) {
85
96
  if (!matchUp || isAnyExit(matchUp.matchUpStatus) || matchUp.matchUpStatus === BYE)
86
97
  return;
@@ -218,6 +229,13 @@ function projectExitStatusCodes(provenance) {
218
229
  sideNumber,
219
230
  }));
220
231
  }
232
+ function isProjectedExitCode(code) {
233
+ if (!code || typeof code !== 'object')
234
+ return false;
235
+ if (code.matchUpStatusCode || code.code)
236
+ return false;
237
+ return !!(code.matchUpStatus || code.previousMatchUpStatus || code.sideNumber);
238
+ }
221
239
  function isPropagatedExit({ matchUp }) {
222
240
  return !!getSideExitProvenance({ matchUp });
223
241
  }
@@ -304,4 +322,4 @@ function withdrawProducedExits({ mappedMatchUps, sourceMatchUpId, }) {
304
322
  return withdrawn;
305
323
  }
306
324
 
307
- export { buildCarriedExitProvenance, buildSideExitProvenance, byeClaimSurvives, carriedExitStatus, clearResolvedSideExitProvenance, clearSideExitProvenance, collapseDoubleExitStatus, deriveExitStateFromProvenance, exitOutcomeCode, exitProducedBy, getNativeSideExitProvenance, getSideExitProvenance, isPropagatedExit, mergeSideExitProvenance, producedExitStatus, projectExitStatusCodes, recordByeClaim, retainForeignProvenance, setSideExitProvenance, withdrawByeClaim, withdrawByeClaimsFrom, withdrawProducedExits };
325
+ export { buildCarriedExitProvenance, buildSideExitProvenance, byeClaimSurvives, carriedExitStatus, clearResolvedSideExitProvenance, clearSideExitProvenance, collapseDoubleExitStatus, deriveExitStateFromProvenance, exitOutcomeCode, exitProducedBy, getNativeSideExitProvenance, getSideExitProvenance, isProjectedExitCode, isPropagatedExit, mergeSideExitProvenance, producedExitStatus, projectExitStatusCodes, recordByeClaim, retainByeClaimsOnly, retainForeignProvenance, setSideExitProvenance, withdrawByeClaim, withdrawByeClaimsFrom, withdrawProducedExits };
@@ -1,7 +1,7 @@
1
1
  import { updateAssignmentParticipantResults } from '../../drawDefinitions/matchUpGovernor/updateAssignmentParticipantResults.mjs';
2
2
  import { processCompetitionMatchUp } from '../../drawDefinitions/competition/processCompetitionMatchUp.mjs';
3
3
  import { updateInContextMatchUp, modifyMatchUpNotice } from '../../notifications/drawNotifications.mjs';
4
- import { clearResolvedSideExitProvenance } from '../matchUpStatus/sideExitProvenance.mjs';
4
+ import { retainByeClaimsOnly, clearResolvedSideExitProvenance } from '../matchUpStatus/sideExitProvenance.mjs';
5
5
  import { getCompetitionPolicy } from '../../../query/drawDefinition/competition/getCompetitionPolicy.mjs';
6
6
  import { getAllStructureMatchUps } from '../../../query/matchUps/getAllStructureMatchUps.mjs';
7
7
  import { getAppliedPolicies } from '../../../query/extensions/getAppliedPolicies.mjs';
@@ -24,7 +24,7 @@ import { CONTAINER } from '../../../constants/drawDefinitionConstants.mjs';
24
24
  import { SUCCESS } from '../../../constants/resultConstants.mjs';
25
25
  import { TEAM } from '../../../constants/matchUpTypes.mjs';
26
26
  import { completedMatchUpStatuses } from '../../../constants/matchUpStatusConstants.mjs';
27
- import { DEFAULTED, IN_PROGRESS, WALKOVER, DOUBLE_WALKOVER, AWAITING_RESULT, SUSPENDED, INCOMPLETE } from '../../../constants/matchUpStatusValues.mjs';
27
+ import { DEFAULTED, BYE, IN_PROGRESS, WALKOVER, DOUBLE_WALKOVER, AWAITING_RESULT, SUSPENDED, INCOMPLETE } from '../../../constants/matchUpStatusValues.mjs';
28
28
 
29
29
  function modifyMatchUpScore(params) {
30
30
  const stack = 'modifyMatchUpScore';
@@ -158,7 +158,9 @@ function resolveDualMatchUpTarget({ isDualMatchUp, drawDefinition, matchUpId, ma
158
158
  function applyScoreAndStatus({ matchUpStatusCodes, removeWinningSide, matchUpStatus, matchUpFormat, removeScore, winningSide, matchUp, score, }) {
159
159
  const walkoverStatuses = new Set([WALKOVER, DOUBLE_WALKOVER]);
160
160
  if ((matchUpStatus && walkoverStatuses.has(matchUpStatus)) || removeScore) {
161
- const survivingProvenance = isAnyExit(matchUpStatus) ? matchUp.sideExitProvenance : undefined;
161
+ const survivingProvenance = isAnyExit(matchUpStatus)
162
+ ? matchUp.sideExitProvenance
163
+ : retainByeClaimsOnly(matchUpStatus === BYE ? matchUp.sideExitProvenance : undefined);
162
164
  Object.assign(matchUp, { ...toBePlayed });
163
165
  if (survivingProvenance)
164
166
  matchUp.sideExitProvenance = survivingProvenance;
@@ -1,3 +1,3 @@
1
- var version = "7.1.1";
1
+ var version = "7.2.0";
2
2
 
3
3
  export { version };
@@ -1,5 +1,6 @@
1
- import { getStructureInconsistencies, isPropagatedExit } from './getStructureInconsistencies.mjs';
2
- import { finalize } from '../integrity/inconsistency.mjs';
1
+ import { isPropagatedExit } from '../../mutate/matchUps/matchUpStatus/sideExitProvenance.mjs';
2
+ import { getStructureInconsistencies } from './getStructureInconsistencies.mjs';
3
+ import { finalize, hasErrorSeverity } from '../integrity/inconsistency.mjs';
3
4
  import { isFedLoserEligible } from '../matchUp/isFedLoserEligible.mjs';
4
5
  import { getAllDrawMatchUps } from '../matchUps/drawMatchUps.mjs';
5
6
  import { WINNER, LOSER, QUALIFYING } from '../../constants/drawDefinitionConstants.mjs';
@@ -80,7 +81,7 @@ function droppedProgressionForLink(link, sourceStructure, sourceStructureMatchUp
80
81
  (!link.source?.roundNumber || matchUp.roundNumber === link.source.roundNumber));
81
82
  const inconsistencies = [];
82
83
  for (const matchUp of roundMatchUps) {
83
- if (isPropagatedExit(matchUp))
84
+ if (isPropagatedExit({ matchUp }))
84
85
  continue;
85
86
  const loserSideNumber = matchUp.winningSide === 1 ? 2 : 1;
86
87
  const sideNumber = isLoserLink ? loserSideNumber : matchUp.winningSide;
@@ -152,7 +153,7 @@ function getDrawInconsistencies(params) {
152
153
  const progressionInconsistencies = getProgressionInconsistencies(drawDefinition, inContextDrawMatchUps, structureById);
153
154
  const combined = [...structureInconsistencies, ...linkInconsistencies, ...progressionInconsistencies];
154
155
  const finalized = finalize(combined, { scope: 'DRAW', drawId });
155
- return { ...SUCCESS, valid: finalized.length === 0, inconsistencies: finalized };
156
+ return { ...SUCCESS, valid: !hasErrorSeverity(finalized), inconsistencies: finalized };
156
157
  }
157
158
 
158
159
  export { DANGLING_LINK, DROPPED_PROGRESSION, INELIGIBLE_PROGRESSION, LINK_MISSING_SOURCE_ROUND, SCAN_ERROR, getDrawInconsistencies };
@@ -1,8 +1,8 @@
1
- import { getNativeSideExitProvenance, isPropagatedExit as isPropagatedExit$1 } from '../../mutate/matchUps/matchUpStatus/sideExitProvenance.mjs';
2
- import { finalize } from '../integrity/inconsistency.mjs';
1
+ import { isPropagatedExit, getNativeSideExitProvenance } from '../../mutate/matchUps/matchUpStatus/sideExitProvenance.mjs';
2
+ import { finalize, hasErrorSeverity } from '../integrity/inconsistency.mjs';
3
3
  import { getAllDrawMatchUps } from '../matchUps/drawMatchUps.mjs';
4
4
  import { isExit, isAnyExit } from '../../validators/isExit.mjs';
5
- import { BYE, DOUBLE_WALKOVER, DOUBLE_DEFAULT } from '../../constants/matchUpStatusValues.mjs';
5
+ import { BYE, DOUBLE_WALKOVER, DOUBLE_DEFAULT, TO_BE_PLAYED } from '../../constants/matchUpStatusValues.mjs';
6
6
  import { MISSING_DRAW_DEFINITION } from '../../constants/errorConditionConstants.mjs';
7
7
  import { CONTAINER } from '../../constants/drawDefinitionConstants.mjs';
8
8
  import { SUCCESS } from '../../constants/resultConstants.mjs';
@@ -16,13 +16,11 @@ const DRAW_POSITIONS_NOT_SORTED = 'DRAW_POSITIONS_NOT_SORTED';
16
16
  const EXIT_CODE_ON_WINNER_SIDE = 'EXIT_CODE_ON_WINNER_SIDE';
17
17
  const EXIT_WITHOUT_LOSER = 'EXIT_WITHOUT_LOSER';
18
18
  const PROPAGATED_EXIT_LOST = 'PROPAGATED_EXIT_LOST';
19
+ const STALLED_POSITION = 'STALLED_POSITION';
19
20
  function codeString(code) {
20
21
  const value = typeof code === 'string' ? code : code?.code;
21
22
  return value || undefined;
22
23
  }
23
- function isPropagatedExit(matchUp) {
24
- return isPropagatedExit$1({ matchUp });
25
- }
26
24
  function assignmentOccupied(assignment) {
27
25
  return !!(assignment && (assignment.participantId || assignment.bye || assignment.qualifier));
28
26
  }
@@ -150,6 +148,40 @@ function getLostPropagatedExitInconsistency(matchUp) {
150
148
  matchUpStatus,
151
149
  };
152
150
  }
151
+ function getStalledPositionInconsistencies(scoped, allDrawMatchUps, roundRobinGroupStructureIds) {
152
+ const playableShape = (matchUp) => !matchUp.winningSide && (!matchUp.matchUpStatus || matchUp.matchUpStatus === TO_BE_PLAYED);
153
+ const occupants = (matchUp) => (matchUp.sides ?? []).filter((side) => side?.participantId && !side?.bye);
154
+ const drawMatchUps = allDrawMatchUps.filter((matchUp) => !matchUp.collectionId);
155
+ const anythingPlayable = drawMatchUps.some((matchUp) => playableShape(matchUp) && occupants(matchUp).length === 2);
156
+ if (anythingPlayable)
157
+ return [];
158
+ const hasStarted = drawMatchUps.some((matchUp) => matchUp.winningSide);
159
+ if (!hasStarted)
160
+ return [];
161
+ const inconsistencies = [];
162
+ for (const matchUp of scoped) {
163
+ if (roundRobinGroupStructureIds.has(matchUp.structureId))
164
+ continue;
165
+ if (matchUp.winningSide)
166
+ continue;
167
+ if ((matchUp.sides ?? []).some((side) => side?.bye))
168
+ continue;
169
+ const present = occupants(matchUp);
170
+ if (present.length !== 1)
171
+ continue;
172
+ inconsistencies.push({
173
+ matchUpId: matchUp.matchUpId,
174
+ structureId: matchUp.structureId,
175
+ issueType: STALLED_POSITION,
176
+ severity: 'warning',
177
+ message: `side ${present[0].sideNumber} holds a participant whose opponent can never arrive — ` +
178
+ `no matchUp in the draw is playable`,
179
+ sideNumber: present[0].sideNumber,
180
+ matchUpStatus: matchUp.matchUpStatus,
181
+ });
182
+ }
183
+ return inconsistencies;
184
+ }
153
185
  function getStructureInconsistencies(params) {
154
186
  const { drawDefinition, structureId, matchUpsMap } = params;
155
187
  if (!drawDefinition)
@@ -160,6 +192,7 @@ function getStructureInconsistencies(params) {
160
192
  const inconsistencies = getPhantomPositionInconsistencies(drawDefinition, structureId);
161
193
  const roundRobinGroupStructureIds = new Set();
162
194
  collectRoundRobinGroupStructureIds(drawDefinition.structures, roundRobinGroupStructureIds);
195
+ inconsistencies.push(...getStalledPositionInconsistencies(scoped, inContextDrawMatchUps, roundRobinGroupStructureIds));
163
196
  for (const matchUp of scoped) {
164
197
  const { winningSide, matchUpStatus, matchUpStatusCodes, sides, matchUpId, drawPositions } = matchUp;
165
198
  const filledPositions = (drawPositions ?? []).filter((drawPosition) => typeof drawPosition === 'number');
@@ -205,7 +238,7 @@ function getStructureInconsistencies(params) {
205
238
  loserSide?.drawPosition &&
206
239
  !loserSide.participantId &&
207
240
  !loserSide.bye &&
208
- !isPropagatedExit(matchUp)) {
241
+ !isPropagatedExit({ matchUp })) {
209
242
  inconsistencies.push({
210
243
  ...base,
211
244
  issueType: EXIT_WITHOUT_LOSER,
@@ -217,7 +250,7 @@ function getStructureInconsistencies(params) {
217
250
  inconsistencies.push(advancement);
218
251
  }
219
252
  const finalized = finalize(inconsistencies, { scope: 'STRUCTURE' });
220
- return { ...SUCCESS, valid: finalized.length === 0, inconsistencies: finalized };
253
+ return { ...SUCCESS, valid: !hasErrorSeverity(finalized), inconsistencies: finalized };
221
254
  }
222
255
 
223
- export { BYE_ADVANCEMENT_MISSING, DRAW_POSITIONS_NOT_SORTED, DRAW_POSITION_UNASSIGNED, EXIT_CODE_ON_WINNER_SIDE, EXIT_WITHOUT_LOSER, PROPAGATED_EXIT_LOST, WINNER_NOT_ADVANCED, WINNING_SIDE_ADVANCEMENT_MISMATCH, WINNING_SIDE_WITHOUT_PARTICIPANT, getStructureInconsistencies, isPropagatedExit };
256
+ export { BYE_ADVANCEMENT_MISSING, DRAW_POSITIONS_NOT_SORTED, DRAW_POSITION_UNASSIGNED, EXIT_CODE_ON_WINNER_SIDE, EXIT_WITHOUT_LOSER, PROPAGATED_EXIT_LOST, STALLED_POSITION, WINNER_NOT_ADVANCED, WINNING_SIDE_ADVANCEMENT_MISMATCH, WINNING_SIDE_WITHOUT_PARTICIPANT, getStructureInconsistencies };
@@ -41,8 +41,8 @@ function isActiveDownstream(params) {
41
41
  const provenance = getSideExitProvenance({ matchUp: candidate });
42
42
  return !occupiedSides.every((side) => provenance?.[side.sideNumber]);
43
43
  };
44
- const winnerSideResolved = !!winnerMatchUp?.sides?.find((s) => s?.sideNumber === winnerMatchUp.winningSide)
45
- ?.participant;
44
+ const winnerSideResolved = !!winnerMatchUp?.sides?.find((s) => s?.sideNumber === winnerMatchUp.winningSide)?.participant &&
45
+ !isPropagatedExit({ matchUp: winnerMatchUp });
46
46
  if (contestedDoubleExit(loserMatchUp) || contestedDoubleExit(winnerMatchUp)) {
47
47
  return true;
48
48
  }
@@ -1,6 +1,6 @@
1
1
  import { getDrawInconsistencies } from '../drawDefinition/getDrawInconsistencies.mjs';
2
2
  import { expectedParticipantType } from './participantTypeForEvent.mjs';
3
- import { finalize } from '../integrity/inconsistency.mjs';
3
+ import { finalize, hasErrorSeverity } from '../integrity/inconsistency.mjs';
4
4
  import { getParticipants } from '../participants/getParticipants.mjs';
5
5
  import { MISSING_EVENT } from '../../constants/errorConditionConstants.mjs';
6
6
  import { INDIVIDUAL, PAIR } from '../../constants/participantConstants.mjs';
@@ -62,7 +62,7 @@ function getEventInconsistencies(params) {
62
62
  const typeMismatches = getParticipantTypeMismatches(params, participantMap);
63
63
  const combined = [...drawInconsistencies, ...typeMismatches];
64
64
  const finalized = finalize(combined, { scope: 'EVENT', eventId });
65
- return { ...SUCCESS, valid: finalized.length === 0, inconsistencies: finalized };
65
+ return { ...SUCCESS, valid: !hasErrorSeverity(finalized), inconsistencies: finalized };
66
66
  }
67
67
 
68
68
  export { EVENT_PARTICIPANT_TYPE_MISMATCH, getEventInconsistencies };
@@ -18,6 +18,9 @@ function fnv1a(input) {
18
18
  }
19
19
  return (hash >>> 0).toString(16).padStart(8, '0');
20
20
  }
21
+ function hasErrorSeverity(inconsistencies) {
22
+ return (inconsistencies ?? []).some((inconsistency) => inconsistency.severity === 'error');
23
+ }
21
24
  function inconsistencyFingerprint(inc) {
22
25
  const key = IDENTITY_FIELDS.map((field) => `${field}=${inc[field] ?? ''}`).join('|');
23
26
  return fnv1a(key);
@@ -31,4 +34,4 @@ function finalize(inconsistencies, context) {
31
34
  });
32
35
  }
33
36
 
34
- export { finalize, inconsistencyFingerprint };
37
+ export { finalize, hasErrorSeverity, inconsistencyFingerprint };
@@ -116,6 +116,11 @@ function freeAfter(matchUp, timing) {
116
116
  const finish = finishOf(matchUp, timing);
117
117
  return finish === null ? null : finish + timing.recoveryMinutes;
118
118
  }
119
+ function occupiedFrom(matchUp) {
120
+ return (parseClockMinutes(matchUp.schedule?.startTime) ??
121
+ parseClockMinutes(matchUp.schedule?.scheduledTime) ??
122
+ parseClockMinutes(matchUp.schedule?.endTime));
123
+ }
119
124
  function skipReasonFor(target) {
120
125
  if (target.matchUpStatus === BYE)
121
126
  return 'bye';
@@ -213,6 +218,9 @@ function clashFindings(target, startMinutes, matchUps, timingFor) {
213
218
  overlappedIds.add(clash.participantId);
214
219
  continue;
215
220
  }
221
+ const from = occupiedFrom(neighbour);
222
+ if (from !== null && from > startMinutes)
223
+ continue;
216
224
  const free = freeAfter(neighbour, timing);
217
225
  if (free === null || free <= startMinutes)
218
226
  continue;
@@ -1,5 +1,5 @@
1
1
  import { getEventInconsistencies } from '../event/getEventInconsistencies.mjs';
2
- import { finalize } from '../integrity/inconsistency.mjs';
2
+ import { finalize, hasErrorSeverity } from '../integrity/inconsistency.mjs';
3
3
  import { getParticipants } from '../participants/getParticipants.mjs';
4
4
  import { MISSING_TOURNAMENT_RECORD } from '../../constants/errorConditionConstants.mjs';
5
5
  import { INDIVIDUAL } from '../../constants/participantConstants.mjs';
@@ -42,7 +42,7 @@ function getTournamentInconsistencies(params) {
42
42
  const identityInconsistencies = getIdentityDuplications(tournamentRecord);
43
43
  const combined = [...eventInconsistencies, ...identityInconsistencies];
44
44
  const finalized = finalize(combined, { scope: 'TOURNAMENT', tournamentId });
45
- return { ...SUCCESS, valid: finalized.length === 0, inconsistencies: finalized };
45
+ return { ...SUCCESS, valid: !hasErrorSeverity(finalized), inconsistencies: finalized };
46
46
  }
47
47
 
48
48
  export { PARTICIPANT_IDENTITY_DUPLICATION, getTournamentInconsistencies };