The Problem

Businesses that digitize point‑of‑sale receipts need a reliable way to turn a scanned image into structured data (date, total, line items). Manual entry is error‑prone and costly, while generic OCR tools return raw text that still requires parsing.

What This Does

receipt-ocr bundles two complementary services:

  • src/receipt_ocr/ – a Python package that calls an LLM (OpenAI or Gemini) to interpret raw OCR text and emit a JSON schema of receipt fields. The public CLI lives in src/receipt_ocr/cli.py and the FastAPI wrapper in app/server.py.
  • src/tesseract_ocr/ – a lightweight FastAPI service that runs Tesseract on an image and returns the raw text. Entry points are src/tesseract_ocr/app.py (FastAPI app) and src/tesseract_ocr/main.py (CLI wrapper).

Both modules share utility code (src/receipt_ocr/utils.py, src/tesseract_ocr/utils.py) and a common Docker‑compose setup (app/docker-compose.yml, src/tesseract_ocr/docker-compose.yml).

How It Is Wired

  1. CLI Path – Structured Extraction Execution starts at src/receipt_ocr/cli.py (registered as console script receipt-ocr in pyproject.toml). The CLI parses arguments, loads environment variables via src/receipt_ocr/utils.py.load_env(), and calls process_image() in src/receipt_ocr/processors.py. process_image() invokes the Tesseract module by importing src.tesseract_ocr.main.run_tesseract(image_path) (wrapper around pytesseract or the Docker service). Raw text returned is fed to src/receipt_ocr/parsers.py.parse_with_llm(text), which selects a provider from src/receipt_ocr/providers.py (OpenAI or Gemini) based on OPENAI_API_KEY / OPENAI_BASE_URL. * The parsed JSON is printed or written out; any errors bubble up to the CLI’s main() handler.
  1. FastAPI Path – Receipt Service app/server.py creates a FastAPI instance (app = FastAPI()) and mounts two routers: /ocr → src.tesseract_ocr.app.router (raw text endpoint). /extract → functions in src.receipt_ocr that reuse the same processor/parsers pipeline described above. Requests flow: HTTP → FastAPI → process_image() → Tesseract → LLM → JSON response. The only external side‑effects are network calls to the LLM provider and optional file I/O for uploaded images.
  1. Tesseract Service src/tesseract_ocr/app.py defines a FastAPI router exposing POST /ocr that calls run_tesseract(image_bytes) in src/tesseract_ocr/utils.py. src/tesseract_ocr/main.py provides a CLI (python -m src.tesseract_ocr.main <image>) that directly invokes the same utility.
  • Blast radius – The LLM provider call in providers.py is the widest impact point (external API, latency, cost). All other functions are pure Python or local Docker containers, making them easy to test and replace.
  • No cyclic imports are evident; each module has a clear responsibility: CLI → processor → OCR → LLM.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/receipt-ocr.git
cd receipt-ocr

# Install the package (editable) and dependencies
pip install -e .          # reads pyproject.toml
# or, for isolated env, use uv:
uv pip install -e .

# Set required env vars (see .env.example)
cp .env.example .env
export OPENAI_API_KEY="sk-..."
# optional: export OPENAI_MODEL="gpt-4o"

# Run the CLI on a sample image
receipt-ocr images/receipt.jpg

# Or start the FastAPI service (receipt‑ocr module)
uvicorn app.server:app --host 0.0.0.0 --port 8000

# Tesseract‑only service via Docker
docker compose -f src/tesseract_ocr/docker-compose.yml up --build

The Dockerfiles in app/ and src/tesseract_ocr/ build images that expose ports 8000 (receipt‑ocr) and 8001 (tesseract‑ocr) respectively.

Real‑World Use

A retail SaaS can run the Docker compose stack, POST receipt images to http://localhost:8000/extract, and receive a ready‑to‑store JSON payload ({date, total, items[…]}) without writing custom parsing code. The same payload can be fed into downstream accounting systems.

Code Health & Issues

  • Low – Missing lockfile for app/pyproject.toml – reproducible builds depend on a lock (e.g., uv.lock exists only at root).
  • Low – Duplicate lock files – uv.lock at repository root and app/uv.lock may diverge, causing version drift.
  • Medium – No explicit type hints in core functions – hampers IDE assistance and static analysis.
  • Low – Tests exist (15 files) but CI does not enforce coverage thresholds – consider adding a coverage badge or fail‑fast rule.
  • Low – README documents CLI usage but does not mention the FastAPI entry point – update docs for completeness.

No critical security findings are visible in the static layout; secret keys are kept out of the repo via .env.example.

The Bottom Line

receipt-ocr delivers a usable pipeline: image → Tesseract → LLM → structured JSON, with both CLI and FastAPI interfaces. The codebase is modular and well‑tested, but reproducible builds need a lockfile for the app sub‑project, and documentation could be tighter around the service endpoints. It is suitable for teams that want an off‑the‑shelf receipt parser and are comfortable managing an LLM credential.