Leonard Surac

Back to the start page

In production · since 2026

Kassensturz

My wife and I settled our shared expenses on paper every month. I turned that into an app that now runs our whole household settlement and my finances.

ASP.NET Core 10 · EF Core · Angular 22 · PostgreSQL · ML.NET · QuestPDF · Playwright

Problem

My wife and I settled shared expenses on a paper sheet every month. Whatever was left we pushed into the next month with an arithmetic trick, and when a receipt turned up later, nobody knew which balance was right. My own finances were scattered across several banking apps.

My role

I designed and built the solution, and I run it in my homelab.

Case study

My solution

I built the settlement the way bookkeeping works: a closed month is frozen, and the open amount between us is the sum of immutable entries. Payments and later corrections are entries of their own with a reason, never changes to the past.

  • Settlement periods with closing, fixed-cost templates and PDF and CSV export
  • Account access via PSD2, strictly read-only, receipts from my Paperless
  • I classify bookings with rules, a suggestion inbox and a locally trained model, without an external AI
  • I enter cash on the go as a PWA; plus year comparison, forecast, budgets and subscription detection

Implementation

Classification always starts with rules I can read myself. A rule is a pure function without a database, and only when none matches does the model suggest something:

Kassensturz.Api/Domains/Classification/Application/RuleEngine.cs
public static ClassificationRule? FindMatch(IEnumerable<ClassificationRule> rules, BankBooking booking) =>
    rules
        .Where(rule => rule.IsEnabled)
        .OrderBy(rule => rule.Priority)
        .ThenBy(rule => rule.Id)
        .FirstOrDefault(rule => Matches(rule, booking));

It is built with ASP.NET Core, EF Core and PostgreSQL, the interface with Angular. Every push runs through tests, an API contract check and end-to-end tests before GitHub Actions rolls out over our private network; afterwards an automated acceptance checks the running app.

Decisions

The balance is a ledger
I do not store the open amount; I compute it from immutable entries. More rows and a bit more logic, but every balance can be explained, even months later.
Our data stays apart, even when I make a mistake
Private data carries an owner without a database default. If I forget it in code, there is an error instead of a booking with the wrong person. The price is passing the owner everywhere.
AI without running costs
I rely on rules first, then on a locally trained model. Suggestions apply automatically only with a proven hit rate and never for bookings that touch our shared settlement. Some of it stays manual.

Outcome

My wife and I have used Kassensturz every day since July 2026. Settled months stay unchanged, open amounts can be explained at any time, and my accounts flow in automatically, read-only. Behind it are 12 domains, around 850 backend tests and 23 end-to-end tests.