Skip to main content
Module Location: src/tif1/io_pipeline.py Source Implementation: src/tif1/core.py (re-exported for public API) Dependencies: pandas, polars (optional), pydantic (validation), orjson (JSON parsing)
The io_pipeline module is the core data transformation layer in tif1, responsible for converting raw JSON payloads from the TracingInsights CDN into structured, FastF1-compatible DataFrames. This module orchestrates the entire data flow from network fetch through validation, parsing, column renaming, type coercion, and final DataFrame construction. The pipeline is designed with three primary goals:
  1. Performance: Zero-copy construction, vectorized operations, and minimal memory allocations
  2. Compatibility: 100% FastF1-compatible output with identical column names, types, and ordering
  3. Reliability: Comprehensive validation, error handling, and graceful degradation for malformed data
Internal API: This module contains internal implementation details. The API is subject to change without notice. Most users should use the high-level Session API instead, which provides a stable interface and handles all pipeline operations automatically.Advanced Users Only: Direct use of these functions is intended for:
  • Custom data processing pipelines
  • Performance optimization and profiling
  • Integration with external data sources
  • Testing and debugging data transformations

Architecture Overview

The I/O pipeline is designed as a multi-stage transformation system that prioritizes performance, correctness, and FastF1 compatibility. Each stage is optimized for zero-copy operations where possible, with careful attention to memory efficiency and processing speed.

Design Principles

The pipeline architecture follows these core principles:
  1. Separation of Concerns: Each function has a single, well-defined responsibility
  2. Composability: Functions can be chained together to build complex transformations
  3. Backend Agnostic: Supports both pandas and polars with library-specific optimizations
  4. Fail-Safe: Graceful degradation for malformed data, with optional strict validation
  5. Performance First: Zero-copy construction, vectorized operations, and lazy evaluation where possible

Pipeline Stages

The data transformation pipeline consists of six distinct stages, each handling a specific aspect of the data flow:

Stage Descriptions

Data Flow Characteristics

The pipeline is optimized for the following characteristics:
  • Zero-copy construction: Uses copy=False in pandas and strict=False in polars to avoid unnecessary memory allocations
    • Pandas: pd.DataFrame(data, copy=False) creates views instead of copies when possible
    • Polars: pl.DataFrame(data, strict=False) allows flexible schema inference without strict type checking
    • Result: 30-50% reduction in memory usage for large datasets
  • Batch processing: Processes entire datasets at once using vectorized operations rather than row-by-row iteration
    • All type coercions use pandas/polars vectorized operations
    • Column renaming applied in single operation via dictionary mapping
    • Categorical conversion applied to all columns simultaneously
    • Result: 10-100x faster than row-by-row processing
  • Lazy validation: Validation is optional and can be disabled for maximum performance in production environments
    • Controlled by validate_data, validate_lap_times, and validate_telemetry config flags
    • Non-strict mode logs errors but continues processing
    • Strict mode raises InvalidDataError on validation failures
    • Result: 5-20ms saved per session when validation is disabled
  • Dual backend support: Seamlessly supports both pandas and polars with library-specific optimizations
    • Pandas: Optimized for categorical types, nullable booleans, and timedelta operations
    • Polars: Optimized for lazy evaluation, memory efficiency, and parallel processing
    • Backend selection via lib parameter (“pandas” or “polars”)
    • Result: Users can choose the best backend for their use case
  • FastF1 compatibility: Ensures output DataFrames match FastF1’s column names, types, and ordering conventions
    • Column names: PascalCase (e.g., LapTime, Sector1Time)
    • Column types: timedelta64[ns] for times, float64 for numeric, category for categorical
    • Column order: Matches FastF1’s FASTF1_LAPS_COLUMN_ORDER constant
    • Result: Drop-in replacement for FastF1 with zero code changes

Performance Benchmarks

Typical performance characteristics on modern hardware (Intel i7/AMD Ryzen 7, 16GB RAM):
Performance Tip: For maximum performance, disable validation in production:
This can reduce processing time by 10-30% for large datasets.

Core Concepts

JSON Payload Structure

The pipeline processes several types of JSON payloads, each with a distinct structure optimized for network efficiency and parsing speed.

Lap Data Payload

