Santiago Mansilla .
← All projects

Personal Finance AI

Active

A personal financial assistant that ingests AEB-43 (Norma 43) bank exports into a double-entry ledger and exposes a Claude-powered agent that answers natural-language questions about my finances and renders charts on demand.

NestJS TypeScript Prisma PostgreSQL Redis BullMQ React 18 Vite Tailwind Recharts Anthropic SDK Telegram Bot Hexagonal DDD

What it is

A self-hosted personal finance system. It ingests Spanish bank exports in AEB-43 (Norma 43) format, stores every movement in a strict double-entry ledger, and lets me ask questions in natural language to a Claude-powered agent that has tool access to my own data.

It is the closest thing I have built to “what an accountant who actually understands my data feels like”.

Why I built it

I wanted three things off-the-shelf tools couldn’t give me at the same time:

  • Accuracy. Most personal finance apps rely on category soup. I wanted real bookkeeping — double-entry, P&L, cash flow — so the same question always returns the same number.
  • Privacy. My data lives on my own server. No third party sees the movements.
  • AI-native. I wanted the answers in plain language, with charts when relevant, and decisions documented as ADRs because the architecture matters.

The interesting parts

The ledger is the only source of truth

ADR-002 declares the double-entry ledger (LedgerAccount, JournalEntry, JournalLine) as the single source of truth for any financial query. Bank Transaction rows feed the ledger at import time, and the chat agent never queries Transaction directly — only the ledger.

The payoff: balances and P&L are always consistent because they are derived from the same accounting primitives. Adding a new financial operation forces me to define the journal entries that represent it, which keeps the data model honest.

Hexagonal architecture, but only where it earns its weight

ADR-001 commits to selective hexagonal architecture: full domain/ + application/ + infrastructure/ only in modules with non-trivial business logic (ledger, import, budgets, cashflow, subscriptions). Simple CRUD modules (transactions, goals, reports) stay flat.

The trade-off — inconsistency between modules — is documented and accepted. Empty layers in CRUD modules are worse than a small style mismatch.

A Claude agent with curated tools

The agent (@anthropic-ai/sdk + tool use loop) gets a small, deliberate set of tools:

  • query_ledger — Claude writes SQL against the ledger; only SELECT, with a mandatory workspaceId filter
  • query_budgets, query_reports, get_cashflow_prediction, query_goals, simulate_payroll — structured queries over derived data, no raw SQL
  • generate_chart — returns a Recharts config that the frontend renders

The pattern that pays off: the LLM does not have a generic SQL tool over the whole DB. It has tools shaped around how a financial advisor would ask questions. The validator on query_ledger rejects anything outside the contract.

Async work goes through BullMQ

Imports, recurring-expense reconciliation, scheduled reports, and Telegram bot interactions run through BullMQ + Redis. The HTTP layer never blocks on a slow operation. The Telegram bot (telegraf) lets me ask questions from my phone without opening the web UI.

Status

Active development. The repo currently includes 25+ NestJS modules covering ledger, import, budgets, cashflow, goals, payroll, recurring expenses, subscriptions, alerts, insights, exports (PDF + Excel), and a Telegram bot. ADRs document the decisions that matter. Self-hosted on a Hetzner server in production.

The repo is private — the value of the project for me is in using it, not in distributing it.