Commissioners can add Brackt to any league. Each team drafts one manager from their own league; at season end, that manager's final overall fantasy ranking earns placement points. Resolution fires automatically when all other sports finalize. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
5.6 KiB
5.6 KiB
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.
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
brackttosimulatorTypeEnum. - Add
brackt_resolvedtoauditActionEnum. - Generate migration with
npm run db:generate.
- Add
- Add an admin seed/repair UI action:
- Idempotently creates or repairs the global
Bracktsport and global template sports season. - Template has no participants and a long draft window.
- Idempotently creates or repairs the global
- 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.
- Hide per-league Brackt copies from all sport pickers with
- 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
seasonParticipantswithexternalId = 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
Nteams 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-updatedto the draft room with updatedexpectedValueandvorpValue. - 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.
- Admin sports-season edit when status becomes
- 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 viaexternalId. - Preserve tied ranks as tied
finalPositionvalues. - Mark the private Brackt sports season
completed. - Recalculate standings and create a daily snapshot.
- Write audit log
brackt_resolvedwith{ 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 targetednpm run test:run.
- 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.