File size: 5,388 Bytes
dc03771
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import pandas as pd
import numpy as np
from typing import List, Dict, Optional, Tuple
from collections import Counter

class DataProcessor:
    def __init__(self):
        self.players_df = None
        self.position_limits = {
            "PG": (1, 4),
            "SG": (1, 4),
            "SF": (1, 4),
            "PF": (1, 4),
            "C": (1, 4)
        }
    
    def load_data(self, df: pd.DataFrame) -> None:
        """Load and validate player data"""
        required_cols = ["Player", "Position", "Credits", "Projected Points", 
                        "Team Name", "Lineup Status"]
        if not all(col in df.columns for col in required_cols):
            raise ValueError(f"Missing required columns: {required_cols}")
        
        self.players_df = df.copy()
        self._preprocess_data()
    
    def _preprocess_data(self) -> None:
        """Clean and preprocess the data"""
        # Convert numeric columns
        numeric_cols = ["Credits", "Projected Points"]
        for col in numeric_cols:
            self.players_df[col] = pd.to_numeric(self.players_df[col], errors="coerce").fillna(0)
        
        # Add standard deviation if not present
        if "Std Dev" not in self.players_df.columns:
            self.players_df["Std Dev"] = self._calculate_std_dev()
        
        # Filter out inactive players
        self.players_df = self.players_df[self.players_df["Lineup Status"] != "Out"]
    
    def _calculate_std_dev(self) -> pd.Series:
        """Calculate standard deviation based on position and projected points"""
        pos_factors = {
            "PG": 0.25,
            "SG": 0.22,
            "SF": 0.20,
            "PF": 0.18,
            "C": 0.15
        }
        
        return self.players_df.apply(
            lambda x: x["Projected Points"] * pos_factors.get(x["Position"], 0.20),
            axis=1
        )
    
    def get_boom_bust_analysis(self) -> pd.DataFrame:
        """Generate boom/bust analysis for all players"""
        boom_bust_data = []
        
        for _, player in self.players_df.iterrows():
            proj_points = player["Projected Points"]
            std_dev = player["Std Dev"]
            
            boom_points = proj_points + 1.5 * std_dev
            bust_points = max(0, proj_points - 1.5 * std_dev)
            
            # Calculate probabilities using normal distribution
            boom_prob = 1 - 0.9332  # Probability above 1.5 std dev
            bust_prob = 0.0668      # Probability below -1.5 std dev
            
            boom_bust_data.append({
                "Player": player["Player"],
                "Team": player["Team Name"],
                "Projected Points": proj_points,
                "Boom Points": round(boom_points, 1),
                "Bust Points": round(bust_points, 1),
                "Boom Probability %": round(boom_prob * 100, 1),
                "Bust Probability %": round(bust_prob * 100, 1)
            })
        
        return pd.DataFrame(boom_bust_data)
    
    def calculate_ownership(self, lineups: List[Dict]) -> pd.DataFrame:
        """Calculate ownership percentages from generated lineups"""
        player_count = Counter()
        total_lineups = len(lineups)
        
        # Count player appearances
        for lineup in lineups:
            for player in lineup["players"]:
                player_count[player["Player"]] += 1
        
        ownership_data = []
        for _, player in self.players_df.iterrows():
            count = player_count.get(player["Player"], 0)
            ownership_pct = (count / total_lineups * 100) if total_lineups > 0 else 0
            value_score = (player["Projected Points"] * 500) / player["Credits"] \
                         if player["Credits"] > 0 else 0
            
            ownership_data.append({
                "Player": player["Player"],
                "Team": player["Team Name"],
                "Credits": player["Credits"],
                "Projected Points": player["Projected Points"],
                "Count": count,
                "Ownership %": round(ownership_pct, 2),
                "Value": round(value_score, 2)
            })
        
        return pd.DataFrame(ownership_data)
    
    def validate_lineup(self, lineup: List[Dict]) -> Tuple[bool, str]:
        """Validate a lineup against all constraints"""
        if len(lineup) != 8:
            return False, "Lineup must have exactly 8 players"
        
        # Check salary cap
        total_salary = sum(player["Salary"] for player in lineup)
        if not (48500 <= total_salary <= 50000):
            return False, f"Invalid salary: {total_salary}"
        
        # Check position limits
        pos_counts = Counter(player["Position"] for player in lineup)
        for pos, (min_count, max_count) in self.position_limits.items():
            count = pos_counts.get(pos, 0)
            if not (min_count <= count <= max_count):
                return False, f"Invalid {pos} count: {count}"
        
        # Check team limits
        team_counts = Counter(player["Team"] for player in lineup)
        if max(team_counts.values()) > 5:
            return False, "Too many players from same team"
        
        return True, "Valid lineup"