← Back to Python series
🛠
Practice Projects · ★★★
OOP · CSV · Categories · Monthly summary

Project 9 — Budget Tracker

Track income and expenses by category. Generate monthly summaries, spending breakdowns, and savings rate reports — all stored in CSV.

OOPCSVbudgetcategoriesreport
Duration
2.5 hours
Level
📊 Intermediate-Advanced Applied
Prerequisite
🎯 Intermediate Week 6
OUTCOME
A monthly budget tracker with categorized reports

What you'll learn

  • 1Model transactions with a dataclass
  • 2Categorize income vs. expense transactions
  • 3Compute monthly totals, savings rate, and top expenses
  • 4Load and save all data to CSV

Transaction Model

python
@dataclass
class Transaction:
    date: str       # YYYY-MM-DD
    type: str       # income | expense
    amount: float
    category: str   # food, transport, salary, ...
    note: str = 

Commands

  • add <income|expense> <amount> <category> [note]
  • list [--month YYYY-MM]
  • summary [--month YYYY-MM]
  • categories — show all categories with totals
  • export <filename.csv>

Common Mistakes (FAQ)

Q. My totals are off by a cent (e.g. 0.1 + 0.2 = 0.30000000000000004).

Floating-point can't represent money exactly. For display, round(total, 2); for exact arithmetic, store amounts as integer cents (7250 = $72.50) or use decimal.Decimal. Pick one and stay consistent.

Q. Should expenses be negative or positive?

Cleanest: store a positive amount plus a type field ('income'/'expense'), then balance = sum(income) − sum(expense). Mixing signed amounts and a type field double-counts — choose one convention.

Q. How do I filter by month?

With ISO dates (YYYY-MM-DD), a string prefix compare is enough: tx.date[:7] == '2024-05'. No datetime parsing needed for month filtering.

Q. KeyError when totaling a new category.

Use collections.defaultdict(float) (or totals.get(cat, 0)) so a never-seen category starts at 0 instead of raising.

Q. After reloading the CSV, amounts won't add up.

The csv module returns every field as a string — '50' + '20' is '5020'. Convert on load: amount = float(row['amount']). Also write with newline='' on Windows to avoid blank rows.

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Build the budget tracker

Goal: Implement all commands with CSV storage.

Requirements
  • Transaction dataclass
  • add/list/summary/categories commands
  • CSV persistence
  • Monthly filtering
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