The live tennis-draw sync now fans eliminations out to mirror windows, but the admin bracket UI is a second live scoring path: when an admin enters match results for a shared qualifying major, mirror windows already receive "Qualifying Points Update" posts via the fan-out, yet the "Knocked Out" section was still dropped there. Thread the newly-decided losers (losers of matches reaching completion for the first time this action) through the admin route's fanOutMajorIfPrimary calls in the set-winner and set-round-winners intents, reusing the same per-window elimination translation. Re-scores, complete-round, reprocess, and finalize decide no new losers, so they pass nothing and never re-announce an exit — mirroring populateBracketFromDraw's first-completion rule. Extract the first-completion decision into a pure newlyDecidedLosers() helper shared by both intents and unit-tested directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZGsG5R1q3kyKCaXuysiWJ
25 lines
1.1 KiB
TypeScript
25 lines
1.1 KiB
TypeScript
/**
|
|
* Pure helpers for the bracket-scoring route action, extracted so the
|
|
* knockout-detection logic can be unit-tested without the full action harness.
|
|
*/
|
|
|
|
/**
|
|
* From a set of matches being scored this action, return the season_participant
|
|
* ids knocked out for the FIRST time. A loser counts only when its match was NOT
|
|
* already complete — a re-score/correction of a finished match must not
|
|
* re-announce the exit (mirrors populateBracketFromDraw's first-completion rule in
|
|
* the live-sync path). Entries with an unknown loser (`null`) are skipped.
|
|
*
|
|
* The ids are season_participant ids of the primary window (playoff_matches are
|
|
* keyed by season_participant); the fan-out translates them to each mirror
|
|
* window's own season_participant id before announcing the "Knocked Out" section.
|
|
*/
|
|
export function newlyDecidedLosers(
|
|
entries: Array<{ wasComplete: boolean; loserId: string | null }>
|
|
): string[] {
|
|
return entries
|
|
.filter((e): e is { wasComplete: boolean; loserId: string } =>
|
|
!e.wasComplete && e.loserId !== null
|
|
)
|
|
.map((e) => e.loserId);
|
|
}
|