Source Files: session_laptimes.json, {driver}_tel.json Purpose: Contains lap timing data, sector times, tire information, and track status Structure: Dictionary of arrays (columnar format for efficient parsing)
Key Characteristics:
  • Columnar format: Each field is an array, not an array of objects (faster parsing)
  • Abbreviated keys: Short keys reduce JSON size by ~30% (e.g., "s1" instead of "sector_1_time")
  • Consistent lengths: All arrays must have the same length (validated by Pydantic)
  • Nullable values: null values allowed for optional fields
  • Type flexibility: Numbers can be int or float, booleans can be 0/1 or true/false

Driver Metadata Payload

Source File: drivers.json Purpose: Contains driver information, team assignments, and visual metadata Structure: Array of driver objects
Key Characteristics:
  • Array format: List of driver objects (not a dictionary)
  • 3-letter codes: Driver codes are always 3 uppercase letters (e.g., "VER", "HAM")
  • Team colors: Hex color codes for visualization (e.g., "#3671C6")
  • Headshot URLs: Direct links to driver photos for UI integration

Weather Data Payload

Source File: weather.json Purpose: Contains session weather conditions sampled at regular intervals Structure: Dictionary of arrays (time-series data)
Key Characteristics:
  • Time-series format: Data sampled at regular intervals (typically 60 seconds)
  • Abbreviated keys: wT (time), wAT (air temp), wTT (track temp), etc.
  • Metric units: Temperatures in Celsius, pressure in mbar, wind speed in m/s
  • Boolean rainfall: true/false for rain detection

Race Control Messages Payload

Source File: rcm.json Purpose: Contains race control messages, flags, and safety car deployments Structure: Dictionary of arrays (event log)
Key Characteristics:
  • Event log format: Chronological list of race control events
  • Category types: Flag, SafetyCar, DRS, Other
  • Track status codes: “1” (green), “2” (yellow), “4” (safety car), “5” (red), “6” (VSC), “7” (VSC ending)
  • Sector-specific: Some events apply to specific sectors (1, 2, or 3)
  • Driver-specific: Some events target specific drivers (by driver number)

Column Naming Philosophy

The pipeline transforms abbreviated JSON keys into descriptive, FastF1-compatible column names through a sophisticated mapping system.

Naming Conventions

Transformation Process

The pipeline supports bidirectional mapping to handle both raw and validated JSON:

Mapping Tables

The complete mapping is defined in LAP_RENAME_MAP in src/tif1/core_utils/constants.py: Timing Columns: Speed Columns: Tire Columns: Metadata Columns: Flag Columns: Weather Columns:
Why Abbreviated Keys? The TracingInsights CDN serves millions of requests per month. Using abbreviated keys reduces JSON payload size by ~30%, saving bandwidth and improving load times. The pipeline transparently handles the transformation to readable column names.

Type System

The pipeline enforces a strict type system to ensure data consistency and FastF1 compatibility. All type coercions are performed using vectorized operations for maximum performance.

Type Categories

Type Coercion Rules

Timedelta Conversion:
Numeric Coercion:
Boolean Coercion:
Categorical Conversion:

Column-Specific Types

Lap DataFrame Types:
Weather DataFrame Types:
Telemetry DataFrame Types:

Type Coercion Performance

Type coercion is performed using vectorized operations for maximum performance:
Integer Lap Numbers: Lap numbers are stored as float64 (not int64) to allow NaN values for missing laps. This matches FastF1’s behavior and ensures compatibility. Never cast lap numbers to integers without handling NaN values first.
Categorical Optimization: Categorical types reduce memory usage by 50-80% for columns with low cardinality (Driver, Team, Compound, TrackStatus). However, they add overhead for small datasets. Use polars_lap_categorical=False config to disable categorical types in polars for maximum performance.

API Reference

_validate_json_payload

Validates raw JSON payloads using Pydantic schemas when validation is enabled in the global configuration. This function acts as a gatekeeper, ensuring data integrity before DataFrame construction begins.

Validation Behavior

The validation process is path-aware and applies different schemas based on the resource type: Non-strict mode means validation errors are logged but don’t raise exceptions, allowing the pipeline to continue with potentially imperfect data.

