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