People who liked this post
Spoilers
Draft War Room

  • NFL LEAGUE
    • League Home
    • Power Rankings
    • Hall of Champions
  • League Forums
  • Players
    • Search
    • Draft History
    • Trades
  • Coaches
    • Search
  • Draft
    • War Room

  • Community
  • Log In
Player Eval Tool
League News/General Discussion
  • ‹
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • ›
AlabamaTide
Re: Player Eval Tool
by AlabamaTide @ 3/16/2026 10:18 am
hoping for plug n play
martinwarnett
Re: Player Eval Tool
by martinwarnett @ 3/16/2026 11:58 am
I've taken Seth's single script on an done a lot of work breaking things down, re-structuring; would be a bit much to post it all, given a lot still in progress.

If I can get some time - and prob not looking till Friday - will take Seth's original script and apply the changes there
dfisherman
Re: Player Eval Tool
by dfisherman @ 3/16/2026 12:15 pm
Here is a new version that runs in your browser at port 8765:
import http.server
import json
import io
import cgi
import webbrowser
import pandas as pd

HTML_PAGE = '''<!DOCTYPE html>
<html>
<head>
<title>Player Evaluation by Position</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #1a1a2e; color: #eee; padding: 20px; }
h1 { margin-bottom: 15px; color: #e94560; }
.controls { margin-bottom: 15px; display: flex; gap: 10px; align-items: center; }
.controls button { background: #e94560; color: #fff; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 14px; }
.controls button:hover { background: #c73550; }
.tabs { display: flex; flex-wrap: wrap; gap: 2px; margin-bottom: 0; border-bottom: 2px solid #e94560; }
.tab { padding: 8px 16px; background: #16213e; cursor: pointer; border-radius: 5px 5px 0 0; font-size: 13px; }
.tab.active { background: #e94560; color: #fff; }
.tab-content { display: none; }
.tab-content.active { display: block; }
table { width: 100%; border-collapse: collapse; margin-top: 0; font-size: 13px; }
th { background: #16213e; padding: 8px 10px; text-align: left; cursor: pointer; user-select: none; white-space: nowrap; position: sticky; top: 0; }
th:hover { background: #1a1a4e; }
th .arrow { font-size: 10px; margin-left: 4px; }
td { padding: 6px 10px; border-bottom: 1px solid #2a2a4a; white-space: nowrap; }
tr:hover td { background: #16213e; }
.table-wrap { max-height: 75vh; overflow: auto; border: 1px solid #2a2a4a; border-radius: 0 0 5px 5px; }
.empty { padding: 40px; text-align: center; color: #888; }
#file-input { display: none; }
</style>
</head>
<body>
<h1>Player Evaluation by Position</h1>
<div class="controls">
<input type="file" id="file-input" accept=".xlsx">
<button onclick="uploadFile('all')">Evaluate All Positions</button>
<button onclick="uploadFile('current')">Evaluate Current Position</button>
</div>
<div id="tabs" class="tabs"></div>
<div id="content"></div>
<div class="empty" id="placeholder">Upload an Excel file to begin evaluation.</div>

<script>
let allData = {};
let sortState = {};

function uploadFile(mode) {
const input = document.getElementById('file-input');
input.onchange = function() {
if (!input.files.length) return;
const form = new FormData();
form.append('file', input.files[0]);
form.append('mode', mode);
fetch('/upload', { method: 'POST', body: form })
.then(r => r.json())
.then(data => {
if (data.error) { alert(data.error); return; }
allData = data;
sortState = {};
renderTabs(Object.keys(data));
const firstPos = Object.keys(data)[0];
if (firstPos) showTab(firstPos);
})
.catch(e => alert('Error: ' + e));
input.value = '';
};
input.click();
}

function renderTabs(positions) {
document.getElementById('placeholder').style.display = 'none';
const tabsEl = document.getElementById('tabs');
tabsEl.innerHTML = positions.map(p =>
'<div class="tab" onclick="showTab(\\''+p+'\\')" id="tab-'+p+'">'+p+'</div>'
).join('');
}

function showTab(pos) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
const tab = document.getElementById('tab-' + pos);
if (tab) tab.classList.add('active');
renderTable(pos);
}

function renderTable(pos) {
const players = allData[pos] || [];
const contentEl = document.getElementById('content');
if (!players.length) {
contentEl.innerHTML = '<div class="empty">No qualifying players for ' + pos + '</div>';
return;
}
const baseCols = ['Player', 'Composite Score', 'Current Position', 'Current Weight', 'Estimated Speed', 'Estimated Acceleration'];
const skillCols = new Set();
players.forEach(p => (p['Primary Skills'] || []).forEach(s => {
if (s[0] !== 'MaxSpeedMax' && s[0] !== 'AccelerationMax') skillCols.add(s[0]);
}));
const allCols = baseCols.concat([...skillCols].sort());
const sortKey = sortState[pos] || { col: 'Composite Score', desc: true };
const sorted = [...players].sort((a, b) => {
let va = getVal(a, sortKey.col, baseCols);
let vb = getVal(b, sortKey.col, baseCols);
let na = parseFloat(va), nb = parseFloat(vb);
if (!isNaN(na) && !isNaN(nb)) return sortKey.desc ? nb - na : na - nb;
return sortKey.desc ? String(vb).localeCompare(String(va)) : String(va).localeCompare(String(vb));
});

let html = '<div class="table-wrap"><table><thead><tr>';
allCols.forEach(col => {
const arrow = sortKey.col === col ? (sortKey.desc ? ' &#9660;' : ' &#9650;') : '';
html += '<th onclick="sortBy(\\''+pos+'\\',\\''+col.replace(/'/g,"\\\\'")+'\\')">' + col + '<span class="arrow">' + arrow + '</span></th>';
});
html += '</tr></thead><tbody>';
sorted.forEach(p => {
html += '<tr>';
allCols.forEach(col => {
let v = getVal(p, col, baseCols);
if (typeof v === 'number') v = Math.round(v * 10) / 10;
html += '<td>' + (v !== undefined && v !== null ? v : 'N/A') + '</td>';
});
html += '</tr>';
});
html += '</tbody></table></div>';
contentEl.innerHTML = html;
}

function getVal(player, col, baseCols) {
if (baseCols.includes(col)) return player[col];
const skill = (player['Primary Skills'] || []).find(s => s[0] === col);
return skill ? skill[1] : 'N/A';
}

function sortBy(pos, col) {
const prev = sortState[pos] || { col: 'Composite Score', desc: true };
if (prev.col === col) { sortState[pos] = { col, desc: !prev.desc }; }
else { sortState[pos] = { col, desc: true }; }
renderTable(pos);
}
</script>
</body>
</html>'''


