A new operator runs a test wrong and scraps three boards before anyone notices. It happens more than it should. Most teams track operator certification in spreadsheets that go stale the day they're created.
Tying operator identity to every test run is the part that makes the rest possible: certification gates, yield-by-operator analysis, and an audit trail that survives a regulatory review.
Why Operator Tracking Matters
Regulated industries require it. ISO 13485 (medical devices), AS9100 (aerospace), and IATF 16949 (automotive) all mandate that operators are trained and qualified for the tasks they perform. But even without regulatory pressure, knowing who ran what test matters when you're debugging a yield drop.
Prerequisites
- A TofuPilot account
- Python 3.9+ with
pip install "tofupilot[openhtf]" - An operator authentication method (badge scan, login, or barcode)
Step 1: Record the Operator on Every Run
operated_by is the field that links a run to a person. An email matching a member of your organization links the run to that account; any other value is recorded verbatim as a declared operator name.
operator_tracking.py22 lines
import openhtf as htffrom tofupilot.openhtf import uploadPROCEDURE_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # procedure UUID from the dashboarddef main(): operator_id = input("Scan operator badge: ") test = htf.Test( functional_tests, power_tests, procedure_id=PROCEDURE_ID, part_number="PCB-100-R4", operated_by=operator_id, ) test.add_output_callbacks(upload()) test.execute(lambda: input("Scan DUT serial: "))if __name__ == "__main__": main()Prefer the operator's work email when they have an account, since that links the run to a real member rather than recording a badge string nobody can resolve later.
Step 2: Build an Operator Certification Check
Certification data lives in your own system, not in the test platform. A JSON file works for a small team; an HR API or database is the same shape.
certification_check.py27 lines
import jsonfrom pathlib import PathCERT_FILE = Path("operator_certs.json")def load_certifications() -> dict: """Load operator certification records.""" if CERT_FILE.exists(): return json.loads(CERT_FILE.read_text()) return {}def is_certified(operator_id: str, procedure_name: str) -> bool: """Check if operator is certified for a specific test procedure.""" certs = load_certifications() operator = certs.get(operator_id, {}) return procedure_name in operator.get("certified_procedures", [])def require_certification(operator_id: str, procedure_name: str) -> None: """Block test execution if operator isn't certified.""" if not is_certified(operator_id, procedure_name): raise PermissionError( f"Operator {operator_id} is not certified for {procedure_name}. " f"Contact your line supervisor." )Example certification file:
{ "OP-001": { "name": "Jane Chen", "certified_procedures": ["pcba-fct-v2", "motor-fct", "burn-in-48h"], "certification_date": "2026-01-15", "expiry_date": "2027-01-15" }, "OP-002": { "name": "Mike Torres", "certified_procedures": ["pcba-fct-v2"], "certification_date": "2026-02-01", "expiry_date": "2027-02-01" }}Step 3: Gate the Test on Certification
Run the check before the test starts, so an uncertified operator never reaches the first phase.
certified_test.py26 lines
import openhtf as htffrom tofupilot.openhtf import uploadfrom certification_check import require_certificationPROCEDURE_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"PROCEDURE_NAME = "pcba-fct-v2"def main(): operator_id = input("Scan operator badge: ") require_certification(operator_id, PROCEDURE_NAME) # raises if not certified test = htf.Test( functional_tests, power_tests, procedure_id=PROCEDURE_ID, part_number="PCB-100-R4", operated_by=operator_id, ) test.add_output_callbacks(upload()) test.execute(lambda: input("Scan DUT serial: "))if __name__ == "__main__": main()Gating before htf.Test(...) means an uncertified attempt produces no run at all. If you would rather record the attempt, move the check into a first phase and return htf.PhaseResult.STOP instead.
Worth being clear about what this is: a procedural control, not a security boundary. Anyone who can edit the test script can bypass it. It satisfies the audit requirement that a check exists and is recorded; it does not stop a determined person.
Step 4: Track Certification Expiry
Certifications expire. Warn before, block after.
cert_expiry.py31 lines
from datetime import datefrom certification_check import load_certificationsdef check_certification_status(operator_id: str, procedure_name: str) -> bool: """Check certification validity with advance warning.""" certs = load_certifications() operator = certs.get(operator_id) if not operator: raise PermissionError(f"Unknown operator: {operator_id}") if procedure_name not in operator.get("certified_procedures", []): raise PermissionError( f"{operator['name']} is not certified for {procedure_name}" ) expiry = date.fromisoformat(operator["expiry_date"]) today = date.today() if today > expiry: raise PermissionError( f"Certification expired on {expiry}. Recertification required." ) days_remaining = (expiry - today).days if days_remaining < 30: print(f"WARNING: Certification expires in {days_remaining} days") return TrueStep 5: Analyze Operator Performance
With an operator on every run, you can answer questions that matter:
- Yield by operator: Is one operator consistently lower? They might need retraining.
- Test duration by operator: Slower operators may be following procedures more carefully, or struggling with the equipment.
- Failure modes by operator: If one operator sees more of a specific failure, check their technique.
The dashboard groups runs by operator directly. To compute it yourself, query one operator at a time using the operated_by_names filter and count outcomes:
operator_analysis.py35 lines
import osfrom tofupilot.v2 import TofuPilotPROCEDURE_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"OPERATORS = ["OP-001", "OP-002"]def count_outcomes(client, operator: str) -> dict: counts = {"pass": 0, "fail": 0} cursor = None while True: result = client.runs.list( procedure_ids=[PROCEDURE_ID], operated_by_names=[operator], limit=100, cursor=cursor, ) for run in result.data: counts["pass" if run.outcome == "PASS" else "fail"] += 1 if not result.meta.has_more: break cursor = result.meta.next_cursor return countswith TofuPilot(api_key=os.getenv("TOFUPILOT_API_KEY")) as client: for operator in OPERATORS: stats = count_outcomes(client, operator) total = stats["pass"] + stats["fail"] if not total: print(f"Operator {operator}: no runs") continue print(f"Operator {operator}: {stats['pass'] / total * 100:.1f}% ({total} runs)")Use operated_by_ids instead when your operators are linked organization members rather than declared names.
Two caveats on the number. It counts every run, so a unit that failed and was retested is counted twice; for true first pass yield, take the first run per serial number. And a low figure for one operator is a prompt to look, not a conclusion, since operators rarely test the same mix of products or run the same shifts.
Regulatory Compliance Notes
| Standard | Requirement | How this maps |
|---|---|---|
| ISO 13485 | Documented training records, competency assessment | Operator on every run, certification check before test |
| AS9100 | Personnel qualified for assigned tasks | Pre-test certification gate |
| IATF 16949 | Training effectiveness evaluated | Yield-by-operator analysis |
| FDA 21 CFR 820 | Personnel training documented | Full audit trail with operator identity |
The certification records themselves stay in your system of record. What the test platform contributes is the immutable link from each run back to the person who ran it.
