11 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
07f1356cd3 |
Sync IndyCar standings from OC Blacktop, fall back to ESPN
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m32s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m23s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🧪 Test (push) Successful in 3m29s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m30s
🚀 Deploy / 🐳 Build (push) Successful in 1m17s
🚀 Deploy / 🚀 Deploy (push) Successful in 11s
ESPN's IndyCar standings aggregate lags hours behind a race finish (Palou stuck at 374 an evening after Mid-Ohio, vs. his true 409/P1). Add an OcBlacktopIndyCarStandingsAdapter that reads the fresher OC Blacktop feed and delegates to the existing ESPN IndyCarStandingsAdapter on any failure (missing key, network error, empty payload, or schema drift) so a flaky third party never breaks the sync. Per-row validation throws on missing id/name or unparseable position/points to route to the fallback rather than overwriting real standings with zeros, while still keeping legitimate 0-point backmarkers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
| 0150fb7ab9 |
sync racing standings (#80)
Co-authored-by: Claude <noreply@anthropic.com> Reviewed-on: #80 |
|||
| 273742f02e |
Phase A: external HTTP cron jobs for snapshots, standings sync, and simulation (#79)
## Summary
- Removes the last in-process \`setInterval\` (\`server/snapshots.ts\` 24h loop) and replaces it with an external HTTP cron job via Forgejo Actions
- Adds automated standings sync + conditional simulation: syncs every 2h, only simulates when standings actually changed (detected by comparing \`gamesPlayed\`/\`leagueRank\` before upsert)
- Adds \`GET /healthz\` for Docker healthcheck (Phase B prerequisite)
## What's new
| Endpoint | Triggered by | What it does |
|---|---|---|
| \`POST /admin/jobs/run-daily-snapshots\` | Forgejo schedule \`5 0 * * *\` | Creates daily fantasy standings snapshots for all active/draft seasons |
| \`POST /admin/jobs/sync-and-simulate\` | Forgejo schedule \`0 */2 * * *\` | Syncs standings from external APIs; runs simulation only if standings changed |
| \`GET /healthz\` | Docker / Traefik | Returns 200 \`{ok:true}\` when DB reachable, 503 otherwise |
Both cron endpoints are protected by \`X-Cron-Secret\` header (set \`CRON_SECRET\` in Forgejo repo secrets + production env).
## Schema changes (migration 0118)
Two new nullable columns on \`sports_seasons\`:
- \`standings_last_changed_at\` — written by \`syncStandings()\` when data actually changes
- \`last_simulated_at\` — written by the cron job after a successful simulation run
## Deployment notes
1. Add \`CRON_SECRET\` to Forgejo repo secrets (generate with \`openssl rand -hex 32\`)
2. Add same value to production environment
3. Migration runs automatically via the \`migrate\` container on deploy
## Test plan
- [ ] \`curl -X POST https://brackt.com/admin/jobs/run-daily-snapshots -H "X-Cron-Secret: ..."\` → 200 \`{total, succeeded, errors}\`
- [ ] \`curl -X POST https://brackt.com/admin/jobs/sync-and-simulate -H "X-Cron-Secret: ..."\` → 200 with \`synced\`/\`unchanged\`/\`simulated\` breakdown
- [ ] \`curl https://brackt.com/healthz\` → 200 \`{ok:true}\`
- [ ] Verify Forgejo workflow runs appear in Actions tab after merge
- [ ] Kill web process mid-day; confirm external cron still fires (no in-process dependency)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Chris Parsons <chrisparsons1127@gmail.com>
Reviewed-on: #79
|
|||
|
|
766ba948e1
|
Add MLS standings sync and fix simulator conference resolution (#423)
* Add MLS standings sync and fix simulator conference resolution - New MlsStandingsAdapter using ESPN's free soccer API (no key required), returning Eastern/Western conference data, W/D/L/GF/GA/GD/PTS, conference rank, and home/away records; registered under mls_bracket - Display route now shows soccer table columns and a 9-team playoff cutoff line for mls_bracket (top 9 per conference qualify for MLS Cup Playoffs) - MLS simulator gains a third conference fallback: reads externalId="Eastern" or "Western" on the participant, mirroring the LLWS pool-assignment pattern so admins can bootstrap conference data before the first standings sync - 9 unit tests covering stat mapping, conference normalization, rank ordering, and error paths https://claude.ai/code/session_01WhzXHpv6taXdHzhgvnv83u * Address code review feedback on MLS standings + simulator 1. Update mls-simulator.ts module comment to document the new step-3 externalId fallback in the conference resolution order 2. Tighten normalizeConference to exact-match "Eastern"/"Western Conference" instead of broad substring, preventing false matches on names like "Northeast" 3. Pre-parse statsMap for each entry before the sort so it isn't rebuilt O(n log n) times during comparison 4. Document the externalId name-matching tradeoff on parseConferenceFromExternalId 5. Try ESPN's gamesPlayed stat before falling back to wins+losses+ties sum; export parseConferenceFromExternalId for direct testing 6. Add tests: normalizeConference passthrough, winPct=0 at preseason, and parseConferenceFromExternalId (case-insensitivity, null/undefined, numeric ESPN IDs, unrecognized strings) https://claude.ai/code/session_01WhzXHpv6taXdHzhgvnv83u * Fix lint: replace != null with !== null && !== undefined oxlint enforces eqeqeq; the five != null checks in mls.ts were flagged. https://claude.ai/code/session_01WhzXHpv6taXdHzhgvnv83u --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
caaffda236
|
Deduplicate soccer simulator helpers (#391) | ||
|
|
2848231235
|
Canonical tournament layer: schema + backfill (1/2) (#365)
* refactor(schema): rename per-window tables to season_* prefix Renames participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos to season_* prefixed names. Renames event_results.participant_id to season_participant_id. Phase 1a of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: rename participant.ts model file to season-participant.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(models): update model layer to use renamed schema exports Updated all model files to use the renamed schema exports from Task 1: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantQualifyingTotals → seasonParticipantQualifyingTotals - participantResults → seasonParticipantResults - participantSurfaceElos → seasonParticipantSurfaceElos - eventResults.participantId → eventResults.seasonParticipantId - db.query relation accessors updated - Relation field .participant → .seasonParticipant where applicable - Import paths updated: ./participant → ./season-participant Files updated (14 model files + 3 test files): - draft-pick.ts - draft-utils.ts - event-result.ts - group-stage-match.ts - participant-result.ts - qualifying-points.ts - scoring-calculator.ts - scoring-event.ts - sports-season.ts - surface-elo.ts - team-score-events.ts - cs2-major-stage.ts - golf-skills.ts - participant-expected-value.ts - __tests__/sports-season.clone.test.ts - __tests__/auto-pick.test.ts - __tests__/executeAutoPick.timer.test.ts Typecheck errors decreased: 779 → 499 (280 fewer) All model file errors related to renamed schemas resolved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route layer to use renamed schema exports - Update model import from ~/models/participant to ~/models/season-participant - Rename schema.participants to schema.seasonParticipants - Rename schema.participantResults to schema.seasonParticipantResults - Rename db.query.participants to db.query.seasonParticipants - Update 9 route files and 1 test file Affected files: - admin.sports-seasons.$id.events.$eventId.bracket.server.ts - admin.sports-seasons.$id.participants.tsx - api/draft.force-manual-pick.ts - api/draft.make-pick.ts - api/draft.replace-pick.ts - api/seasons.$seasonId.draft.ts - leagues/$leagueId.draft-board.$seasonId.tsx - leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts - admin/__tests__/sports-seasons-participants.test.ts Error count reduced from 499 to 453 (46 errors fixed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route files for schema rename Update route imports from ~/models/participant to ~/models/season-participant and fix references to .participant/.participantId on event results to use .seasonParticipant/.seasonParticipantId after schema rename. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(services): update simulators and services for renamed schema Update all simulators, services, and server files to use renamed schema tables: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantResults → seasonParticipantResults - eventResults.participantId → eventResults.seasonParticipantId Files updated: - 20 sport simulators (NBA, NHL, NFL, MLB, etc.) - probability-updater.ts - standings-sync/index.ts - sports-data-sync.server.ts - server/socket.ts Typecheck errors reduced from 365 to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: rename per-window tables to season_* prefix * fix(tests): update mock query keys after participants table rename Change mock db.query.participants to db.query.seasonParticipants in test files to match the schema rename from commit |
||
|
|
bde1e6e5f0
|
Add WNBA playoff simulator with SRS-based Elo ratings, fixes #125 (#231)
* Add WNBA playoff simulator with SRS-based Elo ratings, fixes #125 - New WNBASimulator: Monte Carlo (50k sims) projecting remaining regular season games → seeding → R1 Bo3 / Semis Bo5 / Finals Bo7 bracket - Hybrid Elo sourcing: pre-season uses futures odds (ICM); once avg gamesPlayed ≥ 5, switches automatically to SRS-derived Elo (elo = 1500 + srs × 20) - New WnbaStandingsAdapter: fetches ESPN standings + teams endpoints in parallel; includes zero-records for 2026 expansion teams (Portland Fire, Toronto Tempo) not yet in standings - Added srs column to regular_season_standings (migration 0063); stored as net rating proxy (avgPointsFor − avgPointsAgainst) - Added wnba_bracket to simulatorTypeEnum Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix missing afterEach import in wnba standings test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
2e2d8db777
|
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> |
||
|
|
c1be92b2af
|
Add AFL season + finals simulator (closes #126) (#204)
Monte Carlo simulation of the 2026 AFL season and 10-team finals series. Elo ratings are backsolved from Squiggle's projected season win totals using the inverse formula: elo = 1500 - 450×log₁₀((1−wins/23)/(wins/23)). - New `afl_bracket` simulator type (schema + migration) - `afl-simulator.ts`: projects remaining regular season via Elo, seeds the AFL_10 finals bracket (Wildcard → QF/EF → SF → PF → Grand Final), and correctly tracks P5/P6 and P7/P8 as separate scoring tiers - `afl.ts` standings sync adapter pulling from Squiggle API (no auth required) - Finals line on standings display set to 10 for AFL seasons - Substring name matching with longest-key-wins to prevent "Port Adelaide" colliding with "Adelaide" when matching "Port Adelaide Power" - 27 unit tests covering bracket logic, column-sum guarantees, and name matching Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
4bffa40606
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* 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>
|
||
|
|
bcca8b76fa
|
Add regular season standings for NBA/NHL (fixes #89) (#192)
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> |