Parameters

  • path (str): Resource path for error context and schema selection
    • Examples: "drivers.json", "laps/VER/19_tel.json", "weather.json"
    • Used to determine which validation schema to apply
    • Included in error messages for debugging
  • data (dict[str, Any]): Raw JSON dictionary from CDN fetch
    • Must be a dictionary (not a list or primitive)
    • Keys are JSON field names (abbreviated or snake_case)
    • Values are typically lists of primitives or nested dictionaries

Returns

  • dict[str, Any]: Validated and potentially transformed JSON dictionary
    • Keys may be transformed from abbreviated to snake_case
    • Values are type-checked and coerced where necessary
    • Invalid fields may be removed or replaced with defaults

Raises

  • InvalidDataError: If validation fails in strict mode or encounters fatal errors
    • Includes the resource path in the error message
    • Contains detailed validation error information
    • Preserves the original exception as the cause

Special Handling

Telemetry Payload Sanitization: Telemetry payloads receive special treatment to remove validator-only defaults that would break DataFrame construction:
Driver Validation Fallback: Driver validation failures in non-strict mode return the original unvalidated data:

Configuration

Validation is controlled by multiple config flags:

Performance Impact

Validation adds overhead to the data pipeline:
  • Lap data validation: ~5-10ms per session
  • Telemetry validation: ~10-20ms per driver
  • Weather/race control validation: ~1-2ms per session
For maximum performance in production environments, disable validation:

Example Usage

This function uses the global config singleton from config.get_config(). The underlying implementation in async_fetch.py accepts a config parameter for testing, but the exported version in io_pipeline.py always uses the global config.
Validation is most useful during development and debugging. In production, consider disabling validation for maximum performance, especially when processing large datasets or performing batch operations.

_extract_driver_codes

Extracts a set of 3-letter driver codes from the drivers metadata payload. This function is used to quickly determine which drivers participated in a session without processing full metadata.

Parameters

  • drivers (list[dict] | None): List of driver dictionaries from drivers.json, or None
    • Each dictionary must contain a "driver" key with the 3-letter code
    • If None or empty list, returns an empty set
    • Malformed dictionaries without "driver" key are silently skipped

Returns

  • set[str]: Set of unique 3-letter driver codes
    • Examples: {"VER", "HAM", "LEC", "SAI"}
    • Empty set if input is None or empty
    • Duplicates are automatically removed by set construction

Implementation Details

The function performs a simple list comprehension with dictionary key access:

Example Usage

Use Cases

This function is primarily used for:
  1. Session validation: Checking if a session has driver data before processing
  2. Driver filtering: Determining which drivers to fetch telemetry for
  3. Quick lookups: Fast set membership tests without processing full metadata
  4. Debugging: Logging which drivers are present in a session
This function is extremely lightweight and performs no validation or transformation. It’s designed for quick driver enumeration without the overhead of full metadata processing.

_extract_driver_info_map

Extracts driver metadata from the drivers payload and creates a lookup dictionary keyed by driver code. This function provides fast O(1) access to driver information during DataFrame construction.

Parameters

  • drivers (list[dict] | None): List of driver dictionaries from drivers.json, or None
    • Each dictionary contains full driver metadata
    • If None or empty list, returns an empty dictionary
    • Malformed dictionaries without "driver" key are silently skipped

Returns

  • dict[str, dict]: Dictionary mapping driver codes to raw metadata dictionaries
    • Keys: 3-letter driver codes (e.g., "VER", "HAM")
    • Values: Raw JSON dictionaries with all metadata fields
    • Empty dictionary if input is None or empty

Metadata Fields

Each driver metadata dictionary contains the following fields:
The returned dictionary contains raw JSON keys (snake_case or abbreviated), not the renamed DataFrame columns (PascalCase). Column renaming happens later in _process_lap_df. Do not assume DataFrame column names will match these keys.

Implementation Details

The function creates a dictionary comprehension that maps driver codes to their full metadata:

Example Usage

Use Cases

This function is used throughout the pipeline for:
  1. DataFrame enrichment: Adding driver metadata columns to lap DataFrames
  2. Team assignment: Mapping driver codes to team names
  3. Display formatting: Accessing driver names and colors for plotting
  4. Validation: Checking if a driver code is valid for a session

Performance Characteristics

  • Time complexity: O(n) where n is the number of drivers (typically 20)
  • Space complexity: O(n) for the dictionary storage
  • Lookup time: O(1) for accessing driver info by code
