Skip to content

Repository files navigation

scansmith

Reconstruct clean, searchable documents from scrambled, degraded, bilingual scanned bundles.

Runs free & offline with local OCR. Add an Anthropic API key and it gets smarter — an LLM vision layer re-reads exactly the pages local OCR couldn't.

scrambled input bundle reconstructed documents

The problem

Real-world scanned legal/records bundles are a mess: pages from different documents are interleaved and shuffled, the same document appears in multiple copies, scans are degraded photocopies, pages are rotated, and the text is bilingual (e.g. English + Hindi/Devanagari). A naïve "split by blank page" tool falls apart — there is no positional signal left to split on.

scansmith treats it as a reconstruction problem: group scattered pages back into their true documents by content, pick the clearest copy of every page, and emit one named, deduplicated, searchable PDF per document, plus provenance reports and a JSON manifest.

How it works

flowchart LR
    A[scanned<br/>bundle] --> B[ingest]
    B --> C["OCR triage<br/>(Tesseract)"]
    C -. "DEGRADED pages,<br/>only with an API key" .-> V["vision re-OCR<br/>(Claude)"]
    V -.-> D
    C --> D["segment<br/>content clustering"]
    D --> E["order + dedup<br/>clearest copy wins"]
    E --> F["embed<br/>invisible text layer"]
    F --> G["emit<br/>named searchable PDFs<br/>+ reports + manifest"]
Loading
  • Content clustering — pages link by shared rare signals that survive OCR noise: party names (idf-weighted, common surnames ignored), amounts, dates, certificate/case numbers (fuzzy-matched against OCR digit slips). Connected components become documents; title / clause numbering / signature cues order the pages.
  • Copy dedup — perceptual image hash and text agreement must both match, so a duplicate collapses but two same-template pages never merge.
  • Vision escalation (optional) — pages the local pass flags DEGRADED (grainy photocopies, Devanagari without a language pack, handwriting) are re-read by Claude with a faithful-transcription prompt: Devanagari stays Unicode, unreadable spans become [illegible], signatures [signature]. No key → the offline result stands, hard pages stay flagged.

Measured results

Everything is scored against generated ground truth (scansmith eval) — on the seeded 6-document demo bundle:

metric offline core baseline
segmentation pairwise F1 0.947 0.000 ("1 page = 1 doc")
dedup precision / recall 1.000 / 1.000 recall 0 (no dedup)
OCR CER (legible Latin) 0.001 1.0 (no text)

Adding the vision layer (A/B on the same bundle):

metric offline with vision*
segmentation F1 0.947 1.000
documents found (truth: 6) 7 6
Devanagari CER 0.80 0.00
Devanagari pages legible 0/2 2/2

* measured with the deterministic test harness (a seeded backend returning ground-truth text — this is how the escalation path is proven without network); a real vision model typically lands Devanagari CER around 0.1–0.2 on these pages.

The offline core cannot read the Hindi court order, so it fragments; the vision layer transcribes it faithfully and it snaps into one document. That delta is the layered design working.

Quickstart

pip install "scansmith[tesseract]"          # plus the system tesseract-ocr binary (add the
                                            # hin language pack for the Hindi documents)
scansmith gen-sample --out demo/            # fabricate a messy bilingual bundle + ground truth
scansmith process demo/bundle.pdf -o out/   # reconstruct into clean, searchable documents
scansmith eval out/ --truth demo/truth.json # metrics vs ground truth

# Optional smart layer — re-read the hard pages with Claude vision:
pip install "scansmith[llm]"
export ANTHROPIC_API_KEY=sk-...             # or put SCANSMITH_ANTHROPIC_API_KEY in .env
scansmith process demo/bundle.pdf -o out_llm/ --llm anthropic --model claude-opus-4-8
scansmith eval out_llm/ --truth demo/truth.json

Runs fully offline without a key; the smart layer only adds to what the local core produces.

Design

Pluggable pipeline behind small protocols: local OCR engines implement OcrBackend, LLM vision implements LlmBackend — so the tool degrades gracefully from "LLM-augmented" to "local only" without changing the orchestration. All tests are deterministic and offline (the vision path is proven with a seeded fake backend; one live test is API-key-gated). Full design: docs/ARCHITECTURE.md.

Why it exists

Born from reconstructing a real 789-page, 4-file legal bundle (heavily scrambled, bilingual, degraded) where local OCR failed on exactly the pages that mattered and LLM vision was the reliable transcriber. No real documents are included here — everything above is synthetic data from scansmith gen-sample, so the whole pipeline is reproducible and PII-free.

Status

v1.0.0 — all milestones complete: M0 scaffold · M1 synthetic data · M2 local core · M3 LLM smart layer · M4 polish.

License

MIT © 2026 Yashvardhan Chaudhry

About

Reconstruct clean, searchable documents from scrambled, degraded, bilingual scanned bundles — offline OCR core + optional Claude-vision smart layer, measured against synthetic ground truth.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages