← Back to Autonomy

Diagnostics · Data · Foundations

The Data Plumbing Behind Fleet Diagnosis

Every fleet method assumes a clean, joined, trustworthy table. Here is how that table is actually built — joining records by VIN, aligning clocks, and surviving missing data.

Abstract

The fleet posts assumed a tidy table: one row per car, with build attributes, a telemetry summary, and a warranty outcome. That table does not arrive by post — it is assembled from messy raw sources that disagree about identity, time, and meaning. This piece is the unglamorous foundation under the fleet method. We join build records, telemetry, warranty claims, service notes, and DTC logs by VIN into one row per car; survive the record-linkage pitfalls that corrupt the join (typos, duplicate claims, many-to-one rows, remanufactured parts); align events that live on four different clocks to a common time-in-service reference so onset curves and survival models are valid; map and respect missingness instead of imputing over it; harmonize units and drifting DTC dictionaries into one shared data model; and wire it all into an ingestion → canonical store → per-vehicle summary → analysis pipeline with lineage, so any diagnosis can be re-run. The running example is the 6,000-car EV fleet with the P0AE0 charging code — assembled, this time, from the raw.

§ 01 — Why the Foundation Matters

Garbage in, confident-garbage out

Every method in the fleet series — de-mixing the mixture, drawing the cohort boundary, fitting the Weibull, running the CUSUM — opens with the same silent assumption: that you already have a clean, joined, trustworthy table, one row per car.

You almost never do. That table is the output of a long, dull pipeline that nobody writes blog posts about, and when the pipeline is wrong the analysis on top of it is wrong in a particularly dangerous way: confidently wrong. A bad join inflates a failure rate; a clock misalignment bends an onset curve that was actually flat; a silently dropped cohort hides the very cluster you are hunting. The math downstream cannot tell the difference between a real signal and a plumbing artifact — it just computes.

This is the post that sits under posts four through six. The fleet method told you what to do with the table. This one tells you how the table gets built from the raw — and how each step can quietly poison everything above it. We keep the same 6,000-car EV fleet throwing the P0AE0 charging code, with its two real causes: a firmware regression and a bad connector lot.

The unglamorous contract

A diagnosis is only as trustworthy as the table it ran on. Every figure in the fleet posts inherits the assumptions made here — so a mistake in the plumbing is not a small mistake. It is a mistake that wears the costume of a finding.

Part A — Joining the Sources

From five disagreeing systems to one row per car

The car’s story is scattered across systems that were never meant to be read together. The first job is to pull them onto a single spine — and to survive the ways that join goes wrong.

§ 02 — The Entities and the Spine

VIN is the join key; the goal is one row per car

A car’s life is recorded in five places that rarely talk to each other. Each holds a piece of the truth; none holds it all. The spine that pulls them together is the one identifier they share: the VIN.

The sources for our fleet, and what each one knows:

The goal is a single table where every car is exactly one row, carrying its build attributes, a summary of its telemetry, its warranty and service outcome, and its DTC history flattened into features. The VIN is what makes the join possible — and, as the next section shows, the VIN is also where the join breaks.

Figure 1 — Five sources, one spine
VIN join key build records plant · fw · lot · trim telemetry charge · temp · volts warranty claims part · cost · date service notes free text DTC logs P0AE0 · timestamp ⇩ reduce to one row per car the table the fleet method assumes
The VIN is the spine. Five systems each know part of a car’s story; only the VIN lets you stitch them into a single row. Build records are one-per-car already; telemetry and DTC logs are many-per-car and must be summarized down; warranty and service are sparse and present only for cars that came in. The join’s output is the very table every later post quietly assumed it was handed.
Algorithm 1build_vehicle_summary(sources)

input build · telemetry · warranty · service · dtc # keyed, dirtily, by VIN

vins canonical_vin_set(build) # build is the spine: one row per real car

table { }

for v in vins:

row build[v] # plant, build week, fw, connector lot, trim

tel telemetry.where(VIN = v) # many rows → reduce to features

row.cold_charge_cycles count(tel.cold_fast_charge)

row.connector_temp_p95 pctl(tel.conn_temp, 95) # may be MISSING

row.dtc_P0AE0 any(dtc.where(VIN = v).code = "P0AE0")

row.warranty dedup(warranty.where(VIN = v)) # collapse duplicate claims

row.in_service_date earliest(warranty, dtc, telemetry, sale)

table[v] row

return table # one row per car — the contract for §§4–6

# the reduce step is where bias enters: every summary throws information away.

§ 03 — Record-Linkage Pitfalls

When the join key lies