This function creates a shallow copy of the metadata dictionaries. Modifying the returned dictionaries will not affect the original input, but modifying nested objects within the dictionaries will affect the original data.

_create_lap_df

Creates a raw DataFrame from lap data JSON with driver and team metadata. This function performs zero-copy construction and handles array length normalization for Python 3.12+ compatibility.

Parameters

  • lap_data (dict): Dictionary of lap data arrays (columnar format, not row-based)
    • Keys: Internal JSON field names like "lap", "time", "s1", "s2", "s3", etc.
    • Values: Lists/arrays of primitive values (numbers, strings, booleans)
    • Structure: All arrays should have the same length (normalized automatically if mismatched)
    • Example:
  • driver (str): 3-letter driver code (e.g., "VER", "HAM", "LEC")
    • Format: Exactly 3 uppercase letters
    • Purpose: Added as a constant column to all rows
    • Validation: No validation performed (assumed valid from upstream)
  • team (str): Full team name (e.g., "Red Bull Racing", "Mercedes", "Ferrari")
    • Format: Free-form string (no length restrictions)
    • Purpose: Added as a constant column to all rows
    • Validation: No validation performed (assumed valid from upstream)
  • lib (str): DataFrame library to use ("pandas" or "polars")
    • pandas: Uses pd.DataFrame(data, copy=False) for zero-copy construction
    • polars: Uses pl.DataFrame(data, strict=False) for flexible schema inference
    • Default: No default (must be explicitly specified)

Returns

  • DataFrame: Raw lap DataFrame with unnormalized column names
    • Columns: Raw JSON keys (e.g., "lap", "time", "s1") + "Driver" + "Team"
    • Types: Inferred from input data (not coerced yet)
    • Order: Arbitrary (column order not guaranteed)
    • Note: Column renaming and type coercion happen later in _process_lap_df

Raw Columns Created

The function creates the following columns (before renaming): Core Timing Columns:
  • lap: Lap number (1-indexed integer/float)
  • time: Lap time in seconds (float)
  • s1, s2, s3: Sector times in seconds (float)
  • sesT: Session time at lap end in seconds (float)
Speed Columns:
  • vi1, vi2: Speed trap 1 and 2 in km/h (float)
  • vfl: Finish line speed in km/h (float)
  • vst: Speed trap in km/h (float)
Tire Columns:
  • compound: Tire compound name (string: SOFT, MEDIUM, HARD, INTERMEDIATE, WET)
  • life: Tire age in laps (integer)
  • stint: Stint number (integer)
  • fresh: Fresh tire flag (boolean)
Metadata Columns:
  • pb: Personal best lap flag (boolean)
  • status: Track status code (string: “1”, “2”, “4”, “5”, “6”, “7”)
  • pos: Position at lap end (integer)
  • dNum: Driver number (string)
  • drv: Driver code (string, may differ from driver parameter)
  • team: Team name (string, may differ from team parameter)
Flag Columns:
  • del: Lap deleted flag (boolean)
  • delR: Deletion reason (string)
  • ff1G: FastF1 generated data flag (boolean)
  • iacc: Accuracy flag (boolean)
Pit Columns:
  • pout: Pit out time in seconds (float)
  • pin: Pit in time in seconds (float)
Session Time Columns:
  • s1T, s2T, s3T: Session times at sector ends in seconds (float)
  • lST: Lap start time in seconds (float)
  • lSD: Lap start date (string)
Weather Columns (per-lap weather data):
  • wT: Weather sample time in seconds (float)
  • wAT: Air temperature in Celsius (float)
  • wTT: Track temperature in Celsius (float)
  • wH: Humidity percentage (float)
  • wP: Pressure in mbar (float)
  • wR: Rainfall flag (boolean)
  • wWD: Wind direction in degrees (float)
  • wWS: Wind speed in m/s (float)
Added Columns:
  • Driver: Driver code from driver parameter (string)
  • Team: Team name from team parameter (string)

Array Length Normalization

The function automatically normalizes mismatched array lengths (required in Python 3.12+):
Normalization Rules:
  1. Calculate maximum length across all arrays
  2. Pad short arrays with None values to match max length
  3. Replicate scalar values to match max length
  4. Handle numpy arrays and other array-like objects

