Switching test data platforms should not mean starting the fleet history from zero. Trend charts, per-serial degradation curves, and capability indices are only as good as the history behind them. This guide shows how to backfill legacy results through the runs API with their original test dates intact.
The key mechanism: a run's started_at is the timestamp analytics use, and the API accepts past dates. Upload date is stored separately, so a run tested in 2024 and imported today lands in 2024 on every chart.
Prerequisites
- An API key (Settings, API keys)
- The procedure created in TofuPilot, and its procedure id
- Legacy results exported to something parseable (CSV, database dump, report files)
Step 1: Map the legacy fields
The minimum viable mapping:
| Legacy field | Runs API field |
|---|---|
| Test date/time | started_at, ended_at (ISO 8601) |
| Overall verdict | outcome (PASS, FAIL, ERROR) |
| Unit serial number | serial_number |
| Part number | part_number |
| Test steps | phases[] with name, outcome, timestamps |
| Measured values and limits | measurements[] per phase, with validators |
Keep phase and measurement names identical to what the live bench will record going forward. Analytics group by name, so Contact Drop A2 in the import and contact_drop_a2 from the new bench would land in two different series.
Step 2: Create runs with original timestamps
from tofupilot import TofuPilotclient = TofuPilot(api_key="...")for row in legacy_rows: client.runs.create( procedure_id="550e8400-e29b-41d4-a716-446655440000", serial_number=row.serial, part_number=row.part_number, outcome=row.verdict, # "PASS" / "FAIL" started_at=row.tested_at, # original date, ISO 8601 ended_at=row.finished_at, phases=[{ "name": "Contact Voltage Drop", "outcome": row.phase_verdict, "started_at": row.tested_at, "ended_at": row.finished_at, "measurements": [{ "name": "Contact Drop A2", "outcome": row.a2_verdict, "measured_value": row.a2_mv, "units": "mV", "validators": [{"operator": "<=", "expected_value": 100, "outcome": row.a2_verdict}], }], }], )Units are created automatically from serial numbers, so a serial with five historical visits ends up with a five-run history without any separate unit setup.
Step 3: Verify in analytics
Set the date range in run analytics or measurement control to cover the imported period and check three things:
- Run counts per month match the legacy system
- Per-serial history is complete: pick a unit with known repeat visits and confirm every visit is there
- Limits came through: measurements show their validators, so Cpk and pass rates compute against the right limits
Step 4: Cut over the live bench
Once history is verified, point the live bench at the same procedure. New runs continue the same series: the first live run on an imported serial extends its existing trend rather than starting a new one, which is the entire point of importing with real timestamps.
Two details worth knowing:
- Imported runs record the API key's user as creator, and the upload date as
created_at. Both are visible on the run detail page; neither affects analytics, which usestarted_at. - Station attribution requires uploading with a station key. For historical data this rarely matters, but record the original site or bench as a measurement or run metadata if you need to filter by it later.