# Omni League Draft Rules - Implementation Summary ## Overview Successfully implemented Omni league draft eligibility rules that enforce: 1. **Team Flex Pick Limits**: Teams can only use (totalRounds - totalSports) flex picks 2. **Global Sport Availability**: Prevents teams from drafting in a way that blocks other teams from meeting the "at least one from each sport" requirement ## Implementation Status: ✅ COMPLETE All phases have been completed and type-checked successfully. --- ## Phase 1: Core Logic ✅ ### Files Created - `app/lib/draft-eligibility.ts` - Core eligibility calculation with full TypeScript types - `app/lib/__tests__/draft-eligibility.test.ts` - Comprehensive test suite (13 tests, all passing) ### Key Functions - `calculateDraftEligibility()` - Main eligibility calculator - Inputs: team picks, all picks, all participants, sports, rounds, teams - Returns: eligible sports, ineligible reasons, flex pick status, sport availability - `getEligibilitySummary()` - Human-readable status summary ### Test Coverage - Team flex pick constraints (3 tests) - Global sport availability constraints (4 tests) - Combined constraints (2 tests) - Edge cases (3 tests) - Summary generation (1 test) All tests pass ✅ --- ## Phase 2: Backend Validation ✅ ### Model Layer Enhancements **`app/models/draft-pick.ts`** - Added `getDraftPicksWithSports()` - Get all picks with sport data - Added `getTeamDraftPicksWithSports()` - Get team-specific picks with sport data **`app/models/participant.ts`** - Added `getParticipantsForSeasonWithSports()` - Get all participants with sport association **`app/models/season-sport.ts`** - Added `getSeasonSportsSimple()` - Get simple sport list for calculations **`app/models/draft-utils.ts`** - Updated `autoPickForTeam()` - Now respects eligibility constraints - Filters queue by eligible sports - Falls back to eligible sports when queue is empty - Signature changed to require `draftRounds` and `allTeamIds` - Updated `getTopAvailableParticipant()` - Now accepts optional `eligibleSportIds` filter ### API Endpoints Updated **`app/routes/api/draft.make-pick.ts`** ✅ - Added eligibility validation before creating pick - Returns specific error reason if sport is ineligible - Validates both team flex limits and global availability **`app/routes/api/draft.force-manual-pick.ts`** ✅ - Added same eligibility validation - Commissioner picks must also respect rules **`app/routes/api/draft.force-autopick.ts`** ✅ - Refactored to use updated `autoPickForTeam()` function - Automatically respects eligibility via autodraft logic ### Validation Flow 1. Load all necessary data (picks, participants, sports, teams) 2. Calculate eligibility using `calculateDraftEligibility()` 3. Check if participant's sport is in `eligibleSportIds` 4. Return 400 error with specific reason if ineligible 5. Proceed with pick if eligible --- ## Phase 3: Frontend UI ✅ ### Draft Room Updates (`app/routes/leagues/$leagueId.draft.$seasonId.tsx`) **New Imports** - `useMemo` from React - `calculateDraftEligibility` and `getEligibilitySummary` from eligibility lib **Eligibility Calculation** - Added `useMemo` hook to calculate user team's eligibility - Recalculates when picks, participants, or team changes - Transforms loader data to match eligibility function signature **Force Pick Eligibility** - Separate `useMemo` for force manual pick dialog - Calculates eligibility for the selected team (not current user) ### Visual Indicators **Participant Table** 1. **Row Styling** - Ineligible participants: red background (`bg-red-50/30 dark:bg-red-950/20`) - Drafted participants: muted with opacity - Eligible participants: normal hover state 2. **Badges** - "Ineligible" badge (destructive variant) for blocked sports - Shows ineligible reason on hover - "Drafted" badge for already picked participants - "Queued" badge only shown if participant is also eligible 3. **Buttons** - "Pick" button disabled when sport is ineligible - "Add to Queue" button disabled for ineligible sports - Tooltips explain why buttons are disabled ### Status Banner (in "My Queue" section) **Displays:** - Flex picks used/available with color coding: - Green when flexes remain - Red when exhausted - Picks by sport breakdown (e.g., "NFL: 2, NBA: 1") - Active restrictions with reasons (first 2 shown) - Example: "NFL: Would prevent other teams from getting NFL (2 left for 3 teams)" **Styling:** - Bordered card with muted background - Clear hierarchy with sections - Color-coded warnings ### Force Manual Pick Dialog **Enhancements:** - Uses `forcePickEligibility` calculation for target team - Filters out ineligible participants: - Red background for ineligible rows - "Ineligible" badge with reason - Disabled "Draft" button - Shows participant sport and EV - Maintains existing search and filter functionality --- ## Technical Details ### Eligibility Algorithm **Team Flex Pick Check:** ```typescript flexPicksUsed = sum of (picks in sport - 1) for each sport with >1 pick flexPicksAvailable = totalRounds - totalSports hasFlexesRemaining = flexPicksUsed < flexPicksAvailable ``` **Global Sport Availability:** ```typescript For each sport: undraftedCount = total participants - drafted participants teamsStillNeeding = teams with 0 picks in this sport canDraftAsFirst = undraftedCount >= teamsStillNeeding canDraftAsFlex = undraftedCount > teamsStillNeeding ``` **Combined Logic:** - If team hasn't drafted from sport: allowed if `canDraftAsFirst` - If team has drafted from sport (flex): allowed if `hasFlexesRemaining AND canDraftAsFlex` ### Data Flow 1. **Loader** loads all necessary data (picks, participants, sports, teams) 2. **Component** calculates eligibility in `useMemo` 3. **UI** renders with eligibility state 4. **User action** triggers API call 5. **API** validates eligibility server-side 6. **Socket** broadcasts pick to all clients 7. **Clients** update state and recalculate eligibility ### Performance - Eligibility calculation: O(teams × rounds) ≈ O(200) for typical draft - Memoized in React - only recalculates when picks change - No database changes needed - Minimal API overhead (one calculation per pick attempt) --- ## Testing ### Unit Tests ✅ - **21 comprehensive tests** covering all eligibility scenarios: - Team flex pick constraints (3 tests) - Global sport availability constraints (4 tests) - Combined constraints (2 tests) - Edge cases (3 tests) - Summary generation (1 test) - **Exact boundary conditions (6 tests)** - Testing critical thresholds - **Draft progression simulation (1 test)** - Multi-pick scenarios - **Late-draft forced choices (2 tests)** - Constraint-driven selections - Run with: `npm test -- app/lib/__tests__/draft-eligibility.test.ts` - **All 21 tests passing** ✅ ### Test Coverage Details The test suite thoroughly validates: - ✅ Team cannot exceed flex pick limit - ✅ Global sport availability protects future teams - ✅ Exact boundary conditions (e.g., undrafted === teamsNeeding) - ✅ Eligibility changes as draft progresses - ✅ Late-draft scenarios where only one sport is eligible - ✅ Complex multi-team scenarios ### Type Safety ✅ - Full TypeScript coverage - Run with: `npm run typecheck` - No errors ### Integration Testing Notes - The core eligibility algorithm is thoroughly unit tested (21 tests) - Backend API endpoints include inline eligibility validation - Autodraft logic (`draft-utils.ts`) uses the tested eligibility function - Manual/E2E testing recommended for full integration validation ### Manual Testing Needed - [ ] E2E test for draft flow with eligibility restrictions - [ ] Test autodraft respects eligibility in live draft - [ ] Test force picks respect eligibility - [ ] Test UI shows correct eligibility status and messaging - [ ] Test with various team/sport/round configurations --- ## Files Modified ### Created 1. `app/lib/draft-eligibility.ts` 2. `app/lib/__tests__/draft-eligibility.test.ts` ### Modified 1. `app/models/draft-pick.ts` 2. `app/models/participant.ts` 3. `app/models/season-sport.ts` 4. `app/models/draft-utils.ts` 5. `app/routes/api/draft.make-pick.ts` 6. `app/routes/api/draft.force-manual-pick.ts` 7. `app/routes/api/draft.force-autopick.ts` 8. `app/routes/leagues/$leagueId.draft.$seasonId.tsx` --- ## Usage Examples ### Backend Validation ```typescript // In API route const eligibility = calculateDraftEligibility( teamId, teamPicks, allPicks, allParticipants, seasonSports, season.draftRounds, allTeams ); if (!eligibility.eligibleSportIds.has(participant.sport.id)) { return Response.json({ error: eligibility.ineligibleReasons[participant.sport.id] }, { status: 400 }); } ``` ### Frontend UI ```typescript // In component const eligibility = useMemo(() => { if (!userTeam) return null; return calculateDraftEligibility(/*...*/); }, [userTeam, picks, availableParticipants, season]); // In render const isEligible = eligibility?.eligibleSportIds.has(participant.sport.id); const ineligibleReason = eligibility?.ineligibleReasons[participant.sport.id]; ``` --- ## Edge Cases Handled 1. **Single sport league**: Flex picks still apply (rounds - 1) 2. **No eligible sports**: API returns clear error, autodraft falls back 3. **All teams drafted from sport**: Global constraint becomes `undrafted > 0` 4. **Empty participant pool**: Returns null, handled gracefully 5. **Mid-draft state changes**: Eligibility recalculates on every pick --- ## Future Enhancements As documented in the plan: 1. Pre-draft validation (warn commissioners of impossible configs) 2. Queue validation warnings 3. Scarcity indicators on participant list 4. Draft strategy recommendations 5. Configurable rules per league 6. Position-based restrictions within sports 7. Commissioner undo/correction tools 8. Analytics on constraint triggers --- ## Deployment Checklist - [x] All code written and tested - [x] TypeScript compilation successful - [x] Unit tests passing - [ ] Manual testing complete - [ ] E2E tests written - [ ] Database migrations (none needed) - [ ] Documentation updated - [ ] Code review completed --- ## Notes - No database schema changes required - All validation happens in application layer - Backward compatible (will work with existing drafts) - Performance impact minimal (simple calculations) - User experience significantly improved with clear feedback