Backend-Specific Behavior

Pandas Backend (lib="pandas"):
Polars Backend (lib="polars"):

Example Usage

Basic Usage:
Handling Missing Data:
Empty DataFrame:

Performance Characteristics

  • Time complexity: O(n × m) where n = number of rows, m = number of columns
  • Space complexity: O(n × m) for DataFrame storage
  • Zero-copy optimization: Avoids data duplication when possible
  • Typical performance:
    • 50 laps × 40 columns: ~1-2ms (pandas), ~2-3ms (polars)
    • 1000 laps × 40 columns: ~10-20ms (pandas), ~15-25ms (polars)
Column Naming: This function does NOT rename columns. Raw JSON keys are preserved exactly as provided. Use _process_lap_df to apply column renaming and type coercion. Attempting to access FastF1-style column names (e.g., "LapTime", "Sector1Time") will fail at this stage.
Driver/Team Columns: The driver and team parameters are added as constant columns to all rows. If the input lap_data already contains "Driver" or "Team" keys, they are removed before adding the parameter values. This ensures consistency and prevents duplicate columns.
Performance Tip: For maximum performance, ensure all arrays in lap_data have the same length before calling this function. Array length normalization adds overhead (~10-20% slower) when lengths are mismatched.

_create_session_df

Creates a DataFrame from session-level data (weather, race control messages, etc.) with automatic column renaming. This function is optimized for zero-copy construction and handles empty datasets gracefully.

Parameters

  • data (dict[str, Any]): Raw data dictionary with arrays (columnar format)
    • Keys: JSON field names (abbreviated or snake_case)
    • Values: Lists/arrays of primitive values
    • Structure: All arrays should have consistent lengths
    • Example:
  • rename_map (dict[str, str]): Column rename mapping dictionary
    • Purpose: Maps JSON keys to DataFrame column names
    • Format: {json_key: dataframe_column}
    • Available maps:
      • WEATHER_RENAME_MAP: Weather data columns
      • RACE_CONTROL_RENAME_MAP: Race control message columns
      • TELEMETRY_RENAME_MAP: Telemetry data columns
      • LAP_RENAME_MAP: Lap timing data columns
    • Location: src/tif1/core_utils/constants.py
  • lib (str): DataFrame library to use ("pandas" or "polars")
    • pandas: Uses pd.DataFrame(data, copy=False) for zero-copy construction
    • polars: Uses pl.DataFrame(data, strict=False) for flexible schema inference

Returns

  • DataFrame: Session DataFrame with renamed columns
    • Columns: Renamed according to rename_map (PascalCase)
    • Types: Inferred from input data (no type coercion applied)
    • Order: Arbitrary (column order not guaranteed)
    • Empty handling: Returns empty DataFrame if input is empty

Column Rename Maps

Weather Rename Map (WEATHER_RENAME_MAP):
Race Control Rename Map (RACE_CONTROL_RENAME_MAP):
Telemetry Rename Map (TELEMETRY_RENAME_MAP):

Implementation Details

The function performs three main operations:
  1. DataFrame Construction: Creates DataFrame using zero-copy optimization
  2. Empty Check: Returns empty DataFrame if input is empty
  3. Column Renaming: Applies rename map to transform column names

Example Usage

Weather Data:
Race Control Messages:
Telemetry Data:
Empty Data Handling:
Validated Data (snake_case keys):

Backend-Specific Behavior

Pandas Backend (lib="pandas"):
Polars Backend (lib="polars"):

Performance Characteristics

  • Time complexity: O(n × m) where n = number of rows, m = number of columns
  • Space complexity: O(n × m) for DataFrame storage
  • Zero-copy optimization: Avoids data duplication when possible
  • Typical performance:
    • Weather data (200 rows × 8 cols): ~1-3ms (pandas), ~2-4ms (polars)
    • Race control (50 rows × 10 cols): ~0.5-2ms (pandas), ~1-3ms (polars)
    • Telemetry (10000 rows × 15 cols): ~50-100ms (pandas), ~40-80ms (polars)

Use Cases

