Sent with QoS 1, not retained.
Sent exactly once per final round, at the same time as blackjack/round_over. Whereas
that topic carries the full, blackjack-specific table state, this is a compact, derived
summary meant to be forwarded to a casino management system. It is
game-agnostic: the same topic and shape describes a Blackjack, Baccarat or UTH round,
so one integration covers a mixed floor. Anything that classifies cards — the dealer
hand, hand values, split and double counts — is per-game detail and stays on
blackjack/round_over.
We pre-compute the main-bet win/loss, side bet uptake, participation and timing so you do
not have to re-implement the game rules. Side bets are reported here as uptake — how many
could place each one and how many did. What they paid is on the game's own summary
(blackjack/summary), since working out a side bet's outcome takes the game's rules.
Units and sign. No field is currency: we detect bet presence and relative size,
never chip denomination, so every money field is a multiple of the bet it settles and
the initial bet is 1.0 by definition. Every money field is net and player-positive
(-1 loss, 0 push, +1 win, -2 a lost doubled hand, and the table's blackjack payout on a
natural: +1.5 at 3:2, +1.2 at 6:5), so nothing in the
payload ever flips sign; the house result for a round is -total_payout_multiplier and
its hold is -total_payout_multiplier / total_bet_units. Every money field here settles
a main bet — side-bet money is in units of the side bet and never mixes with these.
Optional blocks. What a table reports depends on what it senses. Participation and
timing are always there; settlement (and the matching BoxResult.settlement) is
absent on a table that watches bets and card presence without reading the cards, and
timing.phases is absent when the phases inside the game are not tracked. An absent
block means we never measure it — it is not a zero, and it is not the same as
BoxSettlement.settled == False, which is a measurement that failed on this round.
Counting rounds. Round volume is the summaries with active_boxes > 0. Revenue is
the narrower set with settlement is not None and settlement.status != "UNSETTLED" —
do not use the revenue filter to count rounds, or a participation-only table reports
none. Watch sequence to notice rounds you never received: while we are disconnected
from the broker messages are dropped rather than queued, so a gap there means your
totals are incomplete.
See spec.CardGameSummary for the full field documentation, and blackjack/summary
for the same round with the cards, the dealer's hand and every side bet result.
A finished round, in terms every table game shares.
schema_version
SCHEMA_VERSION at the time of publishing. It is bumped when a change could break a
strict schema validator.
sequence
Increments by one per published summary since the tracking system started. Messages are
dropped rather than queued while we are disconnected from the broker, so a jump means
rounds were lost and totals computed from this stream are incomplete. A decrease means
the system restarted.
round_id
The round UUID; it matches the id used on the other topics.
game_type / machine_id / table_name
What was played and where. machine_id identifies the system, table_name the table it
watches (the same name the heartbeat publishes). game_type is free-form, see
GameType.
gaming_day
The gaming day (ISO date) this round belongs to, using the table's configured rollover
time. Casinos bucket revenue by this, not by calendar date.
timing
The round's span and per-phase durations, see RoundTiming.
boxes
Per-box participation and main-bet result, only for boxes that were in play, see
BoxResult.
side_bets
Uptake per configured side bet — how many could place it and how many did, see
SideBetUtilization. What each one paid is on the game's own summary topic.
settlement
The round's main-bet outcome, or None on a table that does not evaluate results, see
RoundSettlement. Count round volume with active_boxes > 0, and count revenue with
settlement is not None and settlement.status != "UNSETTLED" — the two filters are not
the same, and applying the revenue one to volume would discard every round from a
participation-only table.
bet_detection_available
Whether this table can see bets at all. When False, played is always 0 and says
nothing about what players did — do not report it as 0% participation. It reflects
whether a bet sensor is configured, which is the best proxy we have for a deployed chips
model.
diagnostics
De-duplicated names of detected procedure or system issues; use them to flag or exclude
a round.
active_boxes / total_bettors
Plain sums over boxes.
The round's span and where its time went.
started_at is the first relevant detection (the first chip, or the first card when no
betting phase was seen). ended_at is when the last card left the table. Both are ISO
8601 with offset, in the system's local timezone.
betting_seconds runs to the first card and is None without bet detection;
play_seconds runs from the first card to the cleared table. Both are accumulated
occupancy rather than timestamp differences, for the reason given on PhaseSeconds, and
together they make up total_seconds.
phases breaks play_seconds down further where the game's phases are tracked.
Phase timestamps are not repeated here: every transition is published with its exact
time on the game's state change topic.
How the time inside the game split up, for tables that resolve phases within it.
Absent when the tracker only sees the game start and end, and never 0.0 to mean
"unknown". The four fields sum to RoundTiming.play_seconds.
They are accumulated occupancy: the total time spent in each phase, summed over every
visit. The tracker steps backwards when cards are occluded, so a phase can be entered
more than once — which is why these are not differences between phase timestamps. A
phase that was never entered is 0.0.
Per-game mapping: Baccarat has no decision phase (decision_seconds is 0.0) and
house_play_seconds covers the third-card draws; UTH's decision window spans pre-flop
to river.
One betting position that was in play.
"Box" is a position, not a person: bettors counts the main better plus every back
better sharing the position (occupied back-bet positions, de-duplicated across split
hands).
What puts a box in play is the strongest signal that game has: the cards dealt to it
where we track cards, the main bet placed on it where we only track chips.
settlement is the box's main-bet outcome, and is None on tables that do not settle
rounds at all — a bet we know was placed but whose result nobody read. It is then None
on every box of every round from that table.
Main-bet outcome for one box, for games whose rules we evaluate.
num_hands is the number of card-holding hands at the box — greater than 1 after a
blackjack split, and 0 in Baccarat, where the box holds no cards; use BoxResult.bettors
for participation there.
bets_won / bets_lost / bets_pushed count settled main-line bets. Blackjack deals
one main bet per hand, so they sum to num_hands on a fully settled box — except for a
hand that was surrendered, which is none of the three and is visible only in
payout_multiplier. Games where a box settles several main-line bets independently count
each one: a UTH box with a pushed ante and a winning blind and play is 1 pushed / 2 won
against num_hands == 1.
total_bet_units is the amount staked at the box, with the box's initial bet as 1.0:
one unit per hand and two for a doubled hand, so a box that split and doubled one of the
two hands staked 3.0. It is the denominator for hold — the house's edge on this box is
-payout_multiplier / total_bet_units.
payout_multiplier is the net result across the box in those same units.
settled is False when we could not settle every bet at the box, i.e. a hand we could
not read or one the dealer left undecided. total_bet_units and
payout_multiplier are then both partial sums over the bets we did settle, so exclude
the box — or the whole round — from revenue reporting.
Uptake of one configured side bet across the round: who could play it, and who did.
side_bet_id is the stable key to match on; name is for display and is not unique.
scope is "box" for per-position side bets and "table" for table-wide ones.
opportunities counts the betting entities that could have placed it: each box in play
contributes its main better plus every back better present, except a progressive, which
is one per seat. A side bet the game did not offer this round scores 0 (Insurance,
unless the dealer shows an Ace). played is how many of those actually placed it,
capped at opportunities to absorb misdetections.
How the bet ended is not here. An outcome and a payout can only be worked out by
evaluating the game's rules, which this payload deliberately knows nothing about — a
table that watches bets and card presence without reading the cards can still report
everything above. The game's own summary carries the result: for blackjack that is
blackjack/summary, which reports each side bet where it was placed, with its outcome
and what it paid.
The round's main-bet outcome, summed over CardGameSummary.boxes.
Present exactly when the game's rules are evaluated — so a dashboard row needs no
traversal, and its absence marks a table that reports participation only.
The house result for the round is -total_payout_multiplier and its hold is
-total_payout_multiplier / total_bet_units.
Whether the round could be settled. Filter revenue reporting on this.
Derived from the per-box BoxSettlement.settled flags, so it can never disagree with
them. A table that does not settle at all has no RoundSettlement, so it never reports
UNSETTLED — see the module docstring.