The whole pipeline rests on one assumption: that a VIN means the same car everywhere it appears. In practice the key is dirty, and every kind of dirt has its own way of corrupting the table.

The failure modes, and what each one does to a rate:

When the key is too dirty to match exactly, you fall back to fuzzy matching: decide whether two records refer to the same car by scoring how well their fields agree. The classic framework, from Fellegi and Sunter, puts this on a footing — in plain words, each agreeing field (same plant, same build week, near-identical VIN) is evidence the records are the same car, and each disagreeing field is evidence against. You add up the weighted evidence and set two thresholds: above the upper one, declare a match; below the lower one, declare a non-match; in between, send it to a human. It is the same weigh-the-evidence logic as the diagnosis posts, applied to identity instead of fault.

Figure 2 — Four ways the join corrupts the table
1 · VIN typo / OCR 5YJ...1O3 5YJ...103 → failure lost · rate too LOW 2 · duplicate claim claim #4471 claim #4471 (re-bill) → one fault counted twice · rate too HIGH 3 · many-to-one 1 build × 9k telemetry → attributes counted 9,000× · rate garbage 4 · remanufactured part same VIN, new lot ID → car spans two cohorts
Every dirty key bends the rate a different way. Lost matches deflate the failure rate; duplicates inflate it; un-reduced many-to-one rows make it meaningless; remanufactured parts smear a car across cohorts so neither lot rate is right. None of these announce themselves downstream — the de-mixing in §3 of the fleet post just runs on the corrupted counts. Fuzzy matching (Fellegi–Sunter) recovers the typo-broken matches that exact joins drop.
Identity is a diagnosis too

Deciding “are these two records the same car?” is the same evidence-weighing problem as “is this the root cause?” — agreeing fields raise the odds, disagreeing fields lower them, and an uncertain middle band goes to a human. Get identity wrong and every downstream rate is built on sand.

Part B — Making It Analyzable

Clocks, gaps, and meaning

A joined table is not yet an analyzable one. Three more things have to be true: the events must share a clock, the gaps must be understood rather than filled, and the columns must mean what they say.

§ 04 — Time Alignment

Four clocks, one reference

Onset curves and survival models all answer “how long after what?” If the events live on different clocks, the answer is meaningless no matter how careful the statistics on top.

A single fault event drags four different timestamps behind it, and they rarely agree:

Mix these and you get nonsense. Measure failure age from build date and a car that sat unsold for six months looks like it failed late; measure from report date and a slow data pipeline makes the whole fleet look like it failed on the same day the batch landed. The fix is to pick one common reference — almost always time-in-service — and express every event as an offset from it. Only then are two cars comparable, and only then is the onset curve of the fleet post measuring the thing it claims to measure.

Figure 3 — Align every event to time-in-service
raw: different calendar dates → not comparable JanMaySepJan built sale P0AE0 A B aligned to in-service (t=0) → onset curve is valid t = 0 · in service +90d+180d+270d A B both fail ~7 months in service — the real pattern
Comparable only after alignment. On the calendar, cars A and B fail months apart and look unrelated. Re-zeroed to each car’s in-service date, they both fail about seven months in — the actual onset signal. Aligning to build date instead would have charged each car for the time it sat unsold; aligning to report date would have collapsed the fleet onto the pipeline’s schedule. Pick time-in-service, and mind timezones and drifting onboard clocks on the event timestamp.
Algorithm 2align_to_in_service(row, events)

input car row · its events (dtc · claims · charge sessions)

t0 row.in_service_date # common reference — NOT build, NOT report

for e in events:

ts to_utc(e.timestamp, e.timezone) # normalize zone; correct onboard-clock drift

if ts < row.build_date: flag("impossible time") # validation rule

e.t_in_service ts − t0 # offset from in-service, in days

return events sorted by t_in_service # now two cars are comparable

# onset curves & Weibull fits are only valid on this aligned clock.

§ 05 — Missingness & Data Quality

The gaps are not random

The most dangerous cells in the table are the empty ones — not because they are missing, but because why they are missing usually correlates with the very thing you are studying.

Missing data comes in kinds, and they are not interchangeable:

The trap is that missingness is almost never neutral. It ties straight to the selection and survivorship problems from the diagnosis series: the cars that stop reporting are disproportionately the ones in trouble, so dropping them quietly biases every rate downward. The discipline is threefold — classify each missing field by its mechanism, attach provenance to every value (which source, which version, how trusted), and run validation rules that flag the impossible (a charge temperature of 900°C, an in-service date before the build date) rather than letting it flow into the model.

