File size: 4,865 Bytes
6424951
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92a832f
6424951
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Tests for ML model module."""

from unittest.mock import MagicMock, patch

import numpy as np
import pytest

from src.ml.model import ModelLoadError, analyze_team_stats, predict_winner


class TestAnalyzeTeamStats:
    """Tests for analyze_team_stats function."""

    def test_flattens_stats_correctly(
        self, sample_team_stats: list[list[float]]
    ) -> None:
        """Test that team stats are flattened correctly."""
        home_array, away_array, combined = analyze_team_stats(
            sample_team_stats, sample_team_stats
        )

        # Each team has 5 players x 10 stats = 50 values
        assert home_array.shape == (1, 50)
        assert away_array.shape == (1, 50)
        # Combined has both teams = 100 values
        assert combined.shape == (1, 100)

    def test_combined_contains_both_teams(
        self, sample_team_stats: list[list[float]]
    ) -> None:
        """Test that combined array contains both teams' stats."""
        home_stats = [[1.0, 2.0], [3.0, 4.0]]  # 2 players, 2 stats each
        away_stats = [[5.0, 6.0], [7.0, 8.0]]

        _home_array, _away_array, combined = analyze_team_stats(
            home_stats, away_stats
        )

        # Home should be first 4 values, away next 4
        np.testing.assert_array_equal(
            combined[0], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
        )


class TestPredictWinner:
    """Tests for predict_winner function."""

    @patch("src.ml.model.get_winner_model")
    def test_returns_probability_and_prediction(
        self, mock_get_model: MagicMock
    ) -> None:
        """Test that function returns (probability, prediction) tuple."""
        mock_model = MagicMock()
        mock_model.predict.return_value = np.array([[0.75]])
        mock_get_model.return_value = mock_model

        stats = np.random.rand(1, 100)
        probability, prediction = predict_winner(stats)

        assert isinstance(probability, float)
        assert isinstance(prediction, int)
        assert 0.0 <= probability <= 1.0
        assert prediction in (0, 1)

    @patch("src.ml.model.get_winner_model")
    def test_high_probability_predicts_win(
        self, mock_get_model: MagicMock
    ) -> None:
        """Test that high probability (>0.5) predicts home win (1)."""
        mock_model = MagicMock()
        mock_model.predict.return_value = np.array([[0.8]])
        mock_get_model.return_value = mock_model

        stats = np.random.rand(1, 100)
        probability, prediction = predict_winner(stats)

        assert probability == 0.8
        assert prediction == 1

    @patch("src.ml.model.get_winner_model")
    def test_low_probability_predicts_loss(
        self, mock_get_model: MagicMock
    ) -> None:
        """Test that low probability (<0.5) predicts home loss (0)."""
        mock_model = MagicMock()
        mock_model.predict.return_value = np.array([[0.3]])
        mock_get_model.return_value = mock_model

        stats = np.random.rand(1, 100)
        probability, prediction = predict_winner(stats)

        assert probability == 0.3
        assert prediction == 0

    @patch("src.ml.model.get_winner_model")
    def test_invalid_shape_raises_error(
        self, mock_get_model: MagicMock
    ) -> None:
        """Test that invalid input shape raises ValueError."""
        mock_model = MagicMock()
        mock_get_model.return_value = mock_model

        # Wrong shape
        stats = np.random.rand(1, 50)

        with pytest.raises(ValueError) as exc_info:
            predict_winner(stats)

        assert "Expected input shape (1, 100)" in str(exc_info.value)

    @patch("src.ml.model.get_winner_model")
    def test_model_called_with_verbose_zero(
        self, mock_get_model: MagicMock
    ) -> None:
        """Test that model.predict is called with verbose=0."""
        mock_model = MagicMock()
        mock_model.predict.return_value = np.array([[0.5]])
        mock_get_model.return_value = mock_model

        stats = np.random.rand(1, 100)
        predict_winner(stats)

        mock_model.predict.assert_called_once_with(stats, verbose=0)


class TestGetWinnerModel:
    """Tests for get_winner_model caching."""

    @patch("src.ml.model.load_model")
    @patch("src.ml.model.Path")
    def test_raises_error_for_missing_model(
        self, mock_path: MagicMock, mock_load: MagicMock
    ) -> None:
        """Test that missing model file raises ModelLoadError."""
        from src.ml.model import get_winner_model

        # Clear the cache to ensure fresh test
        get_winner_model.clear()

        mock_path_instance = MagicMock()
        mock_path_instance.exists.return_value = False
        mock_path.return_value = mock_path_instance

        with pytest.raises(ModelLoadError) as exc_info:
            get_winner_model("nonexistent.keras")

        assert "not found" in str(exc_info.value)