PCB Defect Co-Pilot
Detects PCB defects with YOLOv8, then maps each detection to its IPC acceptability class and probable upstream manufacturing process root cause — the way a process engineer reads a defect, not a generic ML fork.
Role: Independent build — 2026
Stack: Python 3.11, uv, Ultralytics YOLOv8n, Streamlit, Ollama (gemma3:12b, local), pytest

// Highlights
- Two-domain standards boundary: HRIPCB bare-board defects map to IPC-A-600 / IPC-6012; SolDef_AI solder joints map to IPC-A-610. A schema validator hard-rejects cross-domain leakage so IPC-A-610 strings never appear on a bare-board row (and vice versa).
- Owner-authored mapping layer: each detection is enriched with IPC class, probable upstream process cause, and corrective action — the moat a generic YOLO fork can't reproduce.
- Bare-board (HRIPCB) mAP@50 0.991 / mAP@50-95 0.650 at 1.4 ms/img; assembly (SolDef_AI v2, wired) mAP@50 0.877 / mAP@50-95 0.676 at 6.9 ms/img on ~20× less training data.
- Fully offline: local YOLOv8 + local LLM (Ollama gemma3:12b) — no cloud, no API key. If Ollama is down, detections + the derived `standard` column stay correct; only the enrichment cells fall back to a sentinel.
// Problem
A raw YOLO defect detector gives a class and a bounding box. A process engineer needs to know: which IPC acceptability class does this fail, what upstream manufacturing step probably caused it, and what to change next. Existing PCB YOLO forks stop at the box; they also routinely blur bare-board fabrication defects (IPC-A-600 / IPC-6012 territory) with solder-joint defects (IPC-A-610 territory), which is the wrong standard on the wrong defect.
// Approach
Trained two YOLOv8n detectors — one on HRIPCB (bare-board fabrication) and one on SolDef_AI (assembly solder joints) — and wired both behind a Streamlit domain toggle. On top of the detector, layered an owner-authored IPC mapping table + a single local Ollama (gemma3:12b) call per image that reads only from that table to fill three enrichment columns. Hardened the domain boundary structurally: every mapping row carries a `domain` derived from the markdown section it parses under, and a schema validator rejects any IPC-A-610 string on a bare-board row (and any IPC-A-600/6012 string on an assembly row).
// Outcome
Phase 1 bare-board: mAP@50 0.991, precision 0.982, recall 0.988 on the direct PKU HRIPCB ingest. Phase 3 assembly (shipped v2): mAP@50 0.877, mAP@50-95 0.676 — matches Phase 1's tight-box quality on ~20× less data. A v3 study confirmed the recall lever on the minority `poor_solder` class works (lifted recall 0.583 → 0.733) but couldn't hold the 0.85 precision floor, so v2 stays the shipped assembly model and `poor_solder` recall is documented as a data-floor limit at n=12 val instances. Test suite 191 passing, 0 open bugs, MIT-licensed public repo.
// AI & orchestration
Turn a raw detector into something a process engineer can act on — bounding boxes don't tell you which IPC class the joint fails, which upstream process step caused it, or what to change. Do that without blurring the bare-board and assembly standards, and without a cloud dependency.
Pattern: Detector emits class + box → domain router picks the correct IPC standard by class → single grounded LLM call per image reads the mapping table → schema validator hard-rejects cross-domain contamination before render.
// Technical notes
- Standards boundary is structural, not lexical: `IPCRow.domain` is derived from which markdown section the row lives under; the validator's rejection is a `_IPC_A_600_6012_PATTERN` regex on assembly rows and an `IPC-A-610` reject on bare-board rows — so a hallucinated cross-domain string can't slip through.
- The enrichment is one grounded LLM call per image, not RAG. The mapping table is a hand-authored static markdown source of truth, small enough to fit in the prompt; the `standard` column is derived from the class index and stays correct even if Ollama is offline.
- Phase 3 v3 no-win branch was preserved as evidence: the stacked `--copy-paste 0.5 --cls-pw 1.0` arm proved the recall lever works on seg-labels; the remaining precision gap is the honest open lead, not something to paper over.
- SolDef_AI labels were converted box → YOLO-segment so ultralytics `copy_paste` augmentation actually fires for a `task=detect` model — no segmentation-model rearchitecture required.
// Metrics
Bare-board mAP@50 0.991 · Assembly mAP@50 0.877 · fully offline pipeline
6 bare-board defect classes (HRIPCB, 6,828/1,706 imgs) + 4 solder-joint classes (SolDef_AI, 343/85 imgs)


