Back to projects

Personal project

Money Tracker

Natural-language expense tracking with deterministic rules, bounded AI workflows, and a local dashboard.

How I made an AI assistant accountable to financial rules

Money Tracker is a personal, local-first finance assistant. I designed its product direction and architecture, then used coding agents to help implement it. The central design question was how to turn an uncertain interpretation into a checked, authorized financial operation.

  • Python
  • LangChain / LangGraph
  • llama.cpp
  • PostgreSQL
  • FastAPI / React
  • Langfuse
  • Restic

One financial domain, two interfaces

Telegram and the local dashboard converge on the same application services and authoritative financial data. The model interprets requests; it does not own the ledger. This diagram condenses the implemented components and their responsibilities.

External service

Telegram Bot API

Local workstation

Gateway · long polling
PostgreSQL · durable inbox / outbox
Worker · typed dispatcher
React dashboard
FastAPI · local API
Deterministic finance services
PostgreSQL · finance + audit

llama.cpp supplies local inference to the workflows.

Langfuse observes model calls locally; it is optional and outside the financial source of truth.

Arrows show processing and service calls; inbox, outbox and finance are separate responsibilities within application PostgreSQL. The response returns through the outbox and gateway.

From message to an accountable operation

The gateway persists authorized input before advancing the polling offset. A separate worker processes messages in order, so slow inference does not stop ingestion.

  1. 1Authorize + persist
  2. 2Route intent
  3. 3Run one specialist
  4. 4Render + queue response

Expense / correction

Typed extraction → domain validation → authorized, idempotent write. Category proposals pause for an explicit owner decision.

Query / advice

Scoped read-only tools → deterministic aggregates → model response. This route has no financial write tools.

Decisions and their costs

Bounded graphs + explicit dispatcher

Separate router and specialist contracts keep trusted profile, time and action references in runtime context.

Adding an intent requires an explicit branch and schema; this buys reviewable authority at the cost of more orchestration code.

Application PostgreSQL + inbox/outbox

Financial writes, command claims and audit records use transactions. Messaging persists across restarts.

A sequential worker can delay later messages during retry. External delivery can repeat a Telegram reply; finance idempotency protects the write.

Local inference + Telegram transport

Inference and financial storage stay on the workstation. Long polling avoids a public inbound endpoint.

The machine must be available for processing. Telegram is an external boundary, so the complete interaction is not entirely local.

Versioned evaluation gates

Datasets, deterministic graders and run manifests bind an accepted configuration to model, prompt, schema and tool versions.

A model or prompt change requires targeted regression runs. A plausible answer or confidence score is insufficient evidence.

What the interaction can look like

A schematic rendering of the interaction, not an exact reproduction of the current bot UI. Amounts, categories and wording are invented for this example. The financial model below the conversation explains the two reconciliations.

Simulation in English · fictitious data · not a screenshot
Money Tracker Demo
Example message

Today I spent €36 — €28 on groceries and €8 on household items. I paid with €12 in vouchers and €24 by card.

Illustrative response

Expense draft · €36.00

Groceries €28.00 · Household €8.00
Vouchers €12.00 · Card €24.00

Waiting for your confirmation.

A purchase has two independent allocations

The category dimension answers what the money was spent on. The funding dimension answers how it was paid. Both reconcile to the movement total; reports aggregate the relevant dimension without counting the purchase twice.

Illustrative data, not a real transaction
Movement total€36.00EUR · 3600

By category

Groceries€28.00

Household€8.00

By funding source

Vouchers€12.00

Card€24.00

28 + 8 = 36 · 12 + 24 = 36

  • EUR amounts use integer minor units: €36.00 is stored as 3600, avoiding binary floating-point arithmetic.
  • Corrections replace a validated aggregate and record an audit event. Undo voids the movement instead of deleting its history.

The dashboard makes the domain visible

The implemented React interface includes a monthly overview, transaction management and category budgets. These two schematic examples use one consistent fictitious dataset. The illustrative UI is presented in English; the current application uses Italian.

Dashboard simulation · fictitious data · not screenshots

Monthly overview

Money TrackerOverview · March 2026 · Demo

Where your money is going

Posted transactions only · 3 fictitious transactions

Net spending
€120.00
Personal funds
€108.00
Benefits
€12.00
Monthly budgets

Posted spending / monthly target

Groceries€82.00 / €100.00
Household€8.00 / €40.00
Transport€30.00 / €50.00
Posted movements feed net spending, personal funds, benefits and budget consumption. Drafts are excluded from these totals.

Transaction detail

TransactionsIllustrative transaction · Demo

Example purchase

Posted€36.00
Categories

Groceries€28.00

Household€8.00

Total €36.00 · reconciled

Payment sources

Vouchers€12.00

Card€24.00

Total €36.00 · reconciled

A transaction keeps category and funding allocations distinct. The interface exposes correction and undo; validation and audit remain in the domain.

How the model experiments unfolded

I treated model selection as a sequence of falsifiable experiments on the local workload. Six model weights were explored through text and, where configured, vision profiles. The evidence has different scopes: protocol screening, partial tool experiments, full workflow evaluations and an explicit promotion decision.