Figure 4 — A missingness map: cars × fields
plant fw lot conn-temp (fw≥5.1) warranty telemetry old-fw cars new-fw cars no-temp trim decommissioned healthy fleet present missing
Missingness has a shape. The empty cells are not scattered at random — connector-temperature is blank exactly where firmware is old or the trim has no sensor, and the decommissioned row goes dark precisely on the fields that would reveal a failure. Because the gaps line up with a cause, dropping incomplete rows would delete a biased slice of the fleet. Classify the mechanism, attach provenance, and flag rather than fill.
§ 06 — Schema & Semantics

The same field has to mean the same thing

A joined, time-aligned, gap-aware table can still be quietly wrong if two columns that share a name don’t share a meaning. Harmonizing semantics is the least visible and most violated step.

The fault lines:

The remedy is a canonical data model: one definition per concept, written down, with every source mapped into it. A canonical connector_temp_c column with an explicit unit, a versioned DTC dictionary keyed by model year, a supplier-lot crosswalk. The payoff is exactly what the fleet method assumes without saying so — that when it reads a column, the column means one thing across all 6,000 cars.

ConceptRaw sources disagree onCanonical definition
connector temp°C vs °F; sensor vs inferredconnector_temp_c · °C · measured only
distancekm vs milesodometer_km · kilometers
fault codeP0AE0 meaning drifts by MYdtc + dtc_dict_version keyed by model year
connector lotper-supplier numberinglot_id via supplier crosswalk
in-servicebuild vs sale vs first-drivein_service_date · first telematics heartbeat

Part C — The Platform

Wiring it so a diagnosis can be re-run

The steps so far are logic. A platform makes them repeatable: raw in, canonical store, per-vehicle summary, analysis — with lineage so any finding can be reproduced.

§ 07 — Architecture

Ingestion → canonical store → summary → analysis

The pipeline is four layers, each with one job, each insulating the next from the mess below it. The shape is conventional on purpose — boring infrastructure is reproducible infrastructure.

The layers:

Two cross-cutting choices matter. Batch vs streaming: the per-vehicle summary can be rebuilt nightly in batch for the retrospective analyses, but the surveillance from the rigor post — the CUSUM that catches a forming cluster at fifty cars — needs a streaming path that updates as telemetry lands. And lineage: every value in the summary carries a pointer back to the raw records and the code version that produced it, so a diagnosis is not a one-time artifact but something a regulator or a future engineer can re-run and get the same answer.

Figure 5 — The pipeline, batch and streaming
ingestion raw feeds, as-is canonical store units · ids · clocks one meaning/field per-vehicle summary one row per car analysis de-mix · Weibull · CUSUM nightly batch ──────────► streaming path → feeds CUSUM surveillance (catch a cluster at 50 cars) lineage & reproducibility — every value points back to raw record + code version
One direction, four jobs, full traceability. Raw lands untouched; the canonical store fixes meaning; the summary reduces to one row per car; analysis runs only on that. A streaming side-path keeps the per-cohort CUSUM of the rigor post fresh, while the lineage backbone lets any number be traced back to the records and code that made it — the difference between a diagnosis you can defend and one you merely assert.
§ 08 — Worked Example

Assembling the 6,000-car table from the raw

Put it together end to end. Five messy feeds go in; the one-row-per-car table the fleet de-mix consumes comes out. Here is each decision, in order.

Join build + telemetry + warranty by VIN

spine: 6,000 build rows · ~54M telemetry rows · 412 warranty claims

Build is the spine — exactly 6,000 cars, one row each. Telemetry and DTC logs are reduced to per-car features before joining (never joined raw, or §03’s many-to-one explosion ruins the counts). Warranty and service attach where present.

Dedupe the claims

412 raw claims → 389 distinct repairs

23 rows are re-bills or double entries on the same VIN, same part, same week. Collapse them or the P0AE0 failure rate inflates by ~6%. Fuzzy-match the 4 typo-broken VINs back to their cars so their failures are not silently lost.

Align to in-service date

t = 0 at first telematics heartbeat

Re-zero every event — DTC, claim, charge session — to each car’s in-service date, not build or report date. Now the onset curve measures months-in-service, and the firmware and connector clusters separate cleanly in time.

Handle the 4% missing connector-temperature

238 cars · field absent (old fw / no-temp trim)

The gap is biased — it lines up with old firmware and sensor-less trims (§05). Do not impute a plausible value; that would invent signal exactly where a cause lives. Flag the field as missing with its mechanism and let the model treat it as its own category.

Emit one row per car

6,000 rows × ~40 canonical columns

Build attributes, telemetry summary features, dedup’d warranty outcome, flattened DTC history, missingness flags, provenance. This is the table §3 of the fleet post calls “the data” — now actually built, and defensible.

