All work

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.

backend/data/process_mot.py
- 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
221 847initial tests analysed
28 892of them failed
13.02%overall failure rate
2.3xrisk from lowest to highest band

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.

5% 10% 15% 20% 8.12% 14.03% 16.42% 16.28% 18.33% under 80k 80k to 140k 140k to 200k 200k to 260k over 260k 74 061 tests 75 846 32 461 16 447 22 976

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

See the risk readout

Back to all six projects