Data engineering · contracts & drift

data-contract-guard

A schema + range + enum contract with drift detection for tabular and sensor (rosbag) batches — catching the batch that still parses cleanly but whose meaning changed, before it poisons everything downstream. Offline, no keys, zero dependencies.

Public · synthetic demo
The problem

"It loaded fine" is not "it's correct."

A firmware update starts reporting joint temperature in Fahrenheit instead of Celsius. Every value is still a number, nothing is null, the CSV parses — and your model, dashboard, and alerts all quietly ingest garbage. The gate most pipelines ship — did it parse? — waves it straight through.

The money shot

Same firmware-era batch, checked two ways

① Naive parse-check vs the contract guard
checkverdict on the bad batch
naive did it parse?0 issues → PASS (ships corrupted data)
data-contract-guard36 violations + 2 drift flags → BLOCKED
violations: 36   by kind: {'range': 32, 'enum': 4}
drift · joint_temp_c: mean 45.4 -> 111.8 (z=14.9 > 4.0)     ← that's °F, not °C
drift · gripper_state: unseen values: ['unknown']
smoking gun · joint_temp_c row 0: 113.68 > max 90
0 → 36violations (naive → guard)
z = 14.9joint-temp drift vs baseline
PASS ✅clean batch — no false alarms
② Two explainable layers
CONTRACT  per-column type · min/max · enum · nullable  → structured violations
DRIFT     vs a trusted baseline: numeric mean-shift (z>4) + any unseen category

Run it as a gate: data_contract_guard.run exits non-zero on any violation or drift, so it drops into a nightly job or CI. Every flag names the column and the numbers behind it — cause, not just symptom.

③ Verified, not asserted
5/5contract tests pass
0 → 36naive PASS vs guard BLOCKED (from data/)
fixtures reproducible from a seed

CI regenerates the data from the deterministic generator and fails if it isn't byte-identical, and proves the clean batch passes while the bad batch is blocked — a guard that lets bad data through fails its own CI.

Why rosbag / sensor data

Robot fleets are where silent unit changes are a when, not an if.

The sample stream is robot joint telemetry (joint_temp_c · motor_current_a · battery_v · gripper_state) — the shape you get exporting a rosbag. A schema-plus-drift contract at ingestion is the cheap insurance against a firmware push that changes units or adds a state. The same guard works on any CSV/tabular batch.

Honest limitations
· Synthetic data (60-row baseline + 30-row batches) — a demonstrator of the method, not a benchmark.
· Drift here is mean-shift + new-category. Production adds distribution tests (PSI/KS), seasonality,
  and per-segment baselines — kept simple and explainable on purpose, not comprehensive.
· The contract is hand-written JSON; inferring/versioning contracts and schema evolution are out of scope.
· It guards the data boundary — it does not repair data or root-cause beyond naming the columns.
This is a synthetic sample demonstrating the method. Inspect the contract, the drift detector, and the reproducible fixtures ↗