* fix: NBA play-in advancement writes E7/W7 to wrong participant slot
advanceNBAPlayInWinner was writing the PIR1 7v8 winner to participant1Id
of First Round M3/M7, but those slots already hold E2/W2 (seeded at
generation time). The "already filled" guard fired, silently swallowing
both the winner advancement and the loser's placement into Play-In
Round 2 — so the 7/8 loser never appeared in the second play-in game.
Fix: write the 7v8 winner to participant2Id (the correct null slot) and
update the guard, doc comment, and load-bearing comment accordingly.
Also swap the p2Override arguments in the simulator and test fixture to
match the corrected slot layout (FR M3: E2=p1, E7=p2).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: hasGroupStage check for templates without groupStage property
undefined !== null is true, so templates that omit groupStage (like
nba_20) were incorrectly treated as group-stage templates, triggering
the generate-groups action and the "Invalid template or template has
no group stage" error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the NBA_20 bracket template (20-team structure with play-in
tournament) and rewrites the NBA simulator to be bracket-aware like UCL.
- Add NBA_20 bracket template: PIR1 (4) → PIR2 (2) → First Round (8)
→ Conference Semifinals → Conference Finals → NBA Finals
- Add participantLabels (East/West 1–10) for admin bracket UI slot labels
- Add generateNBA20Bracket and advanceNBAPlayInWinner in playoff-match.ts
with custom play-in advancement logic (PIR1 winners go to two different
destinations)
- Rewrite NBASimulator with two auto-detected modes:
- Bracket-aware (preferred): reads actual bracket state, simulates only
remaining rounds forward using 50k Monte Carlo iterations
- Season-projection (fallback): existing logic for pre-playoff use
- Guard resolveGame/resolveSeries against null participants (throws with
match ID and slot info instead of silently using empty string)
- Document load-bearing First Round match ordering in generateNBA20Bracket
- Add 25 unit tests covering Elo math, team data, template structure,
and all simulator integration paths
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static Basketball-Reference seed probability distributions
(p_1..p_10) with dynamic simulation of each team's remaining games
based on current standings from the DB.
- Load regularSeasonStandings in parallel with participants query
- Compute remainingGames = 82 - gamesPlayed per team
- Pre-compute per-game win probability (Elo vs average opponent 1500)
on TeamEntry at construction time — not inside the hot loop
- Sort conference standings by projected wins to assign seeds, replacing
the drawSeed() weighted-probability approach
- Conference resolved from standings table, falls back to TEAMS_DATA
- Strip all p_1..p_10 seed probability data from TEAMS_DATA
- Move simulateProjectedWins to module scope (no closure captures)
- Remove const N alias; use NUM_SIMULATIONS directly
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds live standings sync and display for bracket-based sports (NBA/NHL),
so league members can see W/L tables and which teams their opponents drafted
during the regular season — not just after the playoff bracket is set.
- New `regular_season_standings` table with upsert-on-conflict sync
- Standings sync service with NHL (api-web.nhle.com) and NBA (ESPN) adapters,
externalId write-back for future syncs, and unmatched-team resolution UI
- `RegularSeasonStandings` component: flat (NBA) + division/wild-card (NHL) modes,
playoff line, TeamOwnerBadge, projected Brackt points (EV), mobile horizontal scroll
- Admin "Sync Standings" card + "Resolve Unmatched" UI on sports season page
- Admin manual standings edit hatch at /admin/sports-seasons/:id/regular-standings
- Show standings above bracket until matches exist; below once bracket is set
- `normalize-team-name` utility extracted to shared lib
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new `nba_bracket` simulator that projects both regular season
seedings and the full NBA playoff bracket, unlike other simulators that
operate on a pre-set bracket stored in the DB.
Algorithm:
- Per iteration: each team draws a conference seed (1–10) weighted by
BBRef seed probabilities, then seeds 7–10 play the Play-In tournament
(single elimination), then the resulting 8-team bracket is simulated
as best-of-7 series using Elo playoff ratings
- Elo source: playoff rating (last 110 games, no regression to mean,
postseason games weighted 3×) — appropriate for playoff matchup quality
- Seed probs source: Basketball-Reference (March 16, 2026), scaled from
conditional to unconditional by multiplying by each team's Playoffs%
Placement tiers map to SimulationProbabilities:
probFirst/Second → NBA champion / Finals loser
probThird/Fourth → Conference Finals losers (2 per sim)
probFifth–Eighth → Conference Semis losers (4 per sim)
Files:
- app/services/simulations/nba-simulator.ts (new)
- drizzle/0046_add_nba_bracket_simulator_type.sql (new)
- database/schema.ts: adds `nba_bracket` to simulatorTypeEnum
- app/services/simulations/registry.ts: registers NBASimulator
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>