Skip to main content
The quali_results.py script fetches qualifying results including Q1, Q2, and Q3 times for each driver.

Script Overview

Location: ~/workspace/source/quali_results.py The script uses the QualifyingResultsFetcher class to:
  1. Fetch qualifying results for specific rounds
  2. Handle rate limiting automatically
  3. Save qualifying data to race directories
  4. Log operations for monitoring

API Endpoint

url = f"https://api.jolpi.ca/ergast/f1/{season}/{round_num}/qualifying.json"
Qualifying data is most complete from 1994 onwards. Earlier years may have incomplete or missing data.

QualifyingResultsFetcher Class

Initialization

class QualifyingResultsFetcher:
    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

def make_request(self, url):
    """Make a request to the API with rate limiting"""
    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 Qualifying Results

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

Main Fetch Method

def fetch_round(self, season, round_num):
    """Fetch qualifying results for a specific season and round"""
    logger.info(
        f"Fetching qualifying 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 information
    race_info = self.get_race_info(season, round_num)

    if not race_info:
        logger.warning(f"No race 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 qualifying results
    qualifying_results = self.get_qualifying_results(season, round_num)

    if qualifying_results:
        qualifying_results_path = race_dir / "quali_results.json"
        self.save_json(qualifying_results, qualifying_results_path)
        logger.info(
            f"Successfully fetched qualifying results for {season} {race_name}"
        )
    else:
        logger.warning(f"No qualifying results found for {season} {race_name}")

Output Structure

Stored at: {year}/{race-name}/quali_results.json

Modern Format (Post-2006)

{
  "MRData": {
    "series": "f1",
    "RaceTable": {
      "season": "2024",
      "round": "1",
      "Races": [
        {
          "season": "2024",
          "round": "1",
          "raceName": "Bahrain Grand Prix",
          "Circuit": {
            "circuitId": "bahrain",
            "circuitName": "Bahrain International Circuit"
          },
          "date": "2024-03-02",
          "QualifyingResults": [
            {
              "number": "1",
              "position": "1",
              "Driver": {
                "driverId": "verstappen",
                "givenName": "Max",
                "familyName": "Verstappen",
                "nationality": "Dutch"
              },
              "Constructor": {
                "constructorId": "red_bull",
                "name": "Red Bull",
                "nationality": "Austrian"
              },
              "Q1": "1:29.179",
              "Q2": "1:28.918",
              "Q3": "1:28.997"
            }
          ]
        }
      ]
    }
  }
}

Empty Response (No Data)

For races where qualifying data is not available:
{
  "MRData": {
    "series": "f1",
    "url": "https://api.jolpi.ca/ergast/f1/1950/5/qualifying.json",
    "limit": "30",
    "offset": "0",
    "total": "0",
    "RaceTable": {
      "season": "1950",
      "round": "5",
      "Races": []
    }
  }
}

Usage Example

Basic Usage

from quali_results import QualifyingResultsFetcher

fetcher = QualifyingResultsFetcher(base_dir=".")
fetcher.fetch_round(2024, 1)

Multiple Rounds

fetcher = QualifyingResultsFetcher(base_dir=".")

for round_num in range(1, 22):
    fetcher.fetch_round(2024, round_num)

logger.info("Qualifying results fetching completed")

Qualifying Data Fields

Each qualifying result contains:
FieldDescription
numberDriver’s car number
positionQualifying position
DriverDriver information
ConstructorTeam information
Q1Q1 session time
Q2Q2 session time (if qualified)
Q3Q3 session time (if qualified)
Only the top 15 drivers participate in Q2, and only the top 10 participate in Q3. Drivers eliminated in earlier sessions won’t have times for subsequent sessions.

Qualifying Format Evolution

The qualifying format has changed throughout F1 history:
PeriodFormat
Pre-2003Single lap qualifying
2003-2005Two sessions (Friday & Saturday)
2006-presentKnockout format (Q1, Q2, Q3)
The data structure varies based on the qualifying format used. Modern races use Q1/Q2/Q3 fields, while older races may have different structures or no data at all.

Logging Configuration

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler("qualifying_results_fetch.log"),
        logging.StreamHandler()
    ]
)

Events

Creates the base directory structure

Race Results

Fetch race results for comparison

Standings

Calculate championship points next

Build docs developers (and LLMs) love