"""Tests for the feature engineering pipeline.""" import numpy as np import pandas as pd import pytest from src.features.vote_processor import VoteProcessor from src.features.advanced_metrics import FatigueIndex, PitchTilt, WeatherContext from src.features.news_rag import NewsRAGPipeline class TestVoteProcessor: def test_fantavote_computation(self): """Verify fantavote = vote + goals*3 + assists - yellow*0.5 - red.""" processor = VoteProcessor() df = pd.DataFrame({ "matchday": [1], "player": ["Test Player"], "team": ["Team A"], "oppteam": ["Team B"], "home": [1], "vote": [6.5], "goals": [1], "assists": [1], "cards_malus": [0.5], "fantavote": [10.0], }) # fantavote should be: 6.5 + 3 + 1 - 0.5 = 10.0 assert df["fantavote"].iloc[0] == 10.0 def test_own_goal_deduction(self): processor = VoteProcessor() scoring = processor.scoring vote = 6.0 goals = 0 # field goal own_goals = 1 # own goal assists = 0 goals_net = goals - own_goals goals_bonus = max(0, goals_net) * scoring["goal"] own_goal_malus = max(0, own_goals) * abs(scoring["own_goal"]) cards_malus = 0 fantavote = vote + goals_bonus + 0 - cards_malus - own_goal_malus assert fantavote == 6.0 - 2.0 # 6 - 2 for own goal def test_compute_player_averages(self): processor = VoteProcessor() votes = pd.DataFrame({ "player": ["A", "A", "A", "A", "B", "B"], "team": ["T1", "T1", "T1", "T1", "T2", "T2"], "vote": [6.0, 7.0, 6.5, 7.5, 6.0, 6.0], }) result = processor.compute_player_averages(votes, min_votes=2) assert "vote_avg" in result.columns assert result["n_matches"].iloc[0] == 4 # Player A has 4 class TestAdvancedMetrics: def test_fatigue_rest_days(self): fi = FatigueIndex() match_dates = pd.Series(["2026-09-20", "2026-09-27", "2026-10-04"]) prev_dates = pd.Series(["2026-09-13", "2026-09-20", "2026-09-27"]) rest = fi.rest_days(match_dates, prev_dates) assert all(rest == 7) def test_pitch_tilt(self): pt = PitchTilt() own = np.array([50.0, 100.0]) opp = np.array([50.0, 50.0]) tilt = pt.pitch_tilt(own, opp) assert abs(tilt[0] - 0.5) < 1e-6 assert abs(tilt[1] - 2.0 / 3.0) < 1e-6 def test_field_tilt(self): pt = PitchTilt() own_passes = np.array([30.0]) opp_passes = np.array([50.0]) tilt = pt.field_tilt(own_passes, opp_passes) assert abs(tilt[0] - 30 / 80) < 1e-6 def test_pressure_regain(self): pt = PitchTilt() efficiency = pt.pressure_regain_efficiency( np.array([10.0]), np.array([100.0]) ) assert efficiency[0] == 0.1 def test_weather_context(self): ctx = WeatherContext.get_context("Milano", 1) # January assert ctx[0] == "cold" assert ctx[1] > 0 ctx = WeatherContext.get_context("Napoli", 12) # December assert ctx[0] == "warm" class TestNewsRAG: def test_simple_extract_injury(self): rag = NewsRAGPipeline() entities = rag._simple_extract( "Lautaro Martinez infortunio: salta la partita contro il Milan. " "L'attaccante ha riportato uno stiramento muscolare." ) injury_entities = [e for e in entities if e["type"] == "INJURY"] assert len(injury_entities) > 0 def test_simple_extract_suspension(self): rag = NewsRAGPipeline() entities = rag._simple_extract( "Barella squalificato per una giornata dopo l'ammonizione. " "Salterà il prossimo turno." ) suspension_entities = [e for e in entities if e["type"] == "SUSPENSION"] assert len(suspension_entities) > 0 def test_simple_extract_tactical(self): rag = NewsRAGPipeline() entities = rag._simple_extract( "La Juventus cambia modulo: passa al 3-5-2 contro l'Inter. " "Cambiaso e Di Lorenzo sulle fasce." ) tactical_entities = [e for e in entities if e["type"] == "TACTICAL_SHIFT"] assert len(tactical_entities) > 0 def test_to_features(self): rag = NewsRAGPipeline() entities = [ {"type": "INJURY", "source_text": "Osimhen injured", "article_link": "", "source": ""}, ] features = rag.to_features(entities, ["Osimhen", "Kvaratskhelia"]) assert features["news_injury_flag"].iloc[0] == 1 assert features["news_injury_flag"].iloc[1] == 0