OllinDocs
Data flow & contracts

Preflop Pack v1

The preflop serving contract — node_path grammar, 169-class truth, and the boundaries bridge.

The preflop half of the product is served under a sibling contract — ollin-study-api/schema/preflop.v1.ts (JSON Schema mirror alongside), mirrored client-side as src/lib/api/preflop-contract.ts. One pack per stack depth carries the complete 8-max decision tree plus the heads-up boundary bridge.

Source: the CEO's HRC solves — 8-max MTT with antes, ChipEV, ten symmetric depths. Extraction: solver-lab/data_plane/extract_preflop_v0.py, which walks the HRC node trees with the same conventions as the validated converter (99/99 vs reference at 30bb).

Conventions

  • All chip amounts are bb unless the field name says _chips; chip→bb conversion divides by settings.bb_chips (HRC blinds[0]), chip-exact, ≤ 3 decimals.
  • Seats are the 8-max labels in table order: UTG, UTG1, LJ, HJ, CO, BTN, SB, BB — indices 0–7, matching the HRC export's player indices (BTN=5, SB=6, BB=7).
  • Hands are the 169 canonical classes ("AA", "AKs", "AKo", …).

169 is the source of truth here

HRC preflop output is per-class, so — unlike postflop NodeDocument v1, where per-combo data is primary — the 169 grid is the preflop source of truth. There is no per-combo preflop data to fall back to.

  • HRC raise convention (verified against 116k all-in nodes): a raise amount is the raise-to total including the player's blind, excluding ante. size_bb on raise/allin actions keeps that convention in bb.

node_path grammar

  • The root node is "root". Every other path is "root-" followed by the actions[].id of each edge taken, joined by "-":

    root-fold-fold-fold-fold-r2.3-fold-fold-call
  • - is the separator because raise ids contain . (decimal bb).

  • A child's path is always `${parent.node_path}-${action.id}`; action ids are unique within a node, so paths are unique.

  • Action ids: fold | call | r<size_bb> | allin, where r<size_bb> is the raise-to total in bb with trailing zeros trimmed (r2.3, r25), and allin is a raise-to for the actor's full stack.

The app's ?line= URL parameter is this grammar minus the root- prefix.

PreflopNodeV1 — one decision node

FieldMeaning
node_path / node_idPath per the grammar; the HRC node id for debugging and family slugs.
seat_to_actSeat label.
actionsIn HRC order; strategy indices align with this order. Each action carries kind, optional size_bb (raise-to for raises; incremental for calls), is_all_in, is_check (the BB option — HRC "X" is kind: "call", size_bb: 0), and next.
strategy169Per-class action frequencies, aligned with actions, summing to ~1 (tolerance ~1e-4). Always all 169 keys — classes with zero arriving weight still carry the solver's strategy so the UI can render a full chart.
range169Arriving (reach) weight per class in [0,1] for the seat to act. Sparse — classes with weight ≤ 1e-6 are omitted.
pot_bb / facing_bbPot (antes + blinds included) and the incremental bb to call (0 = check option).
stacks_bbRemaining stack per seat, length 8, SEAT_ORDER order.
active_seats / players_remainingSeats not yet folded, and their count.

next — what an action leads to

next.typeMeaning
nodeA child decision node exists in the pack (next.node_path).
fold_endEveryone else folded — hand ends, pot awarded.
flop_multiwayClosing call/check → flop with 3+ players (no postflop coverage).
flop_huClosing call/check → heads-up flop. next.boundary indexes into the pack's boundaries array — the preflop→postflop bridge.
showdown_allinClosing call of an all-in (0 behind) — the board runs out with no decisions. HU cases still appear in boundaries so the UI can label the leaf.

PreflopBoundaryV1 — the bridge

One entry per action that closes preflop betting with exactly two players:

Real 10bb boundary (served)
{ "node_path": "root-fold-fold-fold-fold-fold-r2-fold", "action_id": "call",
  "kind": "flop_hu", "line": "srp_btn_bb", "oop": "BB", "ip": "BTN",
  "pot_bb": 5.46, "eff_bb": 7.88,
  "family_id": "10bb_srp_btn_bb_p7v5_n21",
  "coverage": "served", "solved_boards": 184 }
FieldMeaning
node_path + action_idThe decision node and the closing action.
kindflop_hu (a flop is played) or showdown_allin (runout).
lineLine label from the validated converter: srp_btn_bb, srp_sb_bb, srp, limp, 3bet_call, 4bet_call, 5bet_call, hu_line.
oop / ipPostflop seating — OOP is BB first, else SB, else the earlier seat.
pot_bb / eff_bbPot after the closing call (antes included); effective stack behind (0 = all-in).
family_idThe arriving-library slug {depth}bb_{line}_p{oopSeat}v{ipSeat}_n{nodeId} when the boundary is one of the 242 converter-emitted spots; null when unmapped.
coverageserved / solved / library / none — see coverage labels.
solved_boardsSolved flop count for the family, when known.

Settings and meta

PreflopSettingsV1 pins the regime as product metadata: format: "MTT", metric: "ChipEV", table_size: 8, bb_chips (chips per bb, 100 in the current exports), sb_bb: 0.5, ante_bb (0.12bb per player, MTT regular ante), stack_bb (= the pack's depth), solver: "HRC".

PreflopPackMetaV1: campaign, stamp, tier ("preflop-v0"), generator, generated_at, and source_files (the HRC blobs in the Azure preflop-raw container).

Response shapes

The pack itself is stored as serving/{campaign}/{stamp}/preflop-v0/packs/{depth}bb.json (sha256 in the tier manifest). The API slices it into four responses:

ResponseContents
PreflopCatalogV1GET /v1/preflopDepths with node_count, boundary_count, covered_boundaries (coverage served+solved), and per-depth settings.
PreflopDepthResponseV1GET /v1/preflop/{depth}Settings, root node, and the compact index (path / seat / n_actions per node) — not the full nodes map.
PreflopNodeResponseV1GET /v1/preflop/{depth}/node?path=…One node, plus the boundary documents its actions reference (keyed by boundary index) so leaves render without a second request.
PreflopBoundariesResponseV1GET /v1/preflop/{depth}/boundariesAll boundaries with coverage_counts.

Wire examples for each: API reference — preflop endpoints.

On this page