Case Study 1: Building a Program's Recruiting Board
Overview
This case study develops a complete recruiting board management system for a Power 5 program, integrating prospect evaluation, position needs analysis, and class optimization.
Business Context
A Big Ten program needs to: - Evaluate 500+ prospects on their board - Prioritize prospects by fit and likelihood to commit - Manage 85-scholarship roster with 25 annual signees - Balance position needs with best player available - Track prospects through recruiting funnel
Data Description
# Recruiting board structure
board_schema = {
'prospect_id': 'unique identifier',
'name': 'prospect name',
'position': 'primary position',
'state': 'home state',
'high_school': 'high school name',
'class_year': 'graduation year',
# Ratings
'composite_rating': 'float (0.8000-1.0000)',
'star_rating': 'int (2-5)',
'247_rating': 'float',
'rivals_rating': 'float',
'espn_rating': 'float',
# Measurables
'height': 'inches',
'weight': 'pounds',
'forty_time': 'seconds',
'vertical': 'inches',
# Recruiting status
'offer_status': 'offered/not_offered',
'visit_status': 'unofficial/official/committed',
'interest_level': 'high/medium/low',
'commit_probability': 'estimated %',
# Competition
'competing_schools': 'list of competitors',
'leader': 'current leader school'
}
System Architecture
Recruiting Board Manager
class RecruitingBoardManager:
"""
Complete recruiting board management system.
Manages prospects from initial identification through signing,
integrating evaluation, prioritization, and tracking.
"""
def __init__(self, team_name: str, class_year: int):
self.team = team_name
self.class_year = class_year
self.board = pd.DataFrame()
self.position_needs = {}
self.committed = []
# Position targets
self.position_targets = {
'QB': {'min': 1, 'max': 2, 'priority': 'high'},
'RB': {'min': 1, 'max': 3, 'priority': 'medium'},
'WR': {'min': 3, 'max': 5, 'priority': 'high'},
'TE': {'min': 1, 'max': 2, 'priority': 'medium'},
'OL': {'min': 4, 'max': 6, 'priority': 'critical'},
'EDGE': {'min': 2, 'max': 3, 'priority': 'high'},
'DL': {'min': 2, 'max': 4, 'priority': 'medium'},
'LB': {'min': 2, 'max': 4, 'priority': 'medium'},
'CB': {'min': 2, 'max': 3, 'priority': 'high'},
'S': {'min': 1, 'max': 3, 'priority': 'medium'}
}
def add_prospect(self, prospect_data: Dict):
"""Add prospect to board with evaluation."""
evaluation = self._evaluate_prospect(prospect_data)
prospect_data['board_score'] = evaluation['overall_score']
prospect_data['position_fit'] = evaluation['position_fit']
prospect_data['priority_tier'] = self._assign_tier(evaluation)
self.board = pd.concat([
self.board,
pd.DataFrame([prospect_data])
], ignore_index=True)
def _evaluate_prospect(self, prospect: Dict) -> Dict:
"""Generate prospect evaluation."""
# Rating score (40% weight)
rating_score = (prospect.get('composite_rating', 0.85) - 0.8) * 500
# Position need score (25% weight)
position = prospect.get('position', 'ATH')
need_multiplier = self._get_need_multiplier(position)
need_score = need_multiplier * 25
# Physical fit score (20% weight)
physical_score = self._calculate_physical_fit(
prospect.get('measurables', {}),
position
)
# Commit probability score (15% weight)
commit_prob = prospect.get('commit_probability', 0.10)
commit_score = commit_prob * 15
overall = rating_score + need_score + physical_score + commit_score
return {
'overall_score': overall,
'rating_score': rating_score,
'need_score': need_score,
'physical_score': physical_score,
'commit_score': commit_score,
'position_fit': physical_score / 20
}
def _get_need_multiplier(self, position: str) -> float:
"""Get multiplier based on position need."""
targets = self.position_targets.get(position, {})
priority = targets.get('priority', 'low')
multipliers = {
'critical': 1.0,
'high': 0.75,
'medium': 0.50,
'low': 0.25
}
return multipliers.get(priority, 0.25)
def _calculate_physical_fit(self,
measurables: Dict,
position: str) -> float:
"""Calculate physical fit for position."""
# Position-specific standards
standards = {
'QB': {'height': (74, 78), 'weight': (210, 235)},
'RB': {'height': (68, 73), 'weight': (195, 225)},
'WR': {'height': (70, 76), 'weight': (175, 210)},
'OL': {'height': (76, 80), 'weight': (290, 330)},
'EDGE': {'height': (74, 78), 'weight': (240, 265)},
'CB': {'height': (69, 74), 'weight': (175, 200)}
}
if position not in standards:
return 15 # Default average
score = 20
std = standards[position]
for metric, (low, high) in std.items():
value = measurables.get(metric)
if value:
if low <= value <= high:
score += 0
elif value < low:
score -= min(5, (low - value) * 0.5)
else:
score -= min(5, (value - high) * 0.5)
return max(0, score)
def _assign_tier(self, evaluation: Dict) -> str:
"""Assign priority tier based on evaluation."""
score = evaluation['overall_score']
if score >= 80:
return 'Tier 1 - Priority'
elif score >= 60:
return 'Tier 2 - Target'
elif score >= 40:
return 'Tier 3 - Monitor'
else:
return 'Tier 4 - Watch'
def get_board_by_position(self, position: str) -> pd.DataFrame:
"""Get board filtered and sorted by position."""
pos_board = self.board[self.board['position'] == position].copy()
return pos_board.sort_values('board_score', ascending=False)
def update_status(self, prospect_id: str, status_update: Dict):
"""Update prospect recruiting status."""
mask = self.board['prospect_id'] == prospect_id
for key, value in status_update.items():
self.board.loc[mask, key] = value
def record_commitment(self, prospect_id: str):
"""Record a commitment."""
prospect = self.board[self.board['prospect_id'] == prospect_id].iloc[0]
self.committed.append(prospect.to_dict())
# Update position needs
position = prospect['position']
if position in self.position_targets:
current = len([c for c in self.committed if c['position'] == position])
if current >= self.position_targets[position]['min']:
self.position_targets[position]['priority'] = 'low'
def get_class_summary(self) -> Dict:
"""Get current committed class summary."""
if not self.committed:
return {'size': 0, 'avg_rating': 0}
df = pd.DataFrame(self.committed)
return {
'size': len(df),
'avg_rating': df['composite_rating'].mean(),
'star_breakdown': df['star_rating'].value_counts().to_dict(),
'by_position': df['position'].value_counts().to_dict(),
'total_points': (df['composite_rating'].sum() * 100)
}
Prospect Evaluation Engine
class ProspectEvaluationEngine:
"""
Detailed prospect evaluation system.
"""
def __init__(self):
# Evaluation weights by category
self.weights = {
'ratings': 0.35,
'physical': 0.25,
'production': 0.20,
'intangibles': 0.20
}
def full_evaluation(self, prospect: Dict) -> Dict:
"""Generate comprehensive evaluation."""
# Rating evaluation
rating_eval = self._evaluate_ratings(prospect)
# Physical evaluation
physical_eval = self._evaluate_physical(prospect)
# Production evaluation
production_eval = self._evaluate_production(prospect)
# Intangibles evaluation
intangibles_eval = self._evaluate_intangibles(prospect)
# Composite score
composite = (
self.weights['ratings'] * rating_eval['score'] +
self.weights['physical'] * physical_eval['score'] +
self.weights['production'] * production_eval['score'] +
self.weights['intangibles'] * intangibles_eval['score']
)
return {
'prospect_id': prospect.get('prospect_id'),
'name': prospect.get('name'),
'position': prospect.get('position'),
'composite_score': composite,
'ratings': rating_eval,
'physical': physical_eval,
'production': production_eval,
'intangibles': intangibles_eval,
'projection': self._generate_projection(composite, prospect),
'comparable_players': self._find_comparables(prospect)
}
def _evaluate_ratings(self, prospect: Dict) -> Dict:
"""Evaluate recruiting ratings."""
composite = prospect.get('composite_rating', 0.85)
stars = prospect.get('star_rating', 3)
# Normalize to 0-100
score = (composite - 0.80) * 500 # 0.80 = 0, 1.00 = 100
# Agreement bonus
ratings = [
prospect.get('247_rating'),
prospect.get('rivals_rating'),
prospect.get('espn_rating')
]
valid_ratings = [r for r in ratings if r is not None]
if len(valid_ratings) >= 2:
std = np.std(valid_ratings)
if std < 0.01:
score += 5 # Consensus bonus
return {
'score': min(100, max(0, score)),
'composite': composite,
'stars': stars,
'consensus': std < 0.01 if len(valid_ratings) >= 2 else None
}
def _evaluate_physical(self, prospect: Dict) -> Dict:
"""Evaluate physical profile."""
measurables = prospect.get('measurables', {})
position = prospect.get('position')
# Position-specific evaluation
# ... (detailed implementation)
return {
'score': 75, # Placeholder
'strengths': [],
'weaknesses': []
}
def _evaluate_production(self, prospect: Dict) -> Dict:
"""Evaluate high school production."""
stats = prospect.get('statistics', {})
competition = prospect.get('competition_level', 'unknown')
# Adjust for competition
# ... (detailed implementation)
return {
'score': 70, # Placeholder
'raw_stats': stats,
'adjusted_stats': {}
}
def _evaluate_intangibles(self, prospect: Dict) -> Dict:
"""Evaluate intangible factors."""
# Leadership, character, work ethic, etc.
return {
'score': 75, # Placeholder
'notes': []
}
def _generate_projection(self,
composite: float,
prospect: Dict) -> Dict:
"""Generate development projection."""
# Based on composite and position
position = prospect.get('position')
if composite >= 85:
timeline = 'Early contributor (Year 1-2)'
ceiling = 'All-Conference / Draft'
elif composite >= 70:
timeline = 'Contributor (Year 2-3)'
ceiling = 'Starter / Possible Draft'
elif composite >= 55:
timeline = 'Developer (Year 3-4)'
ceiling = 'Rotational / Career Starter'
else:
timeline = 'Project (Year 3+)'
ceiling = 'Depth / Special Teams'
return {
'development_timeline': timeline,
'ceiling': ceiling,
'confidence': 'Medium'
}
def _find_comparables(self, prospect: Dict) -> List[str]:
"""Find comparable historical players."""
# Would use database of historical recruits
return ['Comparable 1', 'Comparable 2', 'Comparable 3']
Results
Sample Recruiting Board Output
RECRUITING BOARD: BIG TEN UNIVERSITY
Class of 2025 | As of November 1
==============================================
TIER 1 - PRIORITY (8 prospects)
--------------------------------
Pos | Name | Rating | State | Status | Score
----|-------------------|--------|-------|-------------|------
OT | Marcus Thompson | 0.9890 | TX | Committed | 94.2
EDGE| Jaylen Williams | 0.9845 | FL | Official | 91.5
WR | DeAndre Smith | 0.9812 | CA | Targeting | 88.3
CB | Malik Johnson | 0.9780 | GA | Official | 86.1
QB | Carson Mitchell | 0.9765 | OH | Committed | 85.4
OG | Trevor Brown | 0.9720 | PA | Targeting | 84.8
DL | James Wilson | 0.9695 | MI | Targeting | 83.2
RB | Antonio Davis | 0.9650 | AL | Unofficial | 81.5
TIER 2 - TARGET (15 prospects)
--------------------------------
[Additional prospects...]
CURRENT COMMITS (12)
--------------------
5-stars: 1 | 4-stars: 8 | 3-stars: 3
Avg Rating: 0.9412
Current Class Rank: #8 nationally
POSITION STATUS:
QB: 1 committed (Target: 1-2) ✓
OL: 3 committed (Target: 4-6) PRIORITY
WR: 2 committed (Target: 3-5) In Progress
EDGE: 1 committed (Target: 2-3) NEED
DB: 2 committed (Target: 3-5) In Progress
...
Class Projection Model
def project_class_completion(board_manager,
target_size: int = 23) -> Dict:
"""
Project final class composition based on current board.
"""
committed = board_manager.committed
current_size = len(committed)
remaining_spots = target_size - current_size
# Get top remaining targets by commit probability
uncommitted = board_manager.board[
~board_manager.board['prospect_id'].isin(
[c['prospect_id'] for c in committed]
)
].copy()
uncommitted = uncommitted.sort_values(
['priority_tier', 'commit_probability'],
ascending=[True, False]
)
# Monte Carlo simulation
simulations = []
for _ in range(1000):
sim_class = committed.copy()
spots = remaining_spots
for _, prospect in uncommitted.iterrows():
if spots <= 0:
break
prob = prospect.get('commit_probability', 0.10)
if np.random.random() < prob:
sim_class.append(prospect.to_dict())
spots -= 1
sim_df = pd.DataFrame(sim_class)
simulations.append({
'size': len(sim_df),
'avg_rating': sim_df['composite_rating'].mean(),
'five_stars': (sim_df['star_rating'] == 5).sum(),
'four_stars': (sim_df['star_rating'] == 4).sum()
})
sim_df = pd.DataFrame(simulations)
return {
'expected_size': sim_df['size'].mean(),
'expected_avg_rating': sim_df['avg_rating'].mean(),
'expected_five_stars': sim_df['five_stars'].mean(),
'expected_four_stars': sim_df['four_stars'].mean(),
'90th_percentile': {
'avg_rating': sim_df['avg_rating'].quantile(0.90)
},
'10th_percentile': {
'avg_rating': sim_df['avg_rating'].quantile(0.10)
}
}
Key Findings
-
Position Need Weighting: Balancing position needs with BPA improved projected development outcomes by 15%
-
Commit Probability Integration: Incorporating commit probability prevents over-targeting low-probability prospects
-
Physical Fit Scores: Position-specific physical evaluation identified 3 prospects better suited for different positions
-
Tier System: The 4-tier prioritization helped staff focus efforts efficiently
-
Simulation Value: Monte Carlo projections accurately predicted final class composition within 1 player on average
Lessons Learned
- Data Quality: Self-reported measurables required verification at camps
- Dynamic Updates: Board rankings needed weekly updates as situations changed
- Competition Tracking: Understanding competitor schools improved offer timing
- Early Identification: Best outcomes came from prospects identified early in process
- Flexibility: Position need priorities required adjustment as commits rolled in