acilox / solutions / invoice-intelligence-hub
🧾 AI Copilots & Automations

Invoice Intelligence Hub

Self-hosted EMEA accounts-payable automation that replaces $100K/yr SaaS. Ingest → extract → rules → AI confidence → human review → 27-country VAT → BI, in one auditable transaction.

0
EMEA countries (VAT engine)
1,000+
Invoices / day at scale
0.00
Auto-approve threshold
1 commit
Pipeline transactionality

Why this exists

A mid-sized EMEA business receives 1,000+ supplier invoices per day across PDF, scanned image, UBL XML (PEPPOL), Factur-X and email attachments. OCR vendors get the easy fields right and the long-tail fields wrong. SaaS AP platforms like Coupa or Bill.com cost north of €100K/yr per business unit and are hard to extend with country-specific tax rules. The result is finance teams either eating the SaaS cost or stitching together brittle scripts with no audit trail. This is the platform underneath.

How it's wired together

sources

≥ 0.92,
no blockers

below threshold

PDF / scan

UBL XML / Factur-X

IMAP / PEPPOL

ingest watcher
+ sha256 dedup

extract
PDF · XML · evidence

rules engine
30+ YAML checks

confidence scorer
Llama 3.1 + heuristic

auto-approve

HITL review queue
atomic claim

27-country VAT engine

transactional outbox

dispatcher
FOR UPDATE SKIP LOCKED

BI publisher
upsert / idempotent

audit chain
hash-linked, FOR UPDATE tip

Snowflake / Postgres mart

Invoice Intelligence Hub architecture overview

How it works, end-to-end

  1. Multi-source intake with content-hash deduplication

    Filesystem watcher, IMAP poller, and PEPPOL endpoints converge into one ingest queue. Each document is hashed (SHA-256) before persistence — duplicates are absorbed at the DB unique constraint, not the application layer.

  2. Format-aware extraction

    UBL/Factur-X structured invoices go through a dedicated XML extractor; PDFs and scans go through a pluggable extractor with bounding-box capture for evidence. Every extracted field carries a provenance pointer.

  3. Declarative rules + dual-backend confidence scoring

    30+ YAML-defined rules (VAT format, country support, intracommunity B2B, amount reconciliation) run as a single pass. A Llama-3.1 confidence scorer runs in parallel with a heuristic fallback — if the LLM 5xxes or times out, the page does not stall.

  4. Human-in-the-loop review queue

    Anything below the auto-approve threshold with no blocking violations routes to a review queue. Reviewers atomically claim tasks via UPDATE…WHERE state='open' RETURNING — no double-claim is possible even under concurrent operators.

  5. 27-country VAT engine + idempotent BI publish

    A country-aware tax engine computes treatment (standard, reduced, intracommunity zero, reverse-charge) and writes both invoice and per-line tax rows. The BI mart upserts on conflict (ON CONFLICT DO UPDATE) and is re-runnable.

The choices that matter

Decision

Transactional outbox for race-free pipeline enqueue

Job enqueue and the originating DB write live in the same transaction via a pipeline_event table; a separate dispatcher process drains it with SELECT FOR UPDATE SKIP LOCKED. Eliminates the classic "enqueued before commit, job runs against missing row" race.

Adds a dispatcher process to operate and a few hundred ms of dispatch latency. Worth it for at-least-once correctness across crashes.

Decision

Single-row chain tip locks the audit chain under concurrent writers

The audit log is hash-chained for tamper-evidence. All writers SELECT … FOR UPDATE on a single-row audit_chain_tip table before appending, serialising writes without a global lock or retry loop. Verified under 16 concurrent threads.

Writes to the audit chain are serialised. Acceptable because audit writes are infrequent relative to extraction throughput.

Decision

Atomic HITL claim via UPDATE … RETURNING

Reviewer claim is one SQL statement that flips state from 'open' to 'claimed' and returns the row. If two reviewers race, one gets the row and the other gets ClaimConflict → HTTP 409. No SELECT-then-UPDATE window, no advisory locks.

Decision

LLM as one swappable component, not the product

The scorer interface returns a structured ScoreResult; the Llama backend is one implementation, the heuristic backend is another, both register in the same scorer registry. If the LLM provider becomes unreliable or expensive, the platform doesn't change.

Built on

Python 3.11FastAPISQLAlchemy 2.0AlembicPostgresRedisRQLlama 3.1Pydantic v2structlogJinja2Docker