Skip to main content

The incomplete vector, pendingunmatched, and channel-agnostic transitions

Incomplete vector at placement (no-guesswork)

The size vector is filled progressively: Betfair's placement response carries only sizeMatched; the remaining / lapsed / cancelled breakdown arrives later via order-sync/stream, and voided at settlement. deriveOrderMatchStatus therefore uses the reconciliation deficit as the completeness signal (ADR-0003):

deficit = requested − (matched + remaining + lapsed + cancelled + voided)
deficit ≈ 0 → vector complete → classify the remainder into its terminal buckets
deficit > dust → the deficit is UNRESOLVED / pending → shown as "pending", NEVER fabricated
deficit < −dust → over-sum → throw FINANCIAL_INTEGRITY

At placement (matched=60, rest unreported): deficit = 40 → "partially matched ₹60 · ₹40 pending". Once sync reports lapsed=40, it becomes "₹40 lapsed". The terminal remainder buckets populate only when the provider actually reports them — this is mandatory, not optional (the user's directive: lossless + no-guesswork, always).

pending is removed as a display state; unmatched covers it

There is no separate pending display status. Both "created/sent, not yet on the book" and "on the book, nothing matched yet" display as unmatched — for the user, both mean the same: your stake isn't matched. (The DB lifecycle status may still distinguish pending/submitted as infra — ADR-0002 — but the derived Display Status collapses them to unmatched.)

Channel-agnostic transition handling

deriveOrderMatchStatus runs identically whether the size vector was last updated by the Betfair stream (ocm/uo), the Betfair REST fallback (listCurrentOrders), or the Bifrost queue — it reads persisted columns, not a channel. The obligation this places on the ingestion side: whichever channel writes the size vector must write the same normalized fields, so the status is stable across a stream→poll failover. (This is the substrate for the later polling→streaming pivot; the status function itself needs no change.)

Lay bets — no change

Verified against nettingEngine.ts (betType: 'back' | 'lay', lay liability −S×(O−1) into the exposure vector, DOMAIN_INTEGRITY throw on unknown betType) and orderService.calcLayLiability (stake × (odds−1)). The size vector is in stake terms for both sides, so deriveOrderMatchStatus is side-agnostic; liability is a derived display figure the product already shows. No status-logic or netting change for lay.