02
When do cars actually start failing?
221 847 real inspection results show exactly where the failure curve bends as mileage climbs. It is not a straight line, and it does not bend where people assume.
- Python
- stream processing
- 5 GB of zips
- PostgreSQL
The UK publishes the result of every MOT inspection it carries out. The catch is that the car I care about, the Corolla E170, was sold in Britain as the Auris, and a UK "Corolla" from 2019 onwards is the next generation entirely. Include it and the sample is quietly poisoned. So the dataset is the platform twin, and it is labelled as a proxy on every single surface that shows it, because a number you have to explain is better than a number that is wrong.
The zips are too large to extract comfortably, and the 2023 archive uses a compression format the Python standard library cannot read at all, so the pipeline streams the CSVs straight out of them. Miles convert to kilometres. Only initial tests count, never retests, because retests would flatter every figure on the page.
- Pipe-delimited ('|'). UK odometer is MILES -> converted to km (x1.60934). - Fail rate uses INITIAL tests only (test_type 'NT'), not retests ('RT'), the standard MOT methodology. Pass = P/PRS, Fail = F, abandoned excluded. - Failure reasons = test_item rows with rfr_type_code 'F' (major/fail; 'M' minor + 'A' advisory don't fail) on those E170-platform NT tests. def is_e170_platform(make: str, model: str, year: int | None) -> str | None: """Return 'auris' / 'corolla' if this row is the E170 platform family, else None.""" if make != "TOYOTA" or year is None: return None if "AURIS" in model and 2013 <= year <= 2019: return "auris" if "COROLLA" in model and 2013 <= year <= 2018: # 2019+ Corolla = E210, exclude return "corolla" return None
Failure rate by mileage
The honest detail most charts would smooth away: the 200 000 to 260 000 band comes in fractionally below the one before it. Real data is lumpy, so the line is drawn as measured.
Source: UK DVSA MOT anonymised bulk, 2023 and 2024. Auris E180 platform twin used as a proxy for the Polish Corolla E170: mechanical failures transfer, body specific findings differ.
How it was done
Choose the honest sample, then say so
The car in question was sold in Britain under a different name, and the British car wearing the same name from 2019 is a different generation. The pipeline selects the platform twin by make, model and year, and every surface that shows the result labels it as a proxy.
Stream, do not extract
The archives are large and one year uses a compression variant the Python standard library cannot read. Rather than unpack 5 GB to disk, the reader opens each member and streams rows straight out of it.
Count only what can fairly be counted
Initial tests only, never retests. A retest happens after the fault has been fixed, so including them would quietly improve every failure rate on the page. Minor and advisory notes are excluded from failures because they do not fail the test.
Convert units at the boundary
The source records miles. Everything downstream is metric, so the conversion happens once, at read time, rather than being repeated and eventually forgotten somewhere.
What this does not tell you
Every dataset has an edge. Here is where this one stops, said plainly, because a number you have to qualify is worth more than one you do not.
- This is British inspection data used as a stand in for a Polish car. Mechanical wear transfers well between identical platforms. Anything body specific, or anything driven by local road salt, climate or inspection culture, does not.
- The curve is not perfectly monotonic: the 200 000 to 260 000 band comes in fractionally below the band before it. That is left visible rather than smoothed, because smoothing it would be inventing a trend.
- Mileage is as recorded at test. It is not independently verified.