Skip to content
Migrating from Legacy Systems

How to Migrate from WATS to TofuPilot

Step-by-step migration from WATS to TofuPilot, covering concept mapping, data migration, and parallel running.

JJulien Buteau
intermediate10 min readMarch 12, 2026

If you're evaluating a WATS alternative, this guide walks through the migration path. Common triggers include cost (WATS Analytics runs at EUR 297/mo per module), a preference for Python-first workflows, or wanting to build on an open-source test framework instead of proprietary data converters.

TofuPilot pairs with OpenHTF (Google's open-source test framework) to give you structured test data, real-time analytics, and a REST API without vendor lock-in on the test execution side.

Concept Mapping

WATS and TofuPilot use different terminology for similar concepts. Here's how they map:

WATS ConceptTofuPilot EquivalentNotes
UUT ReportTest RunOne execution of a test sequence on a unit
Test StepPhaseA phase in OpenHTF, containing measurements
Numeric Limit TestMeasurement with limitsDefined via htf.Measurement().in_range()
String Value TestMeasurement (string)Any measured value, not just numeric
WATS Client / Data ConverterTofuPilot Python SDKfrom tofupilot.openhtf import TofuPilot
WATS DashboardTofuPilot DashboardFPY, Cpk, Pareto, control charts at tofupilot.app
Test Sequence FileOpenHTF TestPython script defining phases and measurements
Station RegistrationAutomaticTofuPilot detects stations from test metadata

Replacing the WATS Data Converter

With WATS, you typically use a data converter (TestStand, LabVIEW, or custom) to serialize test results and push them to the WATS API. With TofuPilot, you write your test logic in OpenHTF and the SDK handles reporting directly.

Here's a typical test that would replace a WATS data converter workflow:

functional_test.py
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot

@htf.measures(
    htf.Measurement("supply_voltage")
        .with_units(units.VOLT)
        .in_range(4.75, 5.25),
    htf.Measurement("clock_frequency")
        .with_units(units.HERTZ)
        .in_range(minimum=7.99e6),
    htf.Measurement("firmware_version"),
)
def power_and_clock_check(test):
    test.measurements.supply_voltage = 5.02
    test.measurements.clock_frequency = 8.00e6
    test.measurements.firmware_version = "v3.2.1"

@htf.measures(
    htf.Measurement("signal_amplitude")
        .with_units(units.VOLT)
        .in_range(0.9, 1.1),
    htf.Measurement("signal_thd")
        .in_range(maximum=0.05),
)
def signal_integrity_check(test):
    test.measurements.signal_amplitude = 1.01
    test.measurements.signal_thd = 0.03

def main():
    test = htf.Test(power_and_clock_check, signal_integrity_check)
    with TofuPilot(test):
        test.execute(test_start=lambda: "SN-10042")

if __name__ == "__main__":
    main()

No data converter needed. The TofuPilot SDK serializes the OpenHTF test record and uploads it automatically. Each phase maps to what WATS calls a test step, and each measurement carries its value, units, and limits.

Migrating Historical Data

You likely have test history in WATS that you want to preserve. TofuPilot's REST API accepts historical runs with explicit timestamps, so you can backfill without losing chronological context.

First, export your data from WATS (CSV export or API). Then import it:

import_wats_history.py
import csv
from datetime import datetime
from tofupilot import TofuPilotClient

client = TofuPilotClient()

with open("wats_export.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        client.create_run(
            procedure_id="functional-test",
            unit_under_test={
                "serial_number": row["SerialNumber"],
                "part_number": row["PartNumber"],
            },
            run_passed=row["Status"] == "Passed",
            started_at=datetime.fromisoformat(row["StartTime"]),
            duration=float(row["Duration"]),
            steps=[
                {
                    "name": row["StepName"],
                    "step_passed": row["StepStatus"] == "Passed",
                    "measurements": [
                        {
                            "name": row["MeasurementName"],
                            "measured_value": float(row["Value"]),
                            "unit": row["Unit"],
                            "lower_limit": float(row["LowLimit"]) if row["LowLimit"] else None,
                            "upper_limit": float(row["HighLimit"]) if row["HighLimit"] else None,
                        }
                    ],
                }
            ],
        )

For complex WATS exports with nested test steps, flatten them into TofuPilot's phase/measurement structure. Each WATS numeric limit test becomes a measurement with lower_limit and upper_limit.

Parallel Running Strategy

Don't cut over in one shot. Run both systems side by side to validate the migration:

  1. Week 1. Pick one test station. Add the TofuPilot integration to its tests while keeping the WATS data converter active. Both systems receive the same data.
  2. Week 2. Compare results in both dashboards. Verify that measurement values, pass/fail outcomes, and station attribution match.
  3. Week 3. If everything aligns, disable the WATS data converter on that station. Roll out to the next station.
  4. Repeat until all stations report to TofuPilot only.

Keep your WATS subscription active during this period. You can export a final data snapshot before canceling.

What You Get After Migration

Once your tests report to TofuPilot, you'll find the analytics at tofupilot.app:

  • FPY trends by station, product, and time range.
  • Cpk and control charts for every measurement, computed automatically.
  • Failure Pareto to identify your top failure modes.
  • Measurement histograms showing distribution against limits.
  • Full traceability per serial number across all test runs.
  • REST API for integrating with your MES, ERP, or custom tooling.

These replace the WATS Analytics module. The difference is that your test execution layer is now open-source (OpenHTF), and you're not locked into a proprietary test framework to get analytics.

More Guides

Put this guide into practice