brackt/docs/agents/brackt-sport-implementation-plan.md
Chris Parsons 5b03b22267
Add Brackt as a league-private meta-sport (#200) (#377)
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>
2026-05-04 20:31:44 -07:00

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 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.