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()