This function is used throughout the pipeline for:
  1. Weather DataFrames: Converting weather JSON to DataFrames
  2. Race Control DataFrames: Converting race control messages to DataFrames
  3. Telemetry DataFrames: Converting telemetry JSON to DataFrames (before lap-specific processing)
  4. Custom Session Data: Any session-level data that needs column renaming
No Type Coercion: This function does NOT perform type coercion. Types are inferred from the input data. For lap DataFrames that require type coercion (timedelta conversion, categorical types, etc.), use _create_lap_df followed by _process_lap_df.
Custom Rename Maps: You can create custom rename maps for specialized data formats. Just provide a dictionary mapping JSON keys to desired DataFrame column names.

_process_lap_df

Post-processes lap DataFrame by applying column renaming, type coercion, categorical conversion, and FastF1-compatible column ordering. This is the final transformation stage that converts raw lap data into a fully FastF1-compatible DataFrame.

Parameters

  • lap_df (DataFrame): Raw lap DataFrame from _create_lap_df
    • Columns: Raw JSON keys (e.g., "lap", "time", "s1", "s2")
    • Types: Inferred types from JSON (not coerced yet)
    • Order: Arbitrary column order
    • Source: Output from _create_lap_df
  • lib (str): DataFrame library ("pandas" or "polars")
    • pandas: Full type coercion with categorical types
    • polars: Selective type coercion (categorical types optional)

Returns

  • DataFrame: Fully processed lap DataFrame with:
    • Renamed columns: PascalCase FastF1-compatible names
    • Proper data types: timedelta64[ns], float64, bool, category, etc.
    • Categorical types: Applied to Driver, Team, Compound, TrackStatus (pandas default)
    • FastF1 column order: Matches FASTF1_LAPS_COLUMN_ORDER constant
    • Additional columns: LapTimeSeconds (float representation of LapTime)

Transformations Applied

The function applies six major transformations in sequence: 1. Duplicate Column Removal (pandas only):
2. Column Renaming:
3. Timedelta Conversion (pandas):
4. Type Coercion (pandas):
5. LapTimeSeconds Column:
6. Categorical Conversion:
7. Column Reordering:

FastF1-Compatible Column Order

The final DataFrame has columns in this exact order (matching FastF1):

Type Coercion Details

Timedelta Columns (pandas):
  • LapTime, Time, Sector1Time, Sector2Time, Sector3Time
  • Sector1SessionTime, Sector2SessionTime, Sector3SessionTime
  • PitOutTime, PitInTime, LapStartTime, WeatherTime
  • Conversion: Float seconds → timedelta64[ns]
  • Method: pd.to_timedelta(values, unit='s')
Numeric Columns (float64):
  • LapNumber, Stint, TyreLife, Position
  • SpeedI1, SpeedI2, SpeedFL, SpeedST
  • AirTemp, TrackTemp, Humidity, Pressure, WindDirection, WindSpeed
  • LapTimeSeconds
  • Conversion: Mixed types → float64
  • Method: pd.to_numeric(values, errors='coerce')
Boolean Columns (bool):
  • IsPersonalBest, FreshTyre, FastF1Generated, IsAccurate, Rainfall
  • Conversion: Mixed boolean representations → bool
  • Method: values.fillna(False).astype(bool)
Nullable Boolean (boolean):
  • Deleted (pandas nullable boolean type)
  • Conversion: Mixed boolean representations → boolean
  • Method: values.astype('boolean')
String Columns (object):
  • DriverNumber, DeletedReason, LapStartDate, QualifyingSession
  • Conversion: No conversion (kept as object dtype)
Categorical Columns (category):
  • Driver, Team, Compound, TrackStatus
  • Conversion: String → category
  • Method: values.astype('category')
  • Memory savings: 50-80% reduction for low-cardinality columns

Backend-Specific Behavior

Pandas Backend (lib="pandas"):
Polars Backend (lib="polars"):

Configuration Options

Categorical Types in Polars:

Example Usage

Basic Processing:
Type Verification:
Memory Comparison:
Polars Processing:

Performance Characteristics

  • Time complexity: O(n × m) where n = number of rows, m = number of columns
  • Space complexity: O(n × m) for DataFrame storage
  • Typical performance (pandas):
    • 50 laps: ~2-5ms
    • 1000 laps: ~20-40ms
    • 10000 laps: ~200-400ms
  • Typical performance (polars):
    • 50 laps: ~3-7ms
    • 1000 laps: ~15-30ms
    • 10000 laps: ~150-300ms

