Files
f1-circuits/circuits_service.py
T
jschoubben 15af1627fb Replace FastF1 with Jolpica API for circuit resolution
FastF1's get_event() couldn't resolve several grand prix names
(Qatar, Brazilian, Mexican, Barcelona), causing 500/404 errors.
Now uses Jolpica's race schedule directly — slugifies circuitName
to match against circuits.json with ASCII fallback for unicode.
2026-05-08 14:54:02 +02:00

121 lines
5.3 KiB
Python

import json
import logging
from typing import Optional
import functools
from enum import Enum
from ergast_service import ErgastService
from models.circuit import Circuit
from models.country import Country
from models.locality import Locality
from models.model_builder import parse_circuits_json
from models.track_layout import TrackLayout
# Define supported formats
class Format(str, Enum):
PNG = "png"
SVG = "svg"
GEOJSON = "geojson"
class CircuitService:
def __init__(self, circuit_data_path: str = "circuits/circuits.json"):
self.circuit_data_path = circuit_data_path
self.circuit_data = self._load_circuit_data()
self._layout_cache = {}
self._circuit_file_cache = {}
self._grand_prix_cache = {}
self.ergast_service = ErgastService(self)
# Configure logging
logging.basicConfig(level=logging.INFO)
self.logger = logging.getLogger(__name__)
@functools.lru_cache()
def _load_circuit_data(self) -> dict[str, Country]:
"""Load and cache circuit data"""
with open(self.circuit_data_path) as f:
return parse_circuits_json(json.load(f))
def find_layout_slug_for_year(self, circuit: Circuit, year: int) -> Optional[str]:
"""Find the appropriate layout slug for a given year"""
cache_key = f"{circuit.name}-{year}"
if cache_key in self._layout_cache:
return self._layout_cache[cache_key]
for layout_slug, _ in circuit.layouts.items():
# Check different layout slug formats
if '-' in layout_slug:
# Handle range format
start_year, *end = layout_slug.split('-')
start_year = int(start_year)
if not end[0]:
# Format: 1967- (no end year)
if year >= start_year:
self._layout_cache[cache_key] = layout_slug
return layout_slug
else:
# Format: 1967-1968
end_year = int(end[0])
if start_year <= year <= end_year:
self._layout_cache[cache_key] = layout_slug
return layout_slug
else:
# Format: 1967 (single year)
if year == int(layout_slug):
self._layout_cache[cache_key] = layout_slug
return layout_slug
return None
def get_countries_list(self) -> Optional[list[Country]]:
"""Get list of all available countries with their slugs"""
return [country for _, country in self.circuit_data.items()]
def get_country_details(self, country_slug: str) -> Optional[Country]:
"""Get details for a specific country"""
return self.circuit_data.get(country_slug)
def get_localities_list(self, country_slug: str) -> Optional[list[Locality]]:
"""Get list of cities for a specific country"""
return [locality for _, locality in self.circuit_data[country_slug].localities.items()]
def get_locality_details(self, country_slug: str, locality_slug: str) -> Optional[Locality]:
"""Get details for a specific city"""
return self.circuit_data[country_slug].localities.get(locality_slug)
def get_circuits_list(self, country_slug: str, locality_slug: str) -> Optional[list[Circuit]]:
"""Get list of circuits for a specific city"""
return [circuit_data for _, circuit_data in self.circuit_data[country_slug].localities.get(locality_slug).circuits.items()]
def get_circuit_details(self, country_slug: str, locality_slug: str, circuit_slug: str) -> Optional[Circuit]:
"""Get circuit details and available formats"""
return self.circuit_data[country_slug].localities.get(locality_slug).circuits.get(circuit_slug)
def get_layout_details(self, country_slug: str, locality_slug: str, circuit_slug: str, layout_slug: str) -> Optional[TrackLayout]:
"""Get details for a specific layout"""
return self.circuit_data[country_slug].localities.get(locality_slug).circuits.get(circuit_slug).layouts.get(
layout_slug)
def get_circuit_layout_by_ergast_data(self, grand_prix_name: str, season: int) -> Optional[TrackLayout]:
"""
Find and return the circuit layout file path based on Ergast F1 API race data format.
"""
country_slug, locality_slug, circuit_slug = self.ergast_service.find_slugs_for_grand_prix(grand_prix_name, season)
if country_slug is None or locality_slug is None or circuit_slug is None:
self.logger.error(f"Error finding circuit layout in Ergast for grand prix {grand_prix_name} in season {season}")
return None
circuit = self.get_circuit_details(country_slug, locality_slug, circuit_slug)
if circuit is None:
self.logger.error(f"Error finding circuit details for Ergast country, locality and circuit slug: {country_slug}, {locality_slug}, {circuit_slug}")
return None
layout_slug = self.find_layout_slug_for_year(circuit, season)
if layout_slug is None:
self.logger.error(f"Error finding layout slug for year {season} for circuit {circuit_slug}")
return None
return self.get_layout_details(country_slug, locality_slug, circuit_slug, layout_slug)