425 lines
16 KiB
Markdown
425 lines
16 KiB
Markdown
|
|
# Omni League Draft Rules Implementation Plan
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
Implement draft restrictions for Omni leagues where teams must draft at least one participant from each sport, with limited "flex" picks for duplicates.
|
|||
|
|
|
|||
|
|
## Business Rules
|
|||
|
|
|
|||
|
|
### Core Rule 1: Team Flex Pick Limit
|
|||
|
|
- **Must draft at least one from each sport**
|
|||
|
|
- **Flex picks** = Total rounds - Number of sports
|
|||
|
|
- Once all flex picks are used, can only draft from sports not yet drafted
|
|||
|
|
|
|||
|
|
### Core Rule 2: Global Sport Availability (NEW)
|
|||
|
|
- **Cannot draft from a sport if it would prevent other teams from fulfilling their requirement**
|
|||
|
|
- For each sport, track:
|
|||
|
|
- Undrafted participants remaining in that sport
|
|||
|
|
- Teams that haven't drafted from that sport yet
|
|||
|
|
- A team can draft from a sport only if:
|
|||
|
|
- **First pick in sport**: `undraftedInSport >= teamsStillNeedingSport`
|
|||
|
|
- **Flex pick in sport**: `undraftedInSport > teamsStillNeedingSport`
|
|||
|
|
|
|||
|
|
### Example 1: Team Flex Limit
|
|||
|
|
- 5 rounds, 3 sports (NFL, NBA, NHL)
|
|||
|
|
- Flex picks available = 5 - 3 = 2
|
|||
|
|
- Scenario:
|
|||
|
|
- Round 1: NFL player (0 flexes used)
|
|||
|
|
- Round 2: NBA player (0 flexes used)
|
|||
|
|
- Round 3: NFL player (1 flex used - duplicate NFL)
|
|||
|
|
- Round 4: NFL player (2 flexes used - second duplicate NFL)
|
|||
|
|
- Round 5: MUST be NHL (all flexes exhausted, NHL is only sport with 0 picks)
|
|||
|
|
|
|||
|
|
### Example 2: Global Sport Availability
|
|||
|
|
- 10 teams, 5 rounds, 3 sports (NFL, NBA, NHL)
|
|||
|
|
- 12 NFL participants total, 3 teams haven't drafted NFL yet, 3 NFL participants left undrafted
|
|||
|
|
- Current team HAS already drafted 1 NFL participant
|
|||
|
|
- Scenario:
|
|||
|
|
- If current team drafts another NFL participant: 2 NFL participants remain
|
|||
|
|
- But 3 teams still need to draft their first NFL participant
|
|||
|
|
- This would make it impossible for all teams to get NFL
|
|||
|
|
- **Result**: Current team is BLOCKED from drafting NFL (even if they have flex picks available)
|
|||
|
|
|
|||
|
|
- Same scenario, but current team has NOT drafted NFL yet:
|
|||
|
|
- If current team drafts NFL participant: 2 NFL participants remain
|
|||
|
|
- And 2 teams still need to draft their first NFL participant (3 - 1)
|
|||
|
|
- This is exactly enough (2 = 2)
|
|||
|
|
- **Result**: Current team is ALLOWED to draft NFL
|
|||
|
|
|
|||
|
|
### Combined Rules
|
|||
|
|
Both constraints must be satisfied:
|
|||
|
|
1. Team must have flex picks available OR not have drafted from this sport
|
|||
|
|
2. AND drafting from this sport won't prevent other teams from fulfilling requirements
|
|||
|
|
|
|||
|
|
## Technical Changes Required
|
|||
|
|
|
|||
|
|
### 1. Data Model Changes
|
|||
|
|
|
|||
|
|
**Location**: `database/schema.ts`
|
|||
|
|
|
|||
|
|
No schema changes needed - we can derive this from existing data:
|
|||
|
|
- Season has `draftRounds` field
|
|||
|
|
- Season is linked to sports via `seasonSports`
|
|||
|
|
- Draft picks are linked to participants which are linked to sports
|
|||
|
|
|
|||
|
|
### 2. Utility Function - Calculate Eligible Sports
|
|||
|
|
|
|||
|
|
**Location**: `app/lib/draft-eligibility.ts` (new file)
|
|||
|
|
|
|||
|
|
Create utility to determine which sports a team can draft from:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
interface SportAvailability {
|
|||
|
|
sportId: string;
|
|||
|
|
undraftedCount: number;
|
|||
|
|
teamsStillNeeding: number;
|
|||
|
|
canDraftAsFirst: boolean; // undraftedCount >= teamsStillNeeding
|
|||
|
|
canDraftAsFlex: boolean; // undraftedCount > teamsStillNeeding
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface DraftEligibility {
|
|||
|
|
teamId: string;
|
|||
|
|
eligibleSportIds: Set<string>;
|
|||
|
|
ineligibleReasons: Record<string, string>; // sportId -> reason
|
|||
|
|
flexPicksUsed: number;
|
|||
|
|
flexPicksAvailable: number;
|
|||
|
|
picksBySport: Record<string, number>;
|
|||
|
|
sportAvailability: Record<string, SportAvailability>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function calculateDraftEligibility(
|
|||
|
|
teamId: string,
|
|||
|
|
teamPicks: Array<{ participant: { sport: { id: string } } }>,
|
|||
|
|
allPicks: Array<{ teamId: string; participant: { sport: { id: string } } }>,
|
|||
|
|
allParticipants: Array<{ id: string; sport: { id: string } }>,
|
|||
|
|
seasonSports: Array<{ id: string; name: string }>,
|
|||
|
|
totalRounds: number,
|
|||
|
|
allTeams: Array<{ id: string }>
|
|||
|
|
): DraftEligibility
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Logic**:
|
|||
|
|
|
|||
|
|
**Step 1: Calculate team's flex pick status**
|
|||
|
|
1. Count picks per sport for the team
|
|||
|
|
2. Calculate flex picks used = sum of (picks in sport - 1) for each sport with >1 pick
|
|||
|
|
3. Calculate flex picks available = totalRounds - totalSports
|
|||
|
|
4. Determine if team has flexes remaining: `flexPicksUsed < flexPicksAvailable`
|
|||
|
|
|
|||
|
|
**Step 2: Calculate global sport availability**
|
|||
|
|
For each sport:
|
|||
|
|
1. Count total participants in sport
|
|||
|
|
2. Count drafted participants in sport (from allPicks)
|
|||
|
|
3. Calculate undrafted = total - drafted
|
|||
|
|
4. Count teams with 0 picks in this sport
|
|||
|
|
5. Determine:
|
|||
|
|
- `canDraftAsFirst = undraftedCount >= teamsStillNeeding`
|
|||
|
|
- `canDraftAsFlex = undraftedCount > teamsStillNeeding`
|
|||
|
|
|
|||
|
|
**Step 3: Combine constraints**
|
|||
|
|
For each sport, determine eligibility:
|
|||
|
|
1. Check if team has drafted from sport: `currentTeamHasSport = picksBySport[sportId] > 0`
|
|||
|
|
2. If NOT yet drafted from sport:
|
|||
|
|
- Allowed if: sport availability `canDraftAsFirst` is true
|
|||
|
|
- Reason if blocked: "Only X participants left for Y teams needing this sport"
|
|||
|
|
3. If already drafted from sport (would be flex):
|
|||
|
|
- Allowed if: team has flexes remaining AND sport availability `canDraftAsFlex` is true
|
|||
|
|
- Reason if blocked (flex exhausted): "All flex picks used"
|
|||
|
|
- Reason if blocked (global): "Would prevent other teams from getting this sport"
|
|||
|
|
|
|||
|
|
**Step 4: Return eligibility**
|
|||
|
|
- Set of eligible sport IDs
|
|||
|
|
- Map of ineligible reasons for UI display
|
|||
|
|
|
|||
|
|
### 3. Frontend - Draft Room UI
|
|||
|
|
|
|||
|
|
**Location**: `app/routes/leagues/$leagueId.draft.$seasonId.tsx`
|
|||
|
|
|
|||
|
|
#### Changes to Loader
|
|||
|
|
- Load all sports for the season (already done via `seasonSports`)
|
|||
|
|
- Load ALL participants for the season (already done)
|
|||
|
|
- Load ALL teams for the season (already done via `season.teams`)
|
|||
|
|
- Pass sport count to client
|
|||
|
|
- All data needed for eligibility calculation is available
|
|||
|
|
|
|||
|
|
#### Changes to Component State
|
|||
|
|
- Track team's picks by sport (can derive from `picks` state filtered by `userTeam.id`)
|
|||
|
|
- Calculate eligibility when rendering participant list
|
|||
|
|
|
|||
|
|
#### Changes to Participant Table
|
|||
|
|
- Add eligibility check before rendering buttons
|
|||
|
|
- Disable "Pick" button if sport is not eligible
|
|||
|
|
- Disable "Add to Queue" if sport is not eligible
|
|||
|
|
- Show visual indicator (badge/tooltip) explaining why ineligible
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// Pseudo-code
|
|||
|
|
const eligibility = calculateDraftEligibility(
|
|||
|
|
userTeam.id,
|
|||
|
|
picks.filter(p => p.team.id === userTeam.id),
|
|||
|
|
picks, // all picks
|
|||
|
|
availableParticipants, // all participants
|
|||
|
|
seasonSports,
|
|||
|
|
season.draftRounds,
|
|||
|
|
season.teams
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// In participant rendering
|
|||
|
|
const isEligible = eligibility.eligibleSportIds.has(participant.sport.id);
|
|||
|
|
const ineligibleReason = eligibility.ineligibleReasons[participant.sport.id];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Visual Indicators
|
|||
|
|
- Add a status banner showing:
|
|||
|
|
- Flex picks remaining for current team
|
|||
|
|
- Sport-by-sport breakdown showing current team's picks
|
|||
|
|
- Sport availability warnings (e.g., "Only 2 NFL participants left for 3 teams")
|
|||
|
|
- Show pick counts by sport in My Queue section
|
|||
|
|
- Grayed out rows for ineligible participants
|
|||
|
|
- Tooltip/badge explaining why a participant is ineligible:
|
|||
|
|
- "Flex picks exhausted - must draft from [sports list]"
|
|||
|
|
- "Only X participants left for Y teams needing this sport"
|
|||
|
|
- Combination of both constraints
|
|||
|
|
|
|||
|
|
### 4. Backend - API Validation
|
|||
|
|
|
|||
|
|
**Location**: `app/routes/api/draft/make-pick.tsx`
|
|||
|
|
|
|||
|
|
Add validation before creating pick:
|
|||
|
|
1. Load team's existing picks
|
|||
|
|
2. Load season sports count
|
|||
|
|
3. Calculate eligibility
|
|||
|
|
4. Return 400 error if sport is not eligible
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// Validation logic
|
|||
|
|
const teamPicks = await getTeamPicks(teamId, seasonId);
|
|||
|
|
const allPicks = await getAllPicksForSeason(seasonId);
|
|||
|
|
const allParticipants = await getAllParticipantsForSeason(seasonId);
|
|||
|
|
const seasonSports = await getSeasonSports(seasonId);
|
|||
|
|
const allTeams = await getTeamsForSeason(seasonId);
|
|||
|
|
|
|||
|
|
const eligibility = calculateDraftEligibility(
|
|||
|
|
teamId,
|
|||
|
|
teamPicks,
|
|||
|
|
allPicks,
|
|||
|
|
allParticipants,
|
|||
|
|
seasonSports,
|
|||
|
|
season.draftRounds,
|
|||
|
|
allTeams
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!eligibility.eligibleSportIds.has(participant.sport.id)) {
|
|||
|
|
const reason = eligibility.ineligibleReasons[participant.sport.id] ||
|
|||
|
|
"Cannot draft from this sport";
|
|||
|
|
return json(
|
|||
|
|
{ error: reason },
|
|||
|
|
{ status: 400 }
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. Backend - Autodraft Logic
|
|||
|
|
|
|||
|
|
**Location**: `app/models/draft-pick.ts` (autodraft function)
|
|||
|
|
|
|||
|
|
Update autodraft logic to respect eligibility:
|
|||
|
|
1. Calculate team's eligibility
|
|||
|
|
2. Filter queue by eligible sports only
|
|||
|
|
3. If queue has no eligible participants:
|
|||
|
|
- Fall back to highest EV participant from eligible sports
|
|||
|
|
4. If no eligible participants at all (edge case):
|
|||
|
|
- Log error and pause draft
|
|||
|
|
|
|||
|
|
### 6. Force Manual Pick Dialog
|
|||
|
|
|
|||
|
|
**Location**: `app/routes/leagues/$leagueId.draft.$seasonId.tsx` (Force Manual Pick Dialog)
|
|||
|
|
|
|||
|
|
Update the dialog:
|
|||
|
|
1. Calculate target team's eligibility
|
|||
|
|
2. Filter participant list to only eligible sports
|
|||
|
|
3. Gray out / hide ineligible participants
|
|||
|
|
4. Show status message about sport restrictions
|
|||
|
|
|
|||
|
|
### 7. Commissioner Force Autopick
|
|||
|
|
|
|||
|
|
**Location**: `app/routes/api/draft/force-autopick.tsx`
|
|||
|
|
|
|||
|
|
Use same autodraft logic as regular autopick - it will automatically respect eligibility.
|
|||
|
|
|
|||
|
|
## Implementation Steps
|
|||
|
|
|
|||
|
|
### Phase 1: Core Logic
|
|||
|
|
1. Create `app/lib/draft-eligibility.ts` with calculation function
|
|||
|
|
2. Add unit tests for eligibility calculation covering:
|
|||
|
|
- Team flex pick constraints
|
|||
|
|
- Global sport availability constraints
|
|||
|
|
- Combined constraints
|
|||
|
|
- Edge cases
|
|||
|
|
3. Add helper functions to models:
|
|||
|
|
- Get all picks for season with sport data
|
|||
|
|
- Get all participants for season grouped by sport
|
|||
|
|
- Get all teams for season with their picks
|
|||
|
|
|
|||
|
|
### Phase 2: Backend Validation
|
|||
|
|
1. Update `app/routes/api/draft/make-pick.tsx` with validation
|
|||
|
|
2. Update autodraft logic in `app/models/draft-pick.ts`
|
|||
|
|
3. Update `app/routes/api/draft/force-autopick.tsx`
|
|||
|
|
4. Update `app/routes/api/draft/force-manual-pick.tsx`
|
|||
|
|
|
|||
|
|
### Phase 3: Frontend UI
|
|||
|
|
1. Update draft room loader to include sports count
|
|||
|
|
2. Add eligibility calculation to component
|
|||
|
|
3. Update participant table with disabled states
|
|||
|
|
4. Add visual indicators (badges, tooltips)
|
|||
|
|
5. Update Force Manual Pick dialog
|
|||
|
|
6. Add status banner showing flex picks remaining
|
|||
|
|
|
|||
|
|
### Phase 4: Testing
|
|||
|
|
1. Unit tests for eligibility calculation
|
|||
|
|
2. Integration tests for API validation
|
|||
|
|
3. E2E test for draft flow with restrictions
|
|||
|
|
4. Test autodraft respects restrictions
|
|||
|
|
5. Test force picks respect restrictions
|
|||
|
|
|
|||
|
|
## Edge Cases to Handle
|
|||
|
|
|
|||
|
|
1. **Single sport league**: No global restrictions apply (everyone needs same sport, but flex picks still matter)
|
|||
|
|
2. **More sports than rounds**: Error - invalid configuration (impossible to satisfy)
|
|||
|
|
3. **Queue only has ineligible participants**: Autodraft falls back to available pool filtered by eligibility
|
|||
|
|
4. **All participants in eligible sports are drafted**: Draft becomes impossible - should show error/warning
|
|||
|
|
5. **Mid-draft rule change**: Rules apply based on current season configuration
|
|||
|
|
6. **Last few picks**: Global constraints become very restrictive - may force specific picks
|
|||
|
|
7. **All teams have drafted from a sport**: Global constraint for that sport becomes `undrafted > 0` (no minimum needed)
|
|||
|
|
8. **Participant pool imbalanced**: Some sports have very few participants - may trigger global constraints early
|
|||
|
|
9. **Multiple teams on same pick in round**: Each team's eligibility calculated independently
|
|||
|
|
|
|||
|
|
## Testing Scenarios
|
|||
|
|
|
|||
|
|
### Scenario 1: Team Flex Limit
|
|||
|
|
- 3 sports, 5 rounds, 10 teams
|
|||
|
|
- Team drafts 2 from sport A, 1 from sport B
|
|||
|
|
- Verify sport A is disabled (flexes exhausted), sports B and C are enabled
|
|||
|
|
- Make pick from sport C
|
|||
|
|
- Verify only sport B is enabled
|
|||
|
|
|
|||
|
|
### Scenario 2: Global Constraint - Flex Pick Blocked
|
|||
|
|
- 3 sports, 5 rounds, 8 teams
|
|||
|
|
- Sport A has 10 total participants
|
|||
|
|
- 8 picks made from sport A (2 left)
|
|||
|
|
- 3 teams haven't drafted sport A yet (including current team)
|
|||
|
|
- Current team tries to draft sport A as their first from that sport
|
|||
|
|
- Should be ALLOWED (2 >= 3 - 1, which is 2 >= 2)
|
|||
|
|
- Different team that HAS sport A tries to draft another
|
|||
|
|
- Should be BLOCKED (2 > 3 is false)
|
|||
|
|
|
|||
|
|
### Scenario 3: Global Constraint - First Pick Allowed
|
|||
|
|
- 3 sports, 5 rounds, 6 teams
|
|||
|
|
- Sport B has 8 total participants
|
|||
|
|
- 5 picks made from sport B (3 left)
|
|||
|
|
- 3 teams haven't drafted sport B yet
|
|||
|
|
- Current team (one of the 3) tries to draft sport B
|
|||
|
|
- Should be ALLOWED (3 >= 3)
|
|||
|
|
- After pick: 2 left, 2 teams need it
|
|||
|
|
- Next team tries to draft sport B as first
|
|||
|
|
- Should be ALLOWED (2 >= 2)
|
|||
|
|
|
|||
|
|
### Scenario 4: Autodraft with Global Constraints
|
|||
|
|
- 3 sports, 5 rounds
|
|||
|
|
- Queue has only sport A participants
|
|||
|
|
- Team has 2 flexes used (3 sport A picks)
|
|||
|
|
- Global constraint: only 1 sport A left, 2 teams need it
|
|||
|
|
- Autodraft should skip queue AND skip sport A
|
|||
|
|
- Should pick highest EV from sport B or C that's eligible
|
|||
|
|
|
|||
|
|
### Scenario 5: Force Pick with Combined Constraints
|
|||
|
|
- Commissioner forces pick for team
|
|||
|
|
- Team has exhausted flexes (can only pick from sports not yet drafted)
|
|||
|
|
- Global constraints eliminate one of those sports
|
|||
|
|
- Dialog should only show participants from eligible sports
|
|||
|
|
- Should display clear reason for each ineligible sport
|
|||
|
|
|
|||
|
|
### Scenario 6: Late Draft Deadlock Prevention
|
|||
|
|
- 4 teams, 3 sports, 4 rounds
|
|||
|
|
- Near end of draft, only sport C participants left
|
|||
|
|
- 2 teams haven't drafted sport C
|
|||
|
|
- 2 sport C participants left
|
|||
|
|
- Teams should both be able to complete draft
|
|||
|
|
- Verify global constraint math: 2 >= 2 allows first team, then 1 >= 1 allows second
|
|||
|
|
|
|||
|
|
### Scenario 7: Impossible Draft State
|
|||
|
|
- 5 teams, 3 sports, 5 rounds
|
|||
|
|
- Sport A has only 4 total participants
|
|||
|
|
- 4 teams need sport A, 0 picks from sport A made yet
|
|||
|
|
- System should detect this is impossible early
|
|||
|
|
- Consider adding pre-draft validation for commissioners
|
|||
|
|
|
|||
|
|
## Data Dependencies
|
|||
|
|
|
|||
|
|
### For Eligibility Calculation:
|
|||
|
|
- **Season**: `draftRounds`, `id`
|
|||
|
|
- **Season Sports**: All sports in the season (id, name)
|
|||
|
|
- **All Teams**: List of all teams in the season
|
|||
|
|
- **All Draft Picks**: Complete pick history with team and sport info
|
|||
|
|
- Needed to calculate: picks per sport per team, total drafted per sport
|
|||
|
|
- **All Participants**: Complete participant pool with sport association
|
|||
|
|
- Needed to calculate: undrafted count per sport
|
|||
|
|
- **Current Team Picks**: Filtered list for flex calculation
|
|||
|
|
|
|||
|
|
### Query Optimization:
|
|||
|
|
- Most queries already exist in draft room loader
|
|||
|
|
- Need to add: pick counts grouped by (teamId, sportId)
|
|||
|
|
- Need to add: undrafted participant counts by sportId
|
|||
|
|
- Consider caching eligibility calculation result per render
|
|||
|
|
- Recalculate only when picks array changes
|
|||
|
|
|
|||
|
|
## UI/UX Considerations
|
|||
|
|
|
|||
|
|
1. **Clear feedback**: User should understand why they can't pick certain participants
|
|||
|
|
2. **Status visibility**: Show flex picks remaining prominently
|
|||
|
|
3. **Sport breakdown**: Show picks per sport in a visible location
|
|||
|
|
4. **Error messages**: Clear explanation when API rejects a pick
|
|||
|
|
5. **Queue validation**: Warn if queue contains ineligible participants
|
|||
|
|
|
|||
|
|
## Performance Considerations
|
|||
|
|
|
|||
|
|
- Eligibility calculation complexity:
|
|||
|
|
- Team picks iteration: O(picks per team) = O(rounds) ≈ O(20)
|
|||
|
|
- All picks iteration for sport counts: O(total picks) = O(teams × rounds) ≈ O(200)
|
|||
|
|
- Per-sport calculations: O(sports) ≈ O(3-5)
|
|||
|
|
- **Total: O(teams × rounds)** - acceptable for real-time calculation
|
|||
|
|
- Calculate once per render and memoize result
|
|||
|
|
- Recalculate only when picks state changes (use useMemo)
|
|||
|
|
- No database schema changes needed
|
|||
|
|
- API validation adds one calculation step (same complexity)
|
|||
|
|
- Consider pre-computing pick counts in loader for faster initial render
|
|||
|
|
- Frontend calculation more responsive than API call per participant
|
|||
|
|
|
|||
|
|
## Future Enhancements
|
|||
|
|
|
|||
|
|
1. **Pre-draft validation**:
|
|||
|
|
- Validate season configuration (enough participants per sport for all teams)
|
|||
|
|
- Show warnings to commissioner before starting draft
|
|||
|
|
2. **Queue validation and management**:
|
|||
|
|
- Real-time warnings if queue contains ineligible participants
|
|||
|
|
- Auto-reorder queue suggestions
|
|||
|
|
- "Smart queue" that prioritizes based on scarcity
|
|||
|
|
3. **Draft strategy tools**:
|
|||
|
|
- Recommendations: "Sport X is running low, consider drafting soon"
|
|||
|
|
- Scarcity indicators on participant list
|
|||
|
|
- Probability calculations for participant availability
|
|||
|
|
4. **Configurable rules**:
|
|||
|
|
- League settings to customize flex pick rules
|
|||
|
|
- Option to disable global constraints (less restrictive mode)
|
|||
|
|
- Minimum picks per sport (beyond just 1)
|
|||
|
|
5. **Advanced restrictions**:
|
|||
|
|
- Position-based restrictions within sports (QB, RB, etc.)
|
|||
|
|
- Conference/division restrictions
|
|||
|
|
- Team ownership limits (e.g., max 2 players from same NFL team)
|
|||
|
|
6. **Undo/Correction tools**:
|
|||
|
|
- Commissioner ability to undo picks if rules were violated
|
|||
|
|
- Draft state debugging tools
|
|||
|
|
7. **Analytics**:
|
|||
|
|
- Track how often global constraints triggered
|
|||
|
|
- Identify bottleneck sports
|
|||
|
|
- Post-draft report on draft efficiency
|