> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/TracingInsights-Archive/Stats/llms.txt
> Use this file to discover all available pages before exploring further.

# Qualifying Results

> Collecting qualifying session data using quali_results.py

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

```python theme={null}
url = f"https://api.jolpi.ca/ergast/f1/{season}/{round_num}/qualifying.json"
```

<Note>
  Qualifying data is most complete from 1994 onwards. Earlier years may have incomplete or missing data.
</Note>

## QualifyingResultsFetcher Class

### Initialization

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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)

```json theme={null}
{
  "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:

```json theme={null}
{
  "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

```python theme={null}
from quali_results import QualifyingResultsFetcher

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

### Multiple Rounds

```python theme={null}
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:

| Field         | Description                    |
| ------------- | ------------------------------ |
| `number`      | Driver's car number            |
| `position`    | Qualifying position            |
| `Driver`      | Driver information             |
| `Constructor` | Team information               |
| `Q1`          | Q1 session time                |
| `Q2`          | Q2 session time (if qualified) |
| `Q3`          | Q3 session time (if qualified) |

<Note>
  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.
</Note>

## Qualifying Format Evolution

The qualifying format has changed throughout F1 history:

| Period       | Format                           |
| ------------ | -------------------------------- |
| Pre-2003     | Single lap qualifying            |
| 2003-2005    | Two sessions (Friday & Saturday) |
| 2006-present | Knockout format (Q1, Q2, Q3)     |

<Warning>
  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.
</Warning>

## Logging Configuration

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

## Related Scripts

<CardGroup cols={2}>
  <Card title="Events" icon="calendar" href="/data-collection/events">
    Creates the base directory structure
  </Card>

  <Card title="Race Results" icon="flag-checkered" href="/data-collection/race-results">
    Fetch race results for comparison
  </Card>

  <Card title="Standings" icon="trophy" href="/data-collection/standings">
    Calculate championship points next
  </Card>
</CardGroup>