Performance Breakdown

Input Requirements: This function expects a raw lap DataFrame from _create_lap_df. Do not call this function on already-processed DataFrames, as it will fail or produce incorrect results. The function is designed to be called exactly once per lap DataFrame.
Categorical Types: Categorical types provide significant memory savings (50-80%) for columns with low cardinality (Driver, Team, Compound, TrackStatus). However, they add overhead for small datasets (<100 laps). For maximum performance with small datasets, consider disabling categorical types.
LapTimeSeconds Column: The LapTimeSeconds column is added for convenience when you need lap times as float values (e.g., for plotting or calculations). It’s automatically kept in sync with the LapTime column.

Column naming conventions

The I/O pipeline transforms raw JSON keys to FastF1-compatible column names:
The complete mapping is defined in LAP_RENAME_MAP in src/tif1/core_utils/constants.py. Both validated (snake_case) and raw (abbreviated) JSON keys are supported.

Library Support

The pipeline supports both pandas and polars libraries:
Library-specific optimizations:
  • pandas: Uses pd.DataFrame(data, copy=False) for zero-copy construction
  • polars: Uses pl.DataFrame(data, strict=False) with schema inference
  • pandas: Applies categorical types by default for Driver, Team, Compound, TrackStatus
  • polars: Categorical types disabled by default (enable with polars_lap_categorical config)

Data Validation

When validate_data is enabled in config, _validate_json_payload validates raw JSON using Pydantic schemas:
  1. Required fields: Ensures all required fields are present in JSON
  2. Type checking: Validates data types match schema definitions
  3. Value ranges: Checks values are within expected ranges
  4. Referential integrity: Validates driver codes, lap numbers, etc.
Example validation error:
Validation is controlled by the validate_data config option. When disabled, raw JSON is passed through without validation for maximum performance.

Performance Considerations

The I/O pipeline is heavily optimized for speed:
  • Zero-copy construction: Uses copy=False in pandas, strict=False in polars
  • Batch processing: Processes all laps at once, not row-by-row
  • Vectorized operations: Uses numpy/pandas vectorization for type coercion
  • Minimal allocations: Reuses arrays where possible, avoids intermediate copies
  • Lazy categorical: Categorical types applied only when beneficial
Typical performance (pandas lib):
  • Process 50 laps: ~2-5ms
  • Process 1000 laps: ~20-40ms
  • Full session (20 drivers × 50 laps): ~100-200ms
For maximum performance, disable validation (validate_data=False) and use pandas. Polars is faster for very large datasets (>10k laps) but has higher overhead for small datasets.

Internal Implementation

The pipeline maintains two sets of column names:
  • JSON keys: Abbreviated keys like "lap", "s1", "vi1" (raw) or snake_case like "lap_number", "sector_1_time" (validated)
  • DataFrame columns: PascalCase like "LapNumber", "Sector1Time", "SpeedI1"
Renaming happens in _process_lap_df() using LAP_RENAME_MAP from core_utils/constants.py. The map supports both raw and validated JSON keys for maximum compatibility.
The pipeline coerces types to ensure FastF1 compatibility:
  • Lap times (float seconds) → timedelta64[ns]
  • Session times (float seconds) → timedelta64[ns]
  • Lap numbers → float64 (not int, to allow NaN)
  • Boolean flags → bool (fillna False for non-nullable)
  • Deleted flag → boolean (nullable bool)
  • Categorical data → category (pandas only by default)
  • Driver numbers → str (not int, to preserve leading zeros)
Missing values are handled gracefully:
  • Numeric fields: NaN (pandas) or null (polars)
  • String fields: empty string or null
  • Boolean fields: False (fillna applied)
  • Deleted field: null (nullable boolean)
  • Timedelta fields: NaT (not-a-time)
The pipeline never raises errors for missing optional fields. Only validation (when enabled) can raise InvalidDataError for missing required fields.
_create_lap_df normalizes mismatched array lengths (required in Python 3.12+):
  • Calculates max length across all arrays
  • Pads short arrays with None values
  • Replicates scalar values to match max length
This ensures both pandas and polars can construct DataFrames without errors.
Last modified on May 8, 2026