End-to-end OCR + LLM platform for turning messy receipt images/PDFs into structured, reviewable, and continuously improving financial data.
Receipt extraction fails in real-world conditions: noisy scans, inconsistent layouts, ambiguous totals, and vendor-specific line-item formats.
This pipeline addresses that with:
- resilient OCR (Textract primary + Tesseract fallback)
- schema-constrained extraction into typed receipt objects
- confidence-based routing to human review
- catalog normalization (fuzzy + vector matching)
- feedback loops for error analysis and retraining runs
flowchart LR
A[Upload receipt image/PDF] --> B[Preprocess]
B --> C[OCR: Textract -> Tesseract fallback]
C --> D[LLM extraction to Receipt schema]
D --> E[Confidence scoring + warnings]
E --> F[Line-item catalog matching]
F --> G{Confidence level}
G -->|High| H[Auto-complete]
G -->|Medium/Low| I[Review queue]
I --> J[Human corrections]
J --> K[Error analysis]
J --> L[Retraining run]
- Asynchronous ingestion with Celery workers (
/receipts/upload,/receipts/batch) - Typed extraction via Pydantic schema (
Receipt,LineItem, parse warnings, confidence levels) - Human-in-the-loop review with correction/approve/skip workflows
- Product normalization using rapidfuzz first, embeddings fallback (pgvector)
- Analytics APIs for error patterns, calibration, estimated accuracy, and cost summary
- Scheduled learning loop with nightly error analysis and monthly retraining tasks
- Benchmark suite for CORD dataset with OpenAI, Groq, or local heuristic extraction
Copy .env.example to .env and set values:
- Required for LLM extraction:
OPENAI_API_KEYorGROQ_API_KEY - Optional for cloud OCR:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION - For local OCR on Windows, you can set
TESSERACT_CMDif Tesseract is not in PATH
docker compose up --buildpython -m app.scripts.seed_catalog --csv data/sample_products.csv- Swagger UI:
http://localhost:8000/docs - Health check:
http://localhost:8000/health
pip install -r requirements.txt
uvicorn app.main:app --reloadIn separate terminals:
celery -A app.worker.celery_app worker --loglevel=info
celery -A app.worker.celery_app beat --loglevel=infoPOST /receipts/uploadPOST /receipts/batchGET /receipts/batch/{job_id}GET /receipts/{receipt_id}GET /receipts/{receipt_id}/imageGET /receipts/{receipt_id}/ocrGET /receipts/GET /receipts/stats
GET /review/queueGET /review/{review_id}POST /review/{review_id}/correctPOST /review/{review_id}/approvePOST /review/{review_id}/skipGET /review/stats
GET /catalog/products,POST /catalog/products,GET /catalog/searchPOST /catalog/match,POST /catalog/embedGET /analytics/errors,POST /analytics/analyzeGET /analytics/accuracy,GET /analytics/confidence,GET /analytics/costPOST /retrain/trigger,GET /retrain/runs,GET /retrain/runs/{run_id}
GET /health(database + redis status)GET /metrics(Prometheus metrics)
benchmarks/run_cord.py supports three extraction backends:
heuristic→ local/no API costopenai→ OpenAI structured extractiongroq→ Groq OpenAI-compatible structured extraction
Example commands:
python benchmarks/run_cord.py --split test --limit 100 --extraction-mode heuristic
python benchmarks/run_cord.py --split test --limit 100 --extraction-mode openai
python benchmarks/run_cord.py --split test --limit 100 --extraction-mode groqGenerate report markdown from benchmark JSON:
python benchmarks/report.py
python benchmarks/report.py --input benchmarks/results/cord_test_<timestamp>.json --output benchmarks/results/cord_report.mdSource: benchmarks/results/cord_test_20260524_234250.json
- Success: 45/50 (
90%) - Total exact match: 44.4%
- Line-item price exact match: 34.4%
- Line-item name fuzzy match: 36.7%
- Avg latency: 23.73s (p95: 48.38s)
Local checks:
python -m compileall app tests benchmarks
python -m pytest tests -qCI workflow (.github/workflows/ci.yml) runs compile checks and the test suite on push/PR to main.
app/
main.py
worker.py
routers/
services/
models/
scripts/
benchmarks/
run_cord.py
report.py
tests/
docker-compose.yml
requirements.txt
MIT License. See LICENSE.