I paid for a popular bookkeeping tool and it worked well. But the UI kept getting slower, and I wasn't using its reporting, so I had no real view of my numbers.
So I did what I usually do: I scoped a solution, built the smallest thing that worked and tested it.
Result: about 2K lines of Python, 45 tests, my own invoice scanner with a human in the loop, and a couple of make commands to close the month and regenerate an Excel report (synced to Drive) for my accountant.
What closing a month of bookkeeping involves #
I'm an autónomo (freelancer) in Spain. Each month I issue invoices to clients, collect supplier invoices in a mix of currencies, and hand the close to my asesor (accountant).
On paper the paid tool covered all of that, and maybe a higher tier had the report I wanted. But I live in the terminal, and a command-line workflow beats a nicer dashboard. It's the same lesson I teach clients: build around how people work, not around how a vendor decided things should work.
Before writing any code, I mapped the workflow and split the steps into rules and interpretation.
Only one step needed AI #
It's tempting to let AI loose on everything, but the map showed only one step where an LLM added value: extracting fields from supplier PDFs with varying layouts. I use Claude by default, but the model call is one function, so swapping in a local model is a small change.
Everything else is arithmetic (numbering, VAT, IRPF, totals, FX), which calls for plain code with good test coverage.
For example, the code converts currencies at the ECB rate via frankfurter.dev, and the original amount and rate are added in the description: (USD 20.00 @ 0.88511 ECB 2026-10-01).
This also shows that much of integrating AI is about the control layer. The model proposes fields. Python computes, converts, assigns ids and validates. I fix and approve. Tests and logs make it verifiable. See also: the control layer is the product.
The AI proposes, I append #
Nothing touches the ledger until I press a (append):
Each proposed row gets the next EXP-YYYY-NNN id and the extracted fields, with VAT and gross computed by code. Append is blocked while validation errors exist:
choice = input(" [a]ppend / [e]dit / [s]kip / [q]uit: ").strip().lower()
if choice == "a":
if _errors(exp):
print(" fix the errors first (edit), or skip.")
continue
return exp
Extraction misses things, like a receipt without a printed tax ID or a description too vague for my asesor. The edit step catches those:
On approval, the PDF is renamed to EXP-YYYY-NNN_Supplier.pdf. That rename is the whole state machine, no database needed. The rename happens before the row is written. If it crashes in between, the next run proposes that PDF again under the same id, so nothing is lost or recorded twice.
I also fenced off the AI that helped me write the code: reads and edits to my issued invoices and invoice ledger are denied, and every shell command requires my approval.
Boring by design #
I keep most of the system immutable (a habit Rust is reinforcing). The CSV files are the source of truth for invoices and expenses. Issued invoice PDFs are immutable (overwriting one needs --force). Excel is regenerated, never edited.
Every total in the workbook is a formula pointing back to its rows, not a pasted number, so my asesor can click any figure and see where it came from without asking me.
The month close is easy now: drop the PDFs in a folder, make scan, make report (which validates first).
When my asesor confirmed mid-build that the VAT on my expenses wasn't deductible in my case, it had to move into cost. The tests pinned the totals, so it was one commit instead of a week of doubt.
There is one tradeoff: I now need to maintain this system. There's no vendor support, the FX lookup needs a network connection, and tax rules might change. But it's a good trade for a one-person business with tech skills. Full control, one less subscription, and a faster, higher-quality process are worth it.
The transferable part is the order of operations:
- Map the workflow.
- Give the model the one step that needs judgment.
- Code the rules.
- Put a human at the critical decision points.
- Make the results traceable.
If a process in your business is slow, error-prone or lives in one person's head, that's what my Scope First Audit maps out.
P.S. The a/e/s/q prompt treats every field the same, so I extracted the scanner into invoice-scanner. It asks only about low-confidence fields, shows the source text and logs corrections. More on that in an upcoming post.
AI is an accelerator, not a compass. I coach developers to ship faster and understand what they ship. See the path →