vendor-invoice-qa-assistant

Vendor Invoice Digitization & QA Assistant

A local, network-free portfolio MVP that turns synthetic vendor-invoice PDFs into validated structured records, routes uncertain or invalid results to a human reviewer, keeps append-only evidence, and exports approved data.

Synthetic data only. Not production software. No claim is made about real-world accuracy, regulatory compliance, customer usage or time savings.

Live demo: vendor-invoice-app-assistant on Streamlit Community Cloud. No PDF needed: on the Upload page, pick a sample invoice and click Run sample. The hosted demo shares one database across visitors and starts empty again when the app restarts.

1. Overview

Streamlit UI over plain-Python services, a domain layer with exact Decimal/date rules, and a local SQLite database. Extraction is deterministic (no model, no network). An AI-provider protocol, a test-only fake and a Pydantic validation boundary demonstrate structured-output handling; no real provider is connected.

2. Business problem

Accounts-payable and operations teams re-key semi-structured vendor invoices. Manual entry is slow and error-prone (identity, date, currency, tax, arithmetic, duplicates, PO references), while fully automatic extraction is risky on unfamiliar layouts, conflicting values or low confidence. This demo shows a controlled middle path: automate the clean cases, make every exception visible, and require a deliberate human approval.

3. Intended user

A non-technical operations analyst who uploads invoices, reviews exceptions, compares raw and normalized values, corrects or confirms fields, approves valid records, rejects invalid ones, and exports approved data.

4. Workflow

Synthetic PDF -> file-security checks -> deterministic text extraction -> raw field evidence
  -> exact normalization -> validation + duplicate detection -> exception-based review
  -> approve / reject -> approved CSV/JSON export -> append-only audit history + metrics

Demo screenshots

Clean invoice upload Explicit extraction retry
Clean invoice routed to auto-ready Unreadable PDF offers an explicit retry
Human review queue Operational and evaluation metrics
Review queue with masked tax IDs Operational and synthetic evaluation metrics

5. Features

6. Architecture

app.py                     Streamlit entry point (sidebar navigation)
src/invoice_qa/
  domain/                  models, normalization, validation, transitions (no I/O)
  extraction/              Extractor protocol, DeterministicExtractor, AI boundary
  infrastructure/          SQLite repository, file-security checks
  services/                intake, review, metrics, export
  ui/                      page renderers and display helpers
  synthetic.py             deterministic PDF builder shared by tests and the generator
sample_data/, scripts/     manifest, generator, evaluator

A short-lived SQLite connection is opened per repository operation. “Today” is injected through a clock dependency; domain and service code never read the system clock.

7. Validation and confidence rules

8. Human-in-the-loop controls

9. Security and privacy

See SECURITY.md. In short: PDF only, at most 5 MiB (enforced by the Streamlit widget, server config and the application), non-encrypted, structurally readable, at least one page; PDF bytes and full extracted text are never stored; vendor tax IDs are masked outside an explicit session-only edit action; UI errors are generic.

10. Synthetic evaluation methodology and measured results

sample_data/manifest.json defines 13 fixtures (clean USD, clean VND, no PO, missing invoice number, unsupported currency, malformed amount, arithmetic mismatch, future date, low-confidence alias, conflicting total, content duplicate, logical duplicate, excess fractional digits) with expected normalized values, confidence, exact issue-code sets and initial status. scripts/evaluate.py processes them in manifest order against a fresh temporary database with a fixed reference date.

Measured on 2026-10-10 (synthetic, local; code baseline in REVIEW_HANDOFF.md):

Metric Result
Scenarios matching the manifest 13 / 13
Field-level accuracy (fields with defined ground truth) 111 / 111
Initial review rate (review_required at creation / new candidates) 0.75 (9 of 12)
False auto-ready 0
False review 0

These numbers describe a small, hand-built, synthetic set whose expectations were written alongside the rules; they are a regression check, not evidence of real-world accuracy.

11. Local setup

Requires Python 3.11 or newer.

python -m venv .venv
.venv/Scripts/python -m pip install -e ".[dev]"     # Windows; use .venv/bin/python elsewhere
.venv/Scripts/python -m pytest -q
.venv/Scripts/ruff check .
.venv/Scripts/python sample_data/generate_samples.py
.venv/Scripts/python scripts/evaluate.py --manifest sample_data/manifest.json --output .evaluation/report.json
.venv/Scripts/streamlit run app.py

Configuration is read from the process environment only (.env.example documents INVOICE_QA_DB_PATH; no dotenv loader). Default database: .data/invoice_qa.sqlite3 (gitignored).

12. Demo walkthrough

  1. Run the generator (13 evaluation PDFs plus the no-text retry sample), then start the app.
  2. Upload sample_data/generated/fx01-clean-usd.pdf; it routes to auto_ready.
  3. Upload fx06-malformed-amount.pdf; open the Review Queue, correct Subtotal to 1,000.00 and watch the status change.
  4. Upload fx09-low-confidence-alias.pdf; confirm vendor_name (the only confirmable field).
  5. Upload fx12-logical-duplicate.pdf; resolve it by rejecting or confirming distinctness.
  6. Approve a candidate, then download the approved CSV/JSON from the Review Queue.
  7. Open Metrics: Initial review rate stays where it was after you approve or reject, while Current review backlog share falls; run the evaluator command shown there to populate evaluation-only numbers.
  8. Extraction retry (see screenshots/03-extraction-error-with-retry.png and screenshots/04-after-manual-retry.png): upload sample_data/generated/retry-demo-no-text.pdf (written by the generator, a valid PDF with no text layer). The page shows an extraction error and a Retry extraction button. Nothing retries until you click it; each click adds a numbered attempt and keeps the failed ones (it fails again here by design, because the file never has text). Metrics then show the failed attempts and retry attempts.

13. Known limitations

14. Production hardening (not done)

Authentication and RBAC, managed database and retention, monitoring and alerting, a reopen flow, automatic retry policy, OCR, ERP integration, real-provider integration with its own threat model, and a legal/privacy review.

15. Dependency licenses

Direct dependencies: Streamlit, pypdf, ReportLab, Pydantic, pandas (runtime); pytest, Ruff (development). pypdf and ReportLab are permissive open-source libraries. This is not legal advice; confirm licenses before redistribution.

16. Project license status

Licensed under the MIT License.