171 lines
5.2 KiB
Python
Executable File
171 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Weather Module - Open-Meteo API Integration
|
|
Adds weather context for horse racing analysis
|
|
"""
|
|
import requests
|
|
from datetime import datetime
|
|
|
|
# Hippodrome coordinates (France)
|
|
HIPPODROMES = {
|
|
'auteuil': (48.8718, 2.2525),
|
|
' Chantilly': (49.1939, 2.4744),
|
|
'deauville': (49.3563, 0.0775),
|
|
'vincennes': (48.8414, 2.4375),
|
|
'longchamp': (48.8641, 2.2372),
|
|
'saint-cloud': (48.8419, 2.1039),
|
|
'pau': (43.2917, -0.3708),
|
|
'cagnes-sur-mer': (43.6689, 7.1914),
|
|
'lyon-parilly': (45.7378, 4.8092),
|
|
'marseille': (43.2964, 5.3695),
|
|
}
|
|
|
|
def get_weather(hippodrome, date=None):
|
|
"""
|
|
Get weather for hippodrome from Open-Meteo API (FREE!)
|
|
|
|
Args:
|
|
hippodrome: Name of hippodrome
|
|
date: Optional date (YYYY-MM-DD), defaults to today
|
|
|
|
Returns:
|
|
dict: Weather data
|
|
"""
|
|
if hippodrome.lower() not in HIPPODROMES:
|
|
return {'error': f'Hippodrome not found: {hippodrome}'}
|
|
|
|
lat, lon = HIPPODROMES[hippodrome.lower()]
|
|
|
|
# Open-Meteo API (free, no key needed)
|
|
url = f"https://api.open-meteo.com/v1/forecast"
|
|
params = {
|
|
'latitude': lat,
|
|
'longitude': lon,
|
|
'current': 'temperature_2m,relative_humidity_2m,precipitation,rain,weather_code,wind_speed_10m,wind_direction_10m',
|
|
'timezone': 'Europe/Paris'
|
|
}
|
|
|
|
if date:
|
|
# Historical data
|
|
url = f"https://archive-api.open-meteo.com/v1/archive"
|
|
params['start_date'] = date
|
|
params['end_date'] = date
|
|
|
|
try:
|
|
r = requests.get(url, params=params, timeout=10)
|
|
data = r.json()
|
|
|
|
if 'current' in data:
|
|
current = data['current']
|
|
return {
|
|
'hippodrome': hippodrome,
|
|
'temperature': current.get('temperature_2m'),
|
|
'humidity': current.get('relative_humidity_2m'),
|
|
'precipitation': current.get('precipitation'),
|
|
'rain': current.get('rain'),
|
|
'weather_code': current.get('weather_code'),
|
|
'wind_speed': current.get('wind_speed_10m'),
|
|
'wind_direction': current.get('wind_direction_10m'),
|
|
'timestamp': datetime.now().isoformat()
|
|
}
|
|
elif 'daily' in data:
|
|
return {
|
|
'hippodrome': hippodrome,
|
|
'date': date,
|
|
'temperature_max': data['daily'].get('temperature_2m_max', [None])[0],
|
|
'temperature_min': data['daily'].get('temperature_2m_min', [None])[0],
|
|
'precipitation_sum': data['daily'].get('precipitation_sum', [None])[0],
|
|
'rain_sum': data['daily'].get('rain_sum', [None])[0],
|
|
'wind_speed_max': data['daily'].get('wind_speed_10m_max', [None])[0],
|
|
}
|
|
|
|
except Exception as e:
|
|
return {'error': str(e)}
|
|
|
|
def weather_code_to_desc(code):
|
|
"""Convert weather code to description"""
|
|
codes = {
|
|
0: "Ciel dégagé",
|
|
1: "Mainly clear",
|
|
2: "Partly cloudy",
|
|
3: "Overcast",
|
|
45: "Brouillard",
|
|
48: "Fog",
|
|
51: "Bruine légère",
|
|
53: "Bruine modérée",
|
|
55: "Bruine dense",
|
|
61: "Pluie légère",
|
|
63: "Pluie modérée",
|
|
65: "Pluie forte",
|
|
71: "Neige légère",
|
|
73: "Neige modérée",
|
|
75: "Neige forte",
|
|
80: "Averses légères",
|
|
81: "Averses modérées",
|
|
82: "Averses fortes",
|
|
95: "Orage",
|
|
}
|
|
return codes.get(code, f"Code {code}")
|
|
|
|
def get_ground_condition(weather_data):
|
|
"""
|
|
Determine ground condition based on weather
|
|
|
|
Returns: BON, SOUPLE, LOURD, TERRE PERTURBÉE, etc.
|
|
"""
|
|
if 'error' in weather_data:
|
|
return 'INCONNU'
|
|
|
|
precip = weather_data.get('precipitation', 0)
|
|
rain = weather_data.get('rain', 0)
|
|
humidity = weather_data.get('humidity', 0)
|
|
|
|
# Simple logic for turf conditions
|
|
if precip > 10 or rain > 5:
|
|
return 'LOURD'
|
|
elif precip > 5 or rain > 2:
|
|
return 'SOUPLE'
|
|
elif precip > 1:
|
|
return 'BON'
|
|
else:
|
|
return 'BON'
|
|
|
|
def analyze_weather_impact(weather_data, horse_history):
|
|
"""
|
|
Analyze how horse performs in current weather
|
|
|
|
Args:
|
|
weather_data: Current weather conditions
|
|
horse_history: List of past performances with weather
|
|
|
|
Returns:
|
|
dict: Weather impact analysis
|
|
"""
|
|
analysis = {
|
|
'ground_condition': get_ground_condition(weather_data),
|
|
'recommendation': 'NEUTRAL'
|
|
}
|
|
|
|
# Check ground preference based on history
|
|
# This would need historical data to be implemented
|
|
|
|
return analysis
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
# Test
|
|
print("="*50)
|
|
print("Weather Module - Test")
|
|
print("="*50)
|
|
|
|
for hippo in ['Auteuil', 'Vincennes', 'Pau']:
|
|
print(f"\n{hippo}:")
|
|
w = get_weather(hippo)
|
|
if 'error' not in w:
|
|
print(f" Temp: {w.get('temperature')}°C")
|
|
print(f" Humidity: {w.get('humidity')}%")
|
|
print(f" Wind: {w.get('wind_speed')} km/h")
|
|
print(f" Ground: {get_ground_condition(w)}")
|
|
else:
|
|
print(f" Error: {w.get('error')}")
|