import streamlit as st import pandas as pd import numpy as np import gspread from google.oauth2.service_account import Credentials from itertools import combinations from collections import Counter from tqdm import tqdm from data_processor import DataProcessor # 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" } # 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 required_columns = ["Player", "Position", "Credits", "Projected Points", "Team Name", "Lineup Status"] missing_required = [col for col in required_columns if col not in df.columns] if missing_required: raise ValueError(f"Missing required columns: {missing_required}") df["Credits"] = pd.to_numeric(df["Credits"], errors="coerce").fillna(0) df["Projected Points"] = pd.to_numeric(df["Projected Points"], errors="coerce").fillna(0) if "Std Dev" in df.columns: df["Std Dev"] = pd.to_numeric(df["Std Dev"], errors="coerce").fillna(0) else: df["Std Dev"] = 0 return df except Exception as e: st.error(f"Error fetching player data: {str(e)}") return None # Generate valid lineups with progress bar def generate_valid_lineups(players, min_stack=0, max_exposure=100, num_lineups=10): valid_lineups = [] player_combinations = combinations(players, 8) player_usage = Counter() progress_bar = st.progress(0) total_combinations = min(num_lineups, sum(1 for _ in player_combinations)) player_combinations = combinations(players, 8) # Recreate iterator for i, lineup in enumerate(tqdm(player_combinations, total=total_combinations, desc="Generating Lineups")): lineup_df = pd.DataFrame(lineup) lineup_df["Position"] = lineup_df["Position"].str.strip() total_credits = lineup_df["Credits"].sum() total_points = lineup_df["Projected Points"].sum() # Position constraints position_counts = {"PG": 0, "SG": 0, "SF": 0, "PF": 0, "C": 0} for pos in lineup_df["Position"]: if pos in position_counts: position_counts[pos] += 1 if not all(1 <= position_counts[pos] <= 4 for pos in position_counts): continue # Team constraints team_counts = lineup_df["Team Name"].value_counts() if min_stack > 0 and team_counts.max() < min_stack: continue if team_counts.max() > 5: continue # Salary cap if not (48500 <= total_credits <= 50000): continue # Exposure check if any(player_usage.get(p, 0) / (len(valid_lineups) + 1) * 100 > max_exposure for p in lineup_df["Player"]): continue valid_lineups.append((lineup_df, total_credits, total_points, position_counts, team_counts)) for player in lineup_df["Player"]: player_usage[player] += 1 progress_bar.progress(min((i + 1) / total_combinations, 1.0)) if len(valid_lineups) >= num_lineups: break progress_bar.empty() return sorted(valid_lineups, key=lambda x: x[2], reverse=True), player_usage # Generate accurate Boom/Bust analysis def generate_boom_bust_sheet(players_df): boom_bust_data = [] for _, row in players_df.iterrows(): proj_points = row["Projected Points"] std_dev = row["Std Dev"] if std_dev == 0: pos_factor = {"PG": 0.25, "SG": 0.22, "SF": 0.20, "PF": 0.18, "C": 0.15}.get(row["Position"], 0.20) std_dev = proj_points * pos_factor boom_points = proj_points + 1.5 * std_dev bust_points = max(0, proj_points - 1.5 * std_dev) boom_prob = 1 - 0.9332 bust_prob = 0.0668 boom_bust_data.append([ row["Player"], row["Team Name"], proj_points, round(boom_points, 1), round(bust_points, 1), round(boom_prob * 100, 1), round(bust_prob * 100, 1) ]) return pd.DataFrame(boom_bust_data, columns=[ "Player", "Team", "Projected Points", "Boom Points", "Bust Points", "Boom Probability %", "Bust Probability %" ]) # 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()) except Exception as e: st.error(f"Error saving to {sheet_name}: {str(e)}") # Generate Ownership % Data with Player Count and Corrected Value Score def generate_ownership_sheet(players_df, player_usage, total_lineups): ownership_data = [] for _, row in players_df.iterrows(): player_name = row["Player"] player_count = player_usage.get(player_name, 0) ownership_pct = (player_count / total_lineups) * 100 if total_lineups > 0 else 0 value_score = (row["Projected Points"] * 500) / row["Credits"] if row["Credits"] > 0 else 0 ownership_data.append([ player_name, row["Team Name"], row["Credits"], row["Projected Points"], player_count, round(ownership_pct, 2), round(value_score, 2) ]) return pd.DataFrame(ownership_data, columns=[ "Player", "Team", "Credits", "Projected Points", "Count", "Ownership %", "Value" ]) # 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 if filter_type == "Include Players": # Include only lineups where ALL selected players are present for player in players: filtered = filtered[filtered[["Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8"]].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 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8"]].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 st.sidebar.header("Optimization Settings") num_lineups = st.sidebar.slider("Number of Lineups", 1, 50, 10) min_stack = st.sidebar.slider("Minimum Stack", 1, 5, 2) max_stack = st.sidebar.slider("Maximum Stack", min_stack, 5, 4) max_exposure = st.sidebar.slider("Maximum Exposure %", 20, 100, 60) / 100 # Load data df = fetch_player_data() if df is None: st.error("Failed to load player data") return try: data_processor.load_data(df) except ValueError as e: st.error(str(e)) return # Sidebar controls with st.sidebar: st.header("Optimization Settings") min_stack = st.slider("Min players from same team (stacking)", 0, 5, 0) max_exposure = st.slider("Max player exposure (%)", 10, 100, 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 st.session_state.df = df players = df.to_dict("records") st.write(f"Generating {num_lineups} Optimized Lineups...") # Generate Lineups valid_lineups, player_usage = generate_valid_lineups(players, min_stack, max_exposure, num_lineups) if not valid_lineups: st.error("No valid lineups found with given constraints") return # Prepare lineup data lineup_data = [] team_headers = sorted(set(df["Team Name"])) for lineup_df, credits, points, pos_counts, team_counts in valid_lineups: pos_counts_str = f"{pos_counts['PG']}-{pos_counts['SG']}-{pos_counts['SF']}-{pos_counts['PF']}-{pos_counts['C']}" yes_count = sum(lineup_df["Lineup Status"] == "Yes") no_count = sum(lineup_df["Lineup Status"] == "No") team_columns = {team: team_counts.get(team, 0) for team in team_headers} row = lineup_df["Player"].tolist() + [credits, points, pos_counts_str, yes_count, no_count] + list(team_columns.values()) lineup_data.append(row) # Store in session state st.session_state.df_lineups = pd.DataFrame(lineup_data, columns=["Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8", "Credits", "FP", "Pos_Count", "Yes", "No"] + team_headers) st.session_state.filtered_lineups = st.session_state.df_lineups.copy() st.session_state.player_usage = player_usage st.session_state.lineups_generated = True # Save all lineups to Google Sheets save_to_google_sheets("Optimized_Lineups", st.session_state.df_lineups) # Generate and save Boom/Bust Sheet boom_bust_df = generate_boom_bust_sheet(df) save_to_google_sheets("Boom_Bust", boom_bust_df) # Generate and save Ownership Sheet ownership_df = generate_ownership_sheet(df, player_usage, len(valid_lineups)) save_to_google_sheets("Ownership%", ownership_df) # 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 = st.session_state.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.write("Generated Lineups Sample:", st.session_state.df_lineups.head()) st.write(f"{'Players to Include' if filter_option == 'Include Players' else 'Players to Exclude' if filter_option == 'Exclude Players' else 'No Filter'}:", players_to_filter) 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__": main()