2025-10-10 23:04:50 -07:00
|
|
|
{
|
|
|
|
|
"version": "7",
|
|
|
|
|
"dialect": "postgresql",
|
|
|
|
|
"entries": [
|
|
|
|
|
{
|
|
|
|
|
"idx": 0,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1732076135211,
|
|
|
|
|
"tag": "0000_short_donald_blake",
|
|
|
|
|
"breakpoints": true
|
2025-10-11 00:07:39 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 1,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760165302772,
|
|
|
|
|
"tag": "0001_serious_la_nuit",
|
|
|
|
|
"breakpoints": true
|
2025-10-11 00:29:04 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 2,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760166775427,
|
|
|
|
|
"tag": "0002_tired_iron_man",
|
|
|
|
|
"breakpoints": true
|
2025-10-11 00:53:39 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 3,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760167848365,
|
|
|
|
|
"tag": "0003_public_shadowcat",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 4,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760168383812,
|
|
|
|
|
"tag": "0004_spicy_zarda",
|
|
|
|
|
"breakpoints": true
|
2025-10-11 01:03:31 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 5,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760169537260,
|
|
|
|
|
"tag": "0005_abnormal_thunderbird",
|
|
|
|
|
"breakpoints": true
|
2025-10-11 21:10:01 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 6,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760241924414,
|
|
|
|
|
"tag": "0006_needy_junta",
|
|
|
|
|
"breakpoints": true
|
2025-10-12 21:16:00 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 7,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760327817376,
|
|
|
|
|
"tag": "0007_pink_nebula",
|
|
|
|
|
"breakpoints": true
|
2025-10-13 10:30:47 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 8,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760375514773,
|
|
|
|
|
"tag": "0008_salty_trauma",
|
|
|
|
|
"breakpoints": true
|
2025-10-13 15:00:59 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 9,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760392647508,
|
|
|
|
|
"tag": "0009_fuzzy_korvac",
|
|
|
|
|
"breakpoints": true
|
2025-10-14 12:20:36 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 10,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760469105799,
|
|
|
|
|
"tag": "0010_complex_the_stranger",
|
|
|
|
|
"breakpoints": true
|
2025-10-14 21:20:58 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 11,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760501621605,
|
|
|
|
|
"tag": "0011_gifted_mystique",
|
|
|
|
|
"breakpoints": true
|
2025-10-14 22:04:37 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 12,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760504208398,
|
|
|
|
|
"tag": "0012_yielding_phil_sheldon",
|
|
|
|
|
"breakpoints": true
|
2025-10-15 08:58:35 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 13,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760543654915,
|
|
|
|
|
"tag": "0013_pink_the_order",
|
|
|
|
|
"breakpoints": true
|
2025-10-15 21:21:33 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 14,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760588333462,
|
|
|
|
|
"tag": "0014_lowly_gateway",
|
|
|
|
|
"breakpoints": true
|
2025-10-15 21:50:02 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 15,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760589195551,
|
|
|
|
|
"tag": "0015_exotic_loners",
|
|
|
|
|
"breakpoints": true
|
2025-10-15 22:02:21 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 16,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760590495868,
|
|
|
|
|
"tag": "0016_majestic_roulette",
|
|
|
|
|
"breakpoints": true
|
2025-10-16 00:32:48 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 17,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760599350154,
|
|
|
|
|
"tag": "0017_calm_zarda",
|
|
|
|
|
"breakpoints": true
|
2025-10-20 15:03:11 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 18,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1760996053371,
|
|
|
|
|
"tag": "0018_numerous_stardust",
|
|
|
|
|
"breakpoints": true
|
2025-10-21 23:22:17 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 19,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1761112236540,
|
|
|
|
|
"tag": "0019_acoustic_ben_grimm",
|
|
|
|
|
"breakpoints": true
|
2025-10-28 23:39:34 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 20,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1761719248881,
|
|
|
|
|
"tag": "0020_slim_spacker_dave",
|
|
|
|
|
"breakpoints": true
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 21,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1762190473418,
|
|
|
|
|
"tag": "0021_fair_ser_duncan",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 22,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1762191156317,
|
|
|
|
|
"tag": "0022_shiny_mother_askani",
|
|
|
|
|
"breakpoints": true
|
2025-11-03 13:16:37 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 23,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1762200550705,
|
|
|
|
|
"tag": "0023_cynical_jack_power",
|
|
|
|
|
"breakpoints": true
|
2025-11-17 22:19:46 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 24,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763276901482,
|
|
|
|
|
"tag": "0024_faithful_mesmero",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 25,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763277000000,
|
|
|
|
|
"tag": "0025_rename_participant_ev_season_to_sports_season",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 26,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763278000000,
|
|
|
|
|
"tag": "0026_add_source_odds_to_participant_ev",
|
|
|
|
|
"breakpoints": true
|
2026-02-14 22:30:12 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 27,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763279000000,
|
|
|
|
|
"tag": "0027_add_projected_points_to_team_standings",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 28,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763280000000,
|
|
|
|
|
"tag": "0028_add_tournament_groups",
|
|
|
|
|
"breakpoints": true
|
2026-02-19 11:26:40 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 29,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763281000000,
|
|
|
|
|
"tag": "0029_change_participant_ev_to_decimal",
|
|
|
|
|
"breakpoints": true
|
2026-02-23 23:23:24 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 30,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763282000000,
|
|
|
|
|
"tag": "0030_add_draft_picks_unique_slot",
|
|
|
|
|
"breakpoints": true
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 31,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763283000000,
|
|
|
|
|
"tag": "0031_add_autodraft_queue_only",
|
|
|
|
|
"breakpoints": true
|
2026-03-07 21:59:29 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 32,
|
|
|
|
|
"version": "7",
|
2026-03-09 15:34:31 -07:00
|
|
|
"when": 1763284000000,
|
2026-03-07 21:59:29 -08:00
|
|
|
"tag": "0032_consolidate_playoff_pattern",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 33,
|
|
|
|
|
"version": "7",
|
2026-03-09 15:34:31 -07:00
|
|
|
"when": 1763285000000,
|
2026-03-07 21:59:29 -08:00
|
|
|
"tag": "0033_add_schedule_event_type",
|
|
|
|
|
"breakpoints": true
|
2026-03-09 15:34:31 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 34,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763286000000,
|
|
|
|
|
"tag": "0034_add_ev_snapshots",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 35,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763287000000,
|
|
|
|
|
"tag": "0035_increase_prob_precision",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 36,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763288000000,
|
|
|
|
|
"tag": "0036_increase_ev_precision",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 37,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1763289000000,
|
|
|
|
|
"tag": "0037_add_simulator_type_to_sports",
|
|
|
|
|
"breakpoints": true
|
2026-03-10 10:27:58 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 38,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773122866361,
|
|
|
|
|
"tag": "0038_sad_wilson_fisk",
|
|
|
|
|
"breakpoints": true
|
2026-03-10 23:04:51 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 39,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773200000000,
|
|
|
|
|
"tag": "0039_add_ucl_bracket_simulator_type",
|
|
|
|
|
"breakpoints": true
|
Add playoff match game scheduling and odds management (#135)
* Add playoff match games and odds storage
Introduces two new tables for bracket matchup detail storage:
- `playoff_match_games`: tracks individual game schedules within a
series matchup (game number, scheduledAt, status, per-game scores,
winner). Supports scheduled/complete/postponed status enum.
- `playoff_match_odds`: stores moneyline odds per participant per
matchup (single upsert record, no isLatest complexity).
Includes:
- Drizzle schema + relations with CASCADE deletes from playoff_matches
- Migration 0040_fat_puma.sql
- playoff-match-game.ts model with pure helpers: computeSeriesScore,
isSeriesComplete, getSeriesLeader — plus full CRUD
- playoff-match-odds.ts model with pure helpers: americanToImpliedProbability,
impliedProbabilityToAmerican, normalizeOdds — plus upsert/read/delete
- findPlayoffMatchesByEventId and findPlayoffMatchById updated to
include games and odds in their query results
- Bracket server route: add-game, update-game, delete-game,
upsert-odds, delete-odds actions
- Bracket admin UI: expandable per-match panel for game schedule
management and moneyline odds entry
- 41 new unit tests (18 game + 23 odds), all 810 tests passing
https://claude.ai/code/session_01Twt3D1bsEK3eXUhMaz6ee7
* Code review fixes: type safety, abstraction, and React correctness
- Derive PlayoffMatchGameStatus from schema enum instead of hardcoding
the string union, eliminating the duplicate source of truth
- updateGame now returns PlayoffMatchGame | undefined to reflect reality
when no row matches the ID
- Remove TOCTOU check-then-act in update-game action: call updateGame
directly and check the return value instead of a pre-flight findGameById
- Add status enum validation before the cast in update-game action
- Move impliedProbability computation inside upsertMatchOdds so callers
only provide moneylineOdds; the model owns the derivation
- Remove unnecessary dynamic import of americanToImpliedProbability in
the upsert-odds action (was already imported from the same module)
- Fix React list reconciliation bug: replace bare <> fragment with
<Fragment key={match.id}> so React can correctly track rows
https://claude.ai/code/session_01Twt3D1bsEK3eXUhMaz6ee7
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-11 14:17:43 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 40,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773262080928,
|
|
|
|
|
"tag": "0040_fat_puma",
|
|
|
|
|
"breakpoints": true
|
2026-03-15 10:22:42 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 41,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773557500336,
|
|
|
|
|
"tag": "0041_great_magma",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 42,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773600000000,
|
|
|
|
|
"tag": "0042_migrate_event_date_to_starts_at",
|
|
|
|
|
"breakpoints": true
|
2026-03-15 21:52:47 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 43,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773634394633,
|
|
|
|
|
"tag": "0043_demonic_vanisher",
|
|
|
|
|
"breakpoints": true
|
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
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 44,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773700000000,
|
|
|
|
|
"tag": "0044_add_ncaam_bracket_simulator_type",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 45,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773710000000,
|
|
|
|
|
"tag": "0045_add_ncaaw_bracket_simulator_type",
|
|
|
|
|
"breakpoints": true
|
2026-03-16 22:18:50 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 46,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773720000000,
|
|
|
|
|
"tag": "0046_add_nba_bracket_simulator_type",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 47,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773730000000,
|
|
|
|
|
"tag": "0047_fix_nba_bracket_simulator_type",
|
|
|
|
|
"breakpoints": true
|
Partial bracket scoring, code review fixes, and double-chance logic (#156)
## Partial bracket scoring
- `processMatchResult`: new exported function that scores a single match
immediately (loser → final placement, winner → provisional floor).
Called from `set-winner` and `set-round-winners` so points are awarded
as soon as a winner is set, before the full round is complete.
- `set-winner`: passes `eventName` to `processMatchResult`.
- `set-round-winners`: batches side effects — calls `recalculateAffectedLeagues`
and `updateProbabilitiesAfterResult` once after the loop instead of per-match
(`skipSideEffects: true` per match).
## Code review fixes
- **ROUND_CONFIG** (S1): extracted a `RoundScoringConfig` lookup table +
`getRoundConfig()` helper, eliminating three parallel `if/else` chains in
`processPlayoffEvent`, `processMatchResult`, and `getGuaranteedMinimumPosition`.
AFL template overrides live in `TEMPLATE_ROUND_CONFIG` with an explanatory
comment about why the `bracketTemplateId` guard is required.
- **skipSideEffects** (C2): new param on `processMatchResult`; bracket server
uses it to batch standings/probability recalc in `set-round-winners`.
- **eventName** (W1): passed through `processMatchResult` → `recalculateAffectedLeagues`.
- **Round validation** (W2): `set-round-winners` now guards `match.round === round`
before processing each assignment.
- **Comments** (C3/S3/W3): added notes on non-scoring loser assumption,
`isScoring ?? true` default, and AFL Semi-Finals template requirement.
## PlayoffBracket eliminated-teams fix + tests
- Fixed `computeEliminatedByRound` to track participant *appearances* (not just
wins), so AFL QF losers who advance to Semi-Finals via double-chance are
correctly excluded from the QF eliminated list.
- Extracted the logic as an exported pure function for testability.
- Added 4 tests: standard elimination, AFL QF loser → SF win, AFL QF loser →
SF loss, and normal advancement not protecting a later loser.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 10:50:30 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 48,
|
|
|
|
|
"version": "7",
|
2026-03-17 11:16:36 -07:00
|
|
|
"when": 1773765768905,
|
|
|
|
|
"tag": "0048_eager_songbird",
|
Partial bracket scoring, code review fixes, and double-chance logic (#156)
## Partial bracket scoring
- `processMatchResult`: new exported function that scores a single match
immediately (loser → final placement, winner → provisional floor).
Called from `set-winner` and `set-round-winners` so points are awarded
as soon as a winner is set, before the full round is complete.
- `set-winner`: passes `eventName` to `processMatchResult`.
- `set-round-winners`: batches side effects — calls `recalculateAffectedLeagues`
and `updateProbabilitiesAfterResult` once after the loop instead of per-match
(`skipSideEffects: true` per match).
## Code review fixes
- **ROUND_CONFIG** (S1): extracted a `RoundScoringConfig` lookup table +
`getRoundConfig()` helper, eliminating three parallel `if/else` chains in
`processPlayoffEvent`, `processMatchResult`, and `getGuaranteedMinimumPosition`.
AFL template overrides live in `TEMPLATE_ROUND_CONFIG` with an explanatory
comment about why the `bracketTemplateId` guard is required.
- **skipSideEffects** (C2): new param on `processMatchResult`; bracket server
uses it to batch standings/probability recalc in `set-round-winners`.
- **eventName** (W1): passed through `processMatchResult` → `recalculateAffectedLeagues`.
- **Round validation** (W2): `set-round-winners` now guards `match.round === round`
before processing each assignment.
- **Comments** (C3/S3/W3): added notes on non-scoring loser assumption,
`isScoring ?? true` default, and AFL Semi-Finals template requirement.
## PlayoffBracket eliminated-teams fix + tests
- Fixed `computeEliminatedByRound` to track participant *appearances* (not just
wins), so AFL QF losers who advance to Semi-Finals via double-chance are
correctly excluded from the QF eliminated list.
- Extracted the logic as an exported pure function for testability.
- Added 4 tests: standard elimination, AFL QF loser → SF win, AFL QF loser →
SF loss, and normal advancement not protecting a later loser.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 10:50:30 -07:00
|
|
|
"breakpoints": true
|
Grant sitewide admins commissioner-level access in leagues (#162)
* Grant sitewide admins commissioner-level access in leagues
- `isCommissioner()` now returns true for site admins, covering all
commissioner-gated loaders (league home, settings, sport season detail)
and draft API routes (start, pause, resume, rollback, replace-pick,
force-autopick, force-manual-pick, adjust-time-bank, make-pick)
- Added `hasCommissionerRecord()` (DB-only, no admin bypass) for the
"already a commissioner" duplicate-entry check in the settings action,
preventing a false positive when adding a site admin as commissioner
- `isCommissioner()` now runs the admin check and DB query in parallel
via Promise.all to avoid a serial roundtrip on every check
- Added "admin" to the `picked_by_type` enum (migration 0049) so picks
forced by a site admin are recorded accurately in the audit log rather
than as "commissioner"
- 8 unit tests covering both isCommissioner and hasCommissionerRecord
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix draft.force-manual-pick tests broken by isUserAdminByClerkId
The route now calls isUserAdminByClerkId which hits database().query.users,
but the test's mock DB had no query.users entry. Add a vi.mock for
~/models/user and default isUserAdminByClerkId to false in beforeEach.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:41:56 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 49,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773800000000,
|
|
|
|
|
"tag": "0049_add_admin_picked_by_type",
|
|
|
|
|
"breakpoints": true
|
2026-03-18 01:23:53 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 50,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773821310661,
|
|
|
|
|
"tag": "0050_chief_peter_quill",
|
|
|
|
|
"breakpoints": true
|
2026-03-18 22:15:28 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 51,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773900000000,
|
|
|
|
|
"tag": "0051_add_standings_snapshot_unique_index",
|
|
|
|
|
"breakpoints": true
|
2026-03-18 22:40:19 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 52,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773897678653,
|
|
|
|
|
"tag": "0052_new_warbird",
|
|
|
|
|
"breakpoints": true
|
2026-03-20 21:36:39 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 53,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1773903201189,
|
|
|
|
|
"tag": "0053_smooth_kingpin",
|
|
|
|
|
"breakpoints": true
|
2026-03-21 00:12:01 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 54,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774070769112,
|
|
|
|
|
"tag": "0054_massive_vulcan",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 55,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774072156237,
|
|
|
|
|
"tag": "0055_special_vampiro",
|
|
|
|
|
"breakpoints": true
|
2026-03-22 01:57:39 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 56,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774167142673,
|
|
|
|
|
"tag": "0056_jittery_the_fallen",
|
|
|
|
|
"breakpoints": true
|
2026-03-23 08:24:28 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 57,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774239259550,
|
|
|
|
|
"tag": "0057_cultured_doctor_doom",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 58,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774310000000,
|
|
|
|
|
"tag": "0058_add_unique_participant_ev",
|
|
|
|
|
"breakpoints": true
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 59,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774329951326,
|
|
|
|
|
"tag": "0059_mysterious_jack_murdock",
|
|
|
|
|
"breakpoints": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 60,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774330910098,
|
|
|
|
|
"tag": "0060_omniscient_outlaw_kid",
|
|
|
|
|
"breakpoints": true
|
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
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 61,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774410244601,
|
|
|
|
|
"tag": "0061_violet_mephistopheles",
|
|
|
|
|
"breakpoints": true
|
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 62,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774500000000,
|
|
|
|
|
"tag": "0062_add_mlb_bracket_simulator_type",
|
|
|
|
|
"breakpoints": true
|
2026-03-26 00:34:51 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"idx": 63,
|
|
|
|
|
"version": "7",
|
|
|
|
|
"when": 1774504559479,
|
|
|
|
|
"tag": "0063_bored_ultimo",
|
|
|
|
|
"breakpoints": true
|
2025-10-10 23:04:50 -07:00
|
|
|
}
|
|
|
|
|
]
|
Partial bracket scoring, code review fixes, and double-chance logic (#156)
## Partial bracket scoring
- `processMatchResult`: new exported function that scores a single match
immediately (loser → final placement, winner → provisional floor).
Called from `set-winner` and `set-round-winners` so points are awarded
as soon as a winner is set, before the full round is complete.
- `set-winner`: passes `eventName` to `processMatchResult`.
- `set-round-winners`: batches side effects — calls `recalculateAffectedLeagues`
and `updateProbabilitiesAfterResult` once after the loop instead of per-match
(`skipSideEffects: true` per match).
## Code review fixes
- **ROUND_CONFIG** (S1): extracted a `RoundScoringConfig` lookup table +
`getRoundConfig()` helper, eliminating three parallel `if/else` chains in
`processPlayoffEvent`, `processMatchResult`, and `getGuaranteedMinimumPosition`.
AFL template overrides live in `TEMPLATE_ROUND_CONFIG` with an explanatory
comment about why the `bracketTemplateId` guard is required.
- **skipSideEffects** (C2): new param on `processMatchResult`; bracket server
uses it to batch standings/probability recalc in `set-round-winners`.
- **eventName** (W1): passed through `processMatchResult` → `recalculateAffectedLeagues`.
- **Round validation** (W2): `set-round-winners` now guards `match.round === round`
before processing each assignment.
- **Comments** (C3/S3/W3): added notes on non-scoring loser assumption,
`isScoring ?? true` default, and AFL Semi-Finals template requirement.
## PlayoffBracket eliminated-teams fix + tests
- Fixed `computeEliminatedByRound` to track participant *appearances* (not just
wins), so AFL QF losers who advance to Semi-Finals via double-chance are
correctly excluded from the QF eliminated list.
- Extracted the logic as an exported pure function for testability.
- Added 4 tests: standard elimination, AFL QF loser → SF win, AFL QF loser →
SF loss, and normal advancement not protecting a later loser.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 10:50:30 -07:00
|
|
|
}
|