Skip to content

feat: Phase 2 Bookmaker Performance Analytics — backend models, services, routes, and frontend page#49

Merged
WFord26 merged 114 commits into
mainfrom
dev
May 20, 2026
Merged

feat: Phase 2 Bookmaker Performance Analytics — backend models, services, routes, and frontend page#49
WFord26 merged 114 commits into
mainfrom
dev

Conversation

@WFord26

@WFord26 WFord26 commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Completes Phase 2 Bookmaker Performance Analytics — full stack from database models through to a new frontend analytics page.


Backend

New Models (prisma/schema.prisma)

  • BookmakerAnalytics — persists calculated performance scores per bookmaker (sharp rating, best-odds frequency, market efficiency, limit profile, recommendation score, etc.)
  • BookmakerMovementEvent — tracks line movement events per bookmaker for first-mover analysis
  • Enhanced MarketConsensus — added Phase 2 dispersion metrics: consensusPrice, medianLine, meanLine, modeLine, range, interquartileRange, bestValueSide, bestValueBookmaker, bestValueLine, sharpBookWeight

New Service (src/services/bookmaker-analytics.service.ts)

  • calculateBookmakerMetrics(bookmaker) — computes full analytics including sharp rating (1–10), best-odds frequency, first-mover rate, movement lag, market efficiency, uptime, limit profile, and composite recommendation score
  • rankBookmakers(criteria) — returns ranked list sorted by: value | sharpness | reliability | coverage | limits | recommendation

Enhanced Service (src/services/market-consensus.service.ts)

  • identifyOutliers(gameId) — per-market outlier discovery
  • getBookmakerOutlierStats(bookmaker, days) — outlier frequency and average deviation for a bookmaker over a rolling window

New Routes (src/routes/analytics-bookmaker.routes.ts)

  • GET /api/analytics/bookmakers/rankings — ranked bookmaker list (query: criteria)
  • GET /api/analytics/bookmakers/:bookmaker — full metrics for one bookmaker
  • GET /api/analytics/bookmakers/:bookmaker/outliers — outlier stats (query: days)

Migrations

  • 20260514000001_enhance_market_consensus_phase2 — adds Phase 2 columns to market_consensus
  • 20260514000002_add_bookmaker_analytics_phase2 — creates bookmaker_analytics and bookmaker_movement_events tables

Frontend

New Page (src/pages/BookmakerPerformance.tsx) — /analytics/bookmakers

Two-tab layout following the Sharp Money Analytics pattern:

Rankings tab

  • 6 sort criteria selector (Recommended / Best Value / Sharpest / Most Reliable / Best Coverage / Highest Limits)
  • Ranked card list showing recommendation score, sharp rating, best-odds frequency, limit profile badge, and games/sports count
  • Clicking a row switches to the Detail tab

Detail tab

  • Bookmaker dropdown seeded from rankings list
  • 8 stat cards: Sharp Book Rating, Best Odds Frequency, Market Efficiency, Uptime, First Mover Rate, Movement Lag, Odds Update Frequency, Outlier Rate
  • Coverage section: sports covered pills, market type pills, total games/markets
  • Consensus outlier analysis with configurable day window (7 / 30 / 90 days)

Supporting Files

  • src/types/bookmaker.types.tsBookmakerAnalytics, BookmakerOutlierStats, BookmakerRankingCriteria, LimitProfile, response wrappers
  • src/services/bookmaker.service.ts — API client (3 methods)
  • src/store/bookmakerSlice.ts — Redux slice with fetchRankings, fetchBookmakerDetail, fetchOutlierStats thunks and 7 selectors

Wiring

  • store/index.tsbookmaker reducer registered
  • App.tsx — route /analytics/bookmakers
  • components/Header.tsx — "Bookmakers" nav item with bar-chart icon after "Sharp Money"
  • test/test-utils.tsxbookmakerReducer added to mock store so RootState type stays consistent

