feat: Phase 2 Bookmaker Performance Analytics — backend models, services, routes, and frontend page#49
Conversation
- 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>
…d MCP; enhance changelog update script
…ader and unreleased section
… 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.
* 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.
…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.
…ine disagreement fix
- 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>
…d and docker build scripts
- 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.
Backend Test Results ✅Coverage Summary:
|
There was a problem hiding this comment.
💡 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".
| return prisma.bookmakerAnalytics.findMany({ | ||
| orderBy, | ||
| take: 50, | ||
| }); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 👍 / 👎.
Backend Test Results ✅Coverage Summary:
|
There was a problem hiding this comment.
💡 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".
| if (!normalizedBookmaker) { | ||
| throw new Error('bookmaker is required'); | ||
| } |
There was a problem hiding this comment.
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 👍 / 👎.
| prisma.marketConsensus.findMany({ | ||
| where: { | ||
| calculatedAt: { gte: cutoff }, | ||
| }, |
There was a problem hiding this comment.
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 👍 / 👎.
…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.
Backend Test Results ✅Coverage Summary:
|
…sing bookmaker data
Backend Test Results ✅Coverage Summary:
|
There was a problem hiding this comment.
💡 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".
| prisma.bookmakerMovementEvent.findMany({ | ||
| where: { | ||
| detectedAt: { gte: cutoff }, | ||
| }, | ||
| }), |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 👍 / 👎.
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 analysisMarketConsensus— added Phase 2 dispersion metrics:consensusPrice,medianLine,meanLine,modeLine,range,interquartileRange,bestValueSide,bestValueBookmaker,bestValueLine,sharpBookWeightNew 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 scorerankBookmakers(criteria)— returns ranked list sorted by:value|sharpness|reliability|coverage|limits|recommendationEnhanced Service (
src/services/market-consensus.service.ts)identifyOutliers(gameId)— per-market outlier discoverygetBookmakerOutlierStats(bookmaker, days)— outlier frequency and average deviation for a bookmaker over a rolling windowNew 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 bookmakerGET /api/analytics/bookmakers/:bookmaker/outliers— outlier stats (query:days)Migrations
20260514000001_enhance_market_consensus_phase2— adds Phase 2 columns tomarket_consensus20260514000002_add_bookmaker_analytics_phase2— createsbookmaker_analyticsandbookmaker_movement_eventstablesFrontend
New Page (
src/pages/BookmakerPerformance.tsx) —/analytics/bookmakersTwo-tab layout following the Sharp Money Analytics pattern:
Rankings tab
Detail tab
Supporting Files
src/types/bookmaker.types.ts—BookmakerAnalytics,BookmakerOutlierStats,BookmakerRankingCriteria,LimitProfile, response wrapperssrc/services/bookmaker.service.ts— API client (3 methods)src/store/bookmakerSlice.ts— Redux slice withfetchRankings,fetchBookmakerDetail,fetchOutlierStatsthunks and 7 selectorsWiring
store/index.ts—bookmakerreducer registeredApp.tsx— route/analytics/bookmakerscomponents/Header.tsx— "Bookmakers" nav item with bar-chart icon after "Sharp Money"test/test-utils.tsx—bookmakerReduceradded to mock store soRootStatetype stays consistentDocs / Changelog
dashboard/frontend/CHANGELOG.md—[Unreleased]entry addeddashboard/backend/CHANGELOG.md—[Unreleased]entry addeddocs/wiki/API-DOCUMENTATION.md— updateddocs/wiki/Database-Guide.md— updatedVerification
npx tsc --noEmit— zero errorsnpm run prisma:generate(Prisma client must be regenerated against the new migrations)