Add Phase 2 bookmaker performance analytics models and service#48
Conversation
Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9bea1ecc-39b5-435b-a821-1a59643ff3a2 Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
Agent-Logs-Url: https://github.com/WFord26/BetTrack/sessions/9bea1ecc-39b5-435b-a821-1a59643ff3a2 Co-authored-by: WFord26 <117926366+WFord26@users.noreply.github.com>
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: 0255ca8ea0
ℹ️ 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.currentOdds.findMany({ | ||
| where: { bookmaker: normalizedBookmaker }, |
There was a problem hiding this comment.
Bound current-odds rows to the analytics window
When current_odds contains older completed games for a bookmaker, this unbounded fetch pulls every current-odds row ever retained; the sync path upserts these rows and I found no delete/expiry path for them. That makes totalGamesOffered, totalMarketsOffered, sportsCovered, and the coverage portion of recommendationScore grow all-time even though the rest of the method uses the 30-day cutoff, so books with stale historical rows are ranked too highly. Filter these odds by lastUpdated or eligible game status/time to match the analytics window.
Useful? React with 👍 / 👎.
…aker metrics calculation
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: f181516227
ℹ️ 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".
| const consensusByKey = new Map( | ||
| eligibleConsensus.map((row) => [`${row.gameId}:${row.marketType}`, row]) | ||
| ); |
There was a problem hiding this comment.
Use latest consensus row per game-market before scoring
market_consensus is append-only (new rows are created each run), so eligibleConsensus can contain multiple records for the same gameId:marketType. Building consensusByKey directly from that array means whichever duplicate appears last wins, but this query has no orderBy, so the selected row is nondeterministic/stale. That makes marginVsConsensus (and therefore marketEfficiency, sharpBookRating, and recommendationScore) drift unpredictably between runs for identical inputs.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| if (odds.marketType === 'spreads') { | ||
| if (!odds.homeSpread) return null; |
There was a problem hiding this comment.
Treat zero spreads as valid comparable lines
The spread branch treats 0 as missing because of a truthiness check, so pick'em lines (homeSpread = 0) are excluded from marginSamples. In markets where zero spreads occur, this drops valid observations and skews marginVsConsensus, which then propagates into efficiency and recommendation metrics.
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: b18b0d76e4
ℹ️ 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".
| const spanHours = earliestSnapshot | ||
| ? Math.max(1, Math.ceil((now.getTime() - earliestSnapshot.getTime()) / (60 * 60 * 1000))) | ||
| : 1; | ||
|
|
||
| const uptimePercentage = snapshots.length > 0 ? (snapshotHours / spanHours) * 100 : 0; |
There was a problem hiding this comment.
The uptime calculation can exceed 100% because it divides unique UTC hour buckets by ceil(now - earliestSnapshot) hours. If snapshots span a boundary (for example, two different hour buckets within <1 hour), snapshotHours becomes larger than spanHours, inflating uptimePercentage and downstream reliabilityScore/recommendationScore for that bookmaker. Clamp this metric (or adjust the denominator logic) so uptime cannot be greater than 100.
Useful? React with 👍 / 👎.
| const marketEfficiency = clamp( | ||
| 100 - | ||
| marginVsConsensus * this.EFFICIENCY_MARGIN_MULTIPLIER - | ||
| outlierFrequency * this.EFFICIENCY_OUTLIER_WEIGHT, | ||
| 0, |
There was a problem hiding this comment.
Avoid defaulting missing line comparisons to perfect efficiency
When there are no comparable line samples (e.g., no eligible consensus/current odds pairs), marginVsConsensus and outlierFrequency remain 0, which makes marketEfficiency evaluate to 100. That gives inactive or data-sparse books artificially strong efficiency/reliability and can materially distort ranking outputs even though the bookmaker had no usable market data in the lookback window.
Useful? React with 👍 / 👎.
…ces, routes, and frontend page (#49) * feat(auth): implement session-based authentication with OAuth support - 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. * Harden authentication security (#28) * 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. * Improve type safety in bet service and route handlers (#27) * 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> * feat: update bump hashes and enhance bump script documentation * fix(#15): Fix three critical correctness bugs in odds and settlement 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. * Add unified `get_scoreboard` tool with `LEAGUE_SPORT_MAP` to eliminate 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> * feat: implement automated version bump system with changelog updates and file hashing * feat: update version numbers and changelogs for backend, frontend, and MCP; enhance changelog update script * feat: streamline changelog update process by consolidating version header and unreleased section * fix: extend outcome resolution window to 12h and add ESPN status type 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> * fix: Clean up auth-session.middleware.ts and fix async/await in auth.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. * fix: correct changelog bump order and update backend changelog for Issue #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 frontend and MCP changelogs with recent fixes * fix: update package versions and changelogs for backend, frontend, and MCP * fix: Correct odds and settlement logic bugs (Issue #17) (#35) * 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 * Add E2E tests for complete bet lifecycle (#34) * 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> * ci: add 'dev' branch to CI/CD test pipeline - 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 * fix: revert package versions and clean up changelogs for backend, frontend, and MCP * chore: update version to 0.2.10 and enhance CI/CD pipeline for 'dev' branch triggers * fix: address input validation and authorization gaps (Issue #18) - 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 * fix: improve CI/CD test reliability and API sync robustness - 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: improve CI/CD test reliability and API sync robustness (Issue #18) - 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: resolve frontend TypeScript test compilation 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 * docs: update frontend changelog with TypeScript test fixes - Document BetSlip.test.tsx import fixes - Document clvSlice.test.ts async thunk typing improvements - Document test-utils.tsx Redux store typing enhancements * feat: implement data retention policies and cleanup jobs (Issue #19) - 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 * Remove auth dead code and consolidate session/OAuth middleware (#36) * 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> * perf: optimize getStats() to use database aggregation (Issue #20) (#39) 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. * fix: add session-store and auth-session mocks to failing test suites 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) * fix: add @testing-library/dom dev dependency to frontend @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. * security: stop exposing PostgreSQL port in production Docker config (Issue #21) (#40) Change ports: ['5432:5432'] to expose: ['5432'] in docker-compose.prod.yml so the database is only accessible within the Docker network, not from the host or external network. * fix: resolve TypeScript build errors for Sport type and missing redis dependency - Import Sport type and cast firstLegSport in bet.service.ts to fix TS2345 (string not assignable to Sport) - Add redis package to backend dependencies so session-store.service.ts compiles (TS2307) * test+docs: add CLV service tests and API documentation (Issue #3) (#41) - 21 unit tests for CLV service: calculation accuracy, closing line capture, per-bet CLV, report generation, edge cases - Added Analytics CLV API section to API-DOCUMENTATION.md with all 7 endpoints documented (summary, by-sport, by-bookmaker, trends, report, calculate, update-stats) * feat: add wiki documentation standards and new page scaffold instructions * feat: update changelogs and package versions for backend and frontend; add wiki documentation instructions * feat: add GitHub roadmap setup script for milestones, labels, and issues - Created a new script `setup-github-roadmap.sh` to automate the setup of GitHub milestones and issues for the BetTrack project. - The script creates two milestones: "Phase: Now" and "Phase: Next". - Added eight labels for categorizing issues. - Created eleven new issues for the current and next phases with detailed descriptions and acceptance criteria. - Updated five existing issues with the appropriate milestones and labels. - Closed one completed issue related to CLV Tracking. * Refactor UI components for consistency and improved styling - Updated BetHistory, CLVAnalytics, EnhancedDashboard, and Stats pages to use new design tokens and typography. - Replaced inline styles with Tailwind utility classes for better maintainability. - Enhanced loading states and empty states with improved visual elements. - Introduced new color tokens for brand and semantic states in Tailwind configuration. - Added custom text shadow utilities for a retro pixel aesthetic. * docs: update unreleased changelog sections with recent changes Adds entries for UI component refactor, GitHub roadmap setup script, project/API docs, OpenAPI specs, and CLV service unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add unit tests for various components and hooks - Implement tests for Validation Middleware to ensure proper request validation and error handling. - Create tests for ErrorBoundary component to verify error handling and fallback UI. - Add tests for ProtectedRoute component to check authentication and loading states. - Write tests for OddsCell component to validate rendering and interaction. - Implement tests for SportFilter component to ensure correct button rendering and state management. - Add tests for DarkModeContext to verify dark mode functionality and localStorage integration. - Create tests for PreferencesContext to validate user preferences and localStorage updates. - Implement tests for useBetSlip hook to ensure correct bet slip management and API interactions. - Add tests for useGameStats hook to validate game stats fetching and error handling. - Create tests for API service to ensure correct axios configuration and URL resolution. - Update format utility tests to handle date formatting correctly. * feat: enhance bet handling and validation - Updated bet query schema to use z.coerce for limit and offset, ensuring proper number coercion and bounds. - Modified bet update logic to use updateMany with ownership checks, improving security against concurrent modifications. - Implemented recovery sweep in OutcomeResolverService to finalize pending bets with settled legs, preventing stuck states. - Enhanced OAuthService to require email verification before linking accounts, improving security against account hijacking. - Improved error handling in session store service for Redis session clearing. - Updated API key routes tests to reflect new validation responses and stricter input checks. - Refactored DarkModeContext and PreferencesContext tests to directly spy on window.localStorage for better isolation. - Adjusted Vite config for improved test isolation and memory management. * fix: improve health check route and enhance CLV service data handling * test: update ProtectedRoute tests to use render and improve mock handling build: adjust Vite config to optimize memory usage during tests * fix: adjust console log for odds boost visibility in development * chore: update version numbers and changelogs for backend and frontend * feat: enhance AdminSettings and Futures components with data sync functionality * feat: add baseball SVG graphic for sports dashboard * fix: correct baseball image path in SPORT_CONFIG * fix: update bet deletion response to return 204 status and adjust test mocks * fix: update mocked API type definition in useBetSlip and useGameStats tests * chore: update package versions and changelogs for backend and frontend * feat: implement multi-origin support for OAuth redirects and update related tests * fix: add missing Prisma migration for three schema indexes to improve query performance * chore: remove duplicate entry for version 0.2.15 in changelog * fix: improve BetCard styling for dark mode and adjust padding and max height * fix: enhance BetCard flip animation and improve main content overflow behavior * Update documentation and configuration for BetTrack project - Revised agent instructions to clarify off-limits documentation areas. - Updated AVAILABLE-TOOLS.md to reflect the correct project name and tool count. - Fixed links in Backend-Guide.md to include proper file extensions. - Created Configuration-Reference.md detailing environment variables for the MCP Server and Dashboard. - Updated Dashboard-Guide.md to correct links to other guides. - Modified Installation-Guide.md for accurate repository links and installation instructions. - Corrected links in MCP-Server-Guide.md to ensure proper navigation. - Updated Quick-Start.md with correct repository and version information. - Revised README.md to reflect new documentation structure and planned files. - Added Troubleshooting.md to address common issues with the MCP Server and Dashboard. - Updated home.md to ensure all links point to the correct markdown files. - Modified manifest.json to reflect accurate author information and repository links. * feat: add Bookmaker Disagreement Detection feature - Implemented Phase 1 of the Bookmaker Disagreement Detection analytics feature. - Added new types for disagreement data in `disagreement.types.ts`. - Created `HighDisagreementGames` component to display top games with high bookmaker disagreement. - Developed `DisagreementBreakdown` modal for detailed consensus analysis per game. - Introduced `ValueOpportunities` page for filtering and sorting games based on disagreement scores. - Updated routing in `App.tsx` to include the new analytics page. - Enhanced UI with auto-refresh functionality for the disagreement games widget. * feat: implement baseball game state tracking with inning and count details * feat: add team stats lookup by league and team name, update routing and components accordingly * feat: add team stats lookup endpoint by league and team name Co-authored-by: Copilot <copilot@github.com> * feat: update version numbers and changelogs for backend, frontend, and MCP with Bookmaker Disagreement Detection feature * feat: read backend version from API and update footer to display it * feat: Add Line Movement Analytics feature with SteamMoveAlert component - Implemented SteamMoveAlert component to display live steam moves in a dashboard widget. - Created LineMovementAnalytics page to serve as the main analytics dashboard for viewing line movements. - Developed line-movement.service.ts for API calls related to line movement detection and analysis. - Added movementSlice for Redux state management of line movements, including fetching live movements and statistics. - Introduced types for movements in movements.types.ts to ensure type safety across the application. - Updated store configuration to include movementSlice for centralized state management. * feat: Enhance line movement service with error handling and logging for movement retrieval * fix: remove invalid eslint rule disable comment from admin routes * fix: resolve all frontend TypeScript type errors for Phase 1 completion - Add 'movementType' property to MovementFilters interface (replaces 'type') - Make averageMovement and maxMovement accept both number and string types (from API) - Convert string averageMovement to number before passing to getSeverityColor() - Fix Redux store configuration in test-utils.tsx to include movements reducer - Update LineMovementAnalytics to import MovementType for type casting - Update movementSlice initial state to use 'movementType' instead of 'type' All 8 type errors resolved: - SteamMoveAlert.tsx lines 151, 177, 184, 196, 217 (number/string conversion) - LineMovementAnalytics.tsx line 50 (movementType property) - movementSlice.ts line 45 (movementType property) - test-utils.tsx lines 32, 57 (Redux reducer configuration) * fix: group line movement snapshots by bookmaker for accurate detection Problem: Odds snapshots are written per bookmaker/market per sync cycle. Consecutive snapshots in marketSnapshots are usually different bookmakers from the same sync (e.g., DraftKings h2h, FanDuel h2h). Comparing these snapshots with calculateLineChanges only finds overlapping bookmaker keys, so when comparing different bookmakers, no overlap exists and movements are missed entirely (unless the same bookmaker happens to be adjacent). Solution: Replace groupSnapshotsByMarket() with groupSnapshotsByMarketAndBookmaker() to group snapshots by BOTH market type AND bookmaker (key: 'h2h:draftkings'). This ensures: - DraftKings h2h snapshots at T1 and T2 are compared together - FanDuel h2h snapshots at T1 and T2 are compared separately - Each bookmaker's line movements are detected correctly - Multi-bookmaker movements in the same sync cycle are now properly detected Impact: Line movement detection now correctly identifies when individual bookmakers move their lines, enabling accurate steam move classification and analytics. * fix: allow All Types filter to fetch all movement types from backend Problem: When Movement Type filter was set to 'All Types', the dispatch was converting movementType from 'all' to 'steam', which hid reverse, gradual, injury, and normal movements from the page. This occurred even though the backend /analytics/movements/live route fully supports movementType=all to return all movement types. Root Cause: Line 50 in LineMovementAnalytics.tsx was explicitly converting: movementType === 'all' ? 'steam' : movementType This meant users selecting 'All Types' would only see steam moves, defeating the purpose of the 'all' option. Solution: Pass movementType as-is to the API without conversion: movementType: movementType as MovementType | 'all' Now when users select 'All Types', the page correctly fetches movements with movementType=all parameter, showing the complete dataset including reverse, gradual, injury, and normal movements alongside steam moves. Impact: - 'All Types' now displays all movement classifications - Default page view shows complete movement data - Users can see the full spectrum of line movements for better analysis * fix: correct American odds comparators to identify favorable prices Problem: The comparators for h2h, spreads, and totals were using conditional logic that sorted negative American odds in ascending order, putting more negative (worse) prices first. For example, -150 would sort before -105, causing bestValue to point to the worst available price instead of the best. Root Cause: The conditional comparators checked if the price was positive: a.price > 0 ? b - a : a - b This resulted in: - Positive odds: sorted descending (correct: +130 before +110) - Negative odds: sorted ascending (WRONG: -150 before -105) Mathematical Principle: For American odds, the favorable line is simply the numerically HIGHEST price, regardless of sign: Positive: +130 > +110 (more profit potential) Negative: -105 > -150 (less risk, better probability) Solution: Use simple descending numerical sort for all price fields: .sort((a, b) => b.price - a.price) // highest first This correctly identifies best value: - h2h bestHomeOdds: now selects -105 when choosing between -150, -105 - spreads bestOdds: correctly identifies least negative or most positive price - totals bestOver: properly ranks over prices by numeric value Impact: bestValue entries now accurately point to the most favorable odds available in the market, enabling correct value detection and improving betting recommendations. * fix: resolve ESLint no-unexpected-multiline errors Move array accessor [0] to same line as sort() call to fix: - Line 156: bestHomeOdds assignment - Line 201: bestOdds (spreads) assignment - Line 249: bestOver (totals) assignment ESLint no-unexpected-multiline rule requires property access to be on same line as the preceding method/call to avoid ambiguity in parsing. * fix: enable steam move detection by analyzing multi-bookmaker sync batches Critical Issue: The previous grouping fix by bookmaker timeline meant each call to analyzeMovement received only singleton arrays [before] and [after], both containing exactly one bookmaker. This made: - bookmakerCount always = 1 - classifyMovement steam threshold (>= 3) unreachable - Only 'normal' movements persisted; steam alerts never generated Root Cause: Comparing individual bookmaker timelines instead of analyzing snapshot batches from the same sync cycle. In reality, when Odds API syncs, all bookmakers are updated in one cycle (T1), then another cycle occurs (T2). To detect steam, we need to compare T1's full batch (all books) vs T2's full batch to count how many books moved. Solution: Change grouping strategy from 'market:bookmaker' to 'market:timestamp' 1. Group snapshots by market type and capturedAt timestamp 2. Get unique sync times in chronological order 3. Compare consecutive sync cycles: T1 batch vs T2 batch 4. Pass all bookmakers from each cycle to analyzeMovement 5. buildLineSnapshot now contains all bookmakers from that sync 6. calculateLineChanges finds overlapping bookmakers correctly 7. analyzeMovement counts actual bookmakerCount (can be 3+) 8. classifyMovement can now properly classify as steam when >= 3 books move Example Flow: T1: [DraftKings h2h, FanDuel h2h, BetMGM h2h] (sync cycle 1) T2: [DraftKings h2h, FanDuel h2h, BetMGM h2h] (sync cycle 2) → Compare all 3 bookmakers together → If 2+ moved same direction, count as 2+ movement → Can now classify steam (3+ books moving in sync window) Impact: - Steam move detection now works as designed - Multi-bookmaker movements properly classified - Alerts correctly generated for betting value opportunities - Feature achieves original specification * fix: update team detail links in EnhancedGameCard to match new route Problem: After updating the team detail route from /teams/:teamName to /teams/:league/:teamName, EnhancedGameCard was still using the old single- segment URL format for team links: /teams/${game.awayTeamName} /teams/${game.homeTeamName} These links no longer matched any route, causing clicks to land on a 404 page instead of the team detail view. Solution: Update both team links in EnhancedGameCard to include the sport key (league) parameter, matching the format used in GameCard: /teams/${encodeURIComponent(game.sportKey)}/${encodeURIComponent(game.awayTeamName)} /teams/${encodeURIComponent(game.sportKey)}/${encodeURIComponent(game.homeTeamName)} Impact: Team links in EnhancedGameCard now correctly navigate to team detail pages. Both GameCard and EnhancedGameCard use consistent link format. * docs: update Unreleased changelog sections with Phase 1 bug fixes * chore: update package versions and changelogs for line movement detection features * fix: prevent duplicate LineMovement rows from overlapping detection windows Problem: detectMovements(gameId, 10) fetches a 10-minute lookback window and persists every movement it finds. With the job running every 5 minutes, the 5-minute overlap means any snapshot pair in two consecutive windows is inserted twice — inflating movement counts and steam alerts even when no new odds change occurred. Solution: - Job tracks lastRunAt (set only after a fully successful run) - Passes lastRunAt as sinceTime to detectMovements - Service keeps the wider lookback window so there is always a 'before' batch available for comparison - Skips persist for any snapshot pair whose afterTime <= sinceTime (already processed by the previous run) On first run (lastRunAt = null) all movements in the window are persisted as before. On subsequent runs only genuinely new snapshot transitions are saved. * fix: update version to 0.3.1 and document line movement job fixes in changelog * fix: base live disagreements on latest consensus row per game+market Problem: findHighDisagreement queried all MarketConsensus rows from the last 30 minutes where disagreementScore >= threshold. Since the job inserts a new row every 15 minutes, a market that scored high at T+0 but fell below the threshold at T+15 continued appearing in /analytics/disagreement/live until the T+0 row aged out. The same game+market could also appear twice (both rows in window, both above threshold at slightly different scores). Fix: two-step query 1. groupBy(gameId, marketType) with _max: calculatedAt across a 2-hour lookback to find the single latest row per game+market pair 2. Fetch only those specific rows and apply the threshold Threshold is now evaluated exclusively on the current (latest) consensus value, eliminating both stale visibility and duplicate entries. * fix: always fetch full game breakdown in DisagreementBreakdown modal Problem: When opening the breakdown modal from the live disagreement list, the component reused game.consensus from the parent HighDisagreementGame object. That object only contains markets that passed the live-list disagreementScore threshold, so any market below the threshold was silently omitted from the modal even though the /game/:gameId endpoint can return it. Fix: Remove the game.consensus short-circuit. Always fetch from /analytics/disagreement/game/:gameId, which returns the latest consensus row per market type with no score filter applied. * fix: require coordinated direction for steam move classification Problem: analyzeMovement pooled all per-bookmaker changes by absolute value before calling classifyMovement. A split market (e.g. 2 books +1.5, 2 books -1.5 on a spread) produced bookmakerCount=4 and a large avgMovement, easily clearing the steam threshold (>=3 books, >=1.5pt, <2 min) even though no coordinated movement occurred. Fix: partition movers by sign before computing metrics for classifyMovement. The dominant direction group (most books; ties broken by larger avg abs change) provides bookmakerCount and avgMovement. maxMovement still covers all movers. Steam now requires >=3 books moving in the same direction. * fix: give SteamMoveAlert its own Redux state field to stop clobbering page filter Problem: fetchSteamMoves.fulfilled wrote to state.liveMovements, the same field that fetchLiveMovements.fulfilled uses for the page-level filter results. When SteamMoveAlert mounted or auto-refreshed alongside LineMovementAnalytics, it overwrote liveMovements with steam-only results, so All Types / reverse / gradual / injury filter selections were silently replaced every 60 seconds. Fix: - Add steamMoves: LineMovement[] field to MovementState and initialState - fetchSteamMoves.fulfilled writes to state.steamMoves only - Add selectSteamMoves selector - SteamMoveAlert reads from selectSteamMoves instead of selectLiveMovements - state.liveMovements is now exclusively owned by fetchLiveMovements * fix: compare both sides in bestValue detection for all market types Problem: calculateConsensus hardcoded bestValue to report only one side per market type: - h2h: always side='home', compared only homePrice - spreads: always side='home', compared only homeSpreadPrice - totals: always side='over', compared only overPrice If the away/under side had better odds, the API still reported the other side, directing the UI to inferior prices or missing best-value opportunities entirely. A mispriced away moneyline or under was never surfaced. Fix: For each market type, compare both sides and pick whichever has the numerically higher odds (most favorable for bettor): - h2h: compare bestHomeOdds vs bestAwayOdds - spreads: compare bestHomeSpreadPrice vs bestAwaySpreadPrice - totals: compare bestOverPrice vs bestUnderPrice - side field now correctly reflects where actual best value is * docs: update root changelog with Phase 1 bug fixes summary * chore: bump backend and frontend versions to 0.3.2 and 0.4.1, respectively; update changelogs with recent fixes * fix: exclude in-progress/completed games from live disagreement list Problem: The consensus job only calculates rows for 'scheduled' games before kickoff. Once a game starts (status = 'in_progress'), new consensus rows stop being created. But the last pregame consensus row can still fall within the 2-hour lookback window, so /analytics/disagreement/live and the Value Opportunities page continue to advertise the stale pregame opportunities for games that have already started or completed. Fix: Add game.status = 'scheduled' filter to findHighDisagreement query so only pregame consensus rows are returned. In-progress and completed games with old consensus rows are now correctly filtered out. * fix: enable gradual line movement detection in scheduled job Problem: The line movement job called detectMovements with a 10-minute lookback, but classifyMovement classifies a move as 'gradual' only when timeElapsed > 3600 seconds (1 hour). Since every snapshot pair bounded by 10 minutes had timeElapsed < 3600, gradual movements could never be persisted. The gradual filter remained empty in the API and UI. Fix: Increase lookback to 120 minutes (2 hours) to capture slow drift over extended periods. The sinceTime cursor mechanism prevents duplicate persistence on overlapping 5-minute runs. * chore: bump backend version to 0.3.3; update changelog with gradual line movement detection fix * fix: add gradual-detection pass comparing oldest vs newest sync batch Problem: Even with the 120-minute lookback, detectMovements only compared consecutive sync batches (~10 min apart, ~600 s timeElapsed). The classifyMovement 'gradual' threshold requires timeElapsed > 3600 s, so gradual moves could never be detected during normal operation. The gradual filter/UI remained effectively empty unless an hour of snapshots was missing. Fix: Add a second detection pass (Pass 2) that compares the oldest batch in the lookback window directly against the newest batch, giving timeElapsed ~7200 s across the full 120-min window. Persistence is restricted to movementType === 'gradual' results only, preventing double-persistence of steam/normal for the boundary pair. The sinceTime cursor is respected to prevent duplicate rows on overlapping runs. * chore: bump backend version to 0.3.4; update changelog with gradual line movement detection fix * fix: guard consensus by commenceTime; align steam threshold with sync interval Bug 1: findHighDisagreement filtered game.status = 'scheduled' but did not bound commenceTime. When the status job is delayed after kickoff, the game remains 'scheduled' in the database. Its last pregame consensus row can appear in /analytics/disagreement/live until the status is updated or the 2-hour lookback expires. Added game.commenceTime > now() alongside the status filter so games past their start time are always excluded. Bug 2: classifyMovement required timeElapsed < 120 s for steam detection, but the odds sync job runs every 10 minutes by default (~600 s between consecutive batches). Every coordinated multi-book move detected between normal sync cycles failed the 120 s check and was classified as 'normal'. Relaxed threshold to < 900 s (one 10-min sync window + 5-min buffer) so steam moves are correctly identified under standard deployment settings, while still falling below the gradual threshold (> 3600 s). * fix: sync MovementPerformance timeframe with hoursBack prop useState(hoursBack) only sets the initial value; prop changes after mount were ignored. When the parent page updated hoursBack (e.g. 24h -> 7d), the sidebar kept dispatching fetchMovementStats for the old window while the chart showed the new range, presenting mismatched totals. Added useEffect(() => { setTimeframe(hoursBack); }, [hoursBack]) to propagate prop changes into the internal timeframe state. * chore: bump backend version to 0.3.5; update changelog with fix for stale pregame opportunities chore: bump frontend version to 0.4.2; update changelog with fix for movement stats timeframe sync * fix: include away prices in h2h moneyline disagreement scoring 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. * chore: bump backend version to 0.3.6; update changelog for h2h moneyline disagreement fix * Update OpenAPI documentation and enhance dashboard integration - 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. * [Phase 2] Sharp vs Public Money Indicators (#44) * 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> * feat: update version to 0.3.0 in manifest and package files; add build and docker build scripts * chore: release version 0.3.8 for backend and 0.4.3 for frontend - 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 * ci: add docker-publish workflow and fix GHCR image paths - 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). * Document coverage gaps and prioritize backend/frontend test expansion (#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> * feat: add admin_settings table and update build script to bump versions before builds * docs: update API documentation and developer guide for clarity and new features * [Phase 2] Add market consensus metrics, dispersion analytics, and outlier 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> * Add Phase 2 bookmaker performance analytics models and service (#48) * 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> * feat: add Bookmaker Performance Analytics page - 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. * feat: implement team synchronization for multiple sports in StatsSyncService * feat: enhance team synchronization for all sports and update Docker setup for Prisma client * feat: enhance MarketConsensusService to filter records by participating game IDs * Add integration tests for Analytics Sharp Money Routes and improve error 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. * Add unit tests for various services and enhance bookmaker analytics tests - 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. * feat: update averageCLVOffered to null and add error handling for missing bookmaker data --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <copilot@github.com>
This PR introduces Phase 2 bookmaker analytics by adding persistent bookmaker-level metrics and first-mover event tracking, then wiring core backend computation/ranking logic for value, sharpness, and reliability analysis.
Data model: bookmaker analytics + movement leadership
BookmakerAnalyticsmodel to store per-book recommendations and performance signals:averageCLVOffered,bestOddsFrequency,marginVsConsensus,outlierFrequency)firstMoverFrequency,lineMovementLag,sharpBookRating,marketEfficiency)BookmakerMovementEventmodel for first-mover detection (firstMover,firstMoveTime, follower lag JSON, movement size).BookmakerMovementEventtoGameand added supporting indexes.Migration
bookmaker_analyticsbookmaker_movement_eventsgames.Backend service implementation
BookmakerAnalyticsService:calculateBookmakerMetrics(bookmaker)computes composite metrics fromcurrentOdds,marketConsensus, movement events, and snapshot cadence; persists via upsert.rankBookmakers(criteria)returns ranked books forvalue,sharpness,reliability,coverage,limits, and defaultrecommendation.Docs + changelog alignment
Example (service API)
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
api.the-odds-api.com/usr/local/bin/node node /home/REDACTED/work/BetTrack/BetTrack/dashboard/node_modules/.bin/jest --runInBand(dns block)/usr/local/bin/node node /home/REDACTED/work/BetTrack/BetTrack/dashboard/node_modules/.bin/jest --runInBand git conf�� get --global git HooksPath(dns block)checkpoint.prisma.io/usr/local/bin/node /usr/local/bin/node /home/REDACTED/work/BetTrack/BetTrack/dashboard/backend/node_modules/prisma/build/child {"product":"prisma","version":"5.22.0","cli_install_type":"local","information":"","local_timestamp":"2026-05-14T06:17:06Z","project_hash":"b7a1562d","cli_path":"/home/REDACTED/work/BetTrack/BetTrack/dashboard/backend/node_modules/prisma/build/index.js","cl(dns block)/usr/local/bin/node /usr/local/bin/node /home/REDACTED/work/BetTrack/BetTrack/dashboard/backend/node_modules/prisma/build/child {"product":"prisma","version":"5.22.0","cli_install_type":"local","information":"","local_timestamp":"2026-05-14T06:17:14Z","project_hash":"b7a1562d","cli_path":"/home/REDACTED/work/BetTrack/BetTrack/dashboard/backend/node_modules/.bin/prisma","cli_path_has(dns block)/usr/local/bin/node /usr/local/bin/node /home/REDACTED/work/BetTrack/BetTrack/dashboard/backend/node_modules/prisma/build/child {"product":"prisma","version":"5.22.0","cli_install_type":"local","information":"","local_timestamp":"2026-05-14T06:22:23Z","project_hash":"b7a1562d","cli_path":"/home/REDACTED/work/BetTrack/BetTrack/dashboard/backend/node_modules/.bin/prisma","cli_path_has(dns block)If you need me to access, download, or install something from one of these locations, you can either: