Day 10 · Day 10: Advanced Python
Personal Finance Manager
Build a comprehensive Personal Finance Manager that tracks income and expenses by category, generates monthly reports, and uses caching for repeated report calculations — combining OOP, file storage, and performance techniques from the course.
Requirements
1. Create a Transaction class with date, type (income/expense), category, amount, and description.
2. Create a FinanceManager class that stores a list of Transaction objects.
3. Implement add_transaction(), which validates that amount is positive and type is "income" or "expense".
4. Implement get_balance() returning total income minus total expenses.
5. Implement get_report(month) returning a summary dict of income, expenses, and per-category breakdown for a given month, using @lru_cache or manual caching to avoid recomputation for repeated calls with the same month.
6. Implement save_to_file() and load_from_file() using JSON for persistence.
7. Build a menu-driven interface tying everything together.
8. Handle invalid input gracefully throughout (invalid amounts, invalid dates, etc.).
2. Create a FinanceManager class that stores a list of Transaction objects.
3. Implement add_transaction(), which validates that amount is positive and type is "income" or "expense".
4. Implement get_balance() returning total income minus total expenses.
5. Implement get_report(month) returning a summary dict of income, expenses, and per-category breakdown for a given month, using @lru_cache or manual caching to avoid recomputation for repeated calls with the same month.
6. Implement save_to_file() and load_from_file() using JSON for persistence.
7. Build a menu-driven interface tying everything together.
8. Handle invalid input gracefully throughout (invalid amounts, invalid dates, etc.).
import json
import os
from datetime import date
from functools import lru_cache
class Transaction:
VALID_TYPES = ("income", "expense")
def __init__(self, date_str, type_, category, amount, description=""):
if type_ not in self.VALID_TYPES:
raise ValueError(f"type must be one of {self.VALID_TYPES}")
if amount <= 0:
raise ValueError("amount must be positive")
self.date = date_str
self.type = type_
self.category = category
self.amount = float(amount)
self.description = description
def to_dict(self):
return {
"date": self.date,
"type": self.type,
"category": self.category,
"amount": self.amount,
"description": self.description,
}
@classmethod
def from_dict(cls, data):
return cls(data["date"], data["type"], data["category"], data["amount"], data.get("description", ""))
class FinanceManager:
def __init__(self):
self.transactions = []
self._report_cache = {}
def add_transaction(self, transaction):
self.transactions.append(transaction)
self._report_cache.clear() # invalidate cache when data changes
def get_balance(self):
income = sum(t.amount for t in self.transactions if t.type == "income")
expenses = sum(t.amount for t in self.transactions if t.type == "expense")
return income - expenses
def get_report(self, month):
"""month format: YYYY-MM. Cached until transactions change."""
if month in self._report_cache:
return self._report_cache[month]
income_total = 0.0
expense_total = 0.0
categories = {}
for t in self.transactions:
if not t.date.startswith(month):
continue
if t.type == "income":
income_total += t.amount
else:
expense_total += t.amount
categories[t.category] = categories.get(t.category, 0) + t.amount
report = {
"month": month,
"income": income_total,
"expenses": expense_total,
"net": income_total - expense_total,
"by_category": categories,
}
self._report_cache[month] = report
return report
def save_to_file(self, filename="finance.json"):
with open(filename, "w") as f:
json.dump([t.to_dict() for t in self.transactions], f, indent=2)
print("Data saved.")
def load_from_file(self, filename="finance.json"):
if not os.path.exists(filename):
return
with open(filename, "r") as f:
data = json.load(f)
self.transactions = [Transaction.from_dict(d) for d in data]
self._report_cache.clear()
def main():
manager = FinanceManager()
manager.load_from_file()
while True:
print("\n--- Personal Finance Manager ---")
print("1. Add Income")
print("2. Add Expense")
print("3. View Balance")
print("4. Monthly Report")
print("5. Save & Exit")
choice = input("Choose an option: ")
if choice in ("1", "2"):
type_ = "income" if choice == "1" else "expense"
category = input("Category: ")
try:
amount = float(input("Amount: "))
description = input("Description: ")
t = Transaction(str(date.today()), type_, category, amount, description)
manager.add_transaction(t)
print("Transaction added.")
except ValueError as e:
print(f"Error: {e}")
elif choice == "3":
print(f"Current balance: ${manager.get_balance():.2f}")
elif choice == "4":
month = input("Enter month (YYYY-MM): ")
report = manager.get_report(month)
print(f"\nReport for {report['month']}:")
print(f" Income: ${report['income']:.2f}")
print(f" Expenses: ${report['expenses']:.2f}")
print(f" Net: ${report['net']:.2f}")
print(" By category:")
for cat, amt in report["by_category"].items():
print(f" {cat}: ${amt:.2f}")
elif choice == "5":
manager.save_to_file()
print("Goodbye!")
break
else:
print("Invalid option.")
if __name__ == "__main__":
main()
75 XP on completion
Back to Day 10
Your Code
Output
Click "Run" to execute your code...
⏳ Loading Python runtime (first run may take a few seconds)...
Log in to mark this project complete and earn XP.