thecodex.expert · The Codex Family of Knowledge
Tier 2 · Intermediate · Python Project

Expense Tracker

Log expenses with dates and categories, then report totals by month and category. A step up from the budget tracker, with real dates.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want an expense tracker that records each spend with an amount, category, and date, then reports: total spent, spending per category, and spending per month. It builds on summarising by adding dates and grouped reporting — closer to a real finance tool.

Where this shows up: accounting software, expense reports, budgeting apps, any analytics grouped by time. Grouping records by date and category is exactly how dashboards and financial reports are built.

2 How to Think About It

Think about the reporting, before any code:

The plan — in plain English
1. Each expense has an amount, a category, and a date. → 2. Total = sum of all amounts. → 3. By category = group and sum per category. → 4. By month = take the year-month part of each date and group by that. → 5. Persist to a file.

Load expenses

Add: amount, category, date

Save

Report total

Group by category

Group by month from date

Show report

3 The Build — explained part by part

Here is the complete tracker. Each part is explained below.

Pythonexpenses.py
import json
import os
from collections import defaultdict

FILE = "expenses.json"

def load():
    if os.path.exists(FILE):
        with open(FILE) as f:
            return json.load(f)
    return []

def save(expenses):
    with open(FILE, "w") as f:
        json.dump(expenses, f)

def total(expenses):
    return sum(e["amount"] for e in expenses)

def by_category(expenses):
    totals = defaultdict(float)
    for e in expenses:
        totals[e["category"]] += e["amount"]
    return dict(totals)

def by_month(expenses):
    totals = defaultdict(float)
    for e in expenses:
        month = e["date"][:7]      # "2026-06-24" -> "2026-06"
        totals[month] += e["amount"]
    return dict(totals)

if __name__ == "__main__":
    expenses = load()
    expenses.append({"amount": 25.0, "category": "food", "date": "2026-06-24"})
    save(expenses)
    print("Total:", total(expenses))
    print("By category:", by_category(expenses))
    print("By month:", by_month(expenses))
What each part does — in plain words
each expense = {amount, category, date} — a record with three fields. The date is stored as text like "2026-06-24".

total(expenses) — sum every amount in one line.

by_category — the same grouping you learned in the budget tracker: a defaultdict totals each category.

e["date"][:7] — the new trick: take the first 7 characters of the date ("2026-06-24""2026-06") to get the year-month. Grouping by that gives monthly totals.

by_month — group amounts by that year-month string, so you see spending per month without any date library.
Common mistakes — and how to avoid them
✗ Slicing the date wrong — [:4] gives only the year, [:10] the whole day.
✓ Use [:7] to get year-month like 2026-06.
✗ Storing dates in inconsistent formats — then slicing fails.
✓ Always store dates as YYYY-MM-DD so slicing is reliable.
✗ Re-checking if category in totals — unnecessary with defaultdict.
✓ Use defaultdict(float) so keys start at 0 automatically.

4 Test & Prove Each Part

We test the three reports — total, by category, by month — with known expenses.

The total sums all amounts
Category grouping totals each category
Month grouping uses the year-month of the date
Pythontest_expenses.py
from collections import defaultdict


def total(expenses):
    return sum(e["amount"] for e in expenses)


def by_category(expenses):
    totals = defaultdict(float)
    for e in expenses:
        totals[e["category"]] += e["amount"]
    return dict(totals)


def by_month(expenses):
    totals = defaultdict(float)
    for e in expenses:
        totals[e["date"][:7]] += e["amount"]
    return dict(totals)


EXPENSES = [
    {"amount": 10, "category": "food", "date": "2026-06-01"},
    {"amount": 20, "category": "food", "date": "2026-07-01"},
    {"amount": 5, "category": "travel", "date": "2026-06-15"},
]


def test_total():
    """Total is 35."""
    assert total(EXPENSES) == 35


def test_by_category():
    """Food totals 30, travel 5."""
    result = by_category(EXPENSES)
    assert result["food"] == 30
    assert result["travel"] == 5


def test_by_month():
    """June totals 15, July 20."""
    result = by_month(EXPENSES)
    assert result["2026-06"] == 15
    assert result["2026-07"] == 20

Run with pytest -v. Each report is a small function tested with a known set of expenses. The month test proves the date-slicing trick groups correctly.

5 The Interface

ADDamount, category, dateone expense
What it expects
{"amount": 25, "category": "food", "date": "2026-06-24"}
REPORTtotalsoverall, by category, by month
What it returns
Total: 25.0
By category: {'food': 25.0}
By month: {'2026-06': 25.0}

6 Run It & Automate It

Run it locally
python3 expenses.py
Log expenses and view totals by category and month. Saved in expenses.json.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
Total: 25.0
By category: {'food': 25.0}
By month: {'2026-06': 25.0}
If it breaks — how to fix it
🚨 Monthly grouping looks wrong.
Check your dates are YYYY-MM-DD and you slice [:7].
🚨 KeyError in a report.
Use defaultdict(float) instead of a plain dict.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Budget alerts. Warn when a category exceeds a monthly limit. (Teaches: comparing totals to limits.)
  2. Date range filter. Report only expenses between two dates. (Teaches: filtering by date.)
  3. Use real dates. Switch to the datetime module for proper date maths. (Teaches: date handling.)
What you learned
You learned grouped reporting over time: summing totals, grouping by category, and grouping by month using a simple date-string slice. Time-based grouping is the engine behind every financial dashboard. Related: collections, String Methods.