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>
105 lines
5.6 KiB
Markdown
105 lines
5.6 KiB
Markdown
# 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.
|