Grana

Context and problem
I tracked my finances in a spreadsheet and wanted something more organized: how much came in and went out each month, with a balance, splitting personal (PF) and company (PJ) expenses. Grana imports the Nubank statement and builds that on its own. For now it's for personal use; a public version comes later. Next up are recurring bills with due-date alerts and an AI chat to log expenses and ask questions about the records.
Technical decisions
Why Neon instead of Supabase
Supabase was the first choice, but the free plan had no project slot left and pauses the database after 7 days without use. Neon scales to zero when nobody's using it and wakes up on the next request.
How imports never duplicate anything
There are two layers: a hash of the whole file blocks the same statement being uploaded again, and the ID the bank gives each transaction prevents the same row twice. The original file is never stored, only its hash.
Why money is stored as integer cents
Amounts are signed integers in cents. That way there's no floating-point rounding error in a whole month's sum.
Challenges
- 01Nubank's statement comes with the wrong encoding in its header. The file reader was written by hand to deal with it.
- 02A transfer from the company account to the personal one is neither an expense nor income. Grana recognizes these transfers by the tax ID and the account in the description, so the same money isn't counted twice.
Outcome
- 2
- layers against duplicate imports
- 17
- test files with an in-memory Postgres
- 3 days
- between automatic database backups
Next case study
iagogois.com.br →