Skip to main content
The results.py script fetches race results including finishing positions, points awarded, and race outcomes for each driver.

Script Overview

Location: ~/workspace/source/results.py The script uses the RaceResultsFetcher class to:
  1. Fetch race results for specific rounds
  2. Handle rate limiting automatically
  3. Save results to the appropriate race directory
  4. Log all operations for debugging

API Endpoint

url = f"https://api.jolpi.ca/ergast/f1/{season}/{round_num}/results.json"

RaceResultsFetcher Class

Initialization

class RaceResultsFetcher:
    def __init__(self, base_dir="."):
        self.base_dir = Path(base_dir)
        self.base_url = "https://api.jolpi.ca/ergast/f1"
        # Rate limits
        self.burst_limit = 4  # requests per second
        self.last_request_time = 0

Rate-Limited Requests

The class implements adaptive rate limiting:
def make_request(self, url):
    """Make a request to the API with rate limiting"""
    # Ensure we don't exceed burst limit
    current_time = time.time()
    time_since_last_request = current_time - self.last_request_time

    if time_since_last_request < (1 / self.burst_limit):
        sleep_time = (1 / self.burst_limit) - time_since_last_request
        logger.debug(f"Rate limiting: sleeping for {sleep_time:.2f} seconds")
        time.sleep(sleep_time)

    logger.debug(f"Making request to: {url}")
    response = requests.get(url)
    self.last_request_time = time.time()

    if response.status_code == 429:
        logger.warning("Rate limit exceeded. Waiting 30 seconds before retrying.")
        time.sleep(30)
        return self.make_request(url)

    if response.status_code != 200:
        logger.error(
            f"Error fetching data: {response.status_code} - {response.text}"
        )
        return None

    return response.json()

Fetching Race Information

def get_race_info(self, season, round_num):
    """Get race information for a specific season and round"""
    url = f"{self.base_url}/{season}/{round_num}.json"
    data = self.make_request(url)

    if not data:
        return None

    races = data.get("MRData", {}).get("RaceTable", {}).get("Races", [])
    if races:
        return races[0]
    return None

Fetching Results

def get_race_results(self, season, round_num):
    """Get race results for a specific season and round"""
    url = f"{self.base_url}/{season}/{round_num}/results.json"
    return self.make_request(url)

Main Fetch Method

def fetch_round(self, season, round_num):
    """Fetch race results for a specific season and round"""
    logger.info(f"Fetching results for season {season}, round {round_num}")

    # Create season directory
    season_dir = self.base_dir / str(season)
    os.makedirs(season_dir, exist_ok=True)

    # Get race info
    race_info = self.get_race_info(season, round_num)

    if not race_info:
        logger.warning(f"No race info found for season {season}, round {round_num}")
        return

    race_name = self.get_race_folder_name(race_info)

    # Create race directory
    race_dir = season_dir / race_name
    os.makedirs(race_dir, exist_ok=True)

    # Get race results
    race_results = self.get_race_results(season, round_num)

    if race_results:
        race_results_path = race_dir / "results.json"
        self.save_json(race_results, race_results_path)
    else:
        logger.warning(f"No results found for season {season}, round {round_num}")

Output Structure

Stored at: {year}/{race-name}/results.json
{
  "MRData": {
    "series": "f1",
    "url": "https://api.jolpi.ca/ergast/f1/1950/5/results.json",
    "limit": "30",
    "offset": "0",
    "total": "14",
    "RaceTable": {
      "season": "1950",
      "round": "5",
      "Races": [
        {
          "season": "1950",
          "round": "5",
          "raceName": "Belgian Grand Prix",
          "Circuit": {
            "circuitId": "spa",
            "circuitName": "Circuit de Spa-Francorchamps",
            "Location": {
              "lat": "50.4372",
              "long": "5.97139",
              "locality": "Spa",
              "country": "Belgium"
            }
          },
          "date": "1950-06-18",
          "Results": [
            {
              "number": "10",
              "position": "1",
              "positionText": "1",
              "points": "8",
              "Driver": {
                "driverId": "fangio",
                "givenName": "Juan",
                "familyName": "Fangio",
                "dateOfBirth": "1911-06-24",
                "nationality": "Argentine"
              },
              "Constructor": {
                "constructorId": "alfa",
                "name": "Alfa Romeo",
                "nationality": "Swiss"
              },
              "grid": "2",
              "laps": "35",
              "status": "Finished",
              "Time": {
                "millis": "10046000",
                "time": "2:47"
              }
            }
          ]
        }
      ]
    }
  }
}

Usage Example

Basic Usage

from results import RaceResultsFetcher

fetcher = RaceResultsFetcher(base_dir=".")
fetcher.fetch_round(2024, 1)  # Fetch results for 2024 Bahrain GP

Multiple Rounds

fetcher = RaceResultsFetcher(base_dir=".")

for round_num in range(1, 22):
    fetcher.fetch_round(2024, round_num)
    
logger.info("Race results fetching completed")
The script includes logging to both a file (race_results_fetch.log) and the console for easy monitoring.

Results Data Fields

Each result contains:
FieldDescription
numberDriver’s car number
positionFinishing position
positionTextPosition as text (handles special cases)
pointsChampionship points awarded
DriverDriver information (name, nationality, etc.)
ConstructorTeam information
gridStarting grid position
lapsNumber of laps completed
statusFinish status (Finished, Retired, etc.)
TimeFinishing time (winner) or gap to winner
FastestLapFastest lap information (when available)
Not all fields are available for all races, especially historical data. Always check for the existence of fields before accessing them.

Logging Configuration

The script uses structured logging:
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler("race_results_fetch.log"),
        logging.StreamHandler()
    ]
)

Events

Must run first to create race directories

Qualifying

Fetch qualifying results next

Build docs developers (and LLMs) love