testing_vorp_logic / src /streamlit_app.py
James McCool
Refactor user configuration for fantasy football settings to support default values for flex percentages, multipliers, and VORP limiters. Enhanced handling of both nested and flat dictionary structures for improved flexibility in league settings.
6a4d277
raw
history blame
18.2 kB
import pandas as pd
import numpy as np
import requests
import math
import streamlit as st
Dwain_proj = 'https://sheetdb.io/api/v1/svino07zkd6j6?sheet=2025_NFL_Proj_Dist'
dwain_ranks = 'https://sheetdb.io/api/v1/ax8b1ms11bbzt?sheet=Dwain_Season'
# Default configuration dictionaries
type_flex_percentiles = {
'Half PPR': {
'RB': .40,
'WR': .55,
'TE': .05,
},
'PPR': {
'RB': .40,
'WR': .55,
'TE': .05,
},
'Standard': {
'RB': .40,
'WR': .55,
'TE': .05,
},
'Superflex': {
'QB': .95,
'RB': .02,
'WR': .03,
'TE': .00,
},
'TE Premium': {
'RB': .35,
'WR': .50,
'TE': .15,
}
}
pos_vorp_limiters = {
'PPR': {
'QB': .5,
'RB': .75,
'WR': .75,
'TE': .5,
},
'Standard': {
'QB': .25,
'RB': .75,
'WR': .75,
'TE': .5,
},
'Superflex': {
'QB': .5,
'RB': .75,
'WR': .75,
'TE': .5,
},
'TE Premium': {
'QB': .5,
'RB': .75,
'WR': .75,
'TE': .5,
},
'Half PPR': {
'QB': .5,
'RB': .75,
'WR': .75,
'TE': .5,
},
}
flex_multipliers = {
'Half PPR': {
'QB': 2,
'RB': 2,
'WR': 2,
'TE': 2,
},
'PPR': {
'QB': 2,
'RB': 2,
'WR': 2,
'TE': 2,
},
'Standard': {
'QB': 2,
'RB': 2,
'WR': 2,
'TE': 2,
},
'Superflex': {
'QB': 4,
'RB': 2,
'WR': 2,
'TE': 2,
},
'TE Premium': {
'QB': 2,
'RB': 2,
'WR': 2,
'TE': 2,
},
}
base_settings = {
'TEAMS': 12,
'QB': 1,
'RB': 2,
'WR': 3,
'TE': 1,
'FLEX': 1,
'BENCH': 6,
'TYPE': 'Half PPR'
}
league_settings = {
'TEAMS': 12,
'QB': 1,
'RB': 2,
'WR': 3,
'TE': 1,
'FLEX': 2,
'BENCH': 6,
'TYPE': 'Superflex'
}
def create_user_config_interface():
"""Create Streamlit interface for user configuration"""
st.sidebar.header("League Configuration")
# League Type Selection
league_type = st.sidebar.selectbox(
"League Type",
['Half PPR', 'PPR', 'Standard', 'Superflex', 'TE Premium'],
index=0
)
# League Settings
st.sidebar.subheader("League Settings")
teams = st.sidebar.number_input("Number of Teams", min_value=8, max_value=16, value=12)
qb_starters = st.sidebar.number_input("QB Starters", min_value=1, max_value=2, value=1)
rb_starters = st.sidebar.number_input("RB Starters", min_value=1, max_value=3, value=2)
wr_starters = st.sidebar.number_input("WR Starters", min_value=1, max_value=4, value=3)
te_starters = st.sidebar.number_input("TE Starters", min_value=1, max_value=2, value=1)
flex_spots = st.sidebar.number_input("Flex Spots", min_value=0, max_value=3, value=1)
bench_spots = st.sidebar.number_input("Bench Spots", min_value=0, max_value=10, value=6)
# Update league settings based on user input
user_league_settings = {
'TEAMS': teams,
'QB': qb_starters,
'RB': rb_starters,
'WR': wr_starters,
'TE': te_starters,
'FLEX': flex_spots,
'BENCH': bench_spots,
'TYPE': league_type
}
# Flex Percentiles Configuration
st.sidebar.subheader("Flex Position Percentiles")
if league_type == 'Superflex':
qb_flex_pct = st.sidebar.slider("QB Flex %", 0.0, 1.0, 0.95, 0.01)
rb_flex_pct = st.sidebar.slider("RB Flex %", 0.0, 1.0, 0.02, 0.01)
wr_flex_pct = st.sidebar.slider("WR Flex %", 0.0, 1.0, 0.03, 0.01)
te_flex_pct = st.sidebar.slider("TE Flex %", 0.0, 1.0, 0.00, 0.01)
user_flex_percentiles = {
'QB': qb_flex_pct,
'RB': rb_flex_pct,
'WR': wr_flex_pct,
'TE': te_flex_pct,
}
else:
# Get default values for the selected league type
default_rb = type_flex_percentiles.get(league_type, {}).get('RB', 0.4)
default_wr = type_flex_percentiles.get(league_type, {}).get('WR', 0.55)
default_te = type_flex_percentiles.get(league_type, {}).get('TE', 0.05)
rb_flex_pct = st.sidebar.slider("RB Flex %", 0.0, 1.0, default_rb, 0.01)
wr_flex_pct = st.sidebar.slider("WR Flex %", 0.0, 1.0, default_wr, 0.01)
te_flex_pct = st.sidebar.slider("TE Flex %", 0.0, 1.0, default_te, 0.01)
user_flex_percentiles = {
'RB': rb_flex_pct,
'WR': wr_flex_pct,
'TE': te_flex_pct,
}
# Flex Multipliers Configuration
st.sidebar.subheader("Position Multipliers")
default_qb_mult = flex_multipliers.get(league_type, {}).get('QB', 2.0)
default_rb_mult = flex_multipliers.get(league_type, {}).get('RB', 2.0)
default_wr_mult = flex_multipliers.get(league_type, {}).get('WR', 2.0)
default_te_mult = flex_multipliers.get(league_type, {}).get('TE', 2.0)
qb_mult = st.sidebar.number_input("QB Multiplier", min_value=1.0, max_value=5.0, value=float(default_qb_mult), step=0.5)
rb_mult = st.sidebar.number_input("RB Multiplier", min_value=1.0, max_value=5.0, value=float(default_rb_mult), step=0.5)
wr_mult = st.sidebar.number_input("WR Multiplier", min_value=1.0, max_value=5.0, value=float(default_wr_mult), step=0.5)
te_mult = st.sidebar.number_input("TE Multiplier", min_value=1.0, max_value=5.0, value=float(default_te_mult), step=0.5)
user_flex_multipliers = {
'QB': qb_mult,
'RB': rb_mult,
'WR': wr_mult,
'TE': te_mult,
}
# VORP Limiters Configuration
st.sidebar.subheader("VORP Rank Adjustments")
default_qb_vorp = pos_vorp_limiters.get(league_type, {}).get('QB', 0.5)
default_rb_vorp = pos_vorp_limiters.get(league_type, {}).get('RB', 0.75)
default_wr_vorp = pos_vorp_limiters.get(league_type, {}).get('WR', 0.75)
default_te_vorp = pos_vorp_limiters.get(league_type, {}).get('TE', 0.5)
qb_vorp_lim = st.sidebar.slider("QB VORP Limiter", 0.0, 1.0, default_qb_vorp, 0.01)
rb_vorp_lim = st.sidebar.slider("RB VORP Limiter", 0.0, 1.0, default_rb_vorp, 0.01)
wr_vorp_lim = st.sidebar.slider("WR VORP Limiter", 0.0, 1.0, default_wr_vorp, 0.01)
te_vorp_lim = st.sidebar.slider("TE VORP Limiter", 0.0, 1.0, default_te_vorp, 0.01)
user_pos_vorp_limiters = {
'QB': qb_vorp_lim,
'RB': rb_vorp_lim,
'WR': wr_vorp_lim,
'TE': te_vorp_lim,
}
return user_league_settings, user_flex_percentiles, user_flex_multipliers, user_pos_vorp_limiters
def load_projections_data(api: str) -> pd.DataFrame:
calc_columns = ['Ru Yds', 'Ru TDs', 'Rec', 'Rec Yds', 'Rec TDs', 'P Yds', 'P TDs', 'INTs']
ppr_values = [.1, 6, 1, .1, 6, .04, 4, -1]
halfPpr_values = [.1, 6, .5, .1, 6, .04, 4, -1]
standard_values = [.1, 6, 0, .1, 6, .04, 4, -1]
init_data = requests.get(api)
proj_dataframe = pd.DataFrame(init_data.json())
for col in calc_columns:
proj_dataframe[col] = proj_dataframe[col].astype(float)
proj_dataframe['halfPpr'] = proj_dataframe[calc_columns].dot(halfPpr_values)
proj_dataframe['ppr'] = proj_dataframe[calc_columns].dot(ppr_values)
proj_dataframe['standard'] = proj_dataframe[calc_columns].dot(standard_values)
fpts_df = proj_dataframe[['Name', 'SR_ID', 'Pos', 'halfPpr', 'ppr', 'standard']]
return fpts_df
def load_ranks_data(api: str) -> pd.DataFrame:
init_data = requests.get(api)
ranks_dataframe = pd.DataFrame(init_data.json())
ranks_dict = dict(zip(ranks_dataframe['SR_ID'], ranks_dataframe['Rank']))
return ranks_dict
def create_position_frames(frame: pd.DataFrame, ranks: dict) -> pd.DataFrame:
qb_frame = frame[frame['Pos'] == 'QB'].sort_values(by='halfPpr', ascending=False)
rb_frame = frame[frame['Pos'] == 'RB'].sort_values(by='halfPpr', ascending=False)
wr_frame = frame[frame['Pos'] == 'WR'].sort_values(by='halfPpr', ascending=False)
te_frame = frame[frame['Pos'] == 'TE'].sort_values(by='halfPpr', ascending=False)
for slice in [qb_frame, rb_frame, wr_frame, te_frame]:
slice['Rank'] = slice['SR_ID'].map(ranks).replace(np.nan, 0).astype(int)
slice = slice[slice['Rank'] != 0]
slice = slice.sort_values(by='Rank', ascending=True)
overall_frame = pd.concat([qb_frame, rb_frame, wr_frame, te_frame]).reset_index(drop=True)
return overall_frame
def designate_custom_position_reqs(league_settings: dict, flex_percentiles: dict, flex_multipliers: dict) -> dict:
qb_base = league_settings['QB'] * league_settings['TEAMS']
rb_base = league_settings['RB'] * league_settings['TEAMS']
wr_base = league_settings['WR'] * league_settings['TEAMS']
te_base = league_settings['TE'] * league_settings['TEAMS']
# Handle both nested and flat dictionary structures
if 'QB' in flex_percentiles:
# User configuration (flat structure)
qb_rv_index = math.ceil((qb_base) * flex_multipliers['QB'])
rb_rv_index = math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['RB'])) * flex_multipliers['RB'])
wr_rv_index = math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['WR'])) * flex_multipliers['WR'])
te_rv_index = math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['TE'])) * flex_multipliers['TE'])
else:
# Default configuration (nested structure)
qb_rv_index = math.ceil((qb_base) * flex_multipliers[league_settings['TYPE']]['QB'])
rb_rv_index = math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['RB'])) * flex_multipliers[league_settings['TYPE']]['RB'])
wr_rv_index = math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['WR'])) * flex_multipliers[league_settings['TYPE']]['WR'])
te_rv_index = math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['TE'])) * flex_multipliers[league_settings['TYPE']]['TE'])
print(f"Need {qb_rv_index} for QB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
print(f"Need {rb_rv_index} for RB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
print(f"Need {wr_rv_index} for WR in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
print(f"Need {te_rv_index} for TE in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
pos_reqs = {
'QB': qb_rv_index,
'RB': rb_rv_index,
'WR': wr_rv_index,
'TE': te_rv_index,
}
return pos_reqs
def designate_base_position_reqs(league_settings: dict, flex_percentiles: dict, flex_multipliers: dict) -> dict:
qb_base = league_settings['QB'] * league_settings['TEAMS']
rb_base = league_settings['RB'] * league_settings['TEAMS']
wr_base = league_settings['WR'] * league_settings['TEAMS']
te_base = league_settings['TE'] * league_settings['TEAMS']
# Handle both nested and flat dictionary structures
if 'QB' in flex_percentiles:
# User configuration (flat structure)
qb_rv_index = math.ceil(qb_base * flex_multipliers['QB'])
rb_rv_index = math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['RB'])) * flex_multipliers['RB'])
wr_rv_index = math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['WR'])) * flex_multipliers['WR'])
te_rv_index = math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['TE'])) * flex_multipliers['TE'])
else:
# Default configuration (nested structure)
qb_rv_index = math.ceil(qb_base * flex_multipliers[league_settings['TYPE']]['QB'])
rb_rv_index = math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['RB'])) * flex_multipliers[league_settings['TYPE']]['RB'])
wr_rv_index = math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['WR'])) * flex_multipliers[league_settings['TYPE']]['WR'])
te_rv_index = math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['TE'])) * flex_multipliers[league_settings['TYPE']]['TE'])
print(f"Need {qb_rv_index} for QB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
print(f"Need {rb_rv_index} for RB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
print(f"Need {wr_rv_index} for WR in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
print(f"Need {te_rv_index} for TE in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
pos_reqs = {
'QB': qb_rv_index,
'RB': rb_rv_index,
'WR': wr_rv_index,
'TE': te_rv_index,
}
return pos_reqs
def create_halfPpr_rv(frame: pd.DataFrame, pos_reqs: dict) -> dict:
rv_dict = {}
for positions in ['QB', 'RB', 'WR', 'TE']:
rv_dict[f'{positions}'] = frame[frame['Pos'] == positions].head(pos_reqs[positions]).reset_index(drop=True)['halfPpr'].tail(1).values[0]
return rv_dict
def create_custom_rv(frame: pd.DataFrame, pos_reqs: dict, league_settings: dict) -> dict:
if league_settings['TYPE'] == 'Half PPR':
rv_type = 'halfPpr'
elif league_settings['TYPE'] == 'PPR':
rv_type = 'ppr'
elif league_settings['TYPE'] == 'Standard':
rv_type = 'standard'
elif league_settings['TYPE'] == 'Superflex':
rv_type = 'halfPpr'
rv_dict = {}
for positions in ['QB', 'RB', 'WR', 'TE']:
rv_dict[f'{positions}'] = frame[frame['Pos'] == positions].head(pos_reqs[positions]).reset_index(drop=True)[rv_type].tail(1).values[0]
return rv_dict
def assign_vorp(frame: pd.DataFrame, halfPpr_rv: dict, custom_rv: dict, league_settings: dict, pos_vorp_limiters: dict) -> pd.DataFrame:
if league_settings['TYPE'] == 'Half PPR':
rv_type = 'halfPpr'
elif league_settings['TYPE'] == 'PPR':
rv_type = 'ppr'
elif league_settings['TYPE'] == 'Standard':
rv_type = 'standard'
elif league_settings['TYPE'] == 'Superflex':
rv_type = 'halfPpr'
vorp_frame = pd.DataFrame()
for positions in ['QB', 'RB', 'WR', 'TE']:
pos_frame = frame[frame['Pos'] == positions]
pos_frame = pos_frame[pos_frame['Rank'] != 0].reset_index(drop=True)
pos_frame = pos_frame.sort_values(by='Rank', ascending=True)
pos_frame['halfPpr_rv'] = halfPpr_rv[positions]
pos_frame['custom_rv'] = custom_rv[positions]
pos_frame['halfPpr_VORP'] = pos_frame['halfPpr'] - halfPpr_rv[positions]
pos_frame['custom_VORP'] = pos_frame[rv_type] - custom_rv[positions]
vorp_frame = pd.concat([vorp_frame, pos_frame]).reset_index(drop=True)
vorp_frame['halfPpr_vorp_rank'] = vorp_frame['halfPpr_VORP'].rank(method='max', ascending=False)
vorp_frame['custom_vorp_rank'] = vorp_frame['custom_VORP'].rank(method='max', ascending=False)
vorp_frame['vorp_diff'] = vorp_frame['halfPpr_vorp_rank'] - vorp_frame['custom_vorp_rank']
# Handle both nested and flat dictionary structures
for positions in ['QB', 'RB', 'WR', 'TE']:
if 'QB' in pos_vorp_limiters:
# User configuration (flat structure)
limiter = pos_vorp_limiters[positions]
else:
# Default configuration (nested structure)
limiter = pos_vorp_limiters[league_settings['TYPE']][positions]
vorp_frame.loc[vorp_frame['Pos'] == positions, 'Rank_Adjust'] = (vorp_frame['Rank'] - (vorp_frame['vorp_diff'] * limiter)).astype(float)
vorp_frame['custom_rank'] = vorp_frame['Rank_Adjust'].rank(method='first', ascending=True)
print(vorp_frame.sort_values(by='custom_vorp_rank', ascending=True).head(50))
return vorp_frame.sort_values(by='custom_rank', ascending=True)
def main():
st.title("Fantasy Football VORP Calculator")
st.write("Configure your league settings and analyze player values")
# Get user configuration
user_league_settings, user_flex_percentiles, user_flex_multipliers, user_pos_vorp_limiters = create_user_config_interface()
# Load data
try:
projections_df = load_projections_data(Dwain_proj)
ranks_dict = load_ranks_data(dwain_ranks)
# Create position frames
position_df = create_position_frames(projections_df, ranks_dict)
# Calculate position requirements
pos_reqs = designate_custom_position_reqs(user_league_settings, user_flex_percentiles, user_flex_multipliers)
# Calculate replacement values
halfPpr_rv = create_halfPpr_rv(position_df, pos_reqs)
custom_rv = create_custom_rv(position_df, pos_reqs, user_league_settings)
# Calculate VORP and rankings
final_df = assign_vorp(position_df, halfPpr_rv, custom_rv, user_league_settings, user_pos_vorp_limiters)
# Display results
st.header("Player Rankings")
st.dataframe(final_df[['Name', 'Pos', 'Rank', 'custom_rank', 'halfPpr', 'custom_VORP', 'halfPpr_VORP']].head(50))
# Position breakdown
st.header("Position Breakdown")
for pos in ['QB', 'RB', 'WR', 'TE']:
pos_df = final_df[final_df['Pos'] == pos].head(20)
st.subheader(f"Top {pos}s")
st.dataframe(pos_df[['Name', 'Rank', 'custom_rank', 'halfPpr', 'custom_VORP']])
except Exception as e:
st.error(f"Error loading data: {str(e)}")
st.info("Please check your internet connection and try again.")
if __name__ == "__main__":
main()