verify_bronze.py re-checks everything ingestion already wrote as a standalone pass over data/bronze/: which races should be there, whether each has its datasets, the laps quality checks, the same completeness rules ingest.py applies before writing, per-race telemetry coverage, and row counts. It’s the script to reach for after a backfill finishes, or any time you want to confirm Bronze is healthy without re-reading ingestion logs.
Nothing about the seasons is hand-typed. The races a season should have are the rounds on its schedule snapshot (schedule/season=YYYY/, written by ingest.py from FastF1’s event schedule) whose race has finished, so a new season is checked as soon as it’s ingested. Documented source gaps (KNOWN_TELEMETRY_GAPS / KNOWN_THIN in ingestion/src/bronze_checks.py, e.g. 2018 Rd1/Rd2 telemetry) are printed as KNOWN and don’t fail the run; anything else is printed as NEW and makes it exit 1.
1
Run it
2
Read the five sections
The output walks through presence, laps data quality, completeness, telemetry coverage and row counts, each printed as its own block, then a summary of NEW vs KNOWN findings.
--season YYYY limits it to one season; --bronze-dir PATH points it at another bronze root.Only seasons present on disk are checked, so this works identically after a single-race pull,
make ingest-recent, or a full make ingest-all there’s no separate “partial” mode to remember.What each section checks
1. Presence
1. Presence
For every season with a
laps/season=<year>/ directory, every finished round on the schedule snapshot must have race laps, qualifying laps, weather, race_control and telemetry. A finished round with no laps, or a race missing a dataset, is flagged by name; a known gap is reported as KNOWN. With no schedule snapshot the check falls back to the races on disk.2. Laps data quality
2. Laps data quality
Runs
DataQualityEngine’s schema check (required columns) and duplicate lap-key check on every race and qualifying laps file. Fewer than 50 laps is reported as info, not a failure, matching ingestion (red-flagged sessions are short).3. Completeness
3. Completeness
The thin-load rules
ingest.py applies before writing, run on the files instead: tyre columns (Compound, TyreLife, Stint) at least 90% non-null, LapTime at least 25%, as many results rows as drivers who set laps, race Points/GridPosition at least 90% and qualifying Q1 at least 50% non-null, and non-empty weather and race control. This is what catches a load that was written thin and would otherwise be skipped as done forever.4. Telemetry coverage
4. Telemetry coverage
The share of each race’s laps with merged per-lap telemetry. Every healthy race 2018-2025 is at or above 99.7%; below 95% is flagged.
5. Row-count distribution
5. Row-count distribution
For each season and dataset, reports the number of races present and the median/min/max row count per race (from Parquet metadata). Informational.
The all-column null-rate warning
ingest.py logs while writing (see Data Quality) is not re-checked here: most FastF1 lap columns (pit in/out times, for instance) are mostly null by design. Completeness is judged on the key columns in section 3 instead.Regenerating the coverage table
MARKDOWN_SEASONS (2018–2025); widening it adds rows to the published docs, so that is a deliberate change. make docs-coverage wraps this into docs/snippets/bronze-coverage.mdx, the snippet embedded on Known Issues; a CI gate fails if the committed snippet ever drifts from a fresh regeneration, so the table in the docs can’t go stale.
Next
Manifest report
Per-run status and schema-drift detection, instead of a point-in-time disk check.
Architecture
How the data-quality gate runs inline during ingestion itself.