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:
ramseshk
2026-08-11 17:56:03 +08:00
parent 916278a640
commit b0fab62a87
16 changed files with 4955 additions and 21 deletions
+28 -16
View File
@@ -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