DFS_NBA_TEST / app.py
acetwo3ee's picture
Update app.py
c91c8bc verified
Raw History Blame Contribute Delete
17.1 kB
import streamlit as st
import pandas as pd
import gspread
from google.oauth2.service_account import Credentials
from collections import Counter
from data_processor import DataProcessor
from optimizer import DFSOptimizer
# Set page config to wide layout
st.set_page_config(layout="wide")
# Google Sheets Config
SHEET_ID = "1PCugIPyYM1lF6KOCPUY5hL8xbGI5PWd7gVvCyudrB5c"
SHEET_NAME = "Dream11 NBA Data"
OUTPUT_SHEETS = ["Optimized_Lineups", "Boom_Bust", "Ownership%"]
# Service Account Info (unchanged)
SERVICE_ACCOUNT_INFO = {
"type": "service_account",
"project_id": "abiding-state-451516-e6",
"private_key_id": "668f5f97d95b999d0ad706445e1a9a624b6bb263",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCCIBM+5nspxseN\nD/Fls62ZRrCpPhqLX2TS+EDBFwhOtwlLmFVWGUUBBGVXhLRzrLS550p5toyhfOIU\nR2Bd0copSmLkglEwuUyClbswz+MJD4JCVuKbLugm5rMxJSuCwH/vXyvV8LtJESjK\nsxfQZ5uLkXS6i+S+SlNgR5TcdxskUa0/ymCEjiLojrbaZ64eiM75b/gakljI40ee\nZMWV6awSe2+EQnQ2W27H0uk4B2cMjEA0q1SHDRyLhwMNeaW3DIrCmUZntUev/KdD\nsamjgPPA8Z2VMvYh0ONr131EFlXq8atbUwHeefWLsRxeNmpzruJtASv10Y8fa/wl\nzADUaU+9AgMBAAECggEAJxI2dSaDSLH07PgihWUD9Qfyt358Pejwvx/nYZLm3au7\n0MT2qpl/zKRjQXoJLMpooYE/LXiQlVSIFYqw9rqtNuSs+OvgMP8SaAtJX4isJOZR\nOFYaSHo3HqcamuEMRYBDab5rzRsxQOk0FFjeB6d0Xt3X7MNgdXqfukMEAYaJ0cd+\n4LOhlMaDQ8jXdnthXh55Gz7Qyk0W5AjnckWdlaaNZ2y1MVI9zdJ4qDBdreua9p5g\n3mgBe93s2KbCEKBSzCTsoU1GbGw5sBP2w7MyPvXuyIgk2BNdDH5Sz+u/8Wdm7gIh\n8EXwdxWcDJRYxm5tTErqgGDDEvCB/bXaU0Yt2IiKSQKBgQC2mmmUGEUM87b7hOtC\n1+hpEf/iucHaGBrkxW0Fcz2/Gi5mPeb60gbMtbxmFMNPdDaqKQxi5KjhLP/iZgRk\nVWJJsJb/P8xuL64y8sI6NUdTGqTUxqB6CJOriMkLI0b6JVK/vTj3Hd88VthdYwNC\nFWvFi0ckT9Ab3Fp9+309oav6ZwKBgQC2bcTjuwsiXRKzTYoODoF/Z/+NIPCI4BZ5\nwUAwY/otQ0/dQB7B5+DruLnIWCVM4PzXoPvpLgrs/iiyFPDGTlYtUa2rBTfKHr13\nUA0Y97uRvBd6004RAbM/xHoWeX2uSRtD8kzs5TZGlzgPyS0FT7b9nb+1iWPrcMJy\naoQI3hhWOwKBgAlqLrVfBvhi/HU9Zi/dF/OK5QmYkEEhawmOkE9uoKlgvTGyUV1v\nRMB7Qkp1GZjYIhVl5miW54osmsjef614pYetHivs6ypnNPEViSQdKR9EyWjbEN0Z\n4hs/AzaazAcLs5eFNxGurZrb024FM97NYfAefT2HC/DTzKqgM9XgpxBTAoGBAKEW\nQLuO2IAZi3/EwlkzOepU3PWz1nIm3fHZUWLphWoefBE0o3Q52wSK85ITM6y1Js8J\nGiiHPaKTsz+8+fdCCRqvexj+3IqfplIK3+XiK0C+xETy/YZK6Asw5VWfJfmWGHth\nI8gzhj/DG//kdtqvzzgX6cbVcOPfh/QcVka2RustAoGBAJHVNd9QSSd5Z8aFTHmf\nv+oKjnQKzGY7j+K+bU3yxPHxfDrN5ticodaHxbho2VFaI17qcFVHkkX12k2I2O1Z\nV8Nve8i2JkFVEftVpSQ/01AhsbZflDm8ORJet2M8R2o5bcZ4Yc8/shI/j7Sz8BeM\nYMSoAW+xwwELc6a+5m8u60ZU\n-----END PRIVATE KEY-----\n",
"client_email": "dream11-optimizer@abiding-state-451516-e6.iam.gserviceaccount.com",
"client_id": "102537539700971787452",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dream11-optimizer%40abiding-state-451516-e6.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
# Initialize session state variables
if 'lineups_generated' not in st.session_state:
st.session_state.lineups_generated = False
if 'df_lineups' not in st.session_state:
st.session_state.df_lineups = None
if 'filtered_lineups' not in st.session_state:
st.session_state.filtered_lineups = None
# Authenticate Google Sheets
@st.cache_resource
def connect_google_sheets():
try:
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = Credentials.from_service_account_info(SERVICE_ACCOUNT_INFO, scopes=scope)
client = gspread.authorize(creds)
return client
except Exception as e:
st.error(f"Failed to connect to Google Sheets: {str(e)}")
return None
# Fetch player data from Google Sheets
def fetch_player_data():
client = connect_google_sheets()
if not client:
return None
try:
sheet = client.open_by_key(SHEET_ID).worksheet(SHEET_NAME)
df = pd.DataFrame(sheet.get_all_records())
if df.empty:
st.error("No data found in Google Sheet")
return None
return df
except Exception as e:
st.error(f"Error fetching player data: {str(e)}")
return None
# Save data to Google Sheets
def save_to_google_sheets(sheet_name, df):
client = connect_google_sheets()
if not client:
return
try:
sheet = client.open_by_key(SHEET_ID).worksheet(sheet_name)
sheet.clear()
sheet.update([df.columns.tolist()] + df.values.tolist())
return True
except Exception as e:
st.error(f"Error saving to {sheet_name}: {str(e)}")
return False
# Filter lineups based on include or exclude logic
def filter_lineups(df_lineups, players, filter_type):
if not players or len(players) == 0:
return df_lineups
filtered = df_lineups.copy()
player_columns = [f"Player {i}" for i in range(1, 9)]
if filter_type == "Include Players":
# Include only lineups where ALL selected players are present
for player in players:
filtered = filtered[filtered[player_columns].apply(
lambda row: player in row.values, axis=1)]
elif filter_type == "Exclude Players":
# Exclude lineups where ANY of the selected players are present
for player in players:
filtered = filtered[~filtered[player_columns].apply(
lambda row: player in row.values, axis=1)]
return filtered
def main():
st.title("NBA DFS Optimizer")
# Initialize components
data_processor = DataProcessor()
optimizer = DFSOptimizer()
# Sidebar controls
with st.sidebar:
st.header("Optimization Settings")
# Removed stacking controls
max_players_per_team = st.slider("Max players from same team", 1, 5, 5)
max_exposure = st.slider("Max player exposure (%)", 10, 100, 60)
min_salary = st.slider("Min Salary", 48000, 49900, 48500, 100)
max_salary = st.slider("Max Salary", min_salary, 50000, 50000, 100)
num_lineups = st.number_input("Number of lineups to generate", 1, 10000, 10)
if st.button("Run Optimization"):
with st.spinner("Fetching data..."):
df = fetch_player_data()
if df is None:
return
with st.spinner("Processing data and generating lineups..."):
try:
# Process data using the data processor
data_processor.load_data(df)
# Generate lineups using the optimizer
optimizer.setup_model(data_processor.players_df, max_salary)
# Add team constraint without stacking requirement
# Only enforce maximum players per team
teams = data_processor.players_df["Team Name"].unique()
for team in teams:
team_vars = [optimizer.player_vars[f"player_{idx}"]
for idx, row in data_processor.players_df.iterrows()
if row["Team Name"] == team]
if team_vars:
optimizer.model += lpSum(team_vars) <= max_players_per_team
optimizer.add_exposure_constraints(data_processor.players_df, max_exposure/100)
# Generate the specified number of lineups
lineups = []
for i in range(num_lineups):
try:
# Setup optimizer for each lineup
if i > 0:
optimizer.setup_model(data_processor.players_df, max_salary)
# Add team constraint without stacking
for team in teams:
team_vars = [optimizer.player_vars[f"player_{idx}"]
for idx, row in data_processor.players_df.iterrows()
if row["Team Name"] == team]
if team_vars:
optimizer.model += lpSum(team_vars) <= max_players_per_team
# Add exposure constraint
optimizer.add_exposure_constraints(data_processor.players_df, max_exposure/100)
# Add minimum salary constraint
optimizer.model += lpSum([optimizer.player_vars[f"player_{idx}"] * row["Credits"]
for idx, row in data_processor.players_df.iterrows()]) >= min_salary
# Add uniqueness constraint
for prev_lineup in lineups:
prev_players = [p["Player"] for p in prev_lineup["players"]]
overlap_vars = [optimizer.player_vars[f"player_{idx}"]
for idx, row in data_processor.players_df.iterrows()
if row["Player"] in prev_players]
if overlap_vars:
optimizer.model += lpSum(overlap_vars) <= 6 # Allow max 6 players overlap
# Solve the optimization model
from pulp import LpStatus, lpSum, value
optimizer.model.solve()
# Check if a solution was found
if optimizer.model.status != 1: # 1 means optimal solution found
st.warning(f"No optimal solution found for lineup {i+1}. Stopping generation.")
break
# Get the selected players from the solution
selected_players = []
total_points = 0
total_salary = 0
for var_name, var in optimizer.player_vars.items():
if var.value() > 0:
player_idx = int(var_name.split('_')[1])
player_data = data_processor.players_df.iloc[player_idx]
selected_players.append({
"Player": player_data["Player"],
"Position": player_data["Position"],
"Team": player_data["Team Name"],
"Salary": player_data["Credits"],
"Projected": player_data["Projected Points"]
})
total_points += player_data["Projected Points"]
total_salary += player_data["Credits"]
lineups.append({
"players": selected_players,
"total_points": total_points,
"total_salary": total_salary
})
except Exception as e:
st.error(f"Error generating lineup {i+1}: {str(e)}")
continue
if not lineups:
st.error("No valid lineups found with given constraints")
return
# Convert lineups to DataFrame format
lineup_data = []
team_headers = sorted(set(df["Team Name"]))
for lineup in lineups:
players = lineup["players"]
total_credits = lineup["total_salary"]
total_points = lineup["total_points"]
# Count positions
pos_counts = Counter(player["Position"] for player in players)
pos_counts_str = f"{pos_counts.get('PG', 0)}-{pos_counts.get('SG', 0)}-" \
f"{pos_counts.get('SF', 0)}-{pos_counts.get('PF', 0)}-{pos_counts.get('C', 0)}"
# Count lineup status
yes_count = sum(1 for player in players if
df[df["Player"] == player["Player"]]["Lineup Status"].values[0] == "Yes")
no_count = sum(1 for player in players if
df[df["Player"] == player["Player"]]["Lineup Status"].values[0] == "No")
# Count teams
team_counts = Counter(player["Team"] for player in players)
team_columns = {team: team_counts.get(team, 0) for team in team_headers}
# Create lineup row
row = [player["Player"] for player in players]
row += [total_credits, total_points, pos_counts_str, yes_count, no_count]
row += list(team_columns.values())
lineup_data.append(row)
columns = [f"Player {i}" for i in range(1, 9)]
columns += ["Credits", "FP", "Pos_Count", "Yes", "No"] + team_headers
# Store in session state
st.session_state.df_lineups = pd.DataFrame(lineup_data, columns=columns)
st.session_state.filtered_lineups = st.session_state.df_lineups.copy()
st.session_state.lineups_generated = True
# Generate player usage for ownership percentages
all_players = [player for lineup in lineups for player in lineup["players"]]
player_counter = Counter(player["Player"] for player in all_players)
# Save lineups to Google Sheets
save_to_google_sheets("Optimized_Lineups", st.session_state.df_lineups)
# Generate and save Boom/Bust Sheet
boom_bust_df = data_processor.get_boom_bust_analysis()
save_to_google_sheets("Boom_Bust", boom_bust_df)
# Generate and save Ownership Sheet
ownership_df = data_processor.calculate_ownership(lineups)
save_to_google_sheets("Ownership%", ownership_df)
except Exception as e:
st.error(f"Error during optimization: {str(e)}")
import traceback
st.code(traceback.format_exc())
return
# Display results if lineups are generated
if st.session_state.lineups_generated and st.session_state.df_lineups is not None:
# Player filter in sidebar with include/exclude options
with st.sidebar:
st.header("Filter Lineups")
total_players = data_processor.players_df["Player"].tolist() if 'data_processor' in locals() and hasattr(data_processor, 'players_df') else []
if not total_players and 'df' in locals():
total_players = df["Player"].tolist()
filter_option = st.selectbox("Filter by", ["All", "Include Players", "Exclude Players"], key="filter_option")
if filter_option in ["Include Players", "Exclude Players"]:
players_to_filter = st.multiselect(
f"Which players would you like to {'include in' if filter_option == 'Include Players' else 'exclude from'} the lineups?",
options=total_players,
key="player_filter"
)
else:
players_to_filter = []
# Apply filter based on selected option
filtered_lineups = filter_lineups(st.session_state.df_lineups, players_to_filter, filter_option)
st.session_state.filtered_lineups = filtered_lineups
# Display Filtered Optimized Lineups with vertical scroll
st.subheader("Optimized Lineups")
if len(filtered_lineups) > 0:
st.dataframe(
filtered_lineups.style.format({
"Credits": "{:.0f}",
"FP": "{:.1f}",
"Yes": "{:.0f}",
"No": "{:.0f}"
}),
use_container_width=True,
height=400 # Fixed height with vertical scrollbar
)
else:
st.warning(f"No lineups match the selected filter ({filter_option}).")
st.success(f"✅ Generated {len(st.session_state.df_lineups)} lineups (showing {len(filtered_lineups)} after filtering) and saved to Google Sheets!")
if __name__ == "__main__":
from pulp import LpVariable, LpProblem, LpMaximize, LpBinary, lpSum, LpStatus
main()