Files
hermes-config/skills/software-development/web-ui-repair/references/ocr-parser-architecture.md
T
2026-07-12 10:17:17 -04:00

5.2 KiB

Rule-Based OCR Parser Architecture

Reference for the parseWithRules() parser in appointments.html (ShopProQuote). Extracts structured appointment data from Tesseract.js OCR output — no AI/API, pure JavaScript regex, runs entirely in the browser.

Architecture Overview

Raw OCR text
  → Phase 1: Normalize & Clean (strip headers, separators, metadata)
  → Phase 2: Block Splitting (row-anchor primary, blank-line fallback, table fallback)
  → Phase 3: parseOne() recursive extraction per block
  → Output: array of {customerName, customerPhone, customerEmail, vin, vehicleInfo, serviceType, notes}

Phase 2 — Row-Anchor Splitting (Primary)

Instead of splitting on \n\n (fragile — Tesseract's horizontal table scan often produces text with inconsistent blank lines), split on Time + Duration patterns:

var rowAnchorRe = /(\d{1,2}):(\d{2})\s*(AM|PM)\s+(\d+(?:\.\d+)?)\s*hrs?/gi;

Find all anchor positions, slice text at boundaries. Each chunk starts with a time+duration — one appointment per chunk.

Fallback: if < 2 anchors found, revert to \n\n blank-line splitting.

Phase 3 — parseOne() Consume-and-Destroy Order

Critical: extraction order is strict. Each field is matched and removed from the text before the next field is searched — prevents false matches.

1a. Time (anchor)      — extract first, remove
1b. Duration           — minutes/hours, remove
1c. Phone              — 10-digit, any format, remove
1d. Email              — extract before VIN (prevents email digits matching VIN/date)
1e. VIN                — 17-char boundary, OCR-tolerant (O→0, I→1, Q→0), remove
1f. Post-VIN cleanup   — strip residual "VIN" label, standalone "RO" token
1g. Advisor sanitize   — hard-strip "Rrahman Grajqevci" and variants
1h. Date               — 3 formats: YYYY-MM-DD, M/D/YYYY, "Mon DD, YYYY"

After structured extraction, remaining text parts are split on \s{2,} OR \n (preserves multi-line OCR structure — do NOT collapse newlines to spaces before splitting).

Advisors / RO Codes

Pattern: AdvisorName [RO_CODE] is stripped from parts. The [CODE] is captured into notes as RO: [CODE].

Cross-Block Name Carry

Trailing ALL-CAPS names at the bottom of a block belong to the NEXT block. Parser detects via serviceType.match(/\s+([A-Z]{2,}(?:\s+[A-Z]{2,}){1,2})\s*$/) and passes as carryName to the next parseOne() call.

Vehicle Extraction

Primary: find part matching year + make (e.g., "2023 Honda")
Fallback: find part matching year OR make alone
Model capture: if next part is ALL-CAPS alphanumeric and not a service keyword, append to vehicle

Header Metadata Stripping

Before block splitting, lines matching schedule headers are removed:

  • Day-of-week + date: Wednesday Jun 18, 2025
  • Single-word labels: Weekly, Advisor, Refresh, Daily

Key Regex Patterns

Field Pattern Notes
Phone \(?\d{3}\)?[\s.\-]*\d{3}[\s.\-]*\d{4} Any format
VIN \b[A-HJ-NPR-Z0-9OIQ]{17}\b/i OCR-tolerant, word-bounded
Email \b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b Extracted pre-VIN
Date (\d{4})[\/\-.](\d{1,2})[\/\-.](\d{1,2}) ISO format primary
Time `\b(\d{1,2}):(\d{2})\s*(AM PM)?\b`
Duration `\b(\d+(?:.\d+)?)\s*(?:min minutes?
Row Anchor `(\d{1,2}):(\d{2})\s*(AM PM)\s+(\d+(?:.\d+)?)\s*hrs?`
Makes 25+ automotive brands Used in vehicle detection
Year `\b(19 20)\d{2}\b`

Pitfalls

  • Double-escape in regex strings: When editing regex in HTML patches, \\d in the file is two characters. Using \\\\d produces four. Verify with grep -n '\\\\\\\\d' after editing.
  • Don't collapse newlines in block processing: The block-iteration step must NOT do .replace(/\n/g, ' '). Parts splitting uses \n as a delimiter; collapsing them first destroys multi-line structure.
  • "RO" token contamination: The text "RO Gary Bowers" has standalone "RO" before the name. Strip \bRO\b after VIN extraction, before advisor stripping.
  • "VIN" label residue: After VIN extraction, the word "VIN" remains. Strip \bVIN\b in post-VIN cleanup.
  • Email false positives: Email addresses contain digits (e.g., garyb9623@gmail.com). Extract email BEFORE VIN to prevent the digits matching VIN patterns.
  • Table fallback only fires when blank-line split produces 1 block. The row-anchor path runs first and can produce multiple blocks — table fallback is a separate code path for the blocks.length === 1 case.

Test Suite

7 test cases in the parser test harness cover:

  1. Two appointments with blank-line separation
  2. Shop header + separator lines
  3. Single appointment
  4. Table format (uniform columns)
  5. Real 3-appointment screenshot with blank lines + email + advisor
  6. OCR horizontal-scan format with NO blank lines (row-anchor test)
  7. Email/code false-positive guard

Run tests via:

cd /mnt/seagate8tb/Websites/ShopProQuote
sed -n '1147,1363p' appointments.html > /tmp/parser_fn.js
echo 'module.exports = parseWithRules;' >> /tmp/parser_fn.js
node -e "var p = require('/tmp/parser_fn.js'); /* ... test cases ... */"