payable-receipt-ocr
Guides

Python API

Complete guide to the recognize() function, its parameters, return type, and error handling.

Import

from payable_receipt_ocr import recognize

Function signature

def recognize(
    image: str | Path,
    *,
    currency: str = "INR",
    tessdata_dir: str | Path | None = None,
    diagnostics: bool = False,
    deadline_seconds: float = 5.0,
    pass_timeout_seconds: float = 2.0,
    runtime_policy: Literal["development", "conformant"] = "development",
) -> RecognitionResult: ...

All parameters after image are keyword-only.

Parameters

image (required)

Path to the receipt image. Accepts str or pathlib.Path. The path is resolved before use; a missing or unreadable file raises InputFileError immediately.

Supported formats: JPG, JPEG, PNG, WebP.

currency

ISO currency code. Only "INR" is supported in the current production scope. Passing any other value raises ConfigurationError.

tessdata_dir

Explicit path to the directory containing eng.traineddata and Devanagari.traineddata. If None, the package falls back to the PAYABLE_RECEIPT_OCR_TESSDATA_DIR environment variable, then to the XDG user data directory. See Tesseract setup.

diagnostics

When True, raw OCR text from every completed pass and the ranked candidate list are included in the result object. These data can contain personal information visible on the receipt.

Diagnostics must be requested at call time. Calling result.to_dict(include_diagnostics=True) on a result produced without diagnostics=True raises ValueError.

See Diagnostics and privacy.

deadline_seconds

Maximum total wall-clock time for the entire recognize() call, in seconds. Default: 5.0.

If the deadline is reached before all planned passes complete, remaining passes are skipped, result.deadline_exceeded is set to True, and result.degraded is set to True. Any degradation caps the evidence grade at "review".

pass_timeout_seconds

Maximum time for each individual Tesseract subprocess pass, in seconds. Default: 2.0.

A pass that exceeds this limit is cancelled, counted in passes_failed, and a pass_timeout warning is added. If all passes time out, OcrEngineError is raised.

runtime_policy

Controls how the function responds when the running environment does not match the pinned conformance baseline:

PolicyBehavior on mismatch
"development" (default)Continues; sets result.runtime.conformant = False
"conformant"Raises RuntimeBaselineError

Model checksum verification runs regardless of policy and always blocks on mismatch.

Return value: RecognitionResult

A frozen dataclass with the following fields:

FieldTypeDescription
totalDecimal | NoneSuggested payable total; None when evidence_grade="none"
currencystr | NoneISO currency code; None when evidence_grade="none"
evidence_grade"strong" | "review" | "none"Corroboration level
requires_confirmationTrueAlways True
authorizes_persistenceFalseAlways False
matched_labelstr | NoneThe label text that anchored the suggestion
label_kind"payment" | "fallback" | NoneWhether the label was a payment label or fallback total
warningstuple[RecognitionWarning, ...]Zero or more warning objects
source_filenamestrBasename of the input image path
source_dimensionstuple[int, int](width, height) of decoded source in pixels
passes_plannedintOCR passes scheduled
passes_completedintPasses that returned a result
passes_failedintPasses that timed out or raised an error
degradedboolTrue if any pass failed or deadline was reached
deadline_exceededboolTrue if deadline was reached
duration_msintTotal wall time in milliseconds
runtimeRuntimeProvenanceRuntime identity

Convenience properties

PropertyTypeNotes
needs_reviewboolevidence_grade != "strong" — backward-compat; not in JSON
pass_countintAlias for passes_completed — backward-compat; not in JSON

Serialization

RecognitionResult.to_dict() returns the JSON-serializable payable-receipt-ocr/1 payload:

result = recognize("receipt.png", currency="INR")
payload = result.to_dict()
# payload["schema_version"] == "payable-receipt-ocr/1"
# payload["result"]["requires_confirmation"] == True
# payload["result"]["authorizes_persistence"] == False

To include diagnostics (only when diagnostics=True was passed to recognize()):

result = recognize("receipt.png", currency="INR", diagnostics=True)
payload = result.to_dict(include_diagnostics=True)

Error handling

All controlled errors inherit from ReceiptOcrError. Catch the base class to handle all of them, or catch specific subclasses:

from payable_receipt_ocr import recognize
from payable_receipt_ocr.errors import (
    ReceiptOcrError,
    InputFileError,
    UnsupportedImageError,
    ConfigurationError,
    RuntimeBaselineError,
    OcrEngineError,
)

try:
    result = recognize("receipt.png", currency="INR")
except InputFileError as e:
    print(f"File not found or unreadable: {e}")
except UnsupportedImageError as e:
    print(f"Image format or size not supported: {e}")
except RuntimeBaselineError as e:
    print(f"Tesseract runtime or model setup is invalid: {e}")
except OcrEngineError as e:
    print(f"All OCR passes failed or the deadline was reached: {e}")
except ReceiptOcrError as e:
    print(f"Recognition error [{e.code}]: {e}")

See Errors and CLI exits for a full table of error codes.

Full example

from decimal import Decimal
from payable_receipt_ocr import recognize
from payable_receipt_ocr.errors import ReceiptOcrError

def suggest_total(image_path: str) -> Decimal | None:
    """Return a suggested total, requiring a human to confirm before saving."""
    try:
        result = recognize(
            image_path,
            currency="INR",
            deadline_seconds=5.0,
            pass_timeout_seconds=2.0,
        )
    except ReceiptOcrError as e:
        print(f"OCR failed [{e.code}]: {e}")
        return None

    # Log any warnings
    for warning in result.warnings:
        print(f"  warning [{warning.code}]: {warning.message}")

    if result.evidence_grade == "none":
        print("No total found. Manual entry required.")
        return None

    # Always show the suggestion to a user — never auto-save
    grade_label = "strong" if result.evidence_grade == "strong" else "low confidence"
    print(f"Suggested total: ₹{result.total} ({grade_label})")
    print("Please confirm before saving.")

    # result.requires_confirmation is True
    # result.authorizes_persistence is False
    return result.total  # caller must confirm before persisting

On this page