* 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>
* Add oxlint and fix all lint errors
- Install oxlint, add .oxlintrc.json with rules for TypeScript/React
- Add npm run lint / lint:fix scripts
- Add Claude PostToolUse hook to run oxlint on every edited file
- Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array
- Fix no-array-index-key (use stable keys or suppress positional cases)
- Fix exhaustive-deps missing dependency in useEffect
- Promote exhaustive-deps and no-array-index-key to errors
- Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-explicit-any warnings and upgrade tsconfig to ES2023
- Replace all `any` types with proper types or `unknown` across ~20 files
- Add typed socket payload interfaces in draft route and useDraftSocket
- Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch)
- Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed
- Fix cascading type errors uncovered by removing any: Map.get narrowing,
participant relation types, ChartDataPoint, Partial<NewSeason> indexing
- Add ParticipantResultWithParticipant type to participant-result model
- Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult)
- Fix duplicate getQPStandings import in sportsSeasonId.server.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Promote no-explicit-any to error
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>
Fixes the "slot is already filled" simulation error when First Four games
have already been played and their winners advanced to the Round of 64.
Changes:
- Fix FF→R64 validation to allow a filled R64 slot when the corresponding
First Four match is already complete (the original bug)
- Fix isComplete guard for E8/FF/Championship: derive loser from stored
participant IDs when loserId is null rather than re-simulating a real game
- Add round count validation for all rounds (R32 16, S16 8, E8 4, FF 2, Champ 1)
to catch malformed brackets before the hot sim loop instead of producing
silent garbage output
- Add participant1Id null check in the First Four path (mirrors the no-FF path)
- Add console.warn when participant IDs are missing from DB (fall-back to
average strength instead of crashing)
- Import NCAA_68.regions from bracket-templates.ts instead of duplicating the
hardcoded fallback in each simulator
- Fix duplicate step-9 comment number in ncaam-simulator.ts
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#115
- Add `NHLSimulator` using NHL's divisional bracket format (top 3 per
division + 2 wildcards per conference, best-of-7 series, parity
factor 800). Elo ratings and seeding probabilities sourced from
elo.harvitronix.com/nhl/2025-2026 as of March 18, 2026.
- Add `nhl_bracket` to `simulatorTypeEnum` with migration.
- Register `NHLSimulator` in the simulator registry.
- Fix simulator type dropdown in admin to auto-generate from the
registry instead of a hardcoded list, so new simulators appear
automatically without a separate UI change.
Code review fixes applied to the simulator:
- Narrow `weightedPick` key type to the five seeding probability fields
(was `keyof NhlTeamData`, allowing `"elo"` which would silently
corrupt results)
- Remove uniform-random fallback in `weightedPick` — zero-weight draws
now return `undefined` and are skipped, preventing eliminated teams
from becoming wildcards
- Exclude unrecognized participants entirely (with a console warning)
instead of silently routing them into the Eastern wildcard pool
- Replace the confusing for-of loop over both conferences (with unused
`champ` variable and `!`-suppressed unitialized vars) with two
explicit sequential East/West blocks
- Track `effectiveN` for skipped degenerate draws so probability
denominators stay accurate; throw if all iterations are skipped
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>
Replaces the duplicated F1Simulator (which was copy-pasted for IndyCar) with a
single AutoRacingSimulator that accepts a race points table at construction time.
Registry entries for f1_standings and indycar_standings remain separate, each
wired to the correct scoring system (F1: top-10, IndyCar: top-26 positions).
Also imports SIMULATOR_TYPES from registry into the admin route to eliminate the
duplicate hardcoded array, and updates stale comments mentioning F1/IndyCar.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- NCAAM: KenPom AEM logistic formula (1/(1+exp(-diff/7.5))), data through 2025-26 March 15
- NCAAW: Barttorvik Barthag Log5 formula (A*(1-B)/(A*(1-B)+B*(1-A))), same bracket structure
- Both simulators: 50,000-iteration Monte Carlo, First Four simulation, honors completed matches
- Track E8+ placements only: champion, finalist, FF losers (3rd/4th), E8 losers (5th–8th)
- Add bracket configuration validation: null R64 slots must exactly match First Four mapping
- Fix DEFAULT_SCORING_RULES to 100/70/45/45/20/20/20/20 (3rd/4th=45, 5th–8th=20)
- Align scoring constants across simulate route, expected-values display, and server action
- Zero out EVs for non-bracket participants on every simulation run (prevents EV inflation)
- Add EV total invariant warning (expected ~340) on expected-values admin page
- 98 unit tests across NCAAM, NCAAW, and UCL simulators — all passing
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements a 16-team UEFA Champions League knockout bracket simulator
using blended Elo + futures odds probabilities (70/30 split). Tracks
integer placement counts per tier to avoid fractional accumulation
drift, ensuring total EV sums to exactly 340 by construction.
- New UCLSimulator class reading live playoffMatches from DB
- Respects completed match results (eliminated teams get exact EV)
- New ucl_bracket simulator_type enum value + migration
- Registered in simulator registry; added to admin sport selector
- 18 unit tests covering math, bracket path, and integration scenarios
Fixes#113
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: EV simulation framework with F1 Monte Carlo simulator
- Add EV snapshot tables (participant_ev_snapshots, team_ev_snapshots) and simulation_status column on sports seasons
- Add ev-snapshot model with upsert and history query functions
- Add simulator framework: types, bracket/F1/golf simulators, registry
- F1 simulator: vig-removed ICM weighted draw (pre-season) + race-by-race Monte Carlo from current standings (in-season); per-position column normalization to prevent floating-point EV drift
- Add admin simulate route and Run Simulation button on sports season page
- Rework futures-odds admin page to save odds then run simulation in one action
- Remove recalculate-probabilities route (superseded by simulate route)
- Remove EV trend chart panel and associated DB queries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: map simulators to sports via simulatorType field
Adds a `simulator_type` enum column to the `sports` table so each sport
can be assigned a specific simulation algorithm rather than deriving it
from the sports season's scoring pattern.
- Add `simulatorTypeEnum` (f1_standings, indycar_standings,
golf_qualifying_points, playoff_bracket) + `simulatorType` nullable
column on `sports` table; migration 0037
- Rewrite simulator registry to key off `SimulatorType` instead of
`ScoringPattern`; indycar_standings shares F1Simulator for now
- `findSportsSeasonById` now returns `SportsSeasonWithSport` so callers
have typed access to `sport.simulatorType`
- Simulate and futures-odds actions read `sport.simulatorType`; guard
fires before setting `simulationStatus: running`
- Admin sport edit page gains a Simulator Type dropdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>