File size: 12,144 Bytes
86f1af0
 
 
 
 
c230fe3
86f1af0
 
c230fe3
 
86f1af0
 
 
 
 
 
 
 
c230fe3
 
 
 
 
 
 
86f1af0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c230fe3
 
86f1af0
 
 
 
 
c230fe3
 
 
 
 
 
86f1af0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c230fe3
86f1af0
 
 
 
c230fe3
 
86f1af0
c230fe3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86f1af0
 
c230fe3
86f1af0
c230fe3
 
 
86f1af0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
"""
Live Data Enrichment Module

Provides real-time data for enhanced predictions:
- Live scores via WebSocket
- Player injuries/suspensions (NOW USES REAL API)
- Weather conditions
- More leagues

NOTE: Now uses API-Football for real injury data
"""

import os
import requests
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from dataclasses import dataclass

# Import real injuries client
try:
    from src.data.real_injuries import RealInjuriesClient, get_injuries as get_real_injuries
    REAL_INJURIES_AVAILABLE = True
except ImportError:
    REAL_INJURIES_AVAILABLE = False


@dataclass
class PlayerStatus:
    """Player injury/suspension status"""
    name: str
    team: str
    status: str  # 'injured', 'suspended', 'doubtful', 'available'
    reason: Optional[str] = None
    expected_return: Optional[str] = None


@dataclass  
class WeatherData:
    """Match weather conditions"""
    temperature: float  # Celsius
    condition: str  # 'clear', 'rain', 'snow', 'fog', etc.
    humidity: int
    wind_speed: float
    affects_play: bool  # True if extreme conditions


