Charlotte, NC
ProjectsAugust 1, 2025

Paperless OCR Enhanced

Tap image to enlarge
Paperless OCR Enhanced is a companion service for Paperless-ngx that repairs weak OCR after a document has already landed in the archive. Instead of trusting Tesseract's first pass when a scan is messy, it sends document pages to LLM vision models that can actually understand what they're looking at. The result is dramatically better extraction on the kinds of documents that make traditional OCR fall apart: handwritten notes, complex table layouts, low-quality scans, and form fields with checkboxes. This project was born out of frustration. I was building a knowledge graph over my document archive, and the quality of everything downstream depended on the quality of the text going in. Tesseract was producing garbage on about 20% of my documents, which meant my entity extraction was missing data and my search results were unreliable. I needed better OCR, and modern vision models turned out to be the answer. I work with document processing pipelines at scale. The pattern of extracting structured data from messy inputs is the same whether you're doing it for an enterprise client or for your own filing cabinet. The difference is that at home, I can pick the exact tools I want and iterate without a change advisory board. The container runs alongside Paperless-ngx and operates on a simple tag-based workflow:
  1. A document gets tagged ocr-redo in Paperless (manually or through automation rules)
  2. The container polls for documents with that tag, downloads the original PDF
  3. Each page is rendered as a high-resolution image at 300 DPI using PyMuPDF
  4. The page image is sent to an LLM vision API with a specialized OCR prompt
  5. The system writes the extracted text back to Paperless's content field for full-text search, and also builds a searchable PDF artifact internally for validation and future replacement workflows
  6. The trigger tag is swapped for ocr-complete (or ocr-failed if something went wrong)
The whole process is invisible to the user. Tag a document, wait a minute, and it reappears in Paperless with dramatically better searchable text. The current live integration does not overwrite the archived PDF; it improves the content field that Paperless search, downstream automation, and the knowledge graph actually consume. This is the feature that sets it apart from just throwing pages at a vision API one at a time. Real documents have context that spans pages. A table that starts on page 3 might have column headers only on page 1. A contract's terms on page 5 reference definitions from page 2. The system maintains a rolling context window as it processes each page:
  • Immediate context: The last portion of the previous page's text, so the model knows what it just read
  • Rolling summary: An accumulated understanding of the document built from context notes the model generates after each page
  • Automatic condensation: Every few pages, older context notes get compressed to keep the prompt size manageable for long documents
This means page 15 of a 20-page document gets processed with awareness of what the document is about, what came before, and how the current page fits in. The difference is significant on multi-page financial statements, contracts, and medical records where context matters for correctly interpreting abbreviations, table continuations, and cross-references. Not every model handles every document type well. Content filters can block medical documents. Rate limits can interrupt batch processing. The system handles this with a three-tier fallback chain:
  1. Primary model processes the page (I currently run Gemini Flash, but any supported provider works)
  2. If blocked by content filters or the response is empty, it falls through to Fallback 1 (Anthropic in my deployment)
  3. If that also fails, it tries Fallback 2 (xAI in my deployment)
  4. Each tier has its own retry logic with exponential backoff (5 retries, starting at 2 seconds, doubling each attempt) for transient errors including rate limits, timeouts, and server errors
The primary/fallback providers are fully configurable via environment variables. In my archive, Gemini Flash handles the vast majority of documents without triggering fallbacks. The chain is there for the edge cases, and it means a batch processing run won't stall on a single problematic document. The container processes multiple documents in parallel using an async semaphore pattern with a default concurrency of 5 documents. When a batch of documents gets tagged for re-OCR, they're picked up and processed concurrently rather than sequentially. Each document still processes its pages in order (context depends on page sequence), but multiple documents run at the same time. The container handles graceful shutdown via SIGTERM/SIGINT. When Docker, Kubernetes, or another orchestrator sends a stop signal, active OCR jobs drain to completion before the container exits. No orphaned half-processed documents. This matters when you're migrating an existing archive. Tagging 50 documents for re-OCR and having them all process within minutes instead of hours makes the difference between a practical migration and one you abandon halfway through. Tesseract is good at what it does. Clean, typed, well-scanned documents come through fine. But the real world sends you:
  • Handwritten notes scribbled on printed forms
  • Complex tables where Tesseract loses column alignment and merges cells
  • Low-contrast scans where faded text becomes unreadable
  • Checkboxes and form fields that Tesseract ignores entirely
  • Mixed content with printed text, stamps, signatures, and handwriting on the same page
Vision models handle all of these because they're not doing character recognition. They're looking at the page the way a human would and describing what they see. The accuracy difference on complex documents is dramatic. The container is configured entirely through environment variables. No config files, no setup wizards. Point it at your Paperless instance, give it an API key, and it works. It runs fine as a Docker Compose sidecar, and I currently run the same image as a small Kubernetes deployment. It supports OpenAI, Anthropic, Google Gemini, and xAI through an OpenAI-compatible endpoint, with per-provider fallback configuration if you want the multi-model chain. Poll interval, concurrency limits, context window sizes, tag names, max pages per document, and even the OCR prompt itself are all configurable through environment variables.
  • Python with async/await for concurrent document processing
  • PyMuPDF for 300 DPI page rendering and internal searchable PDF artifact generation
  • OpenAI-compatible, Anthropic, and Google Gemini SDKs for OpenAI, xAI, Anthropic, and Gemini vision OCR
  • Paperless-ngx REST API for document retrieval and content updates
  • Docker / Kubernetes with automated GitHub Actions CI/CD publishing to GHCR
  • Exponential backoff retry logic (5 retries, 2s base) for resilient API interaction
The project is MIT licensed and available as a pre-built container image. If you're running Paperless-ngx and want better OCR on complex documents, it can be a single docker-compose addition or a small Kubernetes deployment.

Related projects

Garmin MCP Server

Open-source MCP server that exposes 34 Garmin Connect health and fitness tools to any AI assistant, with parallel fetching and Docker support.

blakemccarn.dev

Portfolio and blog built with Next.js 16, deployed to AWS via SST v4 with Cloudflare DNS, staging environments, and full IaC.

Charlotte Wire & Cable

Full-stack business website for a specialty wire distributor: searchable catalog, admin dashboard, and serverless AWS infrastructure via SST.

Paperless Knowledge Graph

Document intelligence system that combines Paperless-ngx, Neo4j, pgvector, Strands Agents, cited answers, and an interactive graph UI.

RapidEPR

Founded and built an AI SaaS product that helps service members across five military services write evaluations, performance statements, and award narratives.