# Bracket Expansion Plan ## Problem Statement Current bracket system only supports 4 and 8 team single-elimination brackets. We need to support: - **Standard brackets**: 4, 8, 16, 32 teams - **March Madness**: 68 teams with unique structure - First Four: 4 play-in games (8 teams) - Round of 64: 32 games (64 teams including First Four winners) - Round of 32, Sweet Sixteen, Elite Eight, Final Four, Championship - **Only Elite Eight and beyond score fantasy points** (per Q18) - Play-in teams can be any seeds (e.g., 11a vs 11b, 16a vs 16b), not just bottom seeds ## Current Implementation **What we have:** - `generateSingleEliminationBracket(eventId, participantIds[])` - Supports 4 or 8 teams only - Creates matches for all rounds automatically - Simple sequential seeding (1 vs 2, 3 vs 4, etc.) **Limitations:** - Fixed bracket sizes (4, 8) - No play-in game support - No concept of "scoring rounds" vs "non-scoring rounds" - No flexible seeding/matchup configuration ## Proposed Solution: Hybrid Approach ### Option 1: Template-Based Bracket System ✅ **RECOMMENDED** Create pre-defined bracket templates for common structures, with manual override capability. #### Bracket Templates ```typescript interface BracketTemplate { id: string; name: string; totalTeams: number; rounds: BracketRound[]; scoringStartsAtRound?: string; // e.g., "Elite Eight" for March Madness } interface BracketRound { name: string; // "First Four", "Round of 64", "Elite Eight", etc. matchCount: number; feedsInto?: string; // Which round winners advance to isScoring: boolean; // Does this round affect fantasy points? } // Example templates: const BRACKET_TEMPLATES = { NCAA_68: { id: 'ncaa_68', name: 'NCAA March Madness (68 teams)', totalTeams: 68, scoringStartsAtRound: 'Elite Eight', rounds: [ { name: 'First Four', matchCount: 4, feedsInto: 'Round of 64', isScoring: false }, { name: 'Round of 64', matchCount: 32, feedsInto: 'Round of 32', isScoring: false }, { name: 'Round of 32', matchCount: 16, feedsInto: 'Sweet Sixteen', isScoring: false }, { name: 'Sweet Sixteen', matchCount: 8, feedsInto: 'Elite Eight', isScoring: false }, { name: 'Elite Eight', matchCount: 4, feedsInto: 'Final Four', isScoring: true }, // 8 teams, share 5-8th { name: 'Final Four', matchCount: 2, feedsInto: 'Championship', isScoring: true }, // Losers share 3-4th { name: 'Championship', matchCount: 1, feedsInto: null, isScoring: true }, // Winner 1st, Loser 2nd ], }, NFL_14: { id: 'nfl_14', name: 'NFL Playoffs (14 teams)', totalTeams: 14, scoringStartsAtRound: 'Wild Card', rounds: [ { name: 'Wild Card', matchCount: 6, feedsInto: 'Divisional', isScoring: false }, // 2 teams get bye { name: 'Divisional', matchCount: 4, feedsInto: 'Conference Championship', isScoring: true }, // QF, share 5-8th { name: 'Conference Championship', matchCount: 2, feedsInto: 'Super Bowl', isScoring: true }, // SF, share 3-4th { name: 'Super Bowl', matchCount: 1, feedsInto: null, isScoring: true }, // Finals, 1st/2nd ], }, NBA_16: { id: 'nba_16', name: 'NBA Playoffs (16 teams)', totalTeams: 16, scoringStartsAtRound: 'Conference Quarterfinals', rounds: [ { name: 'First Round', matchCount: 8, feedsInto: 'Conference Semifinals', isScoring: false }, { name: 'Conference Semifinals', matchCount: 4, feedsInto: 'Conference Finals', isScoring: true }, // QF, share 5-8th { name: 'Conference Finals', matchCount: 2, feedsInto: 'NBA Finals', isScoring: true }, // SF, share 3-4th { name: 'NBA Finals', matchCount: 1, feedsInto: null, isScoring: true }, // Finals, 1st/2nd ], }, SIMPLE_16: { id: 'simple_16', name: 'Simple 16-Team Bracket', totalTeams: 16, scoringStartsAtRound: 'Quarterfinals', rounds: [ { name: 'Round of 16', matchCount: 8, feedsInto: 'Quarterfinals', isScoring: false }, { name: 'Quarterfinals', matchCount: 4, feedsInto: 'Semifinals', isScoring: true }, // Share 5-8th { name: 'Semifinals', matchCount: 2, feedsInto: 'Finals', isScoring: true }, // Share 3-4th { name: 'Finals', matchCount: 1, feedsInto: null, isScoring: true }, // 1st/2nd ], }, SIMPLE_32: { id: 'simple_32', name: 'Simple 32-Team Bracket', totalTeams: 32, scoringStartsAtRound: 'Quarterfinals', rounds: [ { name: 'Round of 32', matchCount: 16, feedsInto: 'Round of 16', isScoring: false }, { name: 'Round of 16', matchCount: 8, feedsInto: 'Quarterfinals', isScoring: false }, { name: 'Quarterfinals', matchCount: 4, feedsInto: 'Semifinals', isScoring: true }, { name: 'Semifinals', matchCount: 2, feedsInto: 'Finals', isScoring: true }, { name: 'Finals', matchCount: 1, feedsInto: null, isScoring: true }, ], }, }; ``` #### UI Flow **Step 1: Select Template** ``` When creating a bracket event: 1. Admin selects bracket template from dropdown 2. Shows preview of rounds and scoring structure 3. Option to customize (add/remove rounds, change scoring start point) ``` **Step 2: Assign Participants** ``` For March Madness (68 teams): - Show all 68 slots grouped by round - First Four: 8 slots (4 matchups) - manual seeding (e.g., "11a vs 11b", "16a vs 16b") - Round of 64: 60 slots (remaining teams) + 4 TBD (from First Four winners) - Admin can assign any participant to any slot For simpler brackets (4, 8, 16): - Linear list of slots - Drag-and-drop or dropdown selection - Traditional seeding (1 vs 16, 2 vs 15, etc.) ``` **Step 3: Generate Bracket** ``` System creates: - All playoff_matches records for all rounds - Properly linked feedsInto relationships - Marks which rounds contribute to scoring - Sets initial participant assignments ``` #### Database Schema Additions ```typescript // Add to playoff_matches table playoff_matches { // ... existing fields // NEW FIELDS: isScoring: boolean (default: true) // Does this match affect fantasy points? templateRound: varchar(50) // "First Four", "Round of 64", etc. seedInfo: varchar(50) // "1 vs 16", "11a vs 11b", etc. (for display) } // Add to scoring_events table scoring_events { // ... existing fields // NEW FIELDS: bracketTemplateId: varchar(50) // "ncaa_68", "nfl_14", etc. scoringStartsAtRound: varchar(50) // Which round starts fantasy scoring } ``` #### Scoring Logic Updates ```typescript /** * Process playoff event completion - updated for template system */ async function processPlayoffEvent(eventId: string) { const event = await getScoringEventById(eventId); const matches = await findPlayoffMatchesByEventId(eventId); // Filter to only scoring matches const scoringMatches = matches.filter(m => m.isScoring); // Determine which placements to assign based on templateRound // Elite Eight losers (4 teams) -> share 5-8th // Final Four losers (2 teams) -> share 3-4th // Championship loser (1 team) -> 2nd // Championship winner (1 team) -> 1st // For non-scoring rounds: assign 0 points (per Q20) const nonScoringLosers = matches .filter(m => !m.isScoring && m.loserId) .map(m => m.loserId); for (const loserId of nonScoringLosers) { await setParticipantResult(loserId, event.sportsSeasonId, null, 0); } } ``` #### Admin UI Changes **Bracket Generation Page:** ```tsx Select Bracket Template {/* Show template details when selected */}