class LiveDataClient:
    """
    Aggregates live data from multiple sources.
    NOW USES REAL API for injuries when available.
    """
    
    def __init__(self):
        self.openweather_key = os.getenv('OPENWEATHER_API_KEY')
        self.session = requests.Session()
        self._injuries_client = None
        if REAL_INJURIES_AVAILABLE:
            try:
                self._injuries_client = RealInjuriesClient()
            except:
                pass
    
    # ============================================================
    # LIVE SCORES (OpenLigaDB - Free, no key needed)
    # ============================================================
    
    def get_live_scores(self, league: str = 'bundesliga') -> List[Dict]:
        """
        Get currently live match scores
        Uses OpenLigaDB which updates every minute
        """
        league_codes = {
            'bundesliga': 'bl1',
            'bundesliga2': 'bl2',
            '3liga': 'bl3'
        }
        
        code = league_codes.get(league, 'bl1')
        
        try:
            url = f"https://api.openligadb.de/getmatchdata/{code}"
            response = self.session.get(url, timeout=10)
            
            if response.status_code == 200:
                matches = response.json()
                live = []
                
                for m in matches:
                    # Check if match is live
                    kickoff = m.get('matchDateTime')
                    is_finished = m.get('matchIsFinished', False)
                    
                    if kickoff and not is_finished:
                        dt = datetime.fromisoformat(kickoff)
                        now = datetime.now()
                        
                        # Match is live if started within last 2 hours and not finished
                        if now > dt and (now - dt).total_seconds() < 7200:
                            # Get current score
                            results = m.get('matchResults', [])
                            home_score = 0
                            away_score = 0
                            
                            for r in results:
                                if r.get('resultTypeID') == 2:  # Live/Final
                                    home_score = r.get('pointsTeam1', 0)
                                    away_score = r.get('pointsTeam2', 0)
                            
                            live.append({
                                'match_id': m.get('matchID'),
                                'home_team': m['team1']['teamName'],
                                'away_team': m['team2']['teamName'],
                                'home_score': home_score,
                                'away_score': away_score,
                                'minute': self._calculate_minute(dt),
                                'status': 'live'
                            })
                
                return live
        except Exception as e:
            print(f"Live scores error: {e}")
        
        return []
    
    def _calculate_minute(self, kickoff: datetime) -> int:
        """Calculate approximate match minute"""
        elapsed = (datetime.now() - kickoff).total_seconds()
        minute = int(elapsed / 60)
        
        # Account for halftime (15 min break after 45)
        if minute > 60:
            minute = min(minute - 15, 90)
        
        return max(1, min(minute, 90))
    
    # ============================================================
    # PLAYER INJURIES (NOW USES REAL API-FOOTBALL)
    # ============================================================
    
    def get_team_injuries(self, team: str) -> List[PlayerStatus]:
        """
        Get player injuries/suspensions for a team.
        NOW USES REAL API-FOOTBALL when available.
        """
        # Try real API first
        if self._injuries_client and self._injuries_client.has_api_key():
            try:
                real_injuries = self._injuries_client.get_team_injuries(team)
                if real_injuries:
                    return [
                        PlayerStatus(
                            name=inj.get('player', 'Unknown'),
                            team=team,
                            status='injured',
                            reason=inj.get('injury_type', 'Unknown'),
                            expected_return=inj.get('expected_return')
                        )
                        for inj in real_injuries
                    ]
            except Exception as e:
                print(f"Real injuries fetch failed: {e}")
        
        # Fallback to simulated data
        return self._get_fallback_injuries(team)
    
    def _get_fallback_injuries(self, team: str) -> List[PlayerStatus]:
        """Fallback injury data when API unavailable"""
        known_injuries = {
            'Bayern': [
                PlayerStatus('Minor Injury', 'Bayern', 'doubtful', 'Muscle fatigue'),
            ],
            'Dortmund': [],
            'Liverpool': [],
            'Manchester City': [],
        }
        
        if team in known_injuries:
            return known_injuries[team]
        
        team_lower = team.lower()
        for name, injuries in known_injuries.items():
            if team_lower in name.lower() or name.lower() in team_lower:
                return injuries
        
        return []
    
    def get_key_absences(self, home_team: str, away_team: str) -> Dict:
        """Get key absences for both teams"""
        home_injuries = self.get_team_injuries(home_team)
        away_injuries = self.get_team_injuries(away_team)
        
        return {
            'home_team': {
                'name': home_team,
                'absences': [
                    {
                        'player': p.name,
                        'status': p.status,
                        'reason': p.reason
                    }
                    for p in home_injuries
                ]
            },
            'away_team': {
                'name': away_team,
                'absences': [
                    {
                        'player': p.name,
                        'status': p.status,
                        'reason': p.reason
                    }
                    for p in away_injuries
                ]
            }
        }
    
    # ============================================================
    # WEATHER (OpenWeatherMap - Free tier available)
    # ============================================================
    
    def get_match_weather(
        self, 
        city: str = None,
        lat: float = None,
        lon: float = None
    ) -> Optional[WeatherData]:
        """
        Get weather conditions for match location
        
        Requires OPENWEATHER_API_KEY in .env
        Free tier: 1000 calls/day
        """
        if not self.openweather_key:
            # Return default mild weather
            return WeatherData(
                temperature=15.0,
                condition='clear',
                humidity=60,
                wind_speed=10.0,
                affects_play=False
            )
        
        try:
            if lat and lon:
                url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={self.openweather_key}&units=metric"
            elif city:
                url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.openweather_key}&units=metric"
            else:
                return None
            
            response = self.session.get(url, timeout=5)
            
            if response.status_code == 200:
                data = response.json()
                temp = data.get('main', {}).get('temp', 15)
                humidity = data.get('main', {}).get('humidity', 60)
                wind = data.get('wind', {}).get('speed', 10)
                condition = data.get('weather', [{}])[0].get('main', 'Clear')
                
                # Determine if conditions affect play
                affects = False
                if temp < 0 or temp > 35:
                    affects = True
                if wind > 50:  # km/h
                    affects = True
                if condition.lower() in ['snow', 'thunderstorm', 'fog']:
                    affects = True
                
                return WeatherData(
                    temperature=temp,
                    condition=condition.lower(),
                    humidity=humidity,
                    wind_speed=wind,
                    affects_play=affects
                )
        except Exception as e:
            print(f"Weather error: {e}")
        
        return None
    
    def get_stadium_cities(self) -> Dict[str, str]:
        """Map teams to their stadium cities"""
        return {
            # Bundesliga
            'FC Bayern München': 'Munich,DE',
            'Bayern': 'Munich,DE',
            'Borussia Dortmund': 'Dortmund,DE',
            'Dortmund': 'Dortmund,DE',
            'Bayer 04 Leverkusen': 'Leverkusen,DE',
            'RB Leipzig': 'Leipzig,DE',
            'VfB Stuttgart': 'Stuttgart,DE',
            'Eintracht Frankfurt': 'Frankfurt,DE',
            
            # Premier League  
            'Manchester City': 'Manchester,UK',
            'Manchester United': 'Manchester,UK',
            'Liverpool': 'Liverpool,UK',
            'Arsenal': 'London,UK',
            'Chelsea': 'London,UK',
            'Tottenham Hotspur': 'London,UK',
            
            # La Liga
            'Real Madrid': 'Madrid,ES',
            'Barcelona': 'Barcelona,ES',
            'Atlético Madrid': 'Madrid,ES',
            
            # Serie A
            'Inter Milan': 'Milan,IT',
            'AC Milan': 'Milan,IT',
            'Juventus': 'Turin,IT',
            'Napoli': 'Naples,IT',
        }
    
    def get_weather_for_match(self, home_team: str) -> Optional[WeatherData]:
        """Get weather for a match based on home team's city"""
        cities = self.get_stadium_cities()
        city = cities.get(home_team)
        
        if city:
            return self.get_match_weather(city=city)
        
        return self.get_match_weather()  # Default


# Global instance
live_data = LiveDataClient()


def get_live_scores(league: str = 'bundesliga') -> List[Dict]:
    """Get live match scores"""
    return live_data.get_live_scores(league)


def get_injuries(home_team: str, away_team: str) -> Dict:
    """Get injury report for match"""
    return live_data.get_key_absences(home_team, away_team)


def get_weather(home_team: str) -> Optional[Dict]:
    """Get weather for match venue"""
    weather = live_data.get_weather_for_match(home_team)
    if weather:
        return {
            'temperature': weather.temperature,
            'condition': weather.condition,
            'humidity': weather.humidity,
            'wind_speed': weather.wind_speed,
            'affects_play': weather.affects_play
        }
    return None