#personal#essay#python#diy#homelab

The app I built for myself instead

A 50-line Python tracker on a Pi that does one thing well and nothing else.

The use case was specific. Around 11am, before a heavy lift, I want to log what I’m about to eat against a target for that workout. I want to be able to tell my watch app to remind me. I want the data to land in a spreadsheet I can later analyse with a Jupyter notebook. I want the entire thing to take ten seconds.

OpenNutriTracker is good but the workflow is six taps. Waistline is good but its export is a separate manual operation. Cronometer was good but I’d already cancelled it. The use case wasn’t quite what any general-purpose tracker was designed for.

So I wrote my own. It is 50 lines of Python. It runs on the Pi in my closet. I use it every day.

This isn’t a manifesto. I’m not arguing everyone should write their own tracker. The general-purpose FOSS apps are correct for the general case. But there’s a class of personal-data tool that’s small enough to be worth writing yourself, and a calorie tracker for one specific use is in that class.

The shape

The full code is in the API tutorial piece. The relevant facts:

  • 50 lines of Python
  • SQLite at ~/.caltrack.db
  • One outbound network call: USDA FDC API for new foods
  • One CLI command for everything (caltrack search, caltrack log, caltrack today)
  • Zero dependencies beyond stdlib + requests

I added two more commands over the years: caltrack week (running daily totals over seven days) and caltrack barcode <gtin> (Open Food Facts lookup). Total length is now 84 lines. It hasn’t grown since.

The watch path

The thing that makes it actually fit my workflow is the watch. I have a Garmin Fenix that runs a small custom data-field. The data-field hits a webhook on my Pi which calls into the script. From the watch I can:

  1. Pick from the last five logged foods.
  2. Tap “log 1 serving.”
  3. Get a notification confirming.

Total time on watch: ~4 seconds. Total time at desk: ~10 seconds for a typed entry.

The webhook is 30 lines of Flask. The watch data-field is about 200 lines of Garmin Connect IQ. Both are private and unmaintained except by me; both have been working without modification for ~14 months.

The notebook

Daily totals go into a CSV on the Pi. Once a week I run a Jupyter notebook that:

  • Plots the 7-day trend
  • Compares the current week’s macros against my training-cycle target
  • Flags any day with weird outlier values

The notebook is 80 lines. It exports a PNG into a folder on my Pi that gets served on a tiny internal Flask page that I open from my laptop on Sunday mornings. It is, technically, “a dashboard.” I would not call it that to my coworkers.

What this approach gets me

Three things, all small but compounding.

Latency. The hosted tracker apps’ median time-to-log is 5–8 seconds, sometimes 15. My CLI is 2–3 seconds typed; my watch path is 4 seconds. For a habit you do many times a day, latency matters more than the small numbers suggest.

Specificity. The app is exactly the shape of the use case. Adding training-cycle targets to a generic tracker would be hard; adding them to my CLI was a 6-line patch. The cost of customisation in my own tool is roughly zero. The cost of customisation in someone else’s tool is “wait for it on the roadmap or fork it.”

Persistence. The script will keep working in 2030 unless I rewrite Python’s stdlib. The general-purpose apps will (probably) keep working but their compatibility with future Android is somebody else’s project. My script’s compatibility with future Python is somebody else’s project too, but Python ages slowly and my code is small enough to port.

What this approach doesn’t get me

General-purpose use. I still use OpenNutriTracker for non-workout meals. The 50-line script is good for the workflow it was built for; it’s bad for everything else. Most days I touch both tools.

Anything mobile-friendly. The CLI is a CLI. The watch path needs a Garmin. There’s no iOS app. There’s no Android app. There’s no web UI beyond the Sunday dashboard. If I switched primary phones I’d have to rebuild the watch glue.

Sharing. This tool is for one person. If my partner wanted to use it, I’d have to add user IDs, auth, and a multi-tenant data model. That’s a different scale of project; I wouldn’t take it on.

What I’d tell someone considering this approach

Three things.

Make sure the use case is real and specific. “I want to track calories” is a general-purpose problem; use a general-purpose tool. “I want to log workout meals from a Garmin watch into a spreadsheet” is a specific problem; that’s where DIY pays off.

Keep it small. The first 100 lines of code are by far the highest leverage. The next 1,000 lines turn a focused tool into a worse general-purpose one. If you find yourself adding social features, you’ve gone too far.

Use the FOSS apps for the rest. I am using OpenNutriTracker daily and writing this on my own tracker because they don’t compete; they cover different use cases.

Two years in

The script has logged about 1,400 entries since I wrote it. I have edited the source twice. I have lost data zero times (Borg backups). I have not paid for it.

It is the most boring useful tool I have ever written. That’s the recommendation.

References