Spaces:
Sleeping
Sleeping
| """ | |
| Circuit Data for F1 Prediction System v3.0. | |
| Contains circuit characteristics and performance data for all 24 circuits | |
| in the 2026 season. Used by feature engineering and probability models. | |
| """ | |
| from typing import Dict, List, Optional | |
| # Circuit data for all 24 circuits in 2026 season | |
| CIRCUITS: Dict[str, Dict] = { | |
| "australia": { | |
| "id": "australia", | |
| "name": "Albert Park Grand Prix Circuit", | |
| "city": "Melbourne", | |
| "country": "Australia", | |
| "round_2026": 1, | |
| "race_date": "2026-03-08", | |
| "sprint_weekend": False, | |
| "circuit_type": ["street", "balanced"], | |
| "lap_count": 58, | |
| "lap_distance_km": 5.303, | |
| "total_distance_km": 307.577, | |
| "safety_car_probability": 0.35, | |
| "overtaking_difficulty": 6, | |
| "power_unit_demand": 6.5, | |
| "brake_demand": 5.0, | |
| "tire_deg_rate": 0.6, | |
| "rain_probability_typical": 0.25, | |
| "drs_zones": 2, | |
| "elevation_change_m": 20, | |
| "first_grand_prix": 1996, | |
| }, | |
| "china": { | |
| "id": "china", | |
| "name": "Shanghai International Circuit", | |
| "city": "Shanghai", | |
| "country": "China", | |
| "round_2026": 2, | |
| "race_date": "2026-03-15", | |
| "sprint_weekend": True, | |
| "circuit_type": ["permanent", "technical"], | |
| "lap_count": 56, | |
| "lap_distance_km": 5.451, | |
| "total_distance_km": 305.066, | |
| "safety_car_probability": 0.28, | |
| "overtaking_difficulty": 7, | |
| "power_unit_demand": 7.0, | |
| "brake_demand": 7.5, | |
| "tire_deg_rate": 0.55, | |
| "rain_probability_typical": 0.30, | |
| "drs_zones": 2, | |
| "elevation_change_m": 12, | |
| "first_grand_prix": 2004, | |
| }, | |
| "japan": { | |
| "id": "japan", | |
| "name": "Suzuka Circuit", | |
| "city": "Suzuka", | |
| "country": "Japan", | |
| "round_2026": 3, | |
| "race_date": "2026-04-06", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "high_speed", "technical"], | |
| "lap_count": 53, | |
| "lap_distance_km": 5.807, | |
| "total_distance_km": 307.471, | |
| "safety_car_probability": 0.32, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 8.0, | |
| "brake_demand": 8.5, | |
| "tire_deg_rate": 0.7, | |
| "rain_probability_typical": 0.35, | |
| "drs_zones": 2, | |
| "elevation_change_m": 133, | |
| "first_grand_prix": 1987, | |
| }, | |
| "bahrain": { | |
| "id": "bahrain", | |
| "name": "Bahrain International Circuit", | |
| "city": "Sakhir", | |
| "country": "Bahrain", | |
| "round_2026": 4, | |
| "race_date": "2026-04-12", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "balanced"], | |
| "lap_count": 57, | |
| "lap_distance_km": 5.412, | |
| "total_distance_km": 308.238, | |
| "safety_car_probability": 0.25, | |
| "overtaking_difficulty": 6, | |
| "power_unit_demand": 6.0, | |
| "brake_demand": 6.5, | |
| "tire_deg_rate": 0.75, | |
| "rain_probability_typical": 0.05, | |
| "drs_zones": 2, | |
| "elevation_change_m": 12, | |
| "first_grand_prix": 2004, | |
| }, | |
| "saudi_arabia": { | |
| "id": "saudi_arabia", | |
| "name": "Jeddah Corniche Circuit", | |
| "city": "Jeddah", | |
| "country": "Saudi Arabia", | |
| "round_2026": 5, | |
| "race_date": "2026-04-26", | |
| "sprint_weekend": True, | |
| "circuit_type": ["street", "high_speed"], | |
| "lap_count": 50, | |
| "lap_distance_km": 6.175, | |
| "total_distance_km": 308.750, | |
| "safety_car_probability": 0.40, | |
| "overtaking_difficulty": 8, | |
| "power_unit_demand": 8.5, | |
| "brake_demand": 6.0, | |
| "tire_deg_rate": 0.8, | |
| "rain_probability_typical": 0.02, | |
| "drs_zones": 2, | |
| "elevation_change_m": 1, | |
| "first_grand_prix": 2021, | |
| }, | |
| "miami": { | |
| "id": "miami", | |
| "name": "Miami International Autodrome", | |
| "city": "Miami", | |
| "country": "USA", | |
| "round_2026": 6, | |
| "race_date": "2026-05-03", | |
| "sprint_weekend": True, | |
| "circuit_type": ["street", "high_downforce"], | |
| "lap_count": 57, | |
| "lap_distance_km": 5.436, | |
| "total_distance_km": 309.835, | |
| "safety_car_probability": 0.38, | |
| "overtaking_difficulty": 7, | |
| "power_unit_demand": 6.5, | |
| "brake_demand": 7.0, | |
| "tire_deg_rate": 0.65, | |
| "rain_probability_typical": 0.40, | |
| "drs_zones": 2, | |
| "elevation_change_m": 6, | |
| "first_grand_prix": 2022, | |
| }, | |
| "canada": { | |
| "id": "canada", | |
| "name": "Circuit Gilles Villeneuve", | |
| "city": "Montreal", | |
| "country": "Canada", | |
| "round_2026": 7, | |
| "race_date": "2026-05-24", | |
| "sprint_weekend": False, | |
| "circuit_type": ["street", "power_unit"], | |
| "lap_count": 70, | |
| "lap_distance_km": 4.361, | |
| "total_distance_km": 305.270, | |
| "safety_car_probability": 0.45, | |
| "overtaking_difficulty": 4, | |
| "power_unit_demand": 7.5, | |
| "brake_demand": 8.0, | |
| "tire_deg_rate": 0.5, | |
| "rain_probability_typical": 0.30, | |
| "drs_zones": 2, | |
| "elevation_change_m": 74, | |
| "first_grand_prix": 1961, | |
| }, | |
| "monaco": { | |
| "id": "monaco", | |
| "name": "Circuit de Monaco", | |
| "city": "Monte Carlo", | |
| "country": "Monaco", | |
| "round_2026": 8, | |
| "race_date": "2026-06-07", | |
| "sprint_weekend": False, | |
| "circuit_type": ["street", "technical"], | |
| "lap_count": 78, | |
| "lap_distance_km": 3.337, | |
| "total_distance_km": 260.286, | |
| "safety_car_probability": 0.55, | |
| "overtaking_difficulty": 9, | |
| "power_unit_demand": 4.0, | |
| "brake_demand": 9.5, | |
| "tire_deg_rate": 0.3, | |
| "rain_probability_typical": 0.25, | |
| "drs_zones": 1, | |
| "elevation_change_m": 41, | |
| "first_grand_prix": 1929, | |
| }, | |
| "spain": { | |
| "id": "spain", | |
| "name": "Circuit de Barcelona-Catalunya", | |
| "city": "MontmelΓ³", | |
| "country": "Spain", | |
| "round_2026": 9, | |
| "race_date": "2026-06-14", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "balanced", "technical"], | |
| "lap_count": 66, | |
| "lap_distance_km": 4.655, | |
| "total_distance_km": 307.230, | |
| "safety_car_probability": 0.22, | |
| "overtaking_difficulty": 6, | |
| "power_unit_demand": 6.5, | |
| "brake_demand": 8.0, | |
| "tire_deg_rate": 0.7, | |
| "rain_probability_typical": 0.20, | |
| "drs_zones": 2, | |
| "elevation_change_m": 33, | |
| "first_grand_prix": 1991, | |
| }, | |
| "austria": { | |
| "id": "austria", | |
| "name": "Red Bull Ring", | |
| "city": "Spielberg", | |
| "country": "Austria", | |
| "round_2026": 10, | |
| "race_date": "2026-06-28", | |
| "sprint_weekend": True, | |
| "circuit_type": ["permanent", "high_speed", "power_unit"], | |
| "lap_count": 71, | |
| "lap_distance_km": 4.318, | |
| "total_distance_km": 306.452, | |
| "safety_car_probability": 0.30, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 8.5, | |
| "brake_demand": 5.0, | |
| "tire_deg_rate": 0.55, | |
| "rain_probability_typical": 0.45, | |
| "drs_zones": 2, | |
| "elevation_change_m": 65, | |
| "first_grand_prix": 1970, | |
| }, | |
| "britain": { | |
| "id": "britain", | |
| "name": "Silverstone Circuit", | |
| "city": "Silverstone", | |
| "country": "United Kingdom", | |
| "round_2026": 11, | |
| "race_date": "2026-07-05", | |
| "sprint_weekend": True, | |
| "circuit_type": ["permanent", "high_speed", "power_unit"], | |
| "lap_count": 52, | |
| "lap_distance_km": 5.891, | |
| "total_distance_km": 306.198, | |
| "safety_car_probability": 0.38, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 9.0, | |
| "brake_demand": 5.5, | |
| "tire_deg_rate": 0.65, | |
| "rain_probability_typical": 0.50, | |
| "drs_zones": 2, | |
| "elevation_change_m": 43, | |
| "first_grand_prix": 1950, | |
| }, | |
| "hungary": { | |
| "id": "hungary", | |
| "name": "Hungaroring", | |
| "city": "MogyorΓ³d", | |
| "country": "Hungary", | |
| "round_2026": 12, | |
| "race_date": "2026-07-19", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "technical", "high_downforce"], | |
| "lap_count": 70, | |
| "lap_distance_km": 4.381, | |
| "total_distance_km": 306.630, | |
| "safety_car_probability": 0.20, | |
| "overtaking_difficulty": 8, | |
| "power_unit_demand": 5.0, | |
| "brake_demand": 6.0, | |
| "tire_deg_rate": 0.6, | |
| "rain_probability_typical": 0.25, | |
| "drs_zones": 1, | |
| "elevation_change_m": 42, | |
| "first_grand_prix": 1986, | |
| }, | |
| "belgium": { | |
| "id": "belgium", | |
| "name": "Circuit de Spa-Francorchamps", | |
| "city": "Stavelot", | |
| "country": "Belgium", | |
| "round_2026": 13, | |
| "race_date": "2026-07-26", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "high_speed", "technical"], | |
| "lap_count": 44, | |
| "lap_distance_km": 7.004, | |
| "total_distance_km": 308.052, | |
| "safety_car_probability": 0.48, | |
| "overtaking_difficulty": 4, | |
| "power_unit_demand": 8.0, | |
| "brake_demand": 7.0, | |
| "tire_deg_rate": 0.6, | |
| "rain_probability_typical": 0.60, | |
| "drs_zones": 2, | |
| "elevation_change_m": 127, | |
| "first_grand_prix": 1950, | |
| }, | |
| "netherlands": { | |
| "id": "netherlands", | |
| "name": "Circuit Zandvoort", | |
| "city": "Zandvoort", | |
| "country": "Netherlands", | |
| "round_2026": 14, | |
| "race_date": "2026-08-30", | |
| "sprint_weekend": True, | |
| "circuit_type": ["permanent", "high_downforce", "technical"], | |
| "lap_count": 72, | |
| "lap_distance_km": 4.259, | |
| "total_distance_km": 306.645, | |
| "safety_car_probability": 0.35, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 6.0, | |
| "brake_demand": 7.5, | |
| "tire_deg_rate": 0.65, | |
| "rain_probability_typical": 0.40, | |
| "drs_zones": 2, | |
| "elevation_change_m": 3, | |
| "first_grand_prix": 1952, | |
| }, | |
| "italy": { | |
| "id": "italy", | |
| "name": "Autodromo Nazionale di Monza", | |
| "city": "Monza", | |
| "country": "Italy", | |
| "round_2026": 15, | |
| "race_date": "2026-09-06", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "high_speed", "power_unit"], | |
| "lap_count": 53, | |
| "lap_distance_km": 5.793, | |
| "total_distance_km": 306.720, | |
| "safety_car_probability": 0.25, | |
| "overtaking_difficulty": 4, | |
| "power_unit_demand": 9.5, | |
| "brake_demand": 4.0, | |
| "tire_deg_rate": 0.5, | |
| "rain_probability_typical": 0.20, | |
| "drs_zones": 2, | |
| "elevation_change_m": 17, | |
| "first_grand_prix": 1950, | |
| }, | |
| "madrid": { | |
| "id": "madrid", | |
| "name": "Madrid Street Circuit", | |
| "city": "Madrid", | |
| "country": "Spain", | |
| "round_2026": 16, | |
| "race_date": "2026-09-13", | |
| "sprint_weekend": False, | |
| "circuit_type": ["street", "balanced"], | |
| "lap_count": 60, | |
| "lap_distance_km": 5.474, | |
| "total_distance_km": 328.440, | |
| "safety_car_probability": 0.32, | |
| "overtaking_difficulty": 6, | |
| "power_unit_demand": 6.5, | |
| "brake_demand": 7.0, | |
| "tire_deg_rate": 0.6, | |
| "rain_probability_typical": 0.25, | |
| "drs_zones": 2, | |
| "elevation_change_m": 25, | |
| "first_grand_prix": 2026, | |
| }, | |
| "azerbaijan": { | |
| "id": "azerbaijan", | |
| "name": "Baku City Circuit", | |
| "city": "Baku", | |
| "country": "Azerbaijan", | |
| "round_2026": 17, | |
| "race_date": "2026-09-20", | |
| "sprint_weekend": False, | |
| "circuit_type": ["street", "high_speed"], | |
| "lap_count": 51, | |
| "lap_distance_km": 6.003, | |
| "total_distance_km": 306.049, | |
| "safety_car_probability": 0.35, | |
| "overtaking_difficulty": 3, | |
| "power_unit_demand": 8.0, | |
| "brake_demand": 4.0, | |
| "tire_deg_rate": 0.55, | |
| "rain_probability_typical": 0.10, | |
| "drs_zones": 2, | |
| "elevation_change_m": 46, | |
| "first_grand_prix": 2016, | |
| }, | |
| "singapore": { | |
| "id": "singapore", | |
| "name": "Marina Bay Street Circuit", | |
| "city": "Singapore", | |
| "country": "Singapore", | |
| "round_2026": 18, | |
| "race_date": "2026-10-04", | |
| "sprint_weekend": True, | |
| "circuit_type": ["street", "high_downforce", "technical"], | |
| "lap_count": 62, | |
| "lap_distance_km": 5.063, | |
| "total_distance_km": 313.880, | |
| "safety_car_probability": 0.42, | |
| "overtaking_difficulty": 8, | |
| "power_unit_demand": 5.0, | |
| "brake_demand": 8.5, | |
| "tire_deg_rate": 0.7, | |
| "rain_probability_typical": 0.70, | |
| "drs_zones": 2, | |
| "elevation_change_m": 21, | |
| "first_grand_prix": 2008, | |
| }, | |
| "usa": { | |
| "id": "usa", | |
| "name": "Circuit of the Americas", | |
| "city": "Austin", | |
| "country": "USA", | |
| "round_2026": 19, | |
| "race_date": "2026-10-18", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "balanced", "technical"], | |
| "lap_count": 56, | |
| "lap_distance_km": 5.513, | |
| "total_distance_km": 308.845, | |
| "safety_car_probability": 0.28, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 7.0, | |
| "brake_demand": 7.5, | |
| "tire_deg_rate": 0.65, | |
| "rain_probability_typical": 0.20, | |
| "drs_zones": 2, | |
| "elevation_change_m": 44, | |
| "first_grand_prix": 2012, | |
| }, | |
| "mexico": { | |
| "id": "mexico", | |
| "name": "AutΓ³dromo Hermanos RodrΓguez", | |
| "city": "Mexico City", | |
| "country": "Mexico", | |
| "round_2026": 20, | |
| "race_date": "2026-10-25", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "high_altitude", "power_unit"], | |
| "lap_count": 71, | |
| "lap_distance_km": 4.304, | |
| "total_distance_km": 305.354, | |
| "safety_car_probability": 0.30, | |
| "overtaking_difficulty": 6, | |
| "power_unit_demand": 8.5, | |
| "brake_demand": 6.0, | |
| "tire_deg_rate": 0.55, | |
| "rain_probability_typical": 0.30, | |
| "drs_zones": 2, | |
| "elevation_change_m": 2250, | |
| "first_grand_prix": 1963, | |
| }, | |
| "brazil": { | |
| "id": "brazil", | |
| "name": "AutΓ³dromo JosΓ© Carlos Pace", | |
| "city": "SΓ£o Paulo", | |
| "country": "Brazil", | |
| "round_2026": 21, | |
| "race_date": "2026-11-08", | |
| "sprint_weekend": True, | |
| "circuit_type": ["permanent", "technical", "balanced"], | |
| "lap_count": 71, | |
| "lap_distance_km": 4.309, | |
| "total_distance_km": 305.879, | |
| "safety_car_probability": 0.40, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 6.0, | |
| "brake_demand": 8.0, | |
| "tire_deg_rate": 0.6, | |
| "rain_probability_typical": 0.45, | |
| "drs_zones": 2, | |
| "elevation_change_m": 14, | |
| "first_grand_prix": 1973, | |
| }, | |
| "las_vegas": { | |
| "id": "las_vegas", | |
| "name": "Las Vegas Strip Circuit", | |
| "city": "Las Vegas", | |
| "country": "USA", | |
| "round_2026": 22, | |
| "race_date": "2026-11-21", | |
| "sprint_weekend": False, | |
| "circuit_type": ["street", "high_speed"], | |
| "lap_count": 50, | |
| "lap_distance_km": 6.201, | |
| "total_distance_km": 310.050, | |
| "safety_car_probability": 0.25, | |
| "overtaking_difficulty": 7, | |
| "power_unit_demand": 8.0, | |
| "brake_demand": 5.0, | |
| "tire_deg_rate": 0.6, | |
| "rain_probability_typical": 0.05, | |
| "drs_zones": 2, | |
| "elevation_change_m": 1, | |
| "first_grand_prix": 2023, | |
| }, | |
| "qatar": { | |
| "id": "qatar", | |
| "name": "Losail International Circuit", | |
| "city": "Al Daayen", | |
| "country": "Qatar", | |
| "round_2026": 23, | |
| "race_date": "2026-11-29", | |
| "sprint_weekend": True, | |
| "circuit_type": ["permanent", "balanced", "technical"], | |
| "lap_count": 57, | |
| "lap_distance_km": 5.380, | |
| "total_distance_km": 306.630, | |
| "safety_car_probability": 0.22, | |
| "overtaking_difficulty": 6, | |
| "power_unit_demand": 6.5, | |
| "brake_demand": 7.0, | |
| "tire_deg_rate": 0.7, | |
| "rain_probability_typical": 0.02, | |
| "drs_zones": 2, | |
| "elevation_change_m": 6, | |
| "first_grand_prix": 2021, | |
| }, | |
| "uae": { | |
| "id": "uae", | |
| "name": "Yas Marina Circuit", | |
| "city": "Abu Dhabi", | |
| "country": "UAE", | |
| "round_2026": 24, | |
| "race_date": "2026-12-06", | |
| "sprint_weekend": False, | |
| "circuit_type": ["permanent", "balanced"], | |
| "lap_count": 55, | |
| "lap_distance_km": 5.281, | |
| "total_distance_km": 290.455, | |
| "safety_car_probability": 0.20, | |
| "overtaking_difficulty": 5, | |
| "power_unit_demand": 6.5, | |
| "brake_demand": 6.0, | |
| "tire_deg_rate": 0.65, | |
| "rain_probability_typical": 0.05, | |
| "drs_zones": 2, | |
| "elevation_change_m": 7, | |
| "first_grand_prix": 2009, | |
| } | |
| } | |
| def get_circuit(circuit_id: str) -> Dict: | |
| """ | |
| Retrieve circuit data by ID. | |
| Args: | |
| circuit_id: Circuit identifier (e.g., 'canada', 'monaco') | |
| Returns: | |
| Dictionary containing circuit characteristics | |
| Raises: | |
| KeyError: If circuit_id not in CIRCUITS | |
| """ | |
| if circuit_id not in CIRCUITS: | |
| raise KeyError(f"Circuit '{circuit_id}' not found in circuit data") | |
| return CIRCUITS[circuit_id] | |
| def get_all_circuits() -> List[Dict]: | |
| """ | |
| Retrieve all circuit data. | |
| Returns: | |
| List of all circuit dictionaries | |
| """ | |
| return list(CIRCUITS.values()) | |
| def circuit_favors_team(circuit_id: str, team: str) -> float: | |
| """ | |
| Return a multiplier indicating how much a circuit favors a particular team. | |
| Args: | |
| circuit_id: Circuit identifier | |
| team: Team identifier (e.g., 'mercedes', 'red_bull') | |
| Returns: | |
| Multiplier between 0.9 and 1.1 indicating circuit favorability | |
| """ | |
| # Default is neutral (1.0) - this would be enhanced with actual data | |
| # QUALITY-08 FIX: Return [0.9, 1.1] range instead of allowing >1.0 multipliers | |
| # that get clamped in the probability model | |
| circuit = get_circuit(circuit_id) | |
| # Example: Some circuits favor certain types of cars | |
| if circuit_id in ["monaco", "azerbaijan"]: | |
| # Street circuits might favor teams with better low-speed performance | |
| if team in ["red_bull", "ferrari"]: | |
| return 1.05 | |
| elif team in ["mercedes", "mclaren"]: | |
| return 0.98 | |
| elif circuit_id in ["hungary"]: | |
| # High-downforce circuits might favor certain teams | |
| if team == "red_bull": | |
| return 1.08 | |
| elif team == "ferrari": | |
| return 1.02 | |
| elif circuit_id in ["belgium", "italy", "britain"]: | |
| # High-speed circuits favor power unit performance | |
| if team in ["mercedes", "ferrari"]: | |
| return 1.05 | |
| elif team == "red_bull": | |
| return 1.03 | |
| # Default neutral multiplier | |
| return 1.0 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PHASE 4: FASTF1 CIRCUIT DATABASE REFRESH | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def refresh_circuits_from_fastf1(seasons: Optional[List[int]] = None) -> int: | |
| """ | |
| Refresh circuit stats from FastF1 historical race data. | |
| Updates the in-memory CIRCUITS dict with: | |
| - safety_car_probability (from actual SC frequency) | |
| - rain_probability_typical (from actual rainfall frequency) | |
| - tire_deg_rate (from observed degradation patterns) | |
| Args: | |
| seasons: List of years to analyse (default: last 3 years) | |
| Returns: | |
| Number of circuits successfully updated | |
| Safe to call even if FastF1 is unavailable β returns 0 on failure. | |
| """ | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| if seasons is None: | |
| seasons = [2025, 2024, 2023] | |
| updated = 0 | |
| try: | |
| from src.data.fastf1_integration import FASTF1_AVAILABLE, get_circuit_historical_stats | |
| if not FASTF1_AVAILABLE: | |
| logger.warning("FastF1 not available β circuit database unchanged") | |
| return 0 | |
| # Build circuit name β circuit_id mapping | |
| name_to_id = {} | |
| for cid, cdata in CIRCUITS.items(): | |
| name_to_id[cdata["name"]] = cid | |
| # Also map by city for circuits with different official names | |
| name_to_id[cdata.get("city", "")] = cid | |
| for cid, cdata in CIRCUITS.items(): | |
| try: | |
| stats = get_circuit_historical_stats(cdata["name"], seasons) | |
| if stats.get("races_analysed", 0) == 0: | |
| continue | |
| # Update safety car probability from actual data | |
| if "safety_car_frequency" in stats: | |
| # Blend: 60% FastF1 data, 40% existing static value | |
| old_val = cdata.get("safety_car_probability", 0.3) | |
| cdata["safety_car_probability"] = round( | |
| 0.6 * stats["safety_car_frequency"] + 0.4 * old_val, 3 | |
| ) | |
| # Update rain probability from actual data | |
| if "avg_rainfall" in stats: | |
| old_val = cdata.get("rain_probability_typical", 0.2) | |
| cdata["rain_probability_typical"] = round( | |
| 0.6 * stats["avg_rainfall"] + 0.4 * old_val, 3 | |
| ) | |
| updated += 1 | |
| logger.info( | |
| f" {cid}: SC={cdata['safety_car_probability']:.2f}, " | |
| f"Rain={cdata['rain_probability_typical']:.2f} " | |
| f"(from {stats['races_analysed']} races)" | |
| ) | |
| except Exception as e: | |
| logger.warning(f" {cid}: skipped β {e}") | |
| continue | |
| logger.info(f"FastF1 circuit refresh: updated {updated}/{len(CIRCUITS)} circuits") | |
| except ImportError: | |
| logger.warning("FastF1 module not found β circuit database unchanged") | |
| except Exception as e: | |
| logger.error(f"FastF1 circuit refresh failed: {e}") | |
| return updated | |
| # ββ EXPORT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| __all__ = [ | |
| "CIRCUITS", | |
| "get_circuit", | |
| "get_all_circuits", | |
| "circuit_favors_team", | |
| # Phase 4 addition | |
| "refresh_circuits_from_fastf1", | |
| ] | |