KB Wiring-Diagram Extraction — Implementation Plan
For agentic workers: execute task-by-task via subagent-driven development. Each task ends with a runnable stdlib
unittestcheck (or a stated live verification). NO git commits, NO code comments (house rules). Steps use- [ ].
Goal: Extract searchable labels/text + best-effort confidence-flagged connection data from the KB wiring diagrams, on existing scans, hybrid CV + vision.
Architecture: A wiring-extract/ module on telep-mainframe. Classical CV (cvtrace.py, run under ocr/venv which has cv2) proposes connectivity; a vision subagent (this session’s model) reads the scan + legend + CV output and emits the fused netlist + searchable text; a small chunker.py patch (kb-vectors) makes the searchable sidecar index alongside the real page.
Design spec: 2026-07-29-kb-wiring-extraction-design. Related: 2026-07-24-kb-vectorize-complete, telep-mainframe.
Global Constraints
- Work on telep-mainframe as
levander;/usr/bin/sshonly. No git commit. No code comments. One decl per line; no inline compound statements. - CV code runs under
/home/levander/ocr/venv(hascv2 4.11.0,numpy,PIL; ADDscikit-image— do NOT install opencv-headless, it conflicts with the full cv2). - Pure-python module code + tests: stdlib
unittest. kb-vectors tests run under/home/levander/kb-vectors/venv; module tests underocr/venv(needs cv2/skimage). - PSU brown-out limit: CV/heavy jobs
OMP_NUM_THREADS=4+nice, ONE heavy job at a time; batch offline; no always-on service. - NON-DESTRUCTIVE: never modify the source scans or the OCR’d
.md; never touchclusters.json. New files only (module dir, per-page sidecars, a chunker patch). - Vision + fusion is subagent-driven (session model; box
claudeis weekly-limited). CV + emit + chunker patch + reindex are box code. - Module dir:
/home/levander/wiring-extract/. Docs live at/home/levander/knowledgebase/manuals-src/docs/. Vectorizer at/home/levander/kb-vectors/(chunker.py, index.py; Qdrantmanuals@127.0.0.1:6333;index_manual= delete-by-manual_id + upsert). page_url=folder + "/" + manual + "/" + page + "/"(page = md filename w/o.md). Images are sibling_page_N_*.jpeg.
Task 0: CV path-tracing spike (GO/NO-GO gate)
Purpose: validate that classical CV adds real connectivity signal on these 1632px scans BEFORE building the full module. If it doesn’t, fall back to vision-only connections (skip Task 3) and record that.
Files:
- Create:
/home/levander/wiring-extract/spike_cvtrace.py - Uses:
ocr/venv+ scikit-image
Steps:
-
/home/levander/ocr/venv/bin/pip install scikit-image; verifyocr/venv/bin/python -c "import cv2, skimage, numpy; print(cv2.__version__, skimage.__version__)". - Copy the pilot page full-res:
cp /home/levander/knowledgebase/manuals-src/docs/suzuki-vitara/wiring-diagrams/_page_14_Figure_0.jpeg /home/levander/wiring-extract/. - Write
spike_cvtrace.py: crop the top-left CRANKING region (the pilot’s hand-verified sub-circuit), then: grayscale → Otsu/adaptive threshold → small-blob despeckle →skimage.morphology.skeletonize→ build a pixel graph (endpoints = 1 neighbor, junctions = ≥3) → count nodes/edges/segments; save a debug overlay PNG (skeleton on the crop). - Run it (
OMP_NUM_THREADS=4 nice ocr/venv/bin/python spike_cvtrace.py). Copy the debug overlay back and LOOK at it. - GATE decision (record in the plan/handoff): does the skeleton follow the actual conductors and yield a plausible node/segment graph matching the pilot cranking topology (battery→fuse→ign switch→starter, ~7 nodes)?
- PASS → proceed to Task 3 (full cvtrace).
- FAIL (skeleton is noise / lines broken/merged beyond use at this res) → SKIP Task 3; connections become vision-only in Task 4; note it. Do not build dead CV complexity.
Task 1: Legend extraction → machine-readable legend.json
Files:
- Create:
/home/levander/wiring-extract/legend.json(data),/home/levander/wiring-extract/legend.py(loader),test_legend.py
Interfaces produced: legend.load(manual_family) -> {colors: {code:name}, symbols: {...}, connector_rules: str, howto: str}.
Steps:
- Vision subagent reads the legend sources for the Vitara family:
013-wire-color-symbols.md+ its image_page_8_Picture_7.jpeg(the color table),011-symbols-and-marks.md,012-abbreviations.md,014-how-to-read-wiring-diagram.md,015-indication-of-connectors-and-how-to-read-them.md. Producelegend.json:colors= full base/stripe abbreviation→name map (B→Black, W→White, R→Red, G→Green, Y→Yellow, Bl→Blue, Lg→Light green, Br→Brown, O→Orange, P→Pink, Gr→Gray, V→Violet, Lbl→Light blue, etc. — read from the image, don’t guess),symbols,connector_rules(how connector/terminal numbers read),howto(flow top→down from fuse/ign to ground). - Write
legend.pyload()reading legend.json;decode_color("B/Y") -> "Black/Yellow"(split on/, map each token, unknown → keep code + mark). -
test_legend.py:decode_colorhandles single + base/stripe + unknown tokens; legend.json parses and has ≥12 colors. Run under any venv (pure). PASS.
Task 2: diagram_index.py — find diagram pages + attach legend
Files: Create /home/levander/wiring-extract/diagram_index.py, test_diagram_index.py
Interfaces produced: list_diagram_pages(manual_dir, folder, manual) -> [{page, page_url, md_path, images:[abs...], legend_family}].
Steps:
- Implement: walk a manual dir, for each
.mdcollect image refs viar"!\[\]\(([^)]+)\)", resolve to sibling abs image paths, formpage_url = folder+"/"+manual+"/"+page+"/", taglegend_family="suzuki-vitara"(Vitara/Sidekick share conventions). Filter to pages that HAVE images (diagram pages). -
test_diagram_index.py: on a temp fixture dir with a md referencing 2 images, returns one page with 2 resolved image paths + correct page_url; a text-only md is excluded. PASS.
Task 3: cvtrace.py — CV connectivity (ONLY if Task 0 gate PASSED)
Files: Create /home/levander/wiring-extract/cvtrace.py, test_cvtrace.py. Runs under ocr/venv.
Interfaces produced: trace(image_path, suppress_boxes=None) -> {nodes:[{id,x,y,kind}], edges:[{a,b,confidence}], nets:[[node_ids]]}.
Steps:
- Implement the pipeline from the spec: preprocess (gray→threshold→despeckle) → optional text/symbol suppression (mask out
suppress_boxesfrom vision component boxes) →skeletonize→ graph (endpoints/junctions) → segment trace → crossing-vs-junction classification (solder-dot blob at 4-way meeting = connect; else hop) → union-find nets. Confidence per edge from trace cleanliness (segment continuity, blob clarity). -
test_cvtrace.py: build a SYNTHETIC binary schematic (numpy array) with two straight nets, one dot-junction (connect), one hop-crossing (no connect); assert net count = expected, the dot merges nets, the hop does not, endpoints detected. PASS underocr/venv. - Real check:
tracethe pilot cranking crop; report nodes/edges/nets vs the hand-verified topology (recall + crossing errors) in the task notes.
Task 4: Vision+fusion (subagent) + emit.py
Files: Create /home/levander/wiring-extract/emit.py, test_emit.py. Per-diagram outputs written under the module’s out/ and the sidecar next to the page.
Interfaces produced: emit.write(page, record) → writes out/<manual>__<page>.wiring.json (full netlist+labels+meta+image ref+confidence), and <page>.wires.txt sidecar in the manual dir (searchable text: labels + decoded colors + connection summary), and out/<manual>__<page>.lines (simple documented connection-line format: <from.pin> -- <color> --> <to.pin> [conf]).
Steps:
- Define the record schema +
emit.write(pure IO).test_emit.py: given a sample record, asserts the 3 files’ content shape (json fields present; sidecar contains labels + colors; lines format correct). PASS. - Per diagram (subagent-driven, one page at a time, serialized): a vision subagent reads the scan (crop+upscale for fine text) +
legend.json+ (if Task 3 passed) thecvtracenet-graph JSON, and produces the fused record: components, terminals, labels/systems (searchable), connections{from,to,wire_color:decoded,net_id,confidence}— CV-confirmed+vision-read = high; weak input downgrades; NEVER fabricate untraceable connections. Then callemit.write. - Provenance: every record marked AI-extracted; sidecar text prefixed with an AI-extracted note; scan path referenced as source of truth.
Task 5: chunker.py sidecar patch (kb-vectors) — index the sidecar onto the real page
Files: Modify /home/levander/kb-vectors/chunker.py (chunk_manual); update test_chunker.py. Back up chunker.py → .bak-wiring first.
Steps:
- In
chunk_manual, after reading a page’s.mdtext, if a sibling{page}.wires.txtexists, append its content totextunder a delimiter (e.g.\n\n[DIAGRAM DATA]\n+ sidecar) BEFORE chunking, so the diagram labels/colors ride on the SAME page’s points with the SAMEpage_url/images. Do not create separate pages. Keep the existing.md-only page discovery (sidecars are not standalone pages). - Update
test_chunker.py: a page with a sibling.wires.txt→ its chunks include the sidecar text and keep the page’spage_url; a page without one is unchanged;test_ignores_non_mdstill holds (sidecars are not indexed as their own pages). Runkb-vectors/venv/bin/python -m unittest test_chunker. PASS.
Task 6: Batch run (Phase 1: Vitara) + reindex + search-win verification
Files: Create /home/levander/wiring-extract/run.py (batch CLI: run <folder> <manual>), reindex helper.
Steps:
-
run.py: for each diagram page (via diagram_index), run cvtrace (if enabled) → dispatch the vision+fusion → emit sidecars/json. Serialized, thread-capped, idempotent per page. Log stats (pages, connections high/med/low, unreadable regions) — no silent caps. - Run Phase 1 on
suzuki-vitara/wiring-diagrams(21 pages). Spot-check the cranking netlist JSON against the scan; confirm confidence flags are honest. - Re-index just that manual:
kb-vectors/venv/bin/python -c "import index; index.index_manual('suzuki-vitara','wiring-diagrams')"(delete-by-manual_id + upsert; safe/incremental). - Search-win check: query a term only present in a diagram (e.g. “IC regulator”, “fuel cut controller”) via the existing search path; confirm the wiring page now returns. Report before/after.
- No regression: kb-vectors + knowledgebase unittest suites pass; source md + scans byte-unchanged; clusters.json untouched.
Self-review
- Spec coverage: labels-searchable (T1,T4,T5,T6), best-effort connections (T0,T3,T4), hybrid CV+vision (T0/T3 CV, T4 vision+fusion), sidecar-vectorized (T5), trust/confidence (T4 provenance+confidence), Vitara-first (T6). Covered.
- Deviations from spec (recon-driven): (a) WireViz jot round-trip DESCOPED — project absent on box; emit JSON + simple
.linesformat instead. (b) Added T1 legend extraction (color table only exists as an image). (c) CV runs underocr/venv; sidecar needs the T5 chunker patch (indexer is.md-only). - Ordering de-risks: T0 spike gates the whole CV investment; vision-only fallback preserved.
- Type consistency:
legend.decode_color,cvtrace.trace,emit.writenames stable across tasks; record schema defined in T4 and consumed by T5/T6.