--- name: ocr-data-extraction description: "Extract structured data (names, phones, VINs, dates) from OCR'd text using rule-based regex parsing — no AI/API needed." version: 1.0.0 author: Hermes Agent license: MIT metadata: hermes: tags: [OCR, parsing, regex, data-extraction, screenshots, browser, Tesseract] related_skills: [ocr-and-documents] --- # OCR → Structured Data Extraction (Rule-Based) Extract structured fields from OCR'd text using pure regex — no API keys, no network calls, no AI. Runs entirely on CPU, either in-browser (Tesseract.js CDN) or server-side (Tesseract CLI). **When to use this approach vs AI/API:** - Data follows predictable patterns (phones, VINs, dates, names) - Latency matters (instant vs 1-3s API call) - Cost matters (free vs per-call billing) - Privacy matters (data stays local) - User explicitly prefers CPU-based — **honor this signal immediately** ## Quick Start (Browser) ```html ``` Then call `Tesseract.recognize(file, 'eng', { logger })` to get text, then run the parser. ## Parser Architecture The parser uses **layered extraction** in this order: ### 1. Block Splitting Split OCR text on blank lines first. If that fails, detect table-like structures (consistent word counts across lines). ### 2. Structured Field Extraction (per block) Extract and REMOVE these from the text first (order matters — early extraction simplifies later parsing): ``` Phone: /\(?\d{3}\)?[\s.\-]*\d{3}[\s.\-]*\d{4}/ VIN: /\b[A-HJ-NPR-Z0-9OIQ]{17}\b/i ← lenient: accepts O→0, I→1, Q→0 Date: YYYY-MM-DD, MM/DD/YYYY, "Jan 15, 2024" Time: 9:00 AM, 1:30PM, 14:00, 8am Duration: /\b(\d+)\s*(?:min|minutes?|hrs?|hours?)\b/i ``` **OCR VIN tolerance**: Tesseract commonly confuses `0→O`, `1→I`. Accept O/I/Q in the regex, then normalize: ```js function fixVin(v) { return v.toUpperCase().replace(/O/g,'0').replace(/I/g,'1').replace(/Q/g,'0'); } ``` ### 3. Remaining Text → Name / Vehicle / Service After removing structured fields, split remaining text on multi-spaces (preserves column structure). Then: - **Name**: first part, or consecutive capitalized words without digits - **Vehicle**: part containing year pattern (`19xx`/`20xx`) or known makes (`ford|toyota|honda|bmw|...`) - **Service**: everything else ### 4. Advisor + RO Code Stripping If the source has `AdvisorName [RO_CODE]` prefixes, strip them: ```js var m = part.match(/^([A-Z][a-z]+)\s+(\[[^\]]+\])\s+(.*)/); if (m) { roCode = m[2]; return m[3]; } // return service, save RO as note ``` ### 5. Multi-Appointment Boundary Detection When multiple appointments appear without blank-line separation, detect boundaries by: - Find the next phone/VIN in the remaining text - **Walk back** to the nearest multi-space gap or capitalized name before it - Split there — text before goes to current appointment, text after recurses - Regex for gap walk-back: `/\s{2,}(?=\S+(?:\s+\S+){0,1}\s*$)/` - Fallback (no gap): detect last 1-2 capitalized words before phone: `/([A-Z][A-Za-z]+(?:\s+\S+){0,1})\s*$/` ### 6. Header/Noise Stripping - Separator lines: `/^[\-–=_*#]{3,}$/` → remove - Short all-caps headers: `< 25 chars, all uppercase letters/spaces` → remove - Table header row: contains `name|phone|date|time|service|vehicle|vin` → skip first line - Known header words: `schedule|appointments|roster|calendar|upcoming` → filter from parts ## Pitfalls - **Don't collapse whitespace before splitting**: Split on `\s{2,}` FIRST, then clean each part. If you collapse to single spaces first, multi-space splitting silently breaks. - **Trailing space before boundary breaks regex**: Phone/VIN markers are often preceded by a space in the OCR. Trim or use `\s*$` in lookahead regexes. - **"O" in VIN ≠ letter O**: Always use lenient VIN regex and normalize. OCR will turn zeros into O's. - **Advisor names vs customer names**: In shop management systems, "Word [CODE]" is advisor+RO, not customer. Strip it. - **Block recursion can re-include old text**: When recursing, pass only the text AFTER the boundary split point, not the full remaining `b` variable. - **Table detection false positives**: Only use line-by-line table parsing when lines have consistent word counts (≤3 word difference from average). Exclude header line before checking consistency. ## Reference File See `references/parser-patterns.md` for the full extraction regex catalog and test cases.