Inspiration

Every company that buys goods gets three documents for the same order: a purchase order from Purchase, a delivery challan from Stores, an invoice from Accounts. Nobody cross-checks them line by line past a couple hundred orders a month. So overbilling doesn't get caught and argued about — it just leaks out quietly, every month, invisibly.

What it does

Ledger Hound reads your POs, challans and invoices and catches what you were overcharged: quantity billed above what was delivered, rates above the contracted price, charges on no PO at all, open-PO spending caps breached, and invoices whose line totals don't equal qty × rate. It then drafts an evidence-backed dispute email per vendor, every discrepancy pointing back to the exact source document.

How we built it

Four Google ADK agents. The Extractor reads a PO/challan/invoice image or PDF directly with Gemini 3.5 on Vertex AI — no separate OCR — and reports what it wasn't sure about. The Matcher and Auditor are deterministic, no LLM: that split is the whole design. The model reads the documents; plain code does the money math, so no rupee figure is ever hallucinated. The Disputer writes the email.

Running on Google Cloud in asia-south1: Cloud Run (2 services + 1 Job), Cloud SQL, Cloud Storage + Eventarc for uploads, and Cloud Scheduler firing the batch match every 10 minutes. FastAPI + Jinja2 on top.

Challenges we ran into

  • Real POs often have no quantity. Open/blanket POs — normal for anything bought repeatedly — set rates and a value cap and leave quantity open. That forced a real remodel: the unit of matching became a release, not an order.
  • GST turns "flag anything not on the PO" into a false-positive machine. Every Indian invoice carries tax lines on no PO. The Extractor now classifies each line and only goods lines get audited for authorization.
  • IAP's org lock cost us weeks. Under a Cloud Identity org, Cloud Run's managed OAuth client admits only in-org identities — outside accounts sign in fine, then get denied regardless of IAM. Fixed with a custom External OAuth client.
  • Eventarc's "at-least-once" is real. Three uploads produced twelve rows.
  • A model can be listed and still unreachable. gemini-3.5-flash 404s on regional Vertex endpoints — same error shape as a nonexistent model. Only global works.

Accomplishments that we're proud of

Every number is traceable to a document, because the arithmetic never touches an LLM. Two Cloud Run services share one image and one database — a public read-only one anyone can open, and a private IAP one where authenticated reviewers approve, with their verified identity stamped on the decision. And an append-only activity log covering every automated run and every manual override.

What we learned

Self-reported LLM confidence is not calibrated. A completely unreadable field once scored 0.78 — above our 0.75 threshold. What actually caught it was the model's per-field uncertainty list, not the number. A single confidence score is not a gate.

Auto mode needs a manual override valve, and the valve needs a log. An incremental match never rewrites a decided dispute; it flags it for a human instead. Silently correcting an approved figure is worse than a stale one someone can see.

What's next for Ledger Hound

Real authorization — the department switch is an MVP persona toggle today, not access control. Stress-testing the matcher's vendor+item+date fallback against genuinely ambiguous data, which is our biggest remaining correctness risk. ERP and email integration so an approved dispute actually sends. And recovery-share pricing: a percentage of what we get back, so the incentives line up.

Built With

Share this project:

Updates

Submission history