"""Tests for the model and optimization modules.""" import numpy as np import pandas as pd import pytest class TestGBMEnsemble: def test_fit_predict(self): from src.models.gbm_model import GBMEnsemble np.random.seed(42) X = pd.DataFrame(np.random.randn(100, 10)) y = pd.Series(np.random.randn(100) * 2 + 6.5) # ~fantavoto range model = GBMEnsemble(n_estimators=50, n_bootstrap=20) model.fit(X, y) preds = model.predict(X) assert len(preds) == len(y) assert preds.dtype == np.float64 def test_distribution_prediction(self): from src.models.gbm_model import GBMEnsemble np.random.seed(42) X = pd.DataFrame(np.random.randn(50, 5)) y = pd.Series(np.random.randn(50) + 6.5) model = GBMEnsemble(n_estimators=50, n_bootstrap=30) model.fit(X, y) mean, std = model.predict_distribution(X) assert len(mean) == len(y) assert len(std) == len(y) assert np.all(std > 0) def test_feature_importance(self): from src.models.gbm_model import GBMEnsemble np.random.seed(42) X = pd.DataFrame(np.random.randn(100, 5)) X.columns = ["f1", "f2", "f3", "f4", "f5"] y = pd.Series(np.random.randn(100) + 6.5) model = GBMEnsemble(n_estimators=50) model.fit(X, y) importance = model.feature_importance() assert len(importance) == 5 class TestSinhArcsinhDistribution: def test_normal_case(self): from src.models.distribution_head import ( SinhArcsinhDistribution, sinh_arcsinh_params, ) raw = np.array([[6.5, 0.5, 0.0, 0.5]]) # loc=6.5, scale~softplus(0.5) loc, scale, skew, tail = sinh_arcsinh_params(raw) dist = SinhArcsinhDistribution(loc, scale, skew, tail) mean = dist.mean() assert 5 < mean[0] < 8 # reasonable range def test_skewed_case(self): from src.models.distribution_head import SinhArcsinhDistribution # Attacker with high upside skew dist = SinhArcsinhDistribution( np.array([7.0]), np.array([1.0]), np.array([1.5]), np.array([1.2]), # positive skew → right tail ) mean = dist.mean() assert mean[0] > 7.0 # right-skewed → mean > loc class TestCardClassifier: def test_fit_predict(self): from src.models.card_model import CardClassifier np.random.seed(42) n = 200 X = pd.DataFrame(np.random.randn(n, 10)) # ~15% yellow card rate y = pd.Series((np.random.rand(n) < 0.15).astype(int)) model = CardClassifier(card_type="yellow") model.fit(X, y) probs = model.predict_proba(X) assert len(probs) == n assert np.all(probs >= 0) and np.all(probs <= 1) class TestAuctionSolver: def test_solve(self): from src.optimization.auction_solver import AuctionSolver, AuctionConfig config = AuctionConfig( total_budget=500, n_gk=3, n_def=8, n_mid=8, n_fwd=6, ) solver = AuctionSolver(config=config) # Create a small player pool players = [] roles = ["P"] * 5 + ["D"] * 15 + ["C"] * 15 + ["A"] * 10 np.random.seed(42) for i, role in enumerate(roles): players.append({ "name": f"Player_{i}", "team": f"Team_{i % 20}", "role": role, "projected_points": np.random.uniform(5, 9), "market_value": np.random.randint(5, 30), }) solver.add_players(pd.DataFrame(players)) result = solver.solve() assert "selected_players" in result assert len(result["selected_players"]) == config.n_gk + config.n_def + config.n_mid + config.n_fwd assert result["total_cost"] <= config.total_budget def test_grid_auction(self): from src.optimization.auction_solver import AuctionSolver, PlayerValuation solver = AuctionSolver() round_players = [ PlayerValuation("Player_A", "Inter", "A", 8.5, 30, 60), PlayerValuation("Player_B", "Milan", "A", 8.0, 25, 55), PlayerValuation("Player_C", "Juventus", "D", 7.0, 15, 35), ] recs = solver.grid_auction_strategy(round_players) assert "Player_A" in recs assert recs["Player_A"]["max_bid"] > 0 class TestLineupSolver: def test_optimize(self): from src.optimization.lineup_solver import LineupSolver, PlayerScore np.random.seed(42) pool = [] for i in range(25): role = ( "P" if i == 0 else "D" if i < 9 else "C" if i < 17 else "A" ) pool.append(PlayerScore( name=f"P{i}", role=role, team="T", oppteam="O", home=True, fv_mean=np.random.uniform(5.5, 8), fv_std=1.0, mv_mean=6.0, mv_std=0.5, starter_prob=np.random.uniform(0.5, 1.0), )) solver = LineupSolver(iters=500) result = solver.optimize(pool) assert "lineup" in result assert len(result["lineup"]) == 11 assert result["win_probability"] > 0 def test_validate_lineup(self): from src.optimization.lineup_solver import LineupSolver, PlayerScore solver = LineupSolver() valid = [ PlayerScore("GK", "P", "T", "O", True, 6.0, 1, 6.0, 0.5), *[PlayerScore(f"D{j}", "D", "T", "O", True, 6.0, 1, 6.0, 0.5) for j in range(4)], *[PlayerScore(f"M{k}", "C", "T", "O", True, 6.0, 1, 6.0, 0.5) for k in range(4)], *[PlayerScore(f"F{l}", "A", "T", "O", True, 6.0, 1, 6.0, 0.5) for l in range(2)], ] assert solver._validate_lineup(valid) # Too few defenders invalid = [ PlayerScore("GK", "P", "T", "O", True, 6.0, 1, 6.0, 0.5), *[PlayerScore(f"D{j}", "D", "T", "O", True, 6.0, 1, 6.0, 0.5) for j in range(2)], *[PlayerScore(f"M{k}", "C", "T", "O", True, 6.0, 1, 6.0, 0.5) for k in range(5)], *[PlayerScore(f"F{l}", "A", "T", "O", True, 6.0, 1, 6.0, 0.5) for l in range(3)], ] assert not solver._validate_lineup(invalid) def test_modificatore(self): from src.optimization.lineup_solver import LineupSolver solver = LineupSolver() # 3 defenders averaging 7.0 → +6 bonus bonus = solver._modificatore_bonus([7.0, 7.0, 7.0]) assert bonus == 6.0 # Average 6.3 → +1 bonus = solver._modificatore_bonus([6.5, 6.0, 6.4]) assert bonus == 1.0 # Average 5.5 → 0 bonus = solver._modificatore_bonus([5.5, 5.5, 5.5]) assert bonus == 0.0 class TestTransferAnalyzer: def test_buy_low(self): from src.optimization.transfer_analyzer import TransferAnalyzer analyzer = TransferAnalyzer() df = pd.DataFrame([ { "name": "Underperformer", "team": "TeamA", "role": "A", "actual_fv_avg": 4.5, "xg": 0.9, "xa": 0.5, "minutes": 900, "market_value": 3, "minutes_trend": 1, "historical_fv_avg": 7.0, }, { "name": "Overperformer", "team": "TeamB", "role": "A", "actual_fv_avg": 9.0, "xg": 0.3, "xa": 0.1, "minutes": 500, "market_value": 30, "minutes_trend": -1, "historical_fv_avg": 6.5, }, ]) buy = analyzer.analyze_buy_low(df) assert len(buy) >= 0 # May or may not find candidates with this data def test_sell_high(self): from src.optimization.transfer_analyzer import TransferAnalyzer analyzer = TransferAnalyzer() df = pd.DataFrame([ { "name": "Overperformer", "team": "TeamB", "role": "A", "actual_fv_avg": 9.0, "xg": 0.3, "xa": 0.1, "minutes": 500, "historical_fv_avg": 6.5, }, ]) sell = analyzer.analyze_sell_high(df) assert len(sell) > 0 assert sell.iloc[0]["recommendation"] == "SELL-HIGH" class TestOpponentModel: def test_predict_lineup(self): from src.optimization.opponent_model import OpponentModel model = OpponentModel() # Add some history model.add_lineup(1, ["GK1", "D1", "D2", "D3", "D4", "M1", "M2", "M3", "M4", "F1", "F2"], "4-4-2") model.add_lineup(2, ["GK1", "D1", "D2", "D3", "D4", "M1", "M2", "M3", "M5", "F1", "F2"], "4-4-2") squad = [ {"name": "GK1", "role": "P", "fv_mean": 6.5, "starter_prob": 0.95}, *[{"name": f"D{j}", "role": "D", "fv_mean": 6.0, "starter_prob": 0.8} for j in range(8)], *[{"name": f"M{k}", "role": "C", "fv_mean": 6.5, "starter_prob": 0.8} for k in range(8)], *[{"name": f"F{l}", "role": "A", "fv_mean": 7.0, "starter_prob": 0.8} for l in range(6)], ] result = model.predict_lineup(squad) assert "predicted_lineup" in result assert len(result["predicted_lineup"]) == 11 assert result["predicted_formation"] == "4-4-2"