Skip to content

OCR Results Collector

The OCR Results Collector is the durable boundary between compute-heavy OCR workers and PostgreSQL. Workers need the receipt volume and RabbitMQ only. The collector is the only OCR component that receives DATABASE_URL.

Data flow

flowchart LR
    P[Backlog publisher] -->|receipts.v1.work| R[(RabbitMQ)]
    R --> W[OCR worker]
    W -->|run-completed then pass-completed| Q[(ocr.results.v1.work)]
    W -->|large cache artifact| O[(Artifact storage)]
    Q --> C[OCR Results Collector]
    C --> D[(PostgreSQL)]
    C -. traces and aggregate metrics .-> T[OpenTelemetry]

The work message is acknowledged only after every result publication is confirmed. The result message is acknowledged only after its PostgreSQL transaction commits. A worker crash before confirmed publication causes the source work to be redelivered. A collector crash before acknowledgement causes the result to be redelivered; the event ID and (run_uuid, pass_id) key make the replay idempotent.

Version 1 event contract

Both event types use a strict JSON envelope. Unknown envelope fields and unsupported versions are rejected.

Field Contract
schema_version Integer 1.
event_type ocr.run-completed.v1 or ocr.pass-completed.v1.
run_uuid Canonical UUID for one OCR run.
pass_id null for a run event; positive integer for a pass event.
source_sha256 Immutable receipt-evidence SHA-256 identity.
source_reference Relative POSIX receipt path.
worker Bounded service name, host, and process ID.
trace_context W3C traceparent and optional tracestate.
occurred_at Timezone-aware ISO 8601 timestamp.
attempt Collector delivery attempt, from 1 through 3.
payload Event-specific validated content.
artifacts URI, SHA-256, media type, byte size, and kind.

The run event contains the selected OCR text and parsed receipt fields used for querying and reconciliation. Pass events contain engine configuration, timing, quality measurements, selection state, and status. Alternate full text, rendered pages, bounding boxes, and other large data stay in the cache artifact referenced by the run event. OCR_ARTIFACT_URI_PREFIX controls the URI prefix; the Kubernetes deployment uses the durable receipt PVC namespace. An S3- or other object-store-backed deployment sets OCR_ARTIFACT_S3_BUCKET, OCR_ARTIFACT_S3_PREFIX, OCR_ARTIFACT_S3_ENDPOINT, and OCR_ARTIFACT_S3_REGION; the worker uploads and confirms the object before it publishes the run event. Copy k8s/ocr-artifact-storage.example.yaml, replace its placeholders, and apply the ConfigMap and Secret. The optional resources are loaded by the worker when present and require no manifest patch.

PostgreSQL records the envelope in budget.ocr_result_events, artifacts in budget.receipt_ocr_artifacts, runs in budget.receipt_ocr_runs, and passes in budget.receipt_ocr_passes. Receipt filenames, hashes, run UUIDs, and pass IDs are trace/log fields and database columns, never Prometheus labels.

Retry and dead-letter behavior

Queue Purpose
ocr.results.v1.work New and redelivered result events.
ocr.results.v1.retry A 60-second bounded retry delay for transient failures.
ocr.results.v1.dead Invalid events and events that fail three attempts.

Retry and dead queues are durable quorum queues with at-least-once dead lettering. A DLQ publication includes bounded error_type and error_message headers. If publishing a replacement cannot be confirmed, the collector does not acknowledge the original delivery.

Deployment and security

Apply the deploy/rabbitmq overlay after running ledger database setup so the KAN-89 migration is present. The brownrook-root GitOps application supplies the KEDA operator and CRDs. The overlay deploys one to two CPU-heavy receipt-worker replicas without PostgreSQL credentials and two lightweight ocr-results-collector replicas with the PostgreSQL secret. RabbitMQ credentials remain required by the publisher, worker, and collector.

The telemetry overlay gives both deployments an mTLS OTLP client. The collector continues durable writes when telemetry export is unavailable because exporters run asynchronously and telemetry initialization errors are contained.

Verification

After deployment:

kubectl -n home-budget rollout status deploy/receipt-worker
kubectl -n home-budget rollout status deploy/ocr-results-collector
kubectl -n home-budget logs deploy/ocr-results-collector --tail=100

Verify one correlated run:

SELECT e.message_id, e.event_type, e.run_uuid, e.pass_id,
       e.trace_context ->> 'traceparent' AS traceparent,
       e.persisted_at
  FROM budget.ocr_result_events e
 WHERE e.run_uuid = :'run_uuid'
 ORDER BY e.persisted_at, e.pass_id NULLS FIRST;

The receipt telemetry dashboard exposes all RabbitMQ queues through its queue selector. Collector metrics report event throughput, persistence latency, retries, failures, and dead-letter routing with bounded event/outcome labels.

Retention, recovery, and rollback

Retain result-event audit rows and artifact references for the same period as their receipt evidence. Deleting receipt evidence cascades its run, pass, artifact, and event rows. Object-storage lifecycle policy must delete the corresponding object no earlier than the database retention period; database deletion does not delete an external object.

For recovery, restore PostgreSQL first, restore artifact storage, then resume the collector and worker. Unacknowledged result events replay automatically. Replay a DLQ event only after correcting its diagnostic cause; the same message ID is safe to replay.

To roll back application code, pause the receipt-worker KEDA ScaledObject, scale the worker to zero, let the collector drain ocr.results.v1.work, then deploy the earlier application version. The schema migration is additive and can remain in place. Do not remove collector tables while a result queue contains messages. A forward rollback restores the new worker and collector against the retained queues and schema, then resumes the scaler.