Skip to content
Test Types & Methods

Track Repair and Rework Data

Learn how to structure OpenHTF tests so repair loops, rework actions, and retests all link to the same serial number in TofuPilot.

JJulien Buteau
intermediate8 min readMarch 14, 2026

Production testing catches defects, but the real value comes from closing the loop: diagnosing failures, repairing units, and retesting them. TofuPilot links every retest to the original serial number so you get a complete history of each unit's journey through your repair process.

The Repair Loop

A typical repair workflow follows this cycle: a unit fails a test, a technician diagnoses the root cause, performs a repair, and the unit goes back through testing. Without structured data, this history gets lost in spreadsheets or paper logs.

TofuPilot solves this by keying everything to the serial number. Every test run against the same DUT automatically appears in its unit history. You don't need special configuration. Just use the same serial number when retesting.

Structure Your Initial Test

Start with a standard OpenHTF test that measures your DUT and uploads results to TofuPilot.

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


@htf.measures(
    htf.Measurement("output_voltage")
    .in_range(minimum=4.8, maximum=5.2)
    .with_units(units.VOLT),
    htf.Measurement("current_draw")
    .in_range(minimum=0.095, maximum=0.105)
    .with_units(units.AMPERE),
)
def functional_check(test):
    test.measurements.output_voltage = 5.05
    test.measurements.current_draw = 0.1012


def main():
    test = htf.Test(functional_check)
    with TofuPilot(test):
        test.execute(test_start=lambda: "SN-20260312-001")


if __name__ == "__main__":
    main()

When this test fails, the unit enters your repair queue.

Record Repair Actions as Metadata

After a technician diagnoses and repairs the unit, capture that context in the retest. Use phase-level metadata to record what was found and what was done.

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


@htf.measures(
    htf.Measurement("repair_code"),
    htf.Measurement("failure_category"),
    htf.Measurement("repair_action"),
)
def record_repair_info(test):
    # These values come from your repair technician's input
    test.measurements.repair_code = "RC-042"
    test.measurements.failure_category = "solder_bridge"
    test.measurements.repair_action = "reworked_U3_solder_joints"


@htf.measures(
    htf.Measurement("output_voltage")
    .in_range(minimum=4.8, maximum=5.2)
    .with_units(units.VOLT),
    htf.Measurement("current_draw")
    .in_range(minimum=0.095, maximum=0.105)
    .with_units(units.AMPERE),
)
def functional_recheck(test):
    test.measurements.output_voltage = 5.01
    test.measurements.current_draw = 0.1003


def main():
    test = htf.Test(record_repair_info, functional_recheck)
    with TofuPilot(test):
        # Same serial number links this retest to the original failure
        test.execute(test_start=lambda: "SN-20260312-001")


if __name__ == "__main__":
    main()

The record_repair_info phase stores the diagnosis and repair action alongside the retest measurements. This data shows up in TofuPilot's run detail view, tied to the same unit.

Use Failure Categories Consistently

Define a standard set of failure categories and repair codes across your team. Consistent naming lets you filter and aggregate repair data later.

Common failure categories for PCBA testing:

CategoryDescription
solder_bridgeUnintended solder connection between pads
cold_jointInsufficient solder wetting
missing_componentComponent not placed during assembly
wrong_valueIncorrect component value populated
damaged_componentComponent damaged during handling or reflow
pcb_defectBoard-level issue (trace crack, via failure)

Store these as string measurements so they're searchable in TofuPilot.

Track Multiple Repair Cycles

Some units need more than one repair attempt. Each retest creates a new run against the same serial number. TofuPilot's unit history view shows the full chain: initial fail, first repair attempt, second repair attempt, and final pass.

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


@htf.measures(
    htf.Measurement("repair_code"),
    htf.Measurement("failure_category"),
    htf.Measurement("repair_action"),
    htf.Measurement("repair_cycle"),
)
def record_repair_info(test):
    test.measurements.repair_code = "RC-043"
    test.measurements.failure_category = "cold_joint"
    test.measurements.repair_action = "reflowed_C12_pads"
    test.measurements.repair_cycle = 2


@htf.measures(
    htf.Measurement("output_voltage")
    .in_range(minimum=4.8, maximum=5.2)
    .with_units(units.VOLT),
    htf.Measurement("current_draw")
    .in_range(minimum=0.095, maximum=0.105)
    .with_units(units.AMPERE),
)
def functional_recheck(test):
    test.measurements.output_voltage = 4.98
    test.measurements.current_draw = 0.0998


def main():
    test = htf.Test(record_repair_info, functional_recheck)
    with TofuPilot(test):
        test.execute(test_start=lambda: "SN-20260312-001")


if __name__ == "__main__":
    main()

Adding a repair_cycle measurement makes it easy to count how many attempts each unit needed.

Analyze Repair Trends in TofuPilot

Once your repair data flows into TofuPilot, the dashboard gives you what you need without writing analysis scripts.

Unit history shows every test run for a serial number in chronological order. You can see exactly when a unit failed, what was repaired, and whether the retest passed.

Failure Pareto charts rank your failure categories by frequency. If solder_bridge dominates, that's a signal to investigate your reflow profile or stencil design.

FPY trends reflect your repair effectiveness over time. A rising FPY after process changes confirms the fix is working. A unit that keeps failing the same test after multiple repairs may point to a deeper design issue.

Separate Test Procedures for Initial Test and Retest

For traceability, consider using distinct procedure names for initial tests and retests. TofuPilot groups runs by procedure, so this separation makes reporting cleaner.

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


@htf.measures(
    htf.Measurement("repair_code"),
    htf.Measurement("repair_action"),
)
def record_repair_info(test):
    test.measurements.repair_code = "RC-042"
    test.measurements.repair_action = "reworked_U3_solder_joints"


@htf.measures(
    htf.Measurement("output_voltage")
    .in_range(minimum=4.8, maximum=5.2)
    .with_units(units.VOLT),
)
def functional_recheck(test):
    test.measurements.output_voltage = 5.02


def main():
    test = htf.Test(record_repair_info, functional_recheck)
    with TofuPilot(test, procedure_name="PCBA Functional Test - Retest"):
        test.execute(test_start=lambda: "SN-20260312-001")


if __name__ == "__main__":
    main()

This way you can compare FPY between initial tests and retests independently, while the unit history still ties everything together under one serial number.

More Guides

Put this guide into practice