All work

Household finance

A finance app for a household of two: each person signs in as themselves, their own accounts stay private until shared, and the groups never add up by accident.

Role
Sole developer
Year
2026
Outcome
The household sees the joint account, each person's own spending and the savings side by side, with income labelled and transfers between its own accounts kept out of spending.
Built with
Node 22, TypeScript, Vue 3, Vite, SQLite (node:sqlite), pdf.js, PSD2 open banking, Vitest, pnpm workspace
Household finance, one person's home screen: the balance, money in and out, the latest movements and the accounts, all made up.
Screenshot on made-up data.

Why I built it

The money was spread over several banks and more than one person. Banking apps show one bank at a time, and most aggregators add everything into a single number, which hides who spent what and counts a transfer between your own accounts as spending.

What it does

  1. Groups that never add up by accident

    Every account sits in a group: the household, each person, savings, and a read-only business group. Totals add up inside one group only. The groups sit along the top, each with a count of movements still to sort (a count, never an amount), and Hide amounts blanks every figure on the screen.

    Illustration: five groups side by side (household, each person, savings and a locked business group), with crossed-out plus signs between them.
  2. Home

    Balance today; money in, money out, and money moved between our own accounts, which counts as neither; where the money went, month by month; and a Needs you card with movements to sort, transfers to confirm, bank consents about to end, missing e-Fatura months, failed backups and shops to confirm.

    The household's Home: balance today, money in and out, money moved between the two people's accounts, the latest movements, what needs them, and where the money went. Made-up people and figures.
  3. Private until shared

    Each person owns their accounts. One switch per account, "Shared with the household", starts off, and only the owner can flip it. Until then the other person sees nothing of it: no label, no amount, not even a hint that there is more. A transfer to a private account shows only as a transfer to the other person. The joint account is visible to both. Once an account is shared, the other person can see and sort it, while connecting, reading and the switch stay with the owner. The server checks all of this on every request; the page never decides what someone sees.

    One person's accounts as cards, one open on Who sees it: shared with the household, with the switch to share it. Made-up banks.
  4. Sorting movements

    Classification runs in a fixed order: transfers between our own accounts first (never income or spending), then money to or from the company, then bank fees, then category rules on shop, text or account. Each match is certain, proposed as a yes-or-no question, or made by hand, and an answer someone undoes is never applied again. Shop names are cleaned of card prefixes, amounts and reference numbers, so one rule catches every visit. A new rule shows what it would do before it is saved: "it would file 14 movements".

    A new rule for one shop, with a preview of the movements it would file before it is saved, beside the list of rules. Made-up shops.
  5. Who is this shop?

    Unknown shops are looked up in a round trip that a person checks at every step. The app exports up to 30 shortened shop names, from card and direct-debit payments only; amounts, dates, accounts and people never leave. A Claude skill looks each one up on the web and writes a results file, which the app reads back with strict checks. Each shop then becomes a card with its logo, a sure or not-sure mark, a link to Maps and its movements, ready to accept, edit or refuse.

    Shops to confirm: four shops worked out by the research, each with its site, address, a sure or not-sure mark, and Accept, Edit or Refuse. Made-up shops.
  6. Banks and statements

    Each person connects their own banks through PSD2 open banking, read-only, with their own consent and key. The joint account is linked once, so it is never counted twice. Consent lasts up to 180 days, and the app warns two weeks before it ends. Movements are read once a day, or on demand with the person present. Older history comes from statements: CSV exports whose running balance must chain row by row, and PDFs read in a sandboxed worker, refused whole if the balance chain breaks or the IBAN is not this account's.

    One person's movements, with a transfer to savings open: the app saw the same amount arrive in another account and asks whether it is one of theirs. Made-up data.
  7. Savings and net worth

    Savings certificates are valued lot by lot every day with Aforro Tracker's engine and the public rates. Deposits, funds and shares are kept by hand with a rate and a maturity. Net worth shows one figure per group, each with its own chart. The Show total switch is off by default and turns itself off again on every visit.

    Net worth: one chart per group, each on its own scale, with Show total off. Made-up figures.
  8. e-Fatura, recurring and reports

    Each person's e-Fatura months are marked fetched, out of date or missing, with a countdown to the yearly deadline, and each person decides whether to share theirs. Payments made three times at a steady gap become recurring, with what is due in the next four weeks and what went up. Reports keep spending, real income and money moved in from our own accounts apart.

    Recurring payments: fixed costs per month, what is due soon, and what went up. Made-up data.

How it works

Built thin
One Node process with no web framework, one SQLite file through the SQLite that comes with Node, amounts in whole cents, and a release that is a single bundled file. It was built to run in very little memory.
Sign-in and hardening
Sign-in is a single sign-on page with a passkey for each person. The app maps each login to a person and refuses any it does not know. A strict content security policy, rate limits, size caps per route, and a read-only container.
Secrets and consent
Bank keys are files outside the database and are left out of backups, so a restored copy can never reach a bank. It never asks for or stores a bank or tax portal password. Everything that calls out (banks, logos, savings rates, Atta Star) sits behind one "online" switch.
Atta Star
It reads two figures from Atta Star through one read-only bridge: business invoices paid with a personal card, and the running balance with the company. The answer is held in memory for five minutes and never stored; if Atta Star does not answer, the tile says so rather than show an old figure. A matching card payment can be marked "business, paid by you", which takes it out of household spending.
Aforro Tracker
Its backup file, plain or encrypted, is uploaded and valued with its engine. The engine is copied in whole and checked by hash, so an edit by hand fails the build.

How it is built and tested

Tests
About 3,000 tests, all on made-up figures. Route tests start a real HTTP server against an in-memory database, with stand-ins for the bank, the PDF statements and Atta Star. Every route has a test that nothing of another person's private account comes back, and one test checks that no query adds money across two groups.
Releases
A green push builds a release archive and its checksum. The job that runs the repository's code cannot publish, and the job that publishes runs none of it. Nightly backups, a mirror, and a restore drill.

How Atta helped

  • It was seeded from Atta Star with a one-time copy of its money code, plus a shared wiki page on what that project had learned about banks, statements and invoices.
  • Two critics review every change that touches money or data, because a single critic was shown to find a defect in one run and miss it in the next.
  • The interface design was picked by the council: reviewers on more than one model scoring candidate designs out of 10.
  • Real statements stay on my computer. Tests run on made-up figures only, and the pre-commit scan refuses tax numbers and IBANs outside test files.