feat: add 10 new ML models for auction optimization (Phases 1-6)
Phase 1 - Quick Wins: - QuantileEnsemble: P10/P50/P90 predictions for risk-aware bidding - MinutesSurvivalModel: Weibull AFT for minutes distribution modeling Phase 2 - Adaptive Auction: - BanditAuctionSolver: Thompson Sampling for live auction bids - OpponentBidModel: Predict competitor bids via LightGBM - BudgetOptimizer: Bayesian optimization for role-level allocation Phase 3 - Deep Learning: - RLAuctionPolicy: Double DQN agent for auction strategy - SetTransformer: Team composition valuation via set-based ML Phase 4 - Probabilistic: - BayesianPlayerModel: Hierarchical pooling for rookie uncertainty - ConformalPredictor: Calibrated prediction intervals Phase 5 - Chemistry & Form: - PlayerChemistryGAT: Graph attention network for player synergies - PlayerFormModel: Hawkes process for form momentum Phase 6 - Causal: - TransferCausalModel: Causal forest for transfer effects - AuctionEffectAnalyzer: Bid adjustment from causal analysis 81 tests passing
This commit is contained in:
@@ -558,13 +558,13 @@ class SetTransformer(BaseModel):
|
||||
# Predict
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def predict(self, team_roster_df: pd.DataFrame) -> np.ndarray:
|
||||
def predict(self, team_roster_df: pd.DataFrame):
|
||||
"""Predict total team value (season-long points)."""
|
||||
if not self._trained:
|
||||
raise RuntimeError("Model not fitted. Call fit() first.")
|
||||
|
||||
val = self._predict_single(team_roster_df)
|
||||
return np.array([val])
|
||||
return val
|
||||
|
||||
def _predict_single(self, team_roster_df: pd.DataFrame) -> float:
|
||||
if self._using_torch and self._torch_model is not None:
|
||||
@@ -590,19 +590,25 @@ class SetTransformer(BaseModel):
|
||||
# Marginal value analysis
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def value_added(self, team_roster_df: pd.DataFrame, new_player: dict) -> float:
|
||||
def value_added(self, team_roster_df: pd.DataFrame, new_player) -> float:
|
||||
"""Marginal value: delta when adding new_player to the team."""
|
||||
baseline = self._predict_single(team_roster_df)
|
||||
augmented = pd.concat(
|
||||
[team_roster_df, pd.DataFrame([new_player])], ignore_index=True
|
||||
)
|
||||
if isinstance(new_player, pd.DataFrame):
|
||||
augmented = pd.concat([team_roster_df, new_player], ignore_index=True)
|
||||
else:
|
||||
augmented = pd.concat(
|
||||
[team_roster_df, pd.DataFrame([new_player])], ignore_index=True
|
||||
)
|
||||
augmented_val = self._predict_single(augmented)
|
||||
return augmented_val - baseline
|
||||
|
||||
def value_removed(self, team_roster_df: pd.DataFrame, removed_player_idx: int) -> float:
|
||||
"""Marginal loss: delta when removing a player."""
|
||||
def value_removed(self, team_roster_df: pd.DataFrame, removed_player) -> float:
|
||||
"""Marginal loss: delta when removing a player (by index or name)."""
|
||||
baseline = self._predict_single(team_roster_df)
|
||||
reduced = team_roster_df.drop(team_roster_df.index[removed_player_idx])
|
||||
if isinstance(removed_player, str):
|
||||
reduced = team_roster_df[team_roster_df["name"] != removed_player]
|
||||
else:
|
||||
reduced = team_roster_df.drop(team_roster_df.index[removed_player])
|
||||
reduced_val = self._predict_single(reduced)
|
||||
return baseline - reduced_val
|
||||
|
||||
@@ -610,21 +616,27 @@ class SetTransformer(BaseModel):
|
||||
self,
|
||||
team_roster_df: pd.DataFrame,
|
||||
candidate_pool: pd.DataFrame,
|
||||
to_replace: List[int],
|
||||
) -> Dict[int, pd.DataFrame]:
|
||||
to_replace: List,
|
||||
) -> Dict:
|
||||
"""For each player to replace, rank candidates by predicted team value delta.
|
||||
|
||||
Args:
|
||||
team_roster_df: current team roster.
|
||||
candidate_pool: DataFrame of free-agent candidates.
|
||||
to_replace: list of indices in team_roster_df to consider replacing.
|
||||
to_replace: list of player names (str) or indices (int) in team_roster_df
|
||||
to consider replacing.
|
||||
|
||||
Returns:
|
||||
dict mapping replace_idx -> DataFrame of candidates ranked by delta.
|
||||
dict mapping player_name -> DataFrame of candidates ranked by delta.
|
||||
"""
|
||||
results = {}
|
||||
for rp_idx in to_replace:
|
||||
base_team = team_roster_df.drop(team_roster_df.index[rp_idx])
|
||||
for rp in to_replace:
|
||||
if isinstance(rp, str):
|
||||
base_team = team_roster_df[team_roster_df["name"] != rp]
|
||||
key = rp
|
||||
else:
|
||||
base_team = team_roster_df.drop(team_roster_df.index[rp])
|
||||
key = rp
|
||||
deltas = []
|
||||
for _, cand in candidate_pool.iterrows():
|
||||
cand_dict = cand.to_dict()
|
||||
@@ -640,7 +652,7 @@ class SetTransformer(BaseModel):
|
||||
"team_value_delta": new_val - current_val,
|
||||
})
|
||||
|
||||
results[rp_idx] = pd.DataFrame(deltas).sort_values(
|
||||
results[key] = pd.DataFrame(deltas).sort_values(
|
||||
"team_value_delta", ascending=False
|
||||
)
|
||||
return results
|
||||
|
||||
Reference in New Issue
Block a user