01 Python Foundations & Developer Setup/1.14 Lab code (reference solutions)/1.14.1 lab/expense_tracker.py
§ 1.14.1section 15 of 16·~9 min
lab/expense_tracker.py
"""Week 1 lab (reference solution): CLI expense tracker.
Run: uv run python expense_tracker.py
Try it yourself before reading this file.
"""
import json
from pathlib import Path
DATA_FILE = Path("expenses.json")
def load_expenses() -> list[dict]:
if DATA_FILE.exists():
return json.loads(DATA_FILE.read_text())
return []
def save_expenses(expenses: list[dict]) -> None:
DATA_FILE.write_text(json.dumps(expenses, indent=2, ensure_ascii=False))
def add_expense(expenses: list[dict]) -> None:
try:
amount = float(input("Amount (₹): "))
except ValueError:
print("Please enter a number.")
return
category = input("Category (food/travel/rent/other): ").strip().lower() or "other"
note = input("Note: ").strip()
expenses.append({"amount": amount, "category": category, "note": note})
save_expenses(expenses)
print("Saved.")
def list_expenses(expenses: list[dict]) -> None:
if not expenses:
print("No expenses yet.")
for i, e in enumerate(expenses, start=1):
print(f"{i:>3}. ₹{e['amount']:>10,.2f} {e['category']:<8} {e['note']}")
def totals_by_category(expenses: list[dict]) -> dict[str, float]:
totals: dict[str, float] = {}
for e in expenses:
totals[e["category"]] = totals.get(e["category"], 0) + e["amount"]
return totals
def main() -> None:
expenses = load_expenses()
menu = "\n1) Add 2) List 3) Totals 4) Quit\n> "
while True:
choice = input(menu).strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
list_expenses(expenses)
elif choice == "3":
for category, total in sorted(totals_by_category(expenses).items()):
print(f"{category:<8} ₹{total:,.2f}")
print(f"{'TOTAL':<8} ₹{sum(e['amount'] for e in expenses):,.2f}")
elif choice == "4":
break
else:
print("Choose 1-4.")
if __name__ == "__main__":
main()