# Brackt as a Draftable Sport Implementation Plan This plan is the implementation source of truth for making Brackt a league-private meta-sport. The design reference is [Brackt sport](brackt-sport.md). ## Summary Implement Brackt as a league-private meta-sport with automatic final resolution. When any non-Brackt sports season is marked completed, the app checks linked fantasy seasons. If every drafted non-Brackt participant is finalized, it resolves that league's private Brackt season automatically. Commissioners still get a manual fallback action for repair and idempotent retry. ## Key Changes - Add schema support: - Add `sportsSeasons.fantasySeasonId`. - Add `brackt` to `simulatorTypeEnum`. - Add `brackt_resolved` to `auditActionEnum`. - Generate migration with `npm run db:generate`. - Add an admin seed/repair UI action: - Idempotently creates or repairs the global `Brackt` sport and global template sports season. - Template has no participants and a long draft window. - Update draftable sports selection: - Hide per-league Brackt copies from all sport pickers with `fantasySeasonId IS NULL`. - Allow the global Brackt template through league creation/settings even with zero participants. - Add Brackt lifecycle helpers: - On league creation or pre-draft settings update, replace the Brackt template link with a private sports season copy. - Insert manager participants directly into `seasonParticipants` with `externalId = team.id`. - Auto-sync private Brackt participants when pre-draft teams are added, removed, or renamed. - Remove the private Brackt season if Brackt is removed before draft start. ## EV And Draft Behavior - Add `BracktSimulator`: - Supports all current league sizes, 6-16 teams. - Uses Harville probabilities for `N` teams and maps only available scoring tiers. - If all projected weights are zero, returns uniform placement probabilities. - Projects each team from drafted non-Brackt EV plus average available EV for each unpicked non-Brackt sport. - Add `runBracktHarvilleForFantasySeason(fantasySeasonId, db)`: - Requires an explicit DB instance. - Runs the simulator, batch-upserts EVs, syncs VORP, rereads stored VORP, and returns `{ participantId, expectedValue, vorpValue }`. - Refactor only the needed EV path to accept an optional provided DB: - `batchUpsertParticipantEVs(inputs, db?)` - `syncVorpForSeason(sportsSeasonId, db?)` - Run Brackt Harville: - Await before autopick participant selection. - Fire-and-forget after manual picks commit. - Await after private Brackt season creation/sync. - Emit `brackt-evs-updated` to the draft room with updated `expectedValue` and `vorpValue`. - Draft room client: - Patches Brackt VORPs from socket updates. - Sorts by effective server VORP. - Never computes Brackt VORP client-side. ## Automatic Resolution - Add a centralized service: - `maybeResolveCompletedBracktForSportsSeason(completedSportsSeasonId, db)` - Called after any non-Brackt sports season status transitions to `completed`. - Hook all completion paths through this helper: - Admin sports-season edit when status becomes `completed`. - `finalize-standings`. - Bracket/qualifying finalization paths that mark or imply sports-season completion. - Any future model helper for status updates should call this from one shared status-transition function. - Automatic resolution flow: - Find fantasy seasons linked to the completed non-Brackt sports season. - For each fantasy season, find its private Brackt season. - Skip leagues without Brackt or already resolved Brackt. - Require every drafted non-Brackt participant to have a finalized, non-partial result. - If ready, resolve Brackt from `teamStandings.currentRank`, mapping team ID to Brackt participant via `externalId`. - Preserve tied ranks as tied `finalPosition` values. - Mark the private Brackt sports season `completed`. - Recalculate standings and create a daily snapshot. - Write audit log `brackt_resolved` with `{ trigger: "auto", completedSportsSeasonId }`. - Manual fallback: - Keep a commissioner-only league settings action to retry resolution. - Re-run is idempotent only: allow if placements match existing Brackt results; block if they would differ. ## Test Plan - Unit tests: - Brackt Harville zero-weight uniform probabilities. - Brackt projection from drafted EV plus average available EV. - Private Brackt creation and team participant sync. - Picker hides private copies and allows the zero-participant global template. - EV/VORP path works with provided DB. - Automatic resolver skips incomplete leagues and resolves ready leagues. - Automatic resolver ignores leagues without Brackt and ignores Brackt-triggered recursion. - Tied-rank resolution and idempotent re-run behavior. - Route/action tests: - League creation with Brackt creates private season and participants. - League settings add/remove/sync Brackt pre-draft. - Admin seed/repair action is idempotent. - Sports-season completion triggers automatic Brackt check. - Manual commissioner retry works only when idempotent or unresolved. - Draft tests: - Autopick updates Brackt EV before top-VORP selection. - Manual pick emits Brackt EV update after commit. - Client socket update patches Brackt VORP sorting. - Verification: - Run `npm run typecheck`, `npm run lint`, and targeted `npm run test:run`. ## Assumptions - Brackt supports 6-16 team leagues. - Brackt template remains a normal sport picker option with a Brackt-specific zero-participant exception. - Pre-draft team edits auto-sync Brackt participants. - Final tied standings remain tied for Brackt scoring. - Automatic resolution is primary; commissioner action is fallback/repair.