The Problem

Credit evaluation teams process 15–20 page loan applications by hand, taking hours per case. This repo automates the document pipeline: OCR text extraction, local LLM field extraction, validation, and stage-based storage — all on local infrastructure with no external AI APIs.

What This Does

src/ocr/label_value_extraction.py extracts key-value pairs from scanned documents using EasyOCR with spatial analysis; src/llm/field_extractor.py validates extracted data against business rules via a local Llama3.1:8b model hosted by Ollama. Results are stored in Azure Blob emulation (Azurite) by processing stage (raw, OCR, LLM, annotated), with metadata in PostgreSQL.

The system is built as a tutorial: nine notebooks under notebooks/ walk from infrastructure setup through a Flask API (src/api/main.py, src/api/routes.py) with Celery async workers (src/tasks/pipeline_tasks.py, src/celery_app.py).

How It Is Wired

Execution starts at run in start_credit_ocr.py:281, which reaches 7 functions. The shortest path from entry to leaving the process: run -> check_prerequisites (subprocess.run) and start -> _setup_database (psycopg2.connect). A full run starts Docker services, waits for them, then serves the API via run_api.py:27 (main).

Everything routes through src/storage/storage.py: get_storage is called from 21 places, upload_blob from 9, blob_path and get_document from 8 each. That module is the dependency hub — 12 modules import it, and churn there is high-blast-radius. The most unstable module is src/integration/pipeline (Ca 5, Ce 9, instability 0.64), which orchestrates OCR → LLM → visualization.

Effects outside the process: 37 functions make outbound network calls, 20 read/write a database, 12 run external commands, 5 touch files. src/dms/adapters.py owns database I/O; src/dms/service.py computes SHA-256 and handles document upload/retrieval; src/api/routes.py wires HTTP to the pipeline.

How To Use It

git clone https://github.com/moses-y/credit-ocr-system
cd credit-ocr-system
# install (pyproject.toml + uv.lock present)
pip install -e .        # or: uv sync
docker compose up -d    # PostgreSQL, Redis, Ollama, Azurite
python start_credit_ocr.py   # starts infrastructure
python run_api.py             # serves the Flask API

Configuration lives in config/credit-ocr-system.conf and config/document_types.conf; copy .env.template to .env for credentials. The README directs setup through notebooks/1-setup/01_setup.ipynb.

Real-World Use

A loan officer uploads data/loan_application.pdf via the API. upload_document stores the blob, Celery runs integrated_pipeline (OCR → LLM extraction → visualization), results land in stage-based containers, and the officer reviews extracted fields with confidence scores and bounding-box overlays in the web UI (src/api/templates/index.html).

Code Health & Issues

Static analysis found 19 findings (2 high, 17 medium). High: deep nesting (max depth 6, in src/dms/adapters.py, start_credit_ocr.py, src/api/routes.py) and 48 duplicated 6-line blocks across 11 files. Notable mediums: broad exception handling in src/storage/storage.py, src/dms/adapters.py, src/dms/service.py; the storage.py hub (12 dependents); high branching in src/llm/validation.py and src/ocr/label_value_extraction.py (23 branch points over 74 lines).

SDLC gaps: no CI workflow, no LICENSE (all rights reserved by default), Dockerfile runs as root with an unpinned base image (python:3.11-slim), and no Dependabot. Note: uv.lock exists despite the "no lockfile" finding — verify which dependency tool is canonical. Notebook 02_ocr_text_extraction.ipynb is 1MB, dominated by stored outputs.

The Bottom Line

A well-structured local-first OCR pipeline that demonstrates a complete credit-document workflow cleanly. It is tutorial-grade, not production-grade: no CI, no license, root containers, and a hot storage hub. Use it to learn the architecture or as a scaffold, not as-is in production.