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 demoA 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.
| check | verdict on the bad batch |
|---|---|
naive did it parse? | 0 issues → PASS (ships corrupted data) |
| data-contract-guard | 36 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
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.
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.
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.
· 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.