VINfwlotconn_temp_cP0AE0mo-in-svcoutcome
5YJ…0042v5.1L-22091 (p95)yes7.2warranty repair
5YJ…0317v5.1L-220missingyes6.8warranty repair
5YJ…1185v4.9L-204missingno11.4healthy
5YJ…2630v5.1L-20458 (p95)no9.1healthy

That table — VIN-joined, deduped, clock-aligned, missingness-flagged, semantically harmonized — is what every figure in the fleet posts silently assumed it was handed. The difference now is that you could re-run it from the raw and get the same rows.

§ 09 — Practical Notes

Field rules for the data plumbing

1
spine first

Let build records define the car set

One row per car comes from the source that is one-per-car by nature. Everything else attaches to that spine; nothing else gets to invent or multiply cars.

2
reduce before join

Summarize many-row sources first

Telemetry and DTC logs are many-per-car. Reduce them to features before joining, or a single car’s attributes get counted thousands of times and every rate goes to garbage.

3
dedupe claims

One failure should count once

Re-bills and double entries inflate failure rates. Collapse duplicate claims on VIN + part + window before you count anything.

4
match the dirty keys

Fuzzy-match before you drop

A typo’d VIN is a lost failure, not a missing car. Use Fellegi–Sunter scoring to recover broken matches; send the uncertain middle to a human.

5
one clock

Align everything to time-in-service

Build, sale, event, and report dates disagree. Pick time-in-service as the origin so onset curves and survival models measure what they claim — and mind timezones.

6
flag, don’t fill

Respect biased missingness

Gaps line up with causes. Classify each missing field by mechanism and flag it; never impute over a gap that sits exactly where a cause lives.

7
one meaning per field

Harmonize units and drifting dictionaries

Celsius and Fahrenheit in one column is a fake second peak; a drifting DTC dictionary merges two faults. Map every source into a versioned canonical model.

8
keep the lineage

Make every diagnosis reproducible

Every value should point back to its raw record and the code version that made it. A finding you cannot re-run is an assertion, not a result.

§ 10 — References & Further Reading

Where these ideas come from

This post leans on databases, data warehousing, record linkage, and data quality. Entry points, grouped by the question they answer.

Relational data & warehousing

E. F. Codd, “A Relational Model of Data for Large Shared Data Banks” (1970). The foundation of the join itself — why a shared key like the VIN lets independent tables be combined.

R. Kimball & M. Ross, The Data Warehouse Toolkit (3rd ed., 2013). Dimensional modeling and conformed dimensions — the discipline behind “one row per car” and a shared canonical model.

Data-intensive systems

M. Kleppmann, Designing Data-Intensive Applications (2017). Batch vs streaming, ingestion, and the architecture of pipelines that stay reproducible at scale.

Record linkage

I. Fellegi & A. Sunter, “A Theory for Record Linkage” (1969). The evidence-weighing framework for deciding whether two records are the same entity when the key is dirty.

Data quality & governance

C. Batini & M. Scannapieco, Data Quality: Concepts, Methodologies and Techniques (2006). A systematic treatment of completeness, accuracy, and the dimensions of quality behind validation rules.

T. Redman, Data Quality: The Field Guide (2001). The practical cost of bad data and how it propagates into confident-but-wrong conclusions.

DAMA International, DAMA-DMBOK: Data Management Body of Knowledge. Provenance, lineage, master data, and the governance vocabulary for a trustworthy canonical store.

A note on these references

These are orientation points into large literatures, cited to show the lineage of the method rather than as the source of any single claim here. Titles and years are given so you can find them.

§ — The Series

The Diagnosis Series

Nine connected parts, from one car’s evidence to a fleet campaign and back to prevention. See the full map & reading guide →

1

Root-Cause Analysis Under Uncertain, Heterogeneous Evidence

Grade, anchor, and fuse messy, unequal-trust clues into one cause.

2

When Is the Evidence Enough?

The sufficiency gate, value of information, and handling conflict.

3

Harder Cases in Diagnosis

Multiple faults, intermittency, and lying sensors.

4

Fleet-Scale Root-Cause Analysis

One code across a population: de-mix, stratify, remediate.

5

Fleet RCA, Made Rigorous

Significance, surveillance, forecasting, closing the loop.

6

Proving Cause with Experiments

Turn observation into proof with staged rollouts.

7

Diagnosis on the Vehicle

Real-time fault detection at the edge, feeding the fleet.

8

The Data Plumbing Behind Fleet Diagnosis  you are here

Joining, aligning, and cleaning the data that makes it work.

9

From Root Cause to No Cause

Prevention and the corrective-action loop — engineer it out.