Skip to Content
Course content

443: Capstone: Building a Data-Backed Personal Finance Tracker

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've spent a lot of time learning the individual components of Python—lists, dictionaries, file handling, and classes. Now, it's time to stop treating them like isolated tools and start treating them like a toolkit. For this capstone, we're building a Personal Finance Tracker. Instead of trying to build a full-blown app with a GUI, we're going to focus on the engine: the logic that takes raw spending data and turns it into something meaningful.

Defining the data source

Most banks let you export your transactions as a CSV file. It's a messy format, but it's the industry standard for a reason. I've created a simple dummy file called transactions.csv with columns for the date, the description, and the amount. To keep this manageable, our first goal is to get that data into Python and calculate our total spending.

import csv

def calculate_total_spend(filename):
    total = 0
    with open(filename, mode='r') as file:
        reader = csv.reader(file)
        next(reader) # Skip the header row
        for row in reader:
            total += float(row[2])
    return total

# Assuming we have a file with: Date, Description, Amount
print(f"Total spent: ${calculate_total_spend('transactions.csv')}")

The "Index Trap" and a better approach

Now, here is where I usually trip up when I'm rushing a prototype. In the code above, I used row[2] to get the amount. That works fine until you realize that if the bank adds a "Category" column in the middle of the CSV, row[2] is suddenly a string like "Groceries" and the whole program crashes with a ValueError. It's fragile code.

I'm going to pivot. Instead of using the csv.reader, I'll use csv.DictReader. This treats every row as a dictionary where the keys are the header names. It makes the code slightly slower, but it's infinitely more readable and robust. Let's rewrite the ingestion logic to use a list of dictionaries.

import csv

def load_transactions(filename):
    transactions = []
    with open(filename, mode='r') as file:
        # DictReader uses the first row as keys for every subsequent row
        reader = csv.DictReader(file)
        for row in reader:
            # Convert amount to float immediately so we don't have to do it later
            row['Amount'] = float(row['Amount'])
            transactions.append(row)
    return transactions

data = load_transactions('transactions.csv')
# Now I can access data[0]['Amount'] instead of data[0][2]

Building a keyword-based categorizer

Raw bank descriptions are useless. "SQ *COFFEE SHOP 123" should just be "Dining". To handle this, I'll create a mapping of keywords to categories. I'll use a dictionary where the keys are categories and the values are lists of keywords. This allows me to easily add new keywords to an existing category without changing the logic.

CATEGORIES = {
    "Dining": ["starbucks", "mcdonalds", "restaurant", "cafe"],
    "Utilities": ["electric", "water", "internet", "verizon"],
    "Shopping": ["amazon", "walmart", "target"],
}

def categorize_transaction(description):
    description = description.lower()
    for category, keywords in CATEGORIES.items():
        for word in keywords:
            if word in description:
                return category
    return "Other" # Default if no keyword matches

# Let's apply this to our data
for t in data:
    t['Category'] = categorize_transaction(t['Description'])

Aggregating the final report

The final piece is the aggregation. I want to know exactly how much I spent per category. A dictionary is perfect here; the key will be the category name, and the value will be the running total. I'll iterate through our categorized list and sum the amounts.

def generate_report(transactions):
    report = {}
    for t in transactions:
        cat = t['Category']
        amount = t['Amount']
        report[cat] = report.get(cat, 0) + amount
    return report

final_report = generate_report(data)
for cat, total in final_report.items():
    print(f"{cat}: ${total:.2f}")

By breaking this down—ingestion, cleaning, categorization, and aggregation—we've built a data pipeline. We moved from a fragile, index-based approach to a flexible, dictionary-based system that can handle changing CSV formats and expanding category lists without breaking.




📋 Practical Task

Implement a Monthly Budget Alert System

Your task is to extend the Finance Tracker by adding a budget alert system. Create a new dictionary called BUDGETS where the keys are categories (e.g., "Dining", "Shopping") and the values are the maximum dollar amount you are willing to spend in that category.

Write a function called check_budget_alerts(report, budgets) that takes the final spending report and the budget dictionary. The function should print a warning message for every category where the actual spending exceeded the budget, including the amount by which you went over.

Requirements:

  • Define a BUDGETS dictionary with at least three categories.
  • The output should look like: "ALERT: Dining exceeded budget by $15.50!"
  • Ensure the function handles categories that exist in the report but are not defined in your budget dictionary (these should be ignored).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.