All projects

Human-approved invoice review and ERPNext drafts

Invoice Review

An AI invoice-to-ERP workflow that extracts PDF data, checks it against deterministic rules, and gives a reviewer the evidence and authority to approve a controlled ERPNext draft.

ERPNext workflow benchmark complete

The approval-gated workflow was benchmarked against direct entry using synthetic invoices and provisioned master data in a local ERPNext sandbox.

Invoice Review workspace showing a synthetic invoice beside extracted fields, validation findings, and an approval blocker.
Review workspace with synthetic fixture data
68%
Lower median elapsed invoice-to-ERP draft time (153s to 49s)
10/10
Expected assisted-workflow results, versus 9/10 through direct entry
98.75%
Exact field match on a sealed 10-invoice licensed synthetic holdout (79/80)

System flow

From intake to a governed outcome.

  1. 01Upload invoice
  2. 02OCR and field proposal
  3. 03Deterministic validation
  4. 04Human review and correction
  5. 05Approval or rejection
  6. 06Idempotent export

The problem

Invoice review becomes fragile when the PDF, extracted values, validation reasons, and approval decision are split across tools. A reviewer needs to see what the system read, where a value came from, why a record is blocked, and what action is still allowed.

I constrained the product to one complete invoice journey. That made it possible to design the failure states, reviewer authority, and export boundary in depth instead of presenting a broad but shallow “document AI” demo.

What I built

Built an AI invoice-to-ERP workflow that turns PDF data into a review-ready proposal, checks it with deterministic rules, and requires human approval before creating an ERPNext Purchase Invoice draft.

The product provides six operational surfaces: Inbox, Invoices, Review, Exports, Quality, and Operations. The main workspace places the source PDF next to proposed fields, validation findings, correction history, and the reviewer decision.

  • Uploaders can submit invoices and respond to correction requests.
  • Finance reviewers can compare the PDF with extracted values, correct fields, reject a record, or approve it when blockers are cleared.
  • Administrators can inspect evaluation results, failed jobs, retries, integrations, and audit events.

Every correction retains the original proposal, before-and-after values, actor, reason, and timestamp. Export is available only after approval and uses an idempotency key to prevent duplicate delivery.

Architecture and decision boundary

The React and TypeScript frontend uses TanStack Query and PDF.js. A FastAPI modular monolith owns authentication, workspace rules, state transitions, validation, audit events, and export eligibility. SQLite and private local file storage keep the demo reproducible; a background worker uses atomic job claims and lease fencing for asynchronous processing.

The extraction layer is provider-based. Mock providers make the entire workflow available without paid credentials. A tested provider configuration sends the document through Mistral OCR and OpenAI structured extraction.

The authority split is explicit:

  1. AI proposes invoice fields and source information.
  2. Application code checks required fields, totals, line items, duplicates, roles, and allowed state transitions.
  3. A human reviewer approves, rejects, or requests correction.

Model confidence alone can never approve an invoice, and the API enforces the same blocker rules as the interface.

Verified outcomes

Reduced median invoice-to-ERP draft time by 68% (153s to 49s) across six paired synthetic invoices; achieved the expected outcome in 10/10 test cases, compared with 9/10 through manual ERPNext entry.

The timing benchmark covered six paired, draft-eligible synthetic invoices completed by one operator in a local ERPNext sandbox with provisioned company, supplier, item, account, tax, currency, and exchange-rate data. The measure is elapsed time from opening the source to a verified draft; active work and waiting time were not recorded separately. Four blocker observations from an earlier run remain in the combined 10-case outcome record but were not included in the paired timing median.

The provider-backed extraction workflow also completed all ten invoices in a sealed, licensed-synthetic holdout. It matched 79 of 80 labeled fields (98.75%) and matched every expected validation code and approval blocker. The remaining miss was one unsupported due date, so this is not presented as perfect extraction or production accuracy.

The public repository includes the holdout aggregate, but not the source PDFs, labels, OCR, or per-document predictions. The 98.75% result therefore cannot be independently rerun from the repository alone.

A separate clean provider diagnostic processed 20 deterministic synthetic invoices and matched all 160 labeled fields. That set had already been used during development, so it remains supporting diagnostic evidence rather than the headline quality result.

The recorded clean release reports at least 91.21% backend line coverage.

Production and engineering evidence

Integrated React, FastAPI, Mistral OCR, OpenAI, and ERPNext; validated an end-to-end Azure deployment using Container Apps, private Blob Storage, Service Bus, and PostgreSQL, processing a synthetic invoice from upload through the review queue.

The workflow delivers approved Purchase Invoice drafts to a real local ERPNext sandbox. The integration covers field mapping, permission checks, idempotency, delivery receipts, reconciliation, and deliberate retries so an uncertain delivery does not silently create a duplicate draft.

This is controlled integration evidence, not a production deployment claim.

I also validated a temporary Azure deployment. Container Apps ran the API, worker, and migration job against Azure Database for PostgreSQL. A synthetic PDF passed ClamAV scanning, private Blob Storage, Service Bus delivery, and mock-provider processing into the needs_review state. Managed Identity and Key Vault supplied scoped access and secrets. The resource group was deleted after the run. This validates the deployment and integration path, not production hosting or Azure-hosted real-provider extraction accuracy.

A newer production-validation harness covers local contract and rehearsal checks for concurrency, queue duplicate and poison-message handling, recovery, metrics, and trace correlation. The planned Azure-live load, worker-recovery, alert-delivery, and autoscaling scenarios have not passed. I do not claim production-grade reliability or measured cloud capacity from that local work.

How this was verified

The holdout uses licensed synthetic data. The release artifact records an older clean evidence commit than the current reviewed GitHub source; the frontmatter keeps those two identities separate.

Failure handling

One diagnostic initially stopped on a localized amount such as 1.250,00. The failed artifact was retained, deterministic number normalization was added, and a regression test covered the case before the clean diagnostic was rerun.

The same approach appears in the runtime design: a failed external delivery does not rewrite an approved decision. The system retains the approved state and the delivery attempt so an operator can investigate or retry it deliberately.

Current boundary

This is a one-operator benchmark for one invoice schema using synthetic invoices and a local ERPNext sandbox. Its mapping requires provisioned master data and cannot be assumed to work unchanged in another company’s ERPNext. It establishes the measured elapsed-time result for that test boundary, not production-user adoption, customer impact, security certification, or multi-user time savings.

Moving beyond that boundary requires finance-user usability testing, legally usable real invoices, managed identity and tenancy, durable object storage, a managed database and backup plan, and live malware scanning.