Skip to content

feat(sportsbook): summarize a player's sportsbook bets per currency - #338

Open
okapitula wants to merge 3 commits into
feat/sportsbook-restriction-readfrom
feat/sportsbook-player-summary
Open

okapitula wants to merge 3 commits into
feat/sportsbook-restriction-readfrom
feat/sportsbook-player-summary

Conversation

@okapitula

@okapitula okapitula commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #337. Review that PR first; this one retargets to dev once it merges.

Summary

Adds the backoffice reads a sportsbook player profile and its finding and bet pages need. GET /backoffice/sportsbook/players/{userId}/summary gives per-currency totals for one player. Each reconciliation finding now carries its bet's currency, the findings list can be filtered by userId, and the admin bet detail says whether align can look up the bet's status.

Why

  • Player summary. A player profile could only get sportsbook totals by paging through every bet. The new route returns, per currency: bet count, stake and payout split into cash and promotion-paid, GGR and open exposure, under the sportsbook view grant.
    • With a date range, a stake counts in the period its bet was accepted and a payout in the period it settled. Cancelled bets count in neither.
    • Open exposure is the stake on every accepted, unsettled bet right now, whatever the range.
    • New (user_id, accepted_at) and (user_id, status, settled_at) indexes on sportsbook_bet serve each part of the filter, so a narrow range reads only that range's bets, not the player's whole history.
    • GGR is cash stake minus payout per bet, the same rule the wallet ledger applies: a promotion-paid stake earns nothing and a promotion payout costs in full.
    • The restriction routes now use the same player check as this route.
  • Finding currency. Finding amounts rendered without a currency. The currency is joined from the bet at read time, so findings that already exist get it too. It is null for a finding with no bet, and a refused stake's detail now names its own currency.
  • Finding player filter. Findings could not be narrowed to one player. The list takes an optional userId, backed by a new (user_id, created_at, id) index.
  • Bet status lookup flag. The bet page could not tell in advance that align would be refused. providerStatusLookup is true only when the bound provider is the bet's provider and has a status lookup, which are the same conditions align checks.

Alternatives considered

  • Netting the summary against the wallet ledger for reopened bets. Not done. It would mean working out, per bet, which part of each shortfall reached the ledger as an offset or collect, and in which period. The summary reads only each bet's latest settlement. For a bet that was accepted and never reopened the two agree. For a reopened bet, a reversal left as debt or written off makes the ledger GGR lower than the summary's (ADR-0047), and the debt list shows what is owed.
  • Storing currency on the finding row. Rejected: that would need a backfill. Joining from the bet covers existing findings with no data migration.

Risks

  • One new betting migration with three plain CREATE INDEX statements, each locking writes on its table while it builds.
    • The index on sportsbook_reconciliation_finding is cheap; that table holds only reconciliation findings.
    • The two on sportsbook_bet block bet placement and settlement for the build time. On a large bet table, run the migration in a quiet window.
  • All contract changes are additive: one new route, two new output fields on existing routes, and one optional input. Existing consumers are unaffected.

@okapitula
okapitula marked this pull request as ready for review October 21, 2026 10:45
@okapitula
okapitula added this pull request to stack #339 October 21, 2026 10:47
@okapitula
okapitula force-pushed the feat/sportsbook-player-summary branch from 24b0a2f to ce6c680 Compare October 21, 2026 12:51
@okapitula
okapitula force-pushed the feat/sportsbook-player-summary branch from ce6c680 to 9e20e27 Compare October 21, 2026 14:14
@okapitula
okapitula force-pushed the feat/sportsbook-player-summary branch from 9e20e27 to d77cd10 Compare October 21, 2026 18:57
@marek-chmielowski-blurify

Copy link
Copy Markdown
Collaborator

packages/core/src/sportsbook/betting/service/bet-query.service.ts:200 — The summary filters by accepted_at and settled_at, but the available indexes only start with (user_id, created_at), so narrow date-range requests still scan the player’s full betting history. Add indexes such as (user_id, accepted_at) and (user_id, settled_at), or split the aggregate into indexable queries.

@okapitula

Copy link
Copy Markdown
Collaborator Author

@marek-chmielowski-blurify Added (user_id, accepted_at) and (user_id, status, settled_at) on sportsbook_bet in 8510f17, so each arm of the summary filter (accepted in range, settled in range, open) uses an index; the planner now combines the three index scans instead of reading the player's whole bet history.

A backoffice player profile needs a player's sportsbook totals without paging through every bet. GET /backoffice/sportsbook/players/{userId}/summary returns, per currency, bet count, stake and payout split into cash and promotion-paid, GGR and open exposure, under the sportsbook view grant.

With a date range a stake counts in the period its bet was accepted and a payout in the period it settled; cancelled bets count in neither. Open exposure is the stake on every accepted, unsettled bet now, whatever the range. GGR is cash stake minus payout, the wallet ledger's rule applied per bet, so a promotion-paid stake earns nothing and a promotion payout costs in full. Totals are summed in SQL from the bet rows as they stand now.

The restriction routes now share this route's player check.
…tus lookup flag

Backoffice finding and bet pages lacked three reads. Finding amounts rendered without a currency, findings could not be narrowed to one player, and the bet page could not tell in advance that align would be refused for a provider with no bet status lookup.

Each finding now carries its bet's currency, joined from the bet at read time so existing findings get it too; it is null for a finding with no bet, and a refused stake's detail now names its own currency. The findings list takes an optional userId filter, backed by a (user_id, created_at, id) index. The admin bet detail carries providerStatusLookup, true only when the bound provider is the bet's provider and has a status lookup, the same conditions align checks.
@okapitula
okapitula force-pushed the feat/sportsbook-player-summary branch from a948bfd to 8fe8f5c Compare October 26, 2026 09:11
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.

3 participants