Rounds:

{selectedTemplate.rounds.map(round => (
{round.name} - {round.matchCount} matches {round.isScoring && Scoring Round}
))}
Assign Participants ({templateTeamCount} slots) {/* For March Madness: group by round */} {template.id === 'ncaa_68' ? ( <>

First Four (Play-in Games)

{/* 4 matchups, 8 slots with seed labels */}

Round of 64

{/* 28 direct assignments + 4 TBD from First Four */} ) : ( /* Simple linear assignment for other brackets */ )}
``` ### Implementation Phases **Phase 2.6: Template System Foundation** - [ ] Define bracket template type and constants - [ ] Add `isScoring` and `templateRound` to playoff_matches schema - [ ] Add `bracketTemplateId` and `scoringStartsAtRound` to scoring_events - [ ] Create database migration - [ ] Update playoff-match model to support templates **Phase 2.7: Simple Template Expansion (4, 8, 16, 32)** - [ ] Create template definitions for 4, 8, 16, 32 team brackets - [ ] Update `generateBracketFromTemplate()` function - [ ] Update bracket generation UI to use template selector - [ ] Test with 16-team and 32-team brackets - [ ] Update scoring calculator for non-scoring rounds (0 points) **Phase 2.8: March Madness Template (68 teams)** - [ ] Create NCAA_68 template definition - [ ] Build First Four matchup assignment UI - [ ] Build Round of 64 with TBD slot handling - [ ] Implement winner advancement from First Four to Round of 64 - [ ] Test complete 68-team bracket flow - [ ] Verify Elite Eight scoring starts correctly **Phase 2.9: NFL/NBA Templates** - [ ] Create NFL_14 template (with bye weeks) - [ ] Create NBA_16 template - [ ] Handle bye week logic (teams skip first round) - [ ] Test both templates ## Alternative Considered: Manual Match Creation **Pros:** - Maximum flexibility - Works for any structure **Cons:** - Too complex for admins - Error-prone (easy to miss connections) - No validation of bracket structure - Time-consuming for large brackets **Verdict:** Not recommended. Templates provide better UX with flexibility where needed. ## Open Questions 1. **Custom Templates**: Should admins be able to save custom bracket templates for reuse? - **Recommendation**: Not in initial implementation. Add if requested. 2. **Seed Validation**: Should system validate proper bracket structure (e.g., 1 seed plays 16 seed)? - **Recommendation**: No. Allow flexible seeding for play-ins and special cases. 3. **Series Support**: Should we support best-of-7 series (NBA/NHL)? - **Recommendation**: Not initially. Just track series winner for now. 4. **Bracket Visualization**: Show traditional bracket tree diagram? - **Recommendation**: Phase 3 enhancement. Current table view sufficient for Phase 2. ## Success Criteria ✅ Admin can create 4, 8, 16, 32 team brackets ✅ Admin can create March Madness 68-team bracket with First Four ✅ Admin can assign any participant to any slot (flexible seeding) ✅ System correctly identifies which rounds score fantasy points ✅ Participants eliminated in non-scoring rounds get 0 points ✅ Elite Eight and beyond calculate placements correctly ✅ Winner advancement works across all bracket sizes