From the first screen to the retained baseline

  1. 1–2 Sep 2026

    Screen the local interfaces

    Pinned quantized weights, llama.cpp, an 8,192-token context and one inference slot. Probes checked structured output, tool calls, tool-result continuation and image transport. A passing screen kept a candidate eligible for further work; it did not establish financial accuracy.

  2. 3 Sep 2026

    Turn failures into new probes

    Qwen3 14B filled fields that the message left unspecified. The extended screen tested optional-field omission and union-shaped output separately. Omission failed; union output worked, motivating more explicit model-facing contracts.

  3. 8 Sep 2026

    Repair contracts and evaluation evidence

    Versioned schemas, prompts and development labels were revised with recorded reasons. The reporting path was tightened so failed or unmeasured required gates could not become a pass. Later text profiles and tool experiments widened the comparison.

  4. 9 Sep 2026

    Promote a complete configuration

    Qwen3 14B passed every applicable blocking gate across the text workflow suites after deterministic read-selection repairs and owner advice review. EVD-004 pinned the accepted model, prompts, schemas and policy. Thresholds were retained; automatic posting stayed disabled.

  5. 10 Sep 2026

    Test the larger challenger

    Qwen3.8 27B was measured after its quantized configuration fitted the workstation. It improved categorization but did not clear routing, tools and advice requirements. It remained experimental; the promoted baseline was retained.

Every model, with the scope of its evidence

Every model, with the scope of its evidence
Model / quantizationExperiments performedObserved outcomeDecision
GPT-OSS 20BMXFP4Initial text protocol screen; later tool-selection experiment.Structured final output and tool-result continuation failed in the initial profile; the later tools run also failed its gates.Not promoted
Qwen3 14BQ5_K_MText screening, omission/union probes, iterative contracts and all text workflow suites.Initial omission failures persisted as capability limits. The adapted application contracts and read policy passed all applicable product gates.Promoted text baseline
Ministral 3 14BQ5_K_MVision screening and text workflow comparisons.Initial tool-result continuation failed. Later extraction and other product gates failed; the owner stopped the final comparison after ingestion failed.Not promoted
Qwen3.5 9BUD-Q5_K_XLVision protocol screen; extended text capability screen and workflow sweep.Capability screens passed in their measured versions. Text extraction passed, but routing, categorization, tools and advice did not clear all gates.Experimental; vision retained for later receipt work
Gemma 4 12BQAT Q4_0Vision protocol screen; partial text tool-selection experiment.Vision transport/protocol checks passed. The text tools experiment failed; there is no complete text-suite comparison in the recorded evidence.Experimental; vision retained for later receipt work
Qwen3.8 27BUD-IQ4_XSExtended text capability screen and all text workflow suites.Capability screening, extraction and categorization passed. Categorization improved over the baseline; routing, tools and advice remained blocked.Experimental; baseline retained

Outcomes belong to the recorded model/runtime/schema combinations. Earlier Ministral text runs loaded a vision projector before the profile definition was corrected. Suite and instruction versions also changed over time, so these rows are a history of evidence, not a uniform leaderboard.

What made a run interpretable

Fix the comparison boundary

Each run recorded model artifact, quantization, runtime, template, context, prompt, schema, tool policy and dataset identity. Candidate comparisons require the same dataset and instruction version. Development regressions are distinguished from an untouched holdout.

Check observable behavior

Routing, extraction, categorization, tools and grounded advice were evaluated separately, with deterministic graders and PostgreSQL fixture postconditions where relevant. Category-review scenarios used a scripted model port to test workflow replay, not model quality.

Inspect failures before deciding

Missing fields, read/write crossover, forbidden tools, injection, fabricated figures and funding semantics were inspected by failure class. Human advice usefulness supplemented the automatic gates; it could not override a failing financial or interpretation requirement.

The decision the experiments changed

The selected unit was a complete application configuration, not model weights alone. Explicit contracts and deterministic read selection made Qwen3 14B usable for the accepted text scope. No qualifying automatic-post subset was established, so new movements remain drafts for owner confirmation.

Image transport is not receipt-extraction evidence; automated receipts remain deferred. Timing observations were specific to this workstation and run configuration. Missing measurements do not count as passes. This case study summarizes the records without reproducing private datasets, outputs or a benchmark score table.

Different evidence for different failure modes

Deterministic software tests

Tests check allocation sums, authorization, command idempotency and audit transitions. PostgreSQL integration and end-to-end checks cover persistence and recovery; model evaluations do not replace these invariants.

Model-dependent evaluations

Text workflows were tested with versioned datasets and explicit promotion gates. Software tests checked deterministic behavior and end-to-end paths; evaluations measured model-dependent routing, tool use, and advice. That distinction kept financial correctness outside the authority of a model score.

Operations are part of the design: durable retry and dead-letter states, local model-call tracing, and Restic encrypted backups. Trace content remains private. Acceptance evidence exists in the project; no performance score or private artifact is reproduced here.

Give development agents a contract

I used product requirements, a step-by-step roadmap, arc42 views, architecture decisions, and repository instructions to define component responsibilities and limits. Each delivery step had exit criteria and evidence, so an implemented feature was not treated as complete solely because code existed.

What exists today

The accepted personal V1 is text-first. It includes a durable private-owner Telegram channel, a protected local dashboard, deterministic finance and persistence, evaluated text workflows, and encrypted backup. Receipt storage and review exist, while automated receipt extraction is deferred. The repository remains private.

What my contribution demonstrates

My contribution connects product scope, system architecture, and verification. In Money Tracker, natural language helps interpret a request, while typed workflows, limited tools, and deterministic domain rules decide what can change. I used architecture documentation and evaluation gates to make that boundary reviewable during AI-assisted development.

What is shared here

The repository is private. This account describes design decisions, without exposing personal financial data, code, or screenshots.