NSBA Draft Analyticsembargoed Β· 2026-06-06

LIVE_DRAFT.md

Live Draft Tool β€” runbook

LIVE DRAFT β€” operator runbook (NSBA4)

Two tools: an ingestion pipeline (scripts/refresh.py) to keep the board current, and the live draft engine (scripts/draft_live.py) you drive on draft day. Everything uses the project venv: .venv/bin/python.

Strategy encoded: docs/findings/40_draft_strategy_v3.md on top of the joint model in docs/findings/43_joint_hierarchical_model.md.


A. Where to drop new data

What Drop it here Notes
New / updated combine results data/raw/combine/nsba4_2026_combine_INPROGRESS.xlsx Overwrite this exact file with the latest export. parse_combine.py reads this filename for the nsba4 season.
New intro pastes intros*.txt at repo root, or data/raw/discord/intros*.txt refresh.py re-runs A39, which auto-flags any handle it hasn't interpreted yet. 2-step flow below.

Ingesting new intros (2 steps). Turning free-form Discord prose into the structured fields (normalized grade, category tags, sandbag/fade flags + the exact quote) needs judgment β€” it is not regex-able β€” so A39 does NOT blindly auto-parse the .txt. Instead: 1. Drop the paste as intros*.txt (repo root) or data/raw/discord/intros*.txt, then run refresh.py. A39 scans the raw text and prints [A39] NEEDS INTERPRETATION: <handle> for every post it hasn't structured yet (staff posters a.new.rag / auride0 are ignored). 2. Interpret the flagged posts (Claude does this from the raw text) into one JSON object per line β€” same keys as A39's intros rows (handle, name, grade, cats, sandbag, sandbag_q, fade, fade_q, notes) β€” appended to data/raw/discord/intros_extra.jsonl. Re-run refresh.py; A39 merges those rows (deduped by handle) and the warning clears.

The original ~45 hand-curated rows stay inline in A39 untouched; intros_extra.jsonl is the clean append path for everything new. (For combine-driven board updates this whole step is optional β€” the value board is driven by combine + game tape, not the intro self-reports; intros feed eligibility, buy-low context, and the no-combine watchlist.)

New combine-only players caveat. reconcile_identities_v2.py merges identities across existing sources but does not mint brand-new canonical_ids for players who appear only in a new combine drop and are not already in canonical_players.csv. If a never-before-seen player takes the nsba4 combine, they may need a manual canonical row before they appear on the board. Watch the "unmatched" lines in the refresh summary.


B. Refresh the board

Run the whole chain (parse combine β†’ GRM ability β†’ master β†’ intros β†’ reconcile β†’ worklist β†’ re-fit joint model):

.venv/bin/python scripts/refresh.py

Full run is ~1 minute and idempotent (same inputs β†’ identical board). It backs up the old board to data/processed/joint_value_board.PREV.csv and prints the top movers vs that backup. It never touches data/draft_state.json.

Flags: - --dry-run β€” print the chain + current summary without executing anything. - --combine-only β€” skip the intros parse (still reconciles + re-fits). - --intros-only β€” skip the combine parse (still reconciles + re-fits). - --no-backup β€” don't snapshot the old board (then no movers diff).

What to check after a refresh: 1. The *** N identity matches need David's confirmation *** block β€” these come from data/processed/reconcile_worklist.csv. Confirm/resolve the high-signal rows (sorted by importance) so the right game tape attaches to the right player. 2. unmatched handles / unmatched to canonical_id β€” intro posters or combine takers we couldn't tie to a canonical player. 3. The top movers β€” sanity-check that new combine/tape moved the board the way you expect.

Outputs that feed the live tool: data/processed/joint_value_board.csv (score/floor/ceiling) and data/processed/joint_ability_posterior.csv (per playerΓ—subject theta + 80% CI).


C. Draft day β€” the live tool

The slot is unknown until ~06-06, so set it at init. Defaults are 14 teams, 6 rounds, snake.

CLI flow

.venv/bin/python scripts/draft_live.py init --slot 9          # [--teams 14 --rounds 6]
.venv/bin/python scripts/draft_live.py recommend --n 5        # who to take at your next pick
.venv/bin/python scripts/draft_live.py me "Rohan Garg"        # record YOUR pick
.venv/bin/python scripts/draft_live.py pick "Kian"            # record an opponent's pick (auto-assigns to team on the clock)
.venv/bin/python scripts/draft_live.py pick "Vishnu" --team 5 # force a team slot if needed
.venv/bin/python scripts/draft_live.py state                  # roster, coverage, whose turn, your next pick #
.venv/bin/python scripts/draft_live.py board --subject ess    # remaining pool by theta in a subject
.venv/bin/python scripts/draft_live.py board                  # remaining pool by YOUR marginal value
.venv/bin/python scripts/draft_live.py undo                   # undo the last pick
.venv/bin/python scripts/draft_live.py ineligible "Some Name" # remove from draftable pool
.venv/bin/python scripts/draft_live.py captain "Some Name"    # remove (pre-assigned captain)

State persists to data/draft_state.json; the tool is stateful across commands.

"Just tell Claude" flow

The engine functions all return structured dicts, so you can drive it conversationally β€” the orchestrator translates and calls: - "I'm slot 9" β†’ init_draft(slot=9) - "Team 3 took Kian" β†’ record_pick("Kian") (auto-assigns to the team on the clock) - "I'll take Rohan" β†’ record_my_pick("Rohan") - "Who should I take?" β†’ recommend(n=5) - "Where do we stand?" β†’ state_summary() - "Show me the best ESS guys left" β†’ board(subject="ess") - "Undo that" β†’ undo_last()

Name resolution is fuzzy across canonical names, intro names, and Discord handles. If a name is ambiguous, the tool returns the candidate list instead of guessing β€” pick the canonical_id (e.g. P0346) or a more specific name.


D. What the recommendations mean

Each rec shows: name Β· fills Β· mv Β· proj [floor–ceiling] Β· slide.


E. Known caveats (verify before relying)


NSBA Draft Analytics Β· embargoed until after the SSB draft Β· ← hub