Commit graph

12 commits

Author SHA1 Message Date
Chris Parsons
e62e9554c9
Add golf qualifying points simulator (Plackett-Luce Monte Carlo) (#223)
* Add golf QP simulator with Plackett-Luce model, fixes #120

- New `participant_golf_skills` table (migration 0061) for SG: Total and
  per-major American odds per player/season
- New `app/models/golf-skills.ts` with getGolfSkillsMap, getGolfSkillsForSeason,
  batchUpsertGolfSkills
- Full `GolfSimulator` implementation replacing the TODO stub: Plackett-Luce
  ranking model (PL_BETA=1.5, FIELD_SIZE=156), 10k Monte Carlo iterations,
  awards QP by finishing position, ranks by total QP across all 4 majors
- New admin route `sports-seasons/:id/golf-skills` with bulk CSV import,
  fuzzy name matching, per-player SG + per-major odds inputs; saves skills
  and auto-runs simulation on submit
- Simulator dropdown on sport admin sorted alphabetically; renamed to
  "Golf Qualifying Points Monte Carlo"
- Golf Skills button shown on sports season admin when simulator type is
  golf_qualifying_points
- Extract normalizeName/diceCoefficient to shared `app/lib/fuzzy-match.ts`,
  removing duplication from surface-elo and golf-skills routes
- Parallelize 4 DB queries in GolfSimulator.simulate() with Promise.all
- O(1) field array removal via swap-to-end + pop (was O(N) splice)
- Fix source tag: performance_model (not elo_simulation) for SG-based model
- 23 unit tests covering americanToImplied, getMajorOddsKey, resolveSkill,
  simulateMajor, and Monte Carlo calibration properties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix oxlint errors: no-non-null-assertion and eqeqeq

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 21:46:02 -07:00
Chris Parsons
618bc57ec1
Replace console.* with structured logger, fix no-inferrable-types (closes #98) (#199)
- Add app/lib/logger.ts: dev passes through to console; prod routes errors
  to Sentry.captureException and warnings to Sentry.captureMessage, with
  extra context preserved. Uses captureMessage (not captureException) for
  string-only args to avoid fabricated stack traces.
- Add server/logger.ts: dev passes through; prod silences log/info but
  keeps warn/error on stderr (Sentry not initialized in that process).
- Replace all console.* calls across 44 app files and 4 server files.
- Upgrade no-console from warn → error in oxlint; exempt logger files and
  scripts/** via overrides.
- Add typescript/no-inferrable-types rule; fix violations in services and
  simulators. Exempt test files (intentional string widening for switch/if
  tests would break under literal type inference).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 13:41:39 -07:00
Chris Parsons
6f5ea35952
Add NHL playoff simulator and fix simulator dropdown (#166)
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>
2026-03-18 01:23:53 -07:00
Chris Parsons
5a48327102 Fix NBA simulator missing from dropdown and enum not applied to prod DB
- Add nba_bracket SelectItem to simulator type dropdown in admin sport edit page
- Add 0046 to _journal.json (it was missing, causing Drizzle to never run the ALTER TYPE on prod)
- Add remediation migration 0047 with IF NOT EXISTS to safely add nba_bracket enum value on next deploy
- Improve error message in updateSport action to surface actual DB error instead of always blaming the slug

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 22:18:50 -07:00
Chris Parsons
e5431ebd0b Fix missing NBA simulator dropdown. 2026-03-16 22:02:50 -07:00
Chris Parsons
84787c9a4b
Consolidate F1/IndyCar simulators into shared AutoRacingSimulator class (#151)
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>
2026-03-16 12:22:08 -07:00
Chris Parsons
47b8935be6
Add NCAAM and NCAAW bracket Monte Carlo simulators (#150)
- 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>
2026-03-15 23:31:47 -07:00
Chris Parsons
70c22d03fc
feat: add UCL bracket Monte Carlo simulator (#131)
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>
2026-03-10 23:04:51 -07:00
Chris Parsons
d784571f29
feat: add meta title to all route pages (#109)
Every route now exports a meta function so the browser title bar reflects
the current page. Static pages use fixed titles; dynamic pages pull names
from loader data with a sensible fallback (e.g. league name, sport season
name, team name).

Titles follow the pattern "Page Name - Brackt" for user-facing routes and
"Page Name - Brackt Admin" for admin routes.

Fixes #74

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:10:52 -07:00
Chris Parsons
c6ba59b0e6
User/chris/ev f1 framework (#93)
* 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>
2026-03-09 15:34:31 -07:00
Chris Parsons
c343521706 feat: add icon support for sports with file upload and active seasons display 2025-10-13 10:30:47 -07:00
Chris Parsons
6814a82b76 feat: add sports detail route and update homepage meta description 2025-10-13 10:04:32 -07:00