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
Local workstation
Inside LangGraph
Five independently compiled graphs. An external typed dispatcher coordinates routing, specialist calls and category-review resumes.
Root router Typed dispatcher Expense / refund / Query / advice / Category review / Correction / undo / posting
- Model boundary
- Deterministic node
- Rules + optional model call
- Conditional edge
Root router
Expense / refund
Query / advice
Category review
The loop crosses an owner decision and a persistent checkpoint. Reproposal may call the model inside settle_review; it is not an additional graph node.
Correction / undo / posting
Undo and posting use trusted movement references without model calls. Only the correction branch invokes extraction and category classification.
The model proposes; application rules validate. Drafts require confirmation before posting. Read queries, category review and movement changes use separate graphs.
llama.cpp supplies local inference to the workflows.
Langfuse observes model calls locally; it is optional and outside the financial source of truth.
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.
- 1Authorize + persist
- 2Route intent
- 3Run one specialist
- 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.
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.
EUR · 3600By 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
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
Transaction detail
Example purchase
Posted€36.00Categories
Groceries€28.00
Household€8.00
Total €36.00 · reconciled
Payment sources
Vouchers€12.00
Card€24.00
Total €36.00 · reconciled
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–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.
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.
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.
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.
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
| Model / quantization | Experiments performed | Observed outcome | Decision |
|---|---|---|---|
| GPT-OSS 20BMXFP4 | Initial 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_M | Text 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_M | Vision 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_XL | Vision 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_0 | Vision 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_XS | Extended 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.