Take Settlement System (Deprecated)
Status: Deprecated — being replaced with new settlement logic Date documented: April 2026
Take Formula
Player Take
T = balancePoints - creditLimit
Agent Take
T = AC(self) + Σ AC(entire recursive downline) - creditLimit
Where:
- AC(self) = agent user's balancePoints
- AC(recursive downline) = direct players' balancePoints + sub-agent users' balancePoints + players under sub-agents' balancePoints
- creditLimit = agent's creditLimit
Sign Convention (Entity Perspective)
- T > 0 → upline owes entity (entity profited)
- T < 0 → entity owes upline (entity lost)
Known Issue
Take includes exposure from open bets. When a bet is placed, balancePoints decreases by stake/liability but creditLimit stays the same — so Take drops immediately even though nothing has been won or lost yet. Take should only reflect realized P&L.
Initial: balance=100, CL=100, exposure=0 → Take = 0 (correct)
Bet placed: balance=90, CL=100, exposure=10 → Take = -10 (wrong — bet hasn't settled)
Bet loses: balance=90, CL=100, exposure=0 → Take = -10 (correct)
Bet wins: balance=110, CL=100, exposure=0 → Take = +10 (correct)
Storage
- Table:
take_balances - Unique key:
(entityType, entityId) - Fields:
currentTake(Decimal 18,4),lastCalculatedAt,cumulativePnl,settledAmount,uplineAgentId
When Take Updates
- Market settlement (bet outcome changes balancePoints)
- Transfer & Settle (balancePoints adjusted during grace)
- Does NOT update on: bet placement, credit allocation
Propagation
propagateTakeAfterMarketSettlement(userId) — updates upward 2 hops:
- Player's own take
- Direct agent's take
- Parent agent's take (master agent)
Uses Serializable isolation with exponential backoff retry (max 3 attempts).
Batch Computation
batchComputePlayerTakes(userIds[])— single query, returns MapbatchComputeAgentTakes(agentIds[])— single query, returns Map
Settlement Period Lifecycle
States
open → grace → finalized
Configuration
Stored in PlatformSettings (key: settlement_config):
periodDurationHours: default 168 (7 days)gracePeriodMinutes: default 1440 (24 hours)pollIntervalMinutes: default 60 (1 hour)
Open Period
- Bets settle normally, takes accumulate
- No settlement actions allowed
- Period auto-created if none exists
Transition to Grace (Atomic)
- Update period status to
grace, setgraceEndsAt - Snapshot ALL entities' takes into
periodSettlementSnapshottable- All rows created as
Pending,settledAmount = 0 - Takes negated for viewer perspective:
snapshot.take = -entityTake - Carryover amounts populated from N-1 finalization
dueDate = graceEndsAt
- All rows created as
- Create overlapping period N+1 in
openstatus
During Grace
- Transfer & Settle operates on frozen snapshot values (not live takes)
- Live takes still update from ongoing bets (which belong to N+1)
- Carryover requests can be submitted and responded to
Finalization (finalizePeriodStatuses())
For each Pending snapshot:
| Condition | Resolved Status |
|---|---|
| settledAmount >= |take| (within 0.01 tolerance) | Settled |
| Explicit carryover accepted during grace | CarriedOver |
| take > 0 (entity owes viewer) + no action | Defaulted |
| take < 0 (viewer owes entity) + no action | CarriedOver |
Unsettled amounts (Defaulted/CarriedOver) auto-propagate as accepted carryover requests to N+1.
Transfer & Settle
Constraints
- Only during grace period
- Reads from frozen snapshot, not live take
- Blocked if active carryover request exists (pending or accepted)
Direction Logic
- Frozen take > 0 (viewer perspective: "entity owes me") → collect → balance adjustment = +amount
- Frozen take < 0 (viewer perspective: "I owe entity") → pay → balance adjustment = -amount
What Changes (Atomic Transaction)
| Field | Change |
|---|---|
user.balancePoints | Incremented by balance adjustment |
takeBalance.currentTake | Updated to live recalculated take |
snapshot.settledAmount | Incremented by amount |
snapshot.settlementStatus | Set to Settled if fully settled |
snapshot.finalSettlementDate | Set to now if fully settled |
Creates transferSettlement audit record with takeBefore, takeAfter, amount, notes.
Partial Settlement
- Allowed — remainder auto-carries at grace end
- Each partial creates a separate
transferSettlementrecord
Carryover System
Three Flows
1. Downline Requests Carryover
- During grace only
- Creates
CarryoverRequestwith statuspending - Upline accepts or rejects
2. Upline Carry Forward
- During grace only
- Instantly accepted, no approval needed
- Creates accepted carryover for period N
- Auto-propagates to N+1
3. Auto-Carry at Grace End
- Unactioned entities at finalization
- Defaulted (entity owes, no action) → auto-carry to N+1
- CarriedOver (upline owes, no action) → auto-carry to N+1
On Accept
- Update snapshot to
CarriedOver - Set
carryoverAmount - Create auto-accepted
CarryoverRequestscoped to N+1
Period Settlement Snapshots
Table: periodSettlementSnapshot
| Field | Type | Purpose |
|---|---|---|
| periodId | string | Period this snapshot belongs to |
| entityType | string | 'agent' | 'player' |
| entityId | string | Agent.id | User.id |
| entityName | string | Display name at snapshot time |
| accountStatus | string | Account status at snapshot time |
| take | Decimal | Frozen take (negated for viewer perspective) |
| settlementStatus | string | Pending | Settled | CarriedOver | Defaulted |
| settledAmount | Decimal | Cumulative settled during grace |
| carryoverAmount | Decimal | Amount carried over from previous period |
| dueDate | DateTime | Grace period end |
| finalSettlementDate | DateTime | When status was finalized |
Agent Settlement Job
File: agentSettlementJob.ts
Polls every pollIntervalMinutes:
- Ensure period exists → create if missing
now >= endDate→ transition to grace (snapshot + create N+1)now >= graceEndsAt→ finalize statuses → process agent settlements → finalize period
Manual Trigger
triggerSettlementCalculation(periodId) — admin can force agent settlement processing.
Commission Interaction
- Commission deducted from
balancePointsafter market settlement - Reduces upline's take (entity owes less or upline owes more)
- Per-user per-market: only when all of that user's orders in the market are settled
- Only on exchange markets (betfair-ex), not bookmakers (bifrost, pinnacle)
- 2% of net positive market P&L
Endpoints
Admin (authenticated)
GET /settlements/periods— list periodsGET /settlements/periods/current— active periodPOST /settlements/periods— create (optional custom startDate/endDate)PATCH /settlements/periods/:id/extend— extend open period end dateGET /settlements/periods/:id/downline-snapshot— snapshot or live downline
Agent (authenticated)
GET /agent/settlements— settlement historyGET /agent/settlements/current— current period statsPOST /agent/settle-downline— settle downline entity (grace only)POST /agent/carry-forward-downline— upline carries forward (grace only)POST /agent/request-carryover— request carryover from upline (grace only)GET /agent/carryover/pending— pending carryover requestsPOST /agent/carryover/:id/respond— accept/reject carryover
Frontend
Agent Settlements Page
- Period selector (current + history)
- Upline take section
- Downline table with tabs: All, Pending, CarriedOver, Defaulted
- Actions: Transfer & Settle modal, Carry Forward, Request Carryover, Respond to carryover
- Grace period banner when in grace
Admin Settlement Periods Page
- Current period card with extend button
- Period list with status filter
- Configuration panel (duration, grace, poll interval)
TransferSettleModal
- Shows frozen take (grace) or live take
- Direction: "Collecting from" or "Paying to"
- Amount input with remaining calculation
- Partial settlement warning
- displayToFp conversion on submit