payable-receipt-ocr
Development

Architecture

Module structure, call direction, privacy model, and why the package has no plugin interface.

payable-receipt-ocr is a single-process, synchronous, stateless library. It reads one image, runs OCR locally, and returns a typed suggestion. It makes no network requests, spawns no background threads, and makes no persistent writes during recognition.

Module map

ModuleVisibilityRole
api.pyPublicrecognize() entry point — validates arguments, delegates to engine
models.pyPublicRecognitionResult, RecognitionWarning, RuntimeProvenance
errors.pyPublicReceiptOcrError and all subclasses
cli.pyPublicCLI adapter — calls recognize(), serializes result.to_dict()
_engine.pyPrivateprocess_receipt() — orchestrates the pipeline stages
_image.pyPrivateImage loading, EXIF correction, deskew, contrast, variant creation
_ocr.pyPrivateOCR pass scheduling, Tesseract subprocess execution, TSV parsing
_interpretation.pyPrivateCandidate extraction, label classification, evidence grading
_runtime.pyPrivateModel discovery, checksum verification, baseline tuple check
_contracts.pyPrivateTyped frozen dataclasses for inter-stage data

Private modules carry a _ prefix. They are not importable from the public namespace and are not covered by the schema stability guarantee.

Public seam

Three things are public and stable:

SurfaceStability
recognize(image, *, currency, …)RecognitionResultStable
RecognitionResult, RecognitionWarning, RuntimeProvenanceStable
JSON schema payable-receipt-ocr/1Stable

Internal module names, function signatures, and data structures may change without notice.

Call direction

recognize()                    [api.py — public]
  └─► process_receipt()        [_engine.py — private orchestrator]
        ├─► load_source_image
        │   prepare_variants   [_image.py]
        ├─► validate_runtime   [_runtime.py]
        ├─► build_schedule
        │   execute_schedule   [_ocr.py]
        └─► interpret          [_interpretation.py]
              └─ typed records  [_contracts.py]

Privacy model

No state between calls. Each recognize() is independent.

No network. No HTTP, DNS, or socket operations.

No persistent writes. Prepared image variants are written to TMPDIR and deleted in finally blocks. A crash can prevent cleanup. Tesseract may create additional temporaries. Use tmpfs for sensitive deployments.

No logging. The package does not emit Python logging records.

Diagnostics are opt-in and per-call. When diagnostics=False (default), no raw OCR text is retained in the result object. When diagnostics=True, the result contains the full recognized text from every pass. Diagnostics cannot be recovered after the call returns without diagnostics=True.

Why there is no plugin interface

The package has no OCR engine plugin or currency provider interface. Tesseract is the only backend; INR is the only production-scope currency.

A plugin system would:

  • Widen the trust boundary (plugins can inject arbitrary OCR results)
  • Complicate holdout reproducibility (results would depend on which plugins are active)
  • Add integration-test surface without a concrete use case

Stateless, local, synchronous

  • Stateless: no cache, session, or connection pool between calls.
  • Local: all computation runs in the caller's process, on the caller's machine.
  • Synchronous: recognize() blocks the calling thread. Concurrency is the service layer's responsibility.

Development vs. conformance

macOS is a fully supported development environment. The conformance environment is ubuntu-24.04 / amd64 / tesseract 5.3.4. Accuracy numbers are only meaningful when measured on the retained conformance instance.

See Runtime conformance for the full policy.

On this page