This project combines file handling (CSV/JSON), data analysis, reporting, and user interface design. We'll create a comprehensive expense tracking system with features like expense categorization, monthly summaries, budgeting, data visualization, and export capabilities.
# EXPENSE TRACKER APPLICATION
import json
import csv
import os
from datetime import datetime, timedelta
from collections import defaultdict
print("EXPENSE TRACKER")
print("=" * 50)
class ExpenseTracker:
"""A comprehensive expense tracking application"""
def __init__(self, data_file="expenses.json"):
self.data_file = data_file
self.expenses = self.load_expenses()
self.categories = [
"Food", "Transportation", "Housing", "Utilities",
"Entertainment", "Healthcare", "Shopping", "Education",
"Travel", "Other"
]
self.budget = self.load_budget()
def load_expenses(self):
"""Load expenses from JSON file"""
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r') as file:
return json.load(file)
except (json.JSONDecodeError, IOError):
print("Warning: Could not load expenses. Starting fresh.")
return []
return []
def save_expenses(self):
"""Save expenses to JSON file"""
try:
with open(self.data_file, 'w') as file:
json.dump(self.expenses, file, indent=2)
except IOError:
print("Error: Could not save expenses.")
def load_budget(self):
"""Load budget from file"""
budget_file = "budget.json"
if os.path.exists(budget_file):
try:
with open(budget_file, 'r') as file:
return json.load(file)
except (json.JSONDecodeError, IOError):
return {category: 0 for category in self.categories}
return {category: 0 for category in self.categories}
def save_budget(self):
"""Save budget to file"""
try:
with open("budget.json", 'w') as file:
json.dump(self.budget, file, indent=2)
except IOError:
print("Error: Could not save budget.")
def display_menu(self):
"""Display main menu"""
print("\n" + "šµ" * 20)
print("EXPENSE TRACKER MENU")
print("šµ" * 20)
print("1. Add New Expense")
print("2. View All Expenses")
print("3. View Expenses by Category")
print("4. View Expenses by Month")
print("5. Set/View Budget")
print("6. Generate Reports")
print("7. Search Expenses")
print("8. Export Data")
print("9. Delete Expense")
print("10. Exit")
print("-" * 40)
# Show quick summary
if self.expenses:
today = datetime.now().date()
month_start = today.replace(day=1)
month_expenses = [e for e in self.expenses
if datetime.strptime(e["date"], "%Y-%m-%d").date() >= month_start]
if month_expenses:
total_month = sum(e["amount"] for e in month_expenses)
print(f"This month's spending: ${total_month:.2f}")
def add_expense(self):
"""Add a new expense"""
print("\n" + "-" * 40)
print("ADD NEW EXPENSE")
print("-" * 40)
# Get expense details
description = input("Description: ").strip()
if not description:
print("Description cannot be empty!")
return
amount = self.get_amount()
category = self.get_category()
date = self.get_date()
payment_method = input("Payment method (cash/card/online): ").strip().lower() or "card"
# Create expense record
expense = {
"id": len(self.expenses) + 1,
"description": description,
"amount": amount,
"category": category,
"date": date,
"payment_method": payment_method,
"added_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
self.expenses.append(expense)
self.save_expenses()
print(f"\nā
Expense added successfully!")
print(f" {description}: ${amount:.2f} under {category}")
# Check if over budget for this category
self.check_budget_alert(category, amount)
def get_amount(self):
"""Get amount with validation"""
while True:
try:
amount = float(input("Amount: $").strip())
if amount <= 0:
print("Amount must be greater than 0")
continue
return amount
except ValueError:
print("Please enter a valid number")
def get_category(self):
"""Get category with validation"""
print("\nCategories:")
for i, category in enumerate(self.categories, 1):
print(f" {i}. {category}")
while True:
try:
choice = input("\nSelect category (1-{}): ".format(len(self.categories))).strip()
if not choice:
return "Other"
idx = int(choice) - 1
if 0 <= idx < len(self.categories):
return self.categories[idx]
else:
print(f"Please enter 1-{len(self.categories)}")
except ValueError:
print("Please enter a valid number")
def get_date(self):
"""Get date with validation"""
while True:
date_str = input("Date (YYYY-MM-DD, Enter for today): ").strip()
if not date_str:
return datetime.now().strftime("%Y-%m-%d")
try:
datetime.strptime(date_str, "%Y-%m-%d")
return date_str
except ValueError:
print("Invalid date format. Please use YYYY-MM-DD")
def view_all_expenses(self, expenses=None):
"""View all expenses"""
if expenses is None:
expenses = self.expenses
if not expenses:
print("\nNo expenses found.")
return
# Sort by date (newest first)
sorted_expenses = sorted(expenses,
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
reverse=True)
print("\n" + "=" * 80)
print(f"ALL EXPENSES ({len(sorted_expenses)} total)")
print("=" * 80)
print(f"{'Date':12} {'Category':15} {'Description':25} {'Amount':10} {'Payment':10}")
print("-" * 80)
total = 0
for expense in sorted_expenses:
date = expense["date"]
category = expense["category"]
description = expense["description"][:23] + "..." if len(expense["description"]) > 23 else expense["description"]
amount = expense["amount"]
payment = expense["payment_method"]
print(f"{date:12} {category:15} {description:25} ${amount:8.2f} {payment:10}")
total += amount
print("-" * 80)
print(f"{'TOTAL':54} ${total:8.2f}")
print("=" * 80)
def view_by_category(self):
"""View expenses grouped by category"""
if not self.expenses:
print("\nNo expenses found.")
return
# Group by category
category_totals = defaultdict(float)
category_expenses = defaultdict(list)
for expense in self.expenses:
category = expense["category"]
category_totals[category] += expense["amount"]
category_expenses[category].append(expense)
print("\n" + "=" * 60)
print("EXPENSES BY CATEGORY")
print("=" * 60)
grand_total = 0
for category in sorted(category_totals.keys()):
total = category_totals[category]
count = len(category_expenses[category])
percentage = (total / sum(category_totals.values()) * 100) if sum(category_totals.values()) > 0 else 0
print(f"\nš {category.upper()}:")
print(f" Total: ${total:.2f} ({percentage:.1f}%)")
print(f" Count: {count} expense(s)")
# Show bar chart
max_bar_length = 30
bar_length = int((percentage / 100) * max_bar_length)
bar = "ā" * bar_length
print(f" {bar} {percentage:.1f}%")
# Show top 3 expenses in this category
sorted_category = sorted(category_expenses[category],
key=lambda x: x["amount"],
reverse=True)[:3]
for exp in sorted_category:
print(f" ⢠{exp['description'][:20]}: ${exp['amount']:.2f} ({exp['date']})")
grand_total += total
print("\n" + "=" * 60)
print(f"GRAND TOTAL: ${grand_total:.2f}")
print("=" * 60)
def view_by_month(self):
"""View expenses grouped by month"""
if not self.expenses:
print("\nNo expenses found.")
return
# Group by month
month_totals = defaultdict(float)
month_expenses = defaultdict(list)
for expense in self.expenses:
date_obj = datetime.strptime(expense["date"], "%Y-%m-%d")
month_key = date_obj.strftime("%Y-%m")
month_totals[month_key] += expense["amount"]
month_expenses[month_key].append(expense)
print("\n" + "=" * 60)
print("EXPENSES BY MONTH")
print("=" * 60)
for month in sorted(month_totals.keys(), reverse=True):
total = month_totals[month]
count = len(month_expenses[month])
# Format month name
month_obj = datetime.strptime(month + "-01", "%Y-%m-%d")
month_name = month_obj.strftime("%B %Y")
print(f"\nš
{month_name}:")
print(f" Total: ${total:.2f}")
print(f" Count: {count} expense(s)")
# Show categories for this month
month_cats = defaultdict(float)
for exp in month_expenses[month]:
month_cats[exp["category"]] += exp["amount"]
# Show top 3 categories
sorted_cats = sorted(month_cats.items(), key=lambda x: x[1], reverse=True)[:3]
for cat, amount in sorted_cats:
percentage = (amount / total * 100) if total > 0 else 0
print(f" ⢠{cat}: ${amount:.2f} ({percentage:.1f}%)")
print("\n" + "=" * 60)
def manage_budget(self):
"""Set and view budget"""
print("\n" + "=" * 50)
print("BUDGET MANAGEMENT")
print("=" * 50)
print("\nCurrent Budget:")
print("-" * 40)
total_budget = 0
for category in self.categories:
budget_amount = self.budget.get(category, 0)
if budget_amount > 0:
print(f"{category:15}: ${budget_amount:.2f}")
total_budget += budget_amount
if total_budget == 0:
print("No budget set yet.")
else:
print(f"\nTotal Monthly Budget: ${total_budget:.2f}")
print("\nOptions:")
print("1. Set budget for a category")
print("2. View budget vs actual")
print("3. Clear all budgets")
print("4. Back to main menu")
choice = input("\nEnter choice: ").strip()
if choice == "1":
print("\nSelect category to set budget:")
for i, category in enumerate(self.categories, 1):
current = self.budget.get(category, 0)
print(f" {i}. {category}: ${current:.2f}")
try:
cat_idx = int(input("\nCategory number: ")) - 1
if 0 <= cat_idx < len(self.categories):
category = self.categories[cat_idx]
amount = float(input(f"Monthly budget for {category}: $"))
self.budget[category] = amount
self.save_budget()
print(f"ā
Budget for {category} set to ${amount:.2f}")
else:
print("Invalid category number.")
except ValueError:
print("Please enter a valid number.")
elif choice == "2":
self.show_budget_vs_actual()
elif choice == "3":
confirm = input("Are you sure you want to clear all budgets? (y/n): ")
if confirm.lower() == 'y':
self.budget = {category: 0 for category in self.categories}
self.save_budget()
print("ā
All budgets cleared.")
elif choice == "4":
return
else:
print("Invalid choice.")
def check_budget_alert(self, category, amount):
"""Check if expense exceeds budget"""
budget_amount = self.budget.get(category, 0)
if budget_amount == 0:
return
# Calculate monthly total for this category
today = datetime.now().date()
month_start = today.replace(day=1)
monthly_total = sum(
e["amount"] for e in self.expenses
if e["category"] == category and
datetime.strptime(e["date"], "%Y-%m-%d").date() >= month_start
)
if monthly_total > budget_amount:
print(f"\nā BUDGET ALERT: You've exceeded your {category} budget!")
print(f" Budget: ${budget_amount:.2f}, Spent: ${monthly_total:.2f}")
print(f" Over budget by: ${monthly_total - budget_amount:.2f}")
elif monthly_total >= budget_amount * 0.8: # 80% of budget
print(f"\nā BUDGET WARNING: You've used {monthly_total/budget_amount*100:.0f}% of your {category} budget")
def show_budget_vs_actual(self):
"""Show budget vs actual spending"""
if not any(self.budget.values()):
print("\nNo budget set yet.")
return
today = datetime.now().date()
month_start = today.replace(day=1)
print("\n" + "=" * 60)
print("BUDGET VS ACTUAL (THIS MONTH)")
print("=" * 60)
print(f"\n{'Category':15} {'Budget':10} {'Spent':10} {'Remaining':12} {'% Used':8}")
print("-" * 60)
total_budget = 0
total_spent = 0
for category in sorted(self.budget.keys()):
budget = self.budget.get(category, 0)
if budget == 0:
continue
# Calculate monthly spending for this category
monthly_spent = sum(
e["amount"] for e in self.expenses
if e["category"] == category and
datetime.strptime(e["date"], "%Y-%m-%d").date() >= month_start
)
remaining = budget - monthly_spent
percent_used = (monthly_spent / budget * 100) if budget > 0 else 0
status = "ā
" if percent_used <= 100 else "ā "
print(f"{category:15} ${budget:8.2f} ${monthly_spent:8.2f} ${remaining:10.2f} {percent_used:7.1f}% {status}")
total_budget += budget
total_spent += monthly_spent
print("-" * 60)
total_remaining = total_budget - total_spent
total_percent = (total_spent / total_budget * 100) if total_budget > 0 else 0
print(f"{'TOTAL':15} ${total_budget:8.2f} ${total_spent:8.2f} ${total_remaining:10.2f} {total_percent:7.1f}%")
print("=" * 60)
if total_percent > 100:
print("\nā You have exceeded your total monthly budget!")
elif total_percent > 80:
print("\nā You have used more than 80% of your monthly budget")
def generate_reports(self):
"""Generate various reports"""
if not self.expenses:
print("\nNo expenses to generate reports.")
return
print("\n" + "=" * 50)
print("REPORTS")
print("=" * 50)
print("\nAvailable Reports:")
print("1. Monthly Summary Report")
print("2. Category Analysis Report")
print("3. Spending Trends (Last 6 Months)")
print("4. Top Expenses Report")
print("5. Payment Method Analysis")
try:
choice = input("\nSelect report (1-5): ").strip()
if choice == "1":
self.monthly_summary_report()
elif choice == "2":
self.category_analysis_report()
elif choice == "3":
self.spending_trends_report()
elif choice == "4":
self.top_expenses_report()
elif choice == "5":
self.payment_method_report()
else:
print("Invalid choice.")
except Exception as e:
print(f"Error generating report: {e}")
def monthly_summary_report(self):
"""Generate monthly summary report"""
today = datetime.now().date()
current_month = today.strftime("%Y-%m")
# Get current month expenses
month_expenses = [e for e in self.expenses
if e["date"].startswith(current_month)]
if not month_expenses:
print(f"\nNo expenses for {current_month}")
return
total = sum(e["amount"] for e in month_expenses)
avg_per_day = total / today.day if today.day > 0 else total
print(f"\nš MONTHLY SUMMARY REPORT - {current_month}")
print("=" * 60)
print(f"Total Expenses: ${total:.2f}")
print(f"Number of Expenses: {len(month_expenses)}")
print(f"Average per Day: ${avg_per_day:.2f}")
# Projected monthly total
days_in_month = 30 # Simplified
projected_total = (total / today.day * days_in_month) if today.day > 0 else total
print(f"Projected Monthly Total: ${projected_total:.2f}")
# Compare with previous month
prev_month_date = today.replace(day=1) - timedelta(days=1)
prev_month = prev_month_date.strftime("%Y-%m")
prev_month_expenses = [e for e in self.expenses
if e["date"].startswith(prev_month)]
if prev_month_expenses:
prev_total = sum(e["amount"] for e in prev_month_expenses)
change = total - prev_total
change_percent = (change / prev_total * 100) if prev_total > 0 else 0
change_symbol = "ā²" if change > 0 else "ā¼"
print(f"\nComparison with {prev_month}:")
print(f" Previous Month: ${prev_total:.2f}")
print(f" Change: {change_symbol}${abs(change):.2f} ({change_percent:+.1f}%)")
print("=" * 60)
def search_expenses(self):
"""Search expenses by keyword"""
if not self.expenses:
print("\nNo expenses to search.")
return
keyword = input("\nSearch keyword (description/category): ").lower().strip()
if not keyword:
print("Please enter a keyword.")
return
results = []
for expense in self.expenses:
if (keyword in expense["description"].lower() or
keyword in expense["category"].lower()):
results.append(expense)
if results:
print(f"\nFound {len(results)} expense(s) matching '{keyword}':")
self.view_all_expenses(results)
else:
print(f"\nNo expenses found matching '{keyword}'.")
def export_data(self):
"""Export data to CSV"""
if not self.expenses:
print("\nNo expenses to export.")
return
filename = f"expenses_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['id', 'date', 'description', 'amount', 'category', 'payment_method', 'added_date']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for expense in self.expenses:
writer.writerow(expense)
print(f"\nā
Expenses exported to {filename}")
print(f"Total records: {len(self.expenses)}")
def delete_expense(self):
"""Delete an expense"""
if not self.expenses:
print("\nNo expenses to delete.")
return
self.view_all_expenses()
try:
expense_id = int(input("\nEnter expense ID to delete: "))
for i, expense in enumerate(self.expenses):
if expense["id"] == expense_id:
confirm = input(f"Delete '{expense['description']}' (${expense['amount']})? (y/n): ")
if confirm.lower() == 'y':
deleted = self.expenses.pop(i)
self.save_expenses()
print(f"\nā
Expense deleted: {deleted['description']}")
# Reassign IDs
for j, exp in enumerate(self.expenses, 1):
exp["id"] = j
self.save_expenses()
return
print("\nā Expense not found.")
except ValueError:
print("\nā Please enter a valid number.")
def run(self):
"""Run the expense tracker"""
print("\nš° Welcome to Expense Tracker!")
print("Track your expenses and manage your budget.")
while True:
self.display_menu()
try:
choice = input("\nEnter your choice (1-10): ").strip()
if choice == "1":
self.add_expense()
elif choice == "2":
self.view_all_expenses()
elif choice == "3":
self.view_by_category()
elif choice == "4":
self.view_by_month()
elif choice == "5":
self.manage_budget()
elif choice == "6":
self.generate_reports()
elif choice == "7":
self.search_expenses()
elif choice == "8":
self.export_data()
elif choice == "9":
self.delete_expense()
elif choice == "10":
print("\nš Thank you for using Expense Tracker!")
print("Your financial data has been saved.")
break
else:
print("\nā Invalid choice. Please enter 1-10.")
except KeyboardInterrupt:
print("\n\nš Goodbye!")
break
except Exception as e:
print(f"\nā An error occurred: {e}")
# Run the application
if __name__ == "__main__":
tracker = ExpenseTracker()
tracker.run()