Skip to content

Add Phase 2 bookmaker performance analytics models and service#48

Merged
WFord26 merged 5 commits into
devfrom
copilot/phase-2-bookmaker-analytics
May 19, 2026
Merged

Add Phase 2 bookmaker performance analytics models and service#48
WFord26 merged 5 commits into
devfrom
copilot/phase-2-bookmaker-analytics

Conversation

Copilot AI commented May 14, 2026

Copy link
Copy Markdown
Contributor

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

    • Added BookmakerAnalytics model to store per-book recommendations and performance signals:
      • value quality (averageCLVOffered, bestOddsFrequency, marginVsConsensus, outlierFrequency)
      • market-making behavior (firstMoverFrequency, lineMovementLag, sharpBookRating, marketEfficiency)
      • reliability/coverage/limits/reputation fields
    • Added BookmakerMovementEvent model for first-mover detection (firstMover, firstMoveTime, follower lag JSON, movement size).
    • Linked BookmakerMovementEvent to Game and added supporting indexes.
  • Migration

    • Added Prisma migration creating:
      • bookmaker_analytics
      • bookmaker_movement_events
    • Includes unique/index constraints and FK to games.
  • Backend service implementation

    • Added BookmakerAnalyticsService:
      • calculateBookmakerMetrics(bookmaker) computes composite metrics from currentOdds, marketConsensus, movement events, and snapshot cadence; persists via upsert.
      • rankBookmakers(criteria) returns ranked books for value, sharpness, reliability, coverage, limits, and default recommendation.
    • Scoring constants were centralized to avoid hardcoded weighting/threshold drift.
  • Docs + changelog alignment

    • Updated backend changelog with schema/service additions.
    • Updated wiki database guide with new Prisma models and key semantics.
  • Example (service API)

    const analytics = await bookmakerAnalyticsService.calculateBookmakerMetrics('draftkings');
    const sharpRankings = await bookmakerAnalyticsService.rankBookmakers('sharpness');

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
    • Triggering command: /usr/local/bin/node node /home/REDACTED/work/BetTrack/BetTrack/dashboard/node_modules/.bin/jest --runInBand (dns block)
    • Triggering command: /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
    • Triggering command: /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)
    • Triggering command: /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)
    • Triggering command: /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:

Copilot AI linked an issue May 14, 2026 that may be closed by this pull request
10 tasks
Copilot AI changed the title [WIP] Add bookmaker performance analytics feature Add Phase 2 bookmaker performance analytics models and service May 14, 2026
Copilot AI requested a review from WFord26 May 14, 2026 06:29
@WFord26 WFord26 marked this pull request as ready for review May 14, 2026 14:53
@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 44.88%
  • Functions: 48.63%
  • Branches: 29.56%
  • Statements: 44.5%

@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: 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".

Comment on lines +90 to +91
prisma.currentOdds.findMany({
where: { bookmaker: normalizedBookmaker },

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 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 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 44.88%
  • Functions: 48.63%
  • Branches: 29.56%
  • Statements: 44.5%

@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: 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".

Comment on lines +147 to +149
const consensusByKey = new Map(
eligibleConsensus.map((row) => [`${row.gameId}:${row.marketType}`, row])
);

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 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;

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 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 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results ✅

Coverage Summary:

  • Lines: 44.92%
  • Functions: 48.55%
  • Branches: 29.59%
  • Statements: 44.53%

@WFord26 WFord26 merged commit efdbac5 into dev May 19, 2026
4 checks passed
@WFord26 WFord26 deleted the copilot/phase-2-bookmaker-analytics branch May 19, 2026 17:40

@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: 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".

Comment on lines +257 to +261
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;

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 Cap uptime percentage at 100%

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 👍 / 👎.

Comment on lines +233 to +237
const marketEfficiency = clamp(
100 -
marginVsConsensus * this.EFFICIENCY_MARGIN_MULTIPLIER -
outlierFrequency * this.EFFICIENCY_OUTLIER_WEIGHT,
0,

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 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 👍 / 👎.

WFord26 added a commit that referenced this pull request May 20, 2026
…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>
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.

[Phase 2] Bookmaker Analytics

2 participants