The Persistent Tracker
Intermediate · 90 min build · 🏆 milestone project
You are ready for the first program in this course that behaves like a small tool, not a one-off script. The goal is a persistent expense tracker, a command-line program that stores expense records in a JSON file during the page session, loads them back, and handles bad input without crashing.
The browser filesystem is temporary: a page reload clears it. Build your save and load tests inside one run, and think of the file as a practice notebook for this session, not a permanent vault.
What you'll build
You will build one menu-driven expense tracker with these routes:
1: add an expense with a date, category, amount, and note.2: list all expenses and show the total.3: save expenses toexpenses.json.4: load expenses fromexpenses.json.5: add demo expenses so you can test without typing a lot.7: exit cleanly.
An expense record is one dictionary that describes one purchase. A JSON data file is a text file that stores lists and dictionaries in a shape Python can read back with json.load().
The scaffold is starter code with TODO comments that show where your work belongs. The acceptance criteria are checks you can run yourself to prove each stage works.
Checkpoint 1: the menu and data shape
Start with the front desk of the program: the menu. Keep the top-level route code small, and let functions do the work behind each choice.
Acceptance criteria:
- Entering
7printsGoodbyeand stops. - Entering
1,2,3,4, or5reaches the matching function. - Entering another value prints a clear fallback message.
- Expenses are stored as a list of dictionaries.
- Each expense dictionary uses the same keys:
date,category,amount, andnote.
Checkpoint 2: add and list expenses
Build the in-memory tracker before you touch files. In-memory means the data lives in Python values while the program is running, like name tags attached to objects in memory.
Acceptance criteria:
- Choice
1asks for date, category, amount, and note. - A valid amount becomes a
float, not a string. - A non-numeric amount prints a friendly message instead of a traceback.
- Choice
2prints a useful empty-state message when the list is empty. - Choice
2lists every expense and prints the total with two decimal places.
One practical tip: this playground allows up to 20 typed answers per Run, and every manual
add costs five of them (the menu choice plus four fields). When you test repeatedly, lean
on route 5 — that is exactly what the demo data is for.
Checkpoint 3: save and load JSON
Now connect the tracker to a file. Use JSON because the data is already a list of dictionaries; it fits the shape without flattening.
Acceptance criteria:
- In one run, choosing
5, then3, then4, then7proves save and load without depending on a previous Run. save_expenses()writesexpenses.jsonwithencoding="utf-8"andindent=2.load_expenses()returns a list loaded withjson.load().- A missing file prints a friendly message and returns an empty list.
- Broken JSON catches
json.JSONDecodeErrorand returns an empty list.
Do not promise the file survives a page reload. In this browser, the file is for practice during the current page session.
Checkpoint 4: bad input and recovery
This is the robustness checkpoint. Your program should expect ordinary human mistakes: a blank note, a mistyped amount, a missing file, and a menu choice that is not on the list.
Acceptance criteria:
- The program catches
ValueErroraround amount conversion. - It catches
FileNotFoundErroraround loading. - It catches
json.JSONDecodeErroraround loading broken JSON. - It does not use
except Exception:as the main strategy. - The menu keeps messages specific: "amount must be a number" is better than "something went wrong."
Stretch goals
Choose one or two after the base tracker works:
- Add a
6route that shows totals by category using a dictionary. - Add a delete route that removes one expense by number.
- Add a CSV export route using
csv.DictWriter. - Sort expenses by date before listing them.
- Add a small
assertinside a helper to check that every expense has the required keys.
How to get unstuck
If the menu route goes wrong, revisit lesson 4.1 and the menu pattern from the first project. If your list or dictionary shape gets messy, go back to lesson 6.1. If a function prints when it should return, compare it to lesson 7.1. If file code fails, rebuild the write-then-read pattern from lesson 9.1, then add JSON from lesson 9.2. If your program crashes on bad input, narrow the try block and catch the specific exception from lesson 9.3.
Part II ends when this tracker feels like a set of cooperating pieces: functions, collections, files, and exceptions. Part III turns that same kind of program inside out and gives you a new way to model it with objects.