Julian Teh

Building an Expense Tracking System with AI Assistance

Hero image for Building an Expense Tracking System with AI Assistance

I picked up the habit of tracking my expenses when I started working, not so much as a way of budgeting (which is a good idea), but as a way of making myself aware of my own spending habits, so that I could then decide how I wanted to adjust macro behaviors in my life.

After going about a few tracking apps, I eventually ended up using a Google Form, which has lasted for quite a long time now, and which does well in serving my needs. I can easily pop it out on any platform, enter data quickly, and forget about it until I do my periodic reviews of the data.

There was just thing I wasn’t so happy with: I’m still sending data to Google.

Now, this isn’t really a problem, but geeks have to invent problems sometimes to scratch that itch. And there really is something about having full control and ownership over your own data. So I envisioned a new system which would replace Google Forms for my expense tracking.

Architecture

I had a few requirements for this project.

  1. Deployment to be as easy as possible; one-click, or one-command to launch everything needed.
  2. Data to be in my own server, and no data should transit or reside on a third party service.
  3. Using a web framework I’m not familiar with, for my own learning

In theory, this wasn’t very difficult to architect. Docker-compose would solve deployment, a simple sqlite file would suffice for data management, and I could pick almost any newer web framework.

So I picked Nuxt.js, which was a good idea, but the moment I began I felt somewhat in over my head, and the project kind of ground to a halt there while I idled on it for a few months.

AI Assistance

Recently, I had been testing out the newer LLM models, and there were some reports that these could really help in development in a non-trivial way. Not something that snippets could have replaced, like the earlier Copilots. These were able to understand and write coherent code, and while limited, they were able to transform the instructions of a sufficiently experienced engineer (like myself) into working prototypes, which I could then tweak to my liking.

This sparked the idea that I could try using these on my stalled project. It fit all the criteria; I knew what I wanted to write, I could instruct the AI sufficiently, and I could fix any mistakes it made. And it would save me the time of poring over lots of documentation to get over the initial learning curve.

Cash-Register

I named the expense tracking app Cash-Register, and came up with a little logo for it.

The architecture was very simple; A Nuxt.js app would directly write to a sqlite database, which would reside on a mounted data volume. The whole thing would be hosted in a single Docker container, which would be wrapped by a docker-compose to nicely set environment variables and/or volumes.

I wasn’t really interested in making this hyper secure, but since the AI was able to do it, JWT tokens got integrated for authentication of users. So, at least not plaintext flying around, I guess.

The app is hosted on my personal server, and exposed through Cloudflare Tunnels, which I’ve really enjoyed using for accessing my personal apps.

I think the learning around web development here wasn’t very substantial, so I won’t go further on the technical detail here. What was interesting though, is how to use current LLMs for development.

How to use AI in development

A general approach to utilizing these agents is as follows:

  1. Decide the major parameters of your project (e.g. purpose, providers, platforms, etc…); this constraints the AI to respond within your problem space
  2. Decide the architecture and tech stack that you think you could use; this guides the AI to respond with more specific technical advice/action
  3. If the app requires some boilerplate which can be generated by existing tools, generate this first; this reduces the context the AI has to generate
  4. Tell the AI to generate the components one at a time, in order of dependency; This allows the AI to build up its solution knowledge and respond using it
  5. Repeat until the AI starts to give repeated advice/action; At this point you need to have the experience to step in and resolve the issues

I’ve found that at the time of writing, Claude 3.5 Sonnet by Anthropic does the best in generating good and consistent code. ChatGPT 4o by OpenAI and Gemeni 1.5 Flash by Google, and even Codestral by Mistral are similar, but get things wrong too often to be significantly helpful.

I used Continue quite a bit, especially for debugging. This would allow me to upload files as context, which was very useful for refactoring or writing new functionality on top of existing code. For simpler tasks, I would point them to Codestral running locally, to save a few cents.

Overall, I would say the workflow is promising. It’s not always correct, but it gets enough correct that I can focus on guiding it and resolving blockers, and not on trying to understand the whole framework.

With improvements to LLMs coming at the rate that they are, I think it is accurate to say that experienced engineers will suddenly get a boost in development speed for relatively little spending, and junior engineers will learn a totally different way to program, which will be better, but also requiring more spending and tooling (much like we now need IDEs to develop software, but we can develop more complex software). I have mixed feelings about the latter, especially if it turns into reliance on the tools, but this is the trend I think we are seeing.