- Replace Array#sort() with Array#toSorted() (unicorn/no-array-sort).
- Remove non-null assertions (typescript-eslint/no-non-null-assertion) by
asserting the returned state objects directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QVLqaYGXCeSYYqTHZmbRZi
- #1 (correctness): processQualifyingEvent regrouped event_results by placement
using the live group size as the tie count. The "Process Qualifying Points"
button could run mid-bracket and average four placement-3 QF winners down to
7 QP instead of their 9 floor. processQualifyingEvent now delegates bracket
events to processQualifyingBracketEvent (structural tie spans) and skips the
live-count regroup. finalize-bracket simplified to call processQualifyingEvent.
- #2: reprocess-bracket on a finalized qualifying season re-runs
finalizeQualifyingPoints so the deleted final placements are restored.
- #3: replace direct Drizzle deletes in the route with the existing
deleteParticipantResultsBySportsSeasonId model helper.
- #4: extract shared writeEventResultsQP helper; assignCs2EliminationQP and
processQualifyingBracketEvent both use it instead of duplicating the
upsert + recalculateParticipantQP loop.
- #5: extract scoreQualifyingBracket route helper for the repeated branch.
- #6: document that qualifying brackets intentionally skip the fantasy
Recent Scores feed (they surface via QP standings + Discord); scope the
set-winner Discord notification to the entered match.
Adds a regression test locking the structural-tieCount contract (9 QP, not 7).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QVLqaYGXCeSYYqTHZmbRZi
Winning a round in a major bracket (CS2 Champions Stage) now immediately
awards the team the guaranteed-minimum qualifying points for the position
they've locked in — e.g. a QF win guarantees at least a T3 finish (9 QP),
an SF win guarantees 2nd (14 QP), and a Final win earns 1st (20 QP).
This also fixes a bug where CS2 majors (a qualifying_points sport) wrongly
banked actual fantasy points: the generic bracket admin routed results
through processMatchResult/processPlayoffEvent, which write the fantasy
placement table (seasonParticipantResults). For qualifying sports, fantasy
points must come only from finalizeQualifyingPoints across all majors.
- scoring-calculator.ts: add deriveBracketQualifyingStates (pure) and
processQualifyingBracketEvent, deriving each team's (placement, tieCount)
floor from playoffMatches via the existing ROUND_CONFIG and writing QP to
event_results. Export getRoundConfig for testing.
- bracket.server.ts: branch all five write handlers on isQualifyingEvent so
qualifying brackets are scored via the QP path and never write fantasy
placements. reprocess-bracket additionally clears stale
seasonParticipantResults rows, making it the cleanup tool for majors that
already banked points under the old path.
- Add qualifying-bracket-scoring.test.ts covering the floor derivation,
partial/un-set brackets, idempotency, and the worked QP example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QVLqaYGXCeSYYqTHZmbRZi