About the project
Tally
The problem
Month-end close is still manual. An accountant opens hundreds of invoice photos — phone pictures, crumpled receipts, skewed scans — and matches them line by line against a bank statement. It takes two to four hours per batch, every month. A single mistyped digit becomes a tax penalty or a duplicate payment.
Cloud AI is the obvious fix and it's off the table. Supplier accounts, tax IDs and bank statements can't be sent to a third-party API without breaking banking secrecy, SOC2, GDPR and local data-protection law. So the work stays manual.
What Tally does
Tally is an autonomous financial operations agent that runs entirely on-device. You point it at a folder of invoices and a bank statement, and it returns:
- `libro_compras.csv` — an ERP-ready purchase ledger with each invoice marked as reconciled, pending, or flagged.
- `discrepancias.md` — the items that don't match, in plain language.
- `run.jsonl` — a full audit trail of every extraction attempt, retry and validation failure.
No API keys, no network calls, no token cost.
How it works
The core design decision: we assumed the vision model would be wrong, and built the system around that.
1. Perception — QVAC @qvac/sdk runs a 500MB vision-language model locally. Its only job is to look at an invoice image and report what it sees. It does no arithmetic and makes no decisions. Output is constrained to a Zod schema, with automatic retries that feed the validation error back into the prompt.
2. Deterministic validation — TypeScript Everything that has to be correct is computed, not inferred:
- Subtotal + tax = total, recomputed with rounding tolerance
- Tax rate verified against the three legal Colombian rates (0%, 5%, 19%) — not hardcoded to one
- Tax ID checksum validated with DIAN's Modulo 11 algorithm
- Date range and format normalization
- Algebraic recovery: when one value is unreadable, it's reconstructed from the others
3. Reconciliation Deterministic matching against the bank statement by exact amount within a ±3 day window, with fuzzy supplier-name resolution as a tiebreaker for ambiguous candidates.
4. Human triage Anything that fails validation after retries is flagged, never silently written. The operator reviews a categorized alert instead of re-typing data.
What we learned
The most useful result of the weekend was a negative one, and it shaped the architecture:
A 500MB model produces structure reliably — every invoice came back as valid, schema-conformant JSON. What it does not do reliably is read individual digits off a degraded photo. It will return a tax ID with one number wrong, and it will look completely plausible.
That's the failure mode that matters in accounting, and it's invisible without a deterministic layer to catch it. So the metric we optimized isn't how often the model is right — it's whether a wrong value can reach the ledger. It can't.
The perception layer will improve with every model generation. The architecture that makes it trustworthy is the part that has to be built.
Stack
@qvac/sdk— local inference, all on-device- TypeScript / Node 22
- Zod for schema enforcement
- Deterministic rule engine for fiscal validation (Colombia validated end-to-end; the same engine extends to other jurisdictions)
- 60 automated tests, deterministic across consecutive runs
Current limitations
- Digit-level OCR accuracy on heavily degraded images is the main bottleneck. Identified improvements: input resolution tuning, verbatim string extraction with normalization in code rather than asking the model for typed numbers, and a document-optimized vision model.
- Colombian tax rules are validated; other jurisdictions are implemented but untested against real documents.
- Multi-page invoices are processed page by page.
Team
- André Sebastian Landinez Forero
- Sebastian David Mestre Rodriguez
Hacki