class PlayerEvaluationApp:
def __init__(self):
self.allowed_positions = {
"QB": ("QB"),
"RB": ("QB", "RB", "FB", "WR", "TE"),
"WR": ("RB", "WR", "FB", "TE"),
"FB": ("RB", "FB", "TE"),
"TE": ("RB", "FB", "TE"),
"LT": ("LT", "RT", "LG", "RG", "C", "TE"),
"LG": ("LT", "RT", "LG", "RG", "C", "TE"),
"C": ("LT", "RT", "LG", "RG", "C", "TE"),
"RDE": ("RDE", "LDE", "DT", "WLB", "SLB", "MLB"),
"DT": ("RDE", "LDE", "DT", "WLB", "SLB", "MLB"),
"SLB": ("RDE", "LDE", "DT", "WLB", "SLB", "MLB", "CB", "SS", "FS"),
"MLB": ("RDE", "LDE", "DT", "WLB", "SLB", "MLB", "CB", "SS", "FS"),
"WLB": ("RDE", "LDE", "DT", "WLB", "SLB", "MLB", "CB", "SS", "FS"),
"CB": ("WLB", "SLB", "MLB", "CB", "SS", "FS"),
"SS": ("WLB", "SLB", "MLB", "CB", "SS", "FS")
}
self.current_positions = {
"QB": ("QB"),
"RB": ("RB"),
"WR": ("WR"),
"FB": ("FB"),
"TE": ("TE"),
"LT": ("LT", "RT", "LG", "RG", "C"),
"LG": ("LT", "RT", "LG", "RG", "C"),
"C": ("LT", "RT", "LG", "RG", "C"),
"RDE": ("RDE", "LDE", "DT"),
"DT": ("RDE", "LDE", "DT"),
"SLB": ("WLB", "SLB", "MLB"),
"MLB": ("WLB", "SLB", "MLB"),
"WLB": ("WLB", "SLB", "MLB"),
"CB": ("CB", "SS", "FS"),
"SS": ("CB", "SS", "FS")
}
self.position_weight = {
"CB": (191, 96),
"WR": (197, 95),
"SS": (206, 94),
"RB": (217, 92),
"QB": (220, 91),
"WLB": (237, 89),
"SLB": (241, 88),
"FB": (243, 88),
"MLB": (245, 88),
"TE": (257, 86),
"RDE": (276, 84),
"C": (283, 83),
"DT": (300, 80),
"LT": (308, 79),
"LG": (312, 78)
}
self.primary_skills = {
"QB": [
[("PassAccuracyMax", 50), ("ArmStrengthMax", 50), ("IntelligenceMax", 50)],
],
"RB": [
[("MaxSpeedMax", 68), ("AccelerationMax", 52), ("BallCarryingMax", 50), ("BreakTackleMax", 0), ("AvoidFumbleMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 52), ("BallCarryingMax", 0), ("BreakTackleMax", 70), ("AvoidFumbleMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("BallCarryingMax", 50), ("BreakTackleMax", 0), ("AvoidFumbleMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("BallCarryingMax", 0), ("BreakTackleMax", 70), ("AvoidFumbleMax", 50)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("BallCarryingMax", 0), ("BreakTackleMax", 0), ("AvoidFumbleMax", 0)]
],
"WR": [
[("MaxSpeedMax", 68), ("AccelerationMax", 78), ("B&RAvoidanceMax", 50), ("PassRecCourageMax", 50), ("PassCatchingMax", 50), ("RouteRunningMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 78), ("B&RAvoidanceMax", 0), ("PassRecCourageMax", 80), ("PassCatchingMax", 50), ("RouteRunningMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("B&RAvoidanceMax", 80), ("PassRecCourageMax", 50), ("PassCatchingMax", 50), ("RouteRunningMax", 0)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("B&RAvoidanceMax", 50), ("PassRecCourageMax", 50), ("PassCatchingMax", 50), ("RouteRunningMax", 0)],
[("MaxSpeedMax", 90), ("AccelerationMax", 0), ("B&RAvoidanceMax", 0), ("PassRecCourageMax", 0), ("PassCatchingMax", 0), ("RouteRunningMax", 0)]
],
"FB": [
[("MaxSpeedMax", 68), ("AccelerationMax", 58), ("RunBlockingMax", 50), ("PassBlockingMax", 50), ("StrengthMax", 0)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("RunBlockingMax", 50), ("PassBlockingMax", 50), ("StrengthMax", 0)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("RunBlockingMax", 0), ("PassBlockingMax", 0), ("StrengthMax", 0)]
],
"TE": [
[("MaxSpeedMax", 68), ("AccelerationMax", 77), ("B&RAvoidanceMax", 50), ("PassRecCourageMax", 50), ("PassCatchingMax", 50), ("RouteRunningMax", 0)],
[("MaxSpeedMax", 73), ("AccelerationMax", 0), ("B&RAvoidanceMax", 80), ("PassRecCourageMax", 50), ("PassCatchingMax", 50), ("RouteRunningMax", 0)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("B&RAvoidanceMax", 50), ("PassRecCourageMax", 50), ("PassCatchingMax", 50), ("RouteRunningMax", 0)],
[("MaxSpeedMax", 80), ("AccelerationMax", 0), ("B&RAvoidanceMax", 0), ("PassRecCourageMax", 0), ("PassCatchingMax", 0), ("RouteRunningMax", 0)]
],
"C": [
[("MaxSpeedMax", 58), ("AccelerationMax", 55), ("PassBlockingMax", 50), ("StrengthMax", 50), ("ShortSnappingMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 0), ("PassBlockingMax", 50), ("StrengthMax", 50), ("ShortSnappingMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 75), ("PassBlockingMax", 50), ("StrengthMax", 50), ("ShortSnappingMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 0), ("PassBlockingMax", 90), ("StrengthMax", 50), ("ShortSnappingMax", 90)]
],
"LG": [
[("MaxSpeedMax", 65), ("AccelerationMax", 61), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 0), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 75), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 0), ("PassBlockingMax", 90), ("StrengthMax", 50)]
],
"LT": [
[("MaxSpeedMax", 65), ("AccelerationMax", 61), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 0), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 75), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 0), ("PassBlockingMax", 90), ("StrengthMax", 50)]
],
"RG": [
[("MaxSpeedMax", 65), ("AccelerationMax", 61), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 0), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 75), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 0), ("PassBlockingMax", 90), ("StrengthMax", 50)]
],
"RT": [
[("MaxSpeedMax", 65), ("AccelerationMax", 61), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 0), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 75), ("PassBlockingMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 0), ("PassBlockingMax", 90), ("StrengthMax", 50)]
],
"RDE": [
[("MaxSpeedMax", 60), ("AccelerationMax", 50), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 70), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("PassRushMax", 0), ("StrengthMax", 0)]
],
"LDE": [
[("MaxSpeedMax", 60), ("AccelerationMax", 50), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 70), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("PassRushMax", 0), ("StrengthMax", 0)]
],
"DT": [
[("MaxSpeedMax", 60), ("AccelerationMax", 50), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 0), ("AccelerationMax", 75), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("PassRushMax", 0), ("StrengthMax", 0)]
],
"SLB": [
[("MaxSpeedMax", 68), ("AccelerationMax", 60), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("PassRushMax", 50), ("StrengthMax", 50)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("PassRushMax", 0), ("StrengthMax", 0)]
],
"MLB": [
[("MaxSpeedMax", 68), ("AccelerationMax", 60), ("TackleAbilityMax", 0), ("M2MCoverageMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("TackleAbilityMax", 0), ("M2MCoverageMax", 50)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("TackleAbilityMax", 0), ("M2MCoverageMax", 0)]
],
"WLB": [
[("MaxSpeedMax", 68), ("AccelerationMax", 60), ("TackleAbilityMax", 0), ("M2MCoverageMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("TackleAbilityMax", 0), ("M2MCoverageMax", 50)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("TackleAbilityMax", 0), ("M2MCoverageMax", 0)]
],
"CB": [
[("MaxSpeedMax", 68), ("AccelerationMax", 74), ("B&RCoverageMax", 50), ("PunishReceiverMax", 50), ("M2MCoverageMax", 50)],
[("MaxSpeedMax", 68), ("AccelerationMax", 74), ("B&RCoverageMax", 90), ("PunishReceiverMax", 0), ("M2MCoverageMax", 0)],
[("MaxSpeedMax", 60), ("AccelerationMax", 0), ("B&RCoverageMax", 90), ("PunishReceiverMax", 0), ("M2MCoverageMax", 90)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("B&RCoverageMax", 50), ("PunishReceiverMax", 50), ("M2MCoverageMax", 50)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("B&RCoverageMax", 0), ("PunishReceiverMax", 0), ("M2MCoverageMax", 0)]
],
"SS": [
[("MaxSpeedMax", 68), ("AccelerationMax", 72), ("TackleAbilityMax", 0), ("M2MCoverageMax", 50), ("ZoneCoverageMax", 50), ("PunishReceiverMax", 50)],
[("MaxSpeedMax", 78), ("AccelerationMax", 0), ("TackleAbilityMax", 0), ("M2MCoverageMax", 50), ("ZoneCoverageMax", 50), ("PunishReceiverMax", 50)],
[("MaxSpeedMax", 86), ("AccelerationMax", 0), ("TackleAbilityMax", 0), ("M2MCoverageMax", 0), ("ZoneCoverageMax", 0), ("PunishReceiverMax", 0)]
]
}
self.secondary_skills = {
"QB": [
[("LookOffDefMax", 50), ("FieldOfVisionMax", 50), ("MaxSpeedMax", 50), ("AccelerationMax", 50), ("ScramblingSkillMax", 0)],
[("LookOffDefMax", 50), ("FieldOfVisionMax", 50), ("MaxSpeedMax", 50), ("AccelerationMax", 0), ("ScramblingSkillMax", 50)],
[("LookOffDefMax", 50), ("FieldOfVisionMax", 50), ("MaxSpeedMax", 0), ("AccelerationMax", 80), ("ScramblingSkillMax", 50)],
],
"RB": [("IntelligenceMax", 0), ("RouteRunningMax", 0), ("PassCatchingMax", 0), ("PassRecCourageMax", 0)],
"WR": [("IntelligenceMax", 0), ("BallCarryingMax", 0), ("AvoidFumbleMax", 0)],
"FB": [("IntelligenceMax", 0), ("DisciplineMax", 0), ("RouteRunningMax", 0), ("PassCatchingMax", 0), ("PassRecCourageMax", 0)],
"TE": [("IntelligenceMax", 0), ("DisciplineMax", 0), ("StrengthMax", 0), ("RunBlockingMax", 0)],
"C": [("RunBlockingMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"LT": [("RunBlockingMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"LG": [("RunBlockingMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"RG": [("RunBlockingMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"RT": [("RunBlockingMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"LDE": [("TackleAbilityMax", 0), ("RunDefenseMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"RDE": [("TackleAbilityMax", 0), ("RunDefenseMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"DT": [("TackleAbilityMax", 0), ("RunDefenseMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"MLB": [("PunishReceiverMax", 0), ("ZoneCoverageMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0), ("StripBallMax", 0)],
"SLB": [("RunDefenseMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0), ("StripBallMax", 0)],
"WLB": [("PunishReceiverMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0), ("StripBallMax", 0)],
"CB": [("TackleAbilityMax", 0), ("IntelligenceMax", 0), ("DisciplineMax", 0)],
"SS": [("IntelligenceMax", 0), ("DisciplineMax", 0), ("StripBallMax", 0)]
}

def adjust_speed(self, player_weight, position_weight):
weight_diff = position_weight - player_weight
if abs(weight_diff) > 50:
return -7 if weight_diff > 0 else 7
return round(weight_diff * -0.15, 0)

def adjust_acceleration(self, player_weight, position_weight):
weight_diff = position_weight - player_weight
if abs(weight_diff) > 50:
return -5 if weight_diff > 0 else 5
return round(weight_diff * -0.10, 0)

def process_skills(self, skills_list, skill_type, available_columns, row, player, pos, player_weight):
skill_entries = set()
meets_criteria = False

for criteria_set in skills_list:
current_set_meets_criteria = True
for sublist in criteria_set if isinstance(criteria_set, list) else [criteria_set]:
column, min_value = sublist
if column in available_columns:
if column == 'MaxSpeedMax':
adjusted_value = row[column] + self.adjust_speed(player_weight, self.position_weight[pos][0])
percent_value = (adjusted_value / self.position_weight[pos][1]) * 100
skill_entries.add((column, percent_value))
if adjusted_value < min_value:
current_set_meets_criteria = False
elif column == 'AccelerationMax':
adjusted_value = row[column] + self.adjust_acceleration(player_weight, self.position_weight[pos][0])
percent_value = (adjusted_value / self.position_weight[pos][1]) * 100
skill_entries.add((column, percent_value))
if adjusted_value < min_value:
current_set_meets_criteria = False
else:
skill_entries.add((column, row[column]))
if row[column] < min_value:
current_set_meets_criteria = False
else:
current_set_meets_criteria = False

if current_set_meets_criteria:
meets_criteria = True

return list(skill_entries), meets_criteria

def filter_players_by_position(self, df, allowed_positions):
players_by_pos = {pos: [] for pos in allowed_positions.keys()}

for _, row in df.iterrows():
player = row['FirstName'] + ' ' + row["LastName"]
player_pos = row['Pos']
player_weight = row['Weight']
estimated_speed = row['MaxSpeedMax']
estimated_acceleration = row['AccelerationMax']

player_data = {
'Player': player,
'Pos': player_pos,
'Weight': player_weight,
'Estimated Speed': estimated_speed,
'Estimated Acceleration': estimated_acceleration
}

for pos, allowed_pos in allowed_positions.items():
if player_pos in allowed_pos:
entry = player_data.copy()
entry['Estimated Speed'] += self.adjust_speed(player_weight, self.position_weight[pos][0])
entry['Estimated Acceleration'] += + self.adjust_acceleration(player_weight, self.position_weight[pos][0])

available_columns = set(row.keys())

entry['Primary Skills'] = []
primary_meets_criteria = True
if pos in self.primary_skills:
primary_skills_list, primary_meets_criteria = self.process_skills(self.primary_skills[pos], "Primary", available_columns, row, player, pos, player_weight)
entry['Primary Skills'] = primary_skills_list
if pos in ("CB", "SS", "MLB", "WLB"):
ballhawk = (((row["PassCatchingMax"] / 50) * 100) + ((row["RouteRunningMax"] / 50) * 100))
if ballhawk < 100:
entry['Primary Skills'].append(
("Ballhawk", ballhawk)
)
else:
entry['Primary Skills'].append(
("Ballhawk", 100)
)

entry['Secondary Skills'] = []
secondary_meets_criteria = True
if pos in self.secondary_skills:
secondary_skills_list, secondary_meets_criteria = self.process_skills(self.secondary_skills[pos], "Secondary", available_columns, row, player, pos, player_weight)
entry['Secondary Skills'] = secondary_skills_list

if (entry['Primary Skills'] or entry['Secondary Skills']) and primary_meets_criteria and secondary_meets_criteria:
players_by_pos[pos].append(entry)

return players_by_pos

def evaluate_player_skills(self, primary_skills, secondary_skills, players_by_pos, allowed_positions):
evaluated_players_by_pos = {pos: [] for pos in allowed_positions.keys()}

for pos, players in players_by_pos.items():
for player in players:
fullname = player['Player']
position = player['Pos']
weight = player["Weight"]
estimated_speed = player['Estimated Speed']
estimated_acceleration = player['Estimated Acceleration']

skills = {skill: value for skill, value in player['Primary Skills'] + player['Secondary Skills']}

primary_values = [value for skill, value in player['Primary Skills']]
secondary_values = [value for skill, value in player['Secondary Skills']]

primary_avg = sum(primary_values) / len(primary_values) if primary_values else 0
secondary_avg = sum(secondary_values) / len(secondary_values) if secondary_values else 0
composite_score = round(((primary_avg * 2) + secondary_avg) / 3, 1) if primary_values and secondary_values else 0

evaluated_players_by_pos[pos].append({
"Player": fullname,
"Current Position": position,
"Current Weight": int(weight),
"Composite Score": composite_score,
"Estimated Speed": float(estimated_speed),
"Estimated Acceleration": float(estimated_acceleration),
"Primary Skills": [[s, float(v)] for s, v in player['Primary Skills']]
})

return evaluated_players_by_pos

def process_upload(self, file_data, mode):
player_df = pd.read_excel(io.BytesIO(file_data))
player_df.columns = [col[0].upper() + col[1:] if col[0].islower() else col for col in player_df.columns]
if mode == 'current':
positions = self.current_positions
else:
positions = self.allowed_positions
filtered_players = self.filter_players_by_position(player_df, positions)
evaluated_players = self.evaluate_player_skills(self.primary_skills, self.secondary_skills, filtered_players, positions)
return evaluated_players


app = PlayerEvaluationApp()


class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(HTML_PAGE.encode())

def do_POST(self):
if self.path == '/upload':
content_type = self.headers.get('Content-Type', '')
if 'multipart/form-data' not in content_type:
self._json_response({"error": "Invalid content type"})
return

form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
)

file_item = form['file']
mode = form.getvalue('mode', 'all')
file_data = file_item.file.read()

try:
result = app.process_upload(file_data, mode)
self._json_response(result)
except Exception as e:
self._json_response({"error": str(e)})
else:
self.send_response(404)
self.end_headers()

def _json_response(self, data):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())

def log_message(self, format, *args):
pass # Suppress request logging


if __name__ == "__main__":
PORT = 8765
server = http.server.HTTPServer(('127.0.0.1', PORT), Handler)
print(f"Player Evaluation running at http://127.0.0.1:{PORT}")
print("Press Ctrl+C to stop.")
webbrowser.open(f'http://127.0.0.1:{PORT}')
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nServer stopped.")
server.server_close()
martinwarnett
Re: Player Eval Tool
by martinwarnett @ 3/16/2026 12:42 pm
Interesting - not a python developer, might try it later.

Though the obvious issue is with indentations being blatted by the forum server.

I suspect that's why Seth put his on google drive - not sure why people don't use gitlab.
Last edited 3/16/2026 5:49 pm
  • ‹
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • ›
Copyright ©2013-2026 Catalyst Productions | Weather data powered by Visual Crossing
Game Engine Version 4.6 | Website Version cc62118
Terms & Conditions | Privacy Policy