Docs / Changelog

  • dashboard/frontend/CHANGELOG.md[Unreleased] entry added
  • dashboard/backend/CHANGELOG.md[Unreleased] entry added
  • docs/wiki/API-DOCUMENTATION.md — updated
  • docs/wiki/Database-Guide.md — updated

Verification

  • Frontend TypeScript: npx tsc --noEmit — zero errors
  • New backend route file: zero TypeScript errors
  • Pre-existing backend errors in service files resolve after npm run prisma:generate (Prisma client must be regenerated against the new migrations)

WFord26 and others added 30 commits April 2, 2026 21:59
- Added session management using cookies for user authentication.
- Integrated Google and Microsoft OAuth providers for user login.
- Created middleware for handling authentication sessions and user state.
- Updated user model to include active status and provider information.
- Enhanced bet service to associate bets with authenticated users.
- Modified routes to enforce authentication for sensitive endpoints.
- Updated frontend to handle OAuth login flow and session management.
- Added documentation for authentication setup and security features.
* feat(#14): Harden authentication and session management

CRITICAL SECURITY FIXES:
- Removed insecure secret defaults (JWT_SECRET, SESSION_SECRET)
  * Production startup now fails with clear error if secrets not set
  * Development uses dynamic random defaults with warnings
  * Fixes Issue #14 part 1

- Migrated session store from in-memory Map to Redis
  * Sessions now persist across server restarts
  * Supports horizontal scaling
  * Automatic fallback to in-memory for development
  * Fixes Issue #14 part 2

- Protected admin routes with mandatory authentication
  * Admin access no longer bypassed when AUTH_MODE='none'
  * Prevents accidental exposure due to misconfiguration
  * Fixes Issue #14 part 3

- Optimized API key auth from O(n) to O(1)
  * Added keyPrefix index to ApiKey model
  * Now does single bcrypt comparison instead of loading all keys
  * Eliminates DoS vector from bcrypt-timing attacks
  * Fixes Issue #14 part 4

NEW FILES:
- dashboard/backend/src/services/session-store.service.ts
  * ISessionStore interface with Redis and in-memory implementations
  * Transparent fallback if Redis unavailable
  * Proper connection lifecycle management

SCHEMA CHANGES:
- Added index on ApiKey.keyPrefix for O(1) lookups

FILES MODIFIED:
- dashboard/backend/src/config/env.ts
- dashboard/backend/src/middleware/api-key-auth.ts
- dashboard/backend/src/middleware/auth-session.middleware.ts
- dashboard/backend/src/middleware/session.auth.ts
- dashboard/backend/src/server.ts
- dashboard/backend/prisma/schema.prisma
- dashboard/backend/CHANGELOG.md

* feat(tests): add CLV reducer to test utilities for improved testing coverage

* feat: implement version bumping system for BetTrack components

- Added a new script for automatic semantic versioning based on file changes.
- Introduced tracking for MCP Server, Dashboard Backend, and Dashboard Frontend.
- Implemented change detection using file hashing and git diffs.
- Added commands for bumping versions, including force and dry-run options.
- Created a configuration file for managing tracked directories and ignored paths.
- Updated package.json files and a hash state file to reflect version changes.
* Initial plan

* Initial plan - improve type safety across backend

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/a1d4d708-4808-4ee0-bfce-f59afbea3c45

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* Improve type safety across backend (bet.service.ts, bets.routes.ts)

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/a1d4d708-4808-4ee0-bfce-f59afbea3c45

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
…logic (#30)

- Fix home/away detection by matching outcomes with team names instead of array index
  in odds-sync.service.ts. The Odds API doesn't guarantee outcome ordering, so the
  previous array index check could cause home/away odds to be swapped.

- Fix teaser sport resolution by fetching from first leg's game record instead of
  hardcoding to 'nfl' in bet.service.ts. NBA teasers were using NFL odds tables,
  returning incorrect payouts.

- Fix legsSettled counter always returning 0 by removing unused const declaration
  in outcome-resolver.service.ts. Now correctly returns legs.length.

NOTE: TypeScript build currently fails with pre-existing error in auth-session.middleware.ts
that is unrelated to these fixes.
…e per-league duplication (#29) (#31)

* Initial plan

* Add unified get_scoreboard tool and LEAGUE_SPORT_MAP to reduce code duplication

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/bb076146-44b5-4994-a0e9-b717195a5cc3



---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
… safety (#32)

* Initial plan

* fix: extend outcome resolution lookback to 12h and add ESPN status type safety

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/57425a90-a3b6-46de-b828-8211158cd1eb

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* refactor: extract lookback hours into named constant OUTCOME_LOOKBACK_HOURS

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/57425a90-a3b6-46de-b828-8211158cd1eb

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
…routes.ts (Issue #16) (#33)

- Remove 260+ lines of broken duplicate code from auth-session.middleware.ts
  - Deleted duplicate helper functions that used undefined 'sessions' map
  - Kept only async implementation using sessionStore (Redis/in-memory)

- Fix async/await issues in auth.routes.ts
  - Added 'await' to ensureAuthSession(), createAuthenticatedSession(), destroyAuthSession() calls
  - Made logout endpoint async to properly await destroyAuthSession()

- Verify authentication hardening already in place:
  - ✓ env.ts: Requires secrets in production, random dev defaults
  - ✓ session-store.service.ts: Full Redis support with in-memory fallback
  - ✓ session.auth.ts: requireAdminAccess() always protected, even in AUTH_MODE='none'
  - ✓ api-key-auth.ts: O(1) keyPrefix lookup prevents bcrypt DoS

All auth-related TypeScript errors resolved.
…sue #16

- bump-version.mjs: fix replacement order so [Unreleased] stays at top,
  versioned section inserted after --- separator
- dashboard/backend/CHANGELOG.md: document Issue #16 auth-session cleanup fixes
* docs: update changelogs with Issue #17 odds and settlement bug fixes

- odds-sync.service.ts: Fixed home/away detection using team name matching
- bet.service.ts: Fixed teaser sport resolution from first leg's game record
- outcome-resolver.service.ts: Fixed legsSettled counter to return proper count

* fix: bump script should only bump packages with actual changes

- Remove 'args.force' from skip condition in bump loop
- Now only bumps packages detected in changedKeys, regardless of --force flag
- --force flag only bypasses 'no changes detected' exit, doesn't force all packages to be bumped
- Fixes issue where --force patch would bump all packages even if only one changed

* fix: exclude CHANGELOG.md files from hash tracking

- Append /CHANGELOG.md$/ to IGNORED_PATHS to prevent changelog updates from being detected as code changes
- This prevents the bump script from re-bumping all packages just because their changelogs were updated
- Now only actual code/file changes trigger new version bumps, not documentation updates

* chore: regenerate bump hashes with CHANGELOG.md excluded from tracking

* fix: add package.json to ignored paths in bump script

* fix: update package versions and changelogs for backend, frontend, and MCP
* Initial plan

* Add E2E tests for complete bet lifecycle

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/abe6821c-e7fe-472a-b13d-4e3b7c42c2c9

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
- Include 'dev' branch in pull_request trigger alongside main, beta, develop
- Include 'dev' branch in push trigger alongside main, beta
- Ensures tests run automatically on PRs to dev and dev pushes
- Tests will validate backend, frontend, and MCP server code changes
- Add Zod validation schema for site config with URL validation
  - logoUrl and domainUrl now validated using .url() validator
  - Prevents XSS attacks via malicious URL injection

- Add authorization check for force delete in bets routes
  - Force delete now requires admin role
  - Returns 403 Forbidden if non-admin attempts force delete
  - Regular users can still cancel pending bets normally
- Add requireAdminAccess middleware mock to admin routes tests
  - Tests now properly authenticate admin requests
  - Fixes 401 Unauthorized errors on protected endpoints

- Guard against undefined bookmakers in API sync services
  - Futures sync now handles missing bookmakers array gracefully
  - Odds sync now handles missing bookmakers array gracefully
  - Error messages use optional chaining for safe bookmaker.key access
  - Prevents 'Cannot read properties of undefined' errors
- Add requireAdminAccess middleware mock to admin routes tests
  - Tests now properly authenticate admin requests
  - Fixes 401 Unauthorized errors on protected endpoints

- Guard against undefined bookmakers in API sync services
  - Futures sync now handles missing bookmakers array gracefully
  - Odds sync now handles missing bookmakers array gracefully
  - Error messages use optional chaining for safe bookmaker.key access
  - Prevents 'Cannot read properties of undefined' errors
- Fix BetSlip.test.tsx imports: add render import from @testing-library/react
  - Properly imports screen and fireEvent alongside render
  - Ensures vitest can resolve testing library functions

- Fix clvSlice.test.ts async thunk typing:
  - Create properly typed AppDispatch variable in beforeEach
  - Replace all (store.dispatch as AppDispatch) with dispatch calls
  - Fixes 'AsyncThunkAction not assignable to Action' errors
  - Fixes 'Property clv does not exist on unknown' errors

- Fix test-utils.tsx Redux store typing:
  - Use RootState | undefined instead of loose 'as any' typing
  - Properly typed preloadedState in renderWithProviders and createMockStore
  - Ensures correct Redux state shape in tests
- Document BetSlip.test.tsx import fixes
- Document clvSlice.test.ts async thunk typing improvements
- Document test-utils.tsx Redux store typing enhancements
- Add 30-day retention policy for OddsSnapshot records
  - New cleanup job runs daily at 2 AM UTC
  - Prevents unbounded growth of odds history table
  - Automatically deletes snapshots older than 30 days

- Add 90-day retention policy for ApiKeyUsage records
  - Integrated with cleanup job
  - Prevents API usage logs from consuming unbounded disk space

- Add index on OddsSnapshot.capturedAt
  - Optimizes cleanup queries for timestamp filtering
  - Improves performance of any analytics queries on captured_at

- Create cleanup-old-records.job.ts
  - Scheduled cron job (0 2 * * * = 2 AM UTC daily)
  - Logs deletion counts for monitoring
  - Includes error handling and retry logic

- Register cleanup job in server startup
  - Initialized alongside other scheduled jobs
  - Gracefully handles initialization failures
* Initial plan

* refactor: consolidate auth middleware, remove dead code, clean up localStorage token logic

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/d6b8b315-d9de-4c9f-af2e-738dd14322eb

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* feat: consolidate session auth middleware exports and enhance logout handling

* fix: improve validation error messages and enhance site config schema

* Implement feature X to enhance user experience and fix bug Y in module Z

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
Co-authored-by: William Ford <wford@managedsolution.com>
Replace findMany + in-memory loop with:
- Prisma groupBy() for status counts and bet type breakdowns
- Raw SQL with DISTINCT ON for sport breakdown (joins bet_legs → games → sports)
- Promise.all to run all 3 queries in parallel

Eliminates loading all bets + legs + games + sports into memory.
Returns only aggregated numbers from the database.
Both api-keys.routes.test.ts and games.routes.test.ts failed because
the import chain reached session-store.service.ts which imports redis
(not installed as a dependency). Added mocks for:
- session-store.service (prevents redis import)
- auth-session.middleware (bridges test session simulation to route auth)
@testing-library/react v16 requires @testing-library/dom as a peer
dependency for screen and fireEvent exports. Without it, tsc --noEmit
fails with TS2305 in BetSlip.test.tsx.
WFord26 and others added 16 commits May 12, 2026 22:49
…tale pregame opportunities

chore: bump frontend version to 0.4.2; update changelog with fix for movement stats timeframe sync
Previously calculateConsensus scored h2h markets using only home implied
probabilities. A market where away prices diverge while home prices are
stable was treated as low/no disagreement and never appeared in
/analytics/disagreement/live or the Value Opportunities page.

Fix: compute disagreement independently for both sides. disagreementScore
is now max(homeScore, awayScore) so away-only divergence is fully captured.
stdDev comes from the dominant side. Outliers are merged from both sides,
keeping the entry with larger absolute deviation when a bookmaker appears
on both. Consensus line is still reported as home American odds for UI
consistency.
- Bump API version to 0.3.6 and add new tags for Bookmaker Disagreement and Line Movement Analytics.
- Introduce new schemas for LineMovement, MovementStats, ConsensusResult, and HighDisagreementGame in OpenAPI spec.
- Implement new endpoints for retrieving bookmaker disagreement and line movement analytics.
- Add optional dashboard integration instructions in INSTALL_INSTRUCTIONS.md.
- Refactor dashboard_mcp_server.py to improve session management and error handling.
- Update create_bet and get_active_games functions to align with new API structure.
- Add new tools for getting advice context and games with exposure.
* Initial plan

* feat: implement Phase 2 Sharp vs Public Money Indicators

- Add SharpMoneyIndicator Prisma model with migration
- Add sharp-indicator.service.ts with detectSharpSide, calculateConfidence, findContrarianOpportunities, runBatchDetection, getStats
- Add analytics-sharp.routes.ts (/live, /game/:id, /contrarian, /stats)
- Add sharp-indicator.job.ts scheduled every 15 minutes
- Register route and job in routes/index.ts and server.ts
- Add sharp.types.ts, sharp.service.ts, sharpSlice.ts on frontend
- Add SharpMoneyAnalytics.tsx page with Live Indicators + Contrarian Picks tabs
- Add /analytics/sharp route to App.tsx
- Add Sharp Money nav item to Header.tsx
- Update backend/frontend changelogs
- Update API-DOCUMENTATION.md and Database-Guide.md

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/142cef5d-1f82-48bb-8023-e050b23ae379

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* fix: address code review feedback on sharp-indicator service and analytics page

- Use m.marketType from LineMovement record in calculateConfidence (not lineMovement category)
- Fix useEffect dependency array in SharpMoneyAnalytics (remove initial duplicated fetches)
- Standardize matchup format to Away @ Home in analytics-sharp routes

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/142cef5d-1f82-48bb-8023-e050b23ae379

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* fix: resolve all frontend TypeScript errors failing CI

- Add sharpReducer to test-utils.tsx configureStore calls to match RootState
- Add missing afterEach import from vitest in DarkModeContext.test.tsx and PreferencesContext.test.tsx

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/fc51aba6-7b77-4ddc-a145-87b3f2140dcc

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
- backend: Added Sharp vs Public Money Indicators feature (Issue #8)
- frontend: Introduced Sharp vs Public Money Analytics page (Issue #8)
- mcp: Added build.sh and docker-build.sh scripts for macOS/Linux with full feature parity
- Updated versioning in package.json and manifest.json files
- Enhanced build scripts to support version bumping and public visibility for Docker images
- Add .github/workflows/docker-publish.yml: builds and pushes
  backend and frontend images to GHCR on every push to main that
  touches dashboard source files. Uses GITHUB_TOKEN (packages:write)
  which auto-links packages to the repo and inherits its visibility.
  Includes org.opencontainers.image.source label, multi-platform
  builds (amd64/arm64), and per-component GHA layer caching.

- Update release.yml: fix image paths from bettrack-backend/
  bettrack-frontend to bettrack/backend and bettrack/frontend to
  match the new GHCR namespace structure (ghcr.io/wford26/bettrack/
  backend and ghcr.io/wford26/bettrack/frontend).
…#46)

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9013cbcd-779d-4b8c-8e07-5437d9fb26c9

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
# Conflicts:
#	.bump-hashes.json
#	.github/workflows/docker-publish.yml
#	.github/workflows/release.yml
#	dashboard/backend/CHANGELOG.md
#	dashboard/backend/package.json
#	dashboard/frontend/CHANGELOG.md
#	dashboard/frontend/package.json
#	mcp/CHANGELOG.md
#	mcp/manifest.json
#	mcp/package.json
#	scripts/build.sh
…lier discovery (#47)

* Initial plan

* Plan: implement Phase 2 market consensus calculations

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9c93eecb-77e8-4602-89c3-e8698f7bd609

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* Revert unintended lockfile change from setup step

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9c93eecb-77e8-4602-89c3-e8698f7bd609

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* Implement Phase 2 market consensus metrics and outlier service

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9c93eecb-77e8-4602-89c3-e8698f7bd609

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* Refine h2h consensus metrics and migration backfill defaults

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9c93eecb-77e8-4602-89c3-e8698f7bd609

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* Add h2h totals consensus coverage and tighten migration backfill

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9c93eecb-77e8-4602-89c3-e8698f7bd609

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* Finalize consensus backfill defaults and strengthen h2h test assertions

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9c93eecb-77e8-4602-89c3-e8698f7bd609

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
* Initial plan

* feat(backend): add phase 2 bookmaker analytics models and service

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9bea1ecc-39b5-435b-a821-1a59643ff3a2

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* chore: address analytics service review follow-ups

Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9bea1ecc-39b5-435b-a821-1a59643ff3a2

Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>

* feat(backend): filter current odds by game commencement time in bookmaker metrics calculation

* fix(backend): improve null checks and add ordering to bookmaker metrics queries

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
Co-authored-by: William Ford <wford@managedsolution.com>
- Implemented a new page at `/analytics/bookmakers` for comparing and ranking sportsbooks by value, sharpness, reliability, and market coverage.
- Created a two-tab UI with Rankings and Detail views.
- Added TypeScript types for BookmakerAnalytics and related entities.
- Developed API client for fetching rankings, bookmaker details, and outlier stats.
- Integrated Redux slice for managing state related to bookmakers.
- Updated routing and navigation to include the new Bookmaker Performance page.
- Enhanced Header component with a new "Bookmakers" navigation item.
- Added comprehensive styling and layout for the new page.
@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 44.82%
  • Functions: 48.33%
  • Branches: 29.43%
  • Statements: 44.44%

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20ee8717f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +398 to +401
return prisma.bookmakerAnalytics.findMany({
orderBy,
take: 50,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Populate rankings before reading bookmaker_analytics

The rankings endpoint currently reads only persisted rows (rankBookmakers does a plain findMany), but this commit never adds a batch/job path that writes those rows upfront. The only writer is calculateBookmakerMetrics (via upsert) and it is only invoked by the per-bookmaker detail route, so after a fresh migration GET /analytics/bookmakers/rankings will return an empty list and the new page has no way to bootstrap data from the UI. Please compute/update analytics before serving rankings (or lazily backfill inside rankings).

Useful? React with 👍 / 👎.

{Math.round(bm.recommendationScore)}/100 score
</span>
<span className={`text-sm px-2 py-1 rounded border font-medium ${limitBadge(bm.limitProfile, isDarkMode)}`}>
{bm.limitProfile.charAt(0).toUpperCase() + bm.limitProfile.slice(1)} limits · ~${bm.estimatedMaxBet.toLocaleString()} max

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard nullable estimatedMaxBet before formatting

This render path assumes estimatedMaxBet is always present and calls .toLocaleString() unconditionally, but the backend schema defines estimatedMaxBet as nullable (Decimal?). Any analytics row with a null max-bet value will crash the detail tab at render time with a property access error. Add a null-safe display fallback (for example, ) before calling number formatting.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 44.36%
  • Functions: 47.5%
  • Branches: 28.59%
  • Statements: 43.97%

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f42d9c26e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +82 to +84
if (!normalizedBookmaker) {
throw new Error('bookmaker is required');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject unknown bookmakers before writing analytics

This method only validates that the path param is non-empty, so GET /api/analytics/bookmakers/:bookmaker will happily upsert a new analytics row even when that bookmaker has no odds data at all. In that case all metrics are synthesized from empty arrays and persisted, which lets any authenticated caller create arbitrary bookmaker records that later appear in rankings and pollute production analytics. Add a guard (for example, require at least one currentOdds row) and return a 404/validation error instead of writing.

Useful? React with 👍 / 👎.

Comment on lines +109 to +112
prisma.marketConsensus.findMany({
where: {
calculatedAt: { gte: cutoff },
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope consensus reads to the selected bookmaker context

Each detail request loads every market_consensus row from the last 30 days and then filters in memory, which makes latency and memory usage scale with total platform volume rather than the requested bookmaker. As data grows, this endpoint can become slow or timeout even for a single bookmaker lookup. Constrain this query at the database layer (for example by limiting to relevant game IDs/markets) before post-processing.

Useful? React with 👍 / 👎.

WFord26 added 3 commits May 19, 2026 21:36
…ror handling

- Implement integration tests for the following endpoints:
  - GET /api/analytics/sharp/live
  - GET /api/analytics/sharp/game/:gameId
  - GET /api/analytics/sharp/contrarian
  - GET /api/analytics/sharp/stats
- Enhance error handling for service failures in the tests.
- Mock necessary services and database interactions for isolated testing.

Update BookmakerAnalyticsService tests to handle new BetLeg data structure

- Mock BetLeg aggregate function in tests.
- Adjust test cases to account for potential null values in CLV calculations.
- Ensure tests cover scenarios where no bookmaker data exists.

Create smoke tests for bookmaker-analytics job

- Add tests to verify the functionality of the bookmaker-analytics job.
- Ensure that the job triggers correctly and handles errors from individual bookmakers.

Refactor BookmakerPerformance component to handle null scores

- Update scoreColor and scoreBadge functions to manage null values for scores.
- Adjust rendering logic in RankingRow and DetailPanel to display placeholders for null scores.
- Ensure average score calculations handle potential null values gracefully.

Update bookmaker types to allow null values for certain metrics

- Modify BookmakerAnalytics interface to allow null for uptimePercentage and recommendationScore.
- Ensure that averageCLVOffered can also be null until populated.

Enhance OpenAPI documentation for Bookmaker Performance Analytics

- Add new endpoints for bookmaker rankings, sharp bookmakers, and comparisons.
- Define schemas for BookmakerAnalytics and document response structures.
- Ensure all new endpoints require session authentication and provide clear descriptions.
…ests

- Implement unit tests for LineMovementService covering methods like getGameMovements, getMovementsByType, and detectMovements.
- Create tests for SharpIndicatorService, focusing on detecting sharp sides, calculating confidence, and running batch detection.
- Introduce session-store.service tests to validate in-memory session management and Redis integration.
- Add smoke tests for bookmaker-analytics.job to ensure batch calculations and job scheduling work as expected.
- Enhance existing tests in bookmaker-analytics.service to cover edge cases and improve mock implementations.
@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 54.09%
  • Functions: 57.61%
  • Branches: 37.09%
  • Statements: 53.58%

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 54.1%
  • Functions: 57.61%
  • Branches: 37.13%
  • Statements: 53.59%

@WFord26 WFord26 merged commit ab62da2 into main May 20, 2026
8 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c39e155326

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +125 to +129
prisma.bookmakerMovementEvent.findMany({
where: {
detectedAt: { gte: cutoff },
},
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Constrain movement-event reads to the target bookmaker

calculateBookmakerMetrics fetches all bookmaker_movement_events for the 30-day window and only filters to the requested bookmaker in memory. Because runBatchCalculation calls this once per bookmaker, the job repeatedly rescans the same full event set (O(bookmakers × events)), which will inflate runtime and can cause timeouts as event volume grows. Add DB-side constraints (at least by bookmaker and/or relevant game IDs) before post-processing.

Useful? React with 👍 / 👎.

return res.status(400).json({ success: false, error: 'bookmaker is required' });
}

const stats = await marketConsensusService.getBookmakerOutlierStats(bookmaker, days);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize bookmaker key before outlier-stat lookup

This endpoint passes the raw path parameter into getBookmakerOutlierStats, but that service matches bookmaker keys case-sensitively (where: { bookmaker } and o.bookmaker === bookmaker). Unlike the detail/movement endpoints that lowercase the key, requests like /api/analytics/bookmakers/DraftKings/outliers can incorrectly return zero stats even when data exists. Normalize the key before calling the service.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants