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-flash404s on regional Vertex endpoints — same error shape as a nonexistent model. Onlyglobalworks.
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
- artifact-registry
- cloud-build
- cloud-run
- cloud-scheduler
- cloud-sql
- cloud-storage
- docker
- eventarc
- fastapi
- gemini
- google-adk
- google-cloud
- html
- identity-aware-proxy
- jinja
- pillow
- postgresql
- pydantic
- pytest
- python
- ruff
- secret-manager
- sqlalchemy
- vertex-ai
Log in or sign up for Devpost to join the conversation.