Skip to main content

Draft sync

Draft sync is a long-running connector for one league's draft. It records every pick, whoever made it, and publishes them to Redis for the core pipeline's draft listener. It never writes to ESPN, except when it executes a pick the draft clicker delegates to it.

What starts it?​

The core API creates a Kubernetes Job for each draft, live or practice. The Job runs node src/bots/draft_sync.js and stops when the draft finishes or it gives up. It is not a queue consumer.

VariablePurpose
ESPN_LEAGUE_ID, ESPN_TEAM_ID, CURRENT_YEARWhich draft to join.
DRAFT_MODEmock tries to start a practice draft itself.
DRAFT_ROOM_URLAn already-open practice room URL. Takes priority over DRAFT_MODE. Use it for tests only.
DRAFT_HEALTH_INTERVAL_MSHealth heartbeat cadence. Default 10 000.
DRAFT_PICK_SILENCE_WARN_MSWarn when no new pick arrives for this long. Default 180 000.
DRAFT_STALL_TERMINAL_CYCLESReconnect cycles with no picks before the log escalates to an error. Default 3.
DRAFT_COMMAND_POLL_INTERVAL_MSHow often it polls for delegated picks. Default 1 000.
DRAFT_COMMAND_CONFIRM_TIMEOUT_MSHow long it waits for ESPN to confirm a delegated pick. Default 5 000.

Why three ingest tiers?​

The draft room allows one session per league, team and member. A second connection evicts the first, and the protocol has no replay.

TierStrengthWeakness
WebSocketFastest, exact idsMisses every pick while evicted.
RESTAuthoritative, has pick and round numbersReturns zero picks during a practice draft.
DOM observerAlways shows the full boardSlower. Its selectors are unverified.

The pick ledger collapses overlapping observations into one publish plus later corrections. Source precedence is REST, then DOM, then init, then WebSocket. Identity is the player id, not the pick number.

What does it write to Redis?​

Key or channelTypeContent
draft_picks:{leagueId}:{seasonId}StreamEach pick as payload JSON.
draft:{leagueId}:{seasonId}:picksHashPick number to pick JSON. Rehydrated on restart.
draft_picksPub/subEach new or corrected pick.
draft:{leagueId}:healthString, 1 h TTLPer-tier counters, last pick time, WebSocket state and the ownership lease.
draft:{leagueId}:connectedString, 60 s TTLLiveness flag.
draft:{leagueId}:selectingStringWhose turn it is and the clock, from SELECTING and CLOCK frames.
draft:{leagueId}:suggestedStringESPN's own suggested pick from AUTOSUGGEST.
draft:{leagueId}:autodraftStringAutodraft toggles.
draft_commands:{leagueId}StreamRead side: picks delegated by the clicker.
draft_commands:{leagueId}:resultsPub/subWrite side: result of each delegated pick.

How does it recover?​

ConditionBehavior
Socket closesESPN reconnects on its own. The sync restarts the browser only after 30 s with no open socket.
Reconnect cycle with no new picksRetries with backoff (2 s doubling to 30 s), up to 10 attempts.
Draft finishedExits 0. Silence alone is never treated as proof of completion.
Room confirmed gonePermanentError, no retries.
Attempts exhaustedExits 1 with an error.

REST also reconciles every 15 s. SIGTERM exits immediately so the core API can tear the Job down.