from decimal import Decimal import csv import datetime import matplotlib.pyplot as plt import sys class Transaction: def __init__(self, date, description, value, category): self.id = id(self) self.date = date self.description = description self.value = value self.category = category def __repr__(self): return f"{self.date.date()} {self.description} {self.value} € {self.category}" class MonthlyTransactions: def __init__(self, month, transactions): self.month = datetime.datetime.strptime(str(month), "%m") self.transactions = transactions income_categories = [ "Income1", "Income2", "Income3", ] fixed_expenses_categories = [ "Rent", "Commmute", "Utilities", ] variable_expenses_categories = [ "Groceries", "Eating Out", "Entertainment", "Pets", "Travel", "Miscellaneous", ] self.expense_categories = ( fixed_expenses_categories + variable_expenses_categories ) self.income_per_cat = dict.fromkeys(income_categories, 0) self.fixed_expenses_per_cat = dict.fromkeys(fixed_expenses_categories, 0) self.variable_expenses_per_cat = dict.fromkeys(variable_expenses_categories, 0) self.null = 0 self.investments = 0 self.separate_categories(self.transactions) self.expenses_per_cat = { **self.income_per_cat, **self.fixed_expenses_per_cat, **self.variable_expenses_per_cat, } def separate_categories(self, transactions): for transaction in transactions: if transaction.category == "Null": self.null += transaction.value continue if transaction.category == "Investment": self.investments += transaction.value continue try: self.income_per_cat[transaction.category] -= transaction.value continue except KeyError: pass try: self.fixed_expenses_per_cat[transaction.category] += transaction.value continue except KeyError: pass try: self.variable_expenses_per_cat[ transaction.category ] += transaction.value continue except KeyError as e: if ", " in transaction.category: categories = transaction.category.split(", ") print(f"{transaction} has two categories. Allocate each.") values = [] while transaction.value != sum(values): for category in categories: value = Decimal(input(f"Category {category}: ")) values.append(value) new_transactions = [] for value, category in zip(values, categories): new_transactions.append( Transaction( transaction.date, transaction.description, value, category, ) ) self.separate_categories(new_transactions) else: print(repr(e)) print(transaction) sys.exit(2) def income(self): return sum(self.income_per_cat.values()) def fixed_expenses(self): return sum(self.fixed_expenses_per_cat.values()) def variable_expenses(self): return sum(self.variable_expenses_per_cat.values()) def expenses(self): return self.fixed_expenses() + self.variable_expenses() def __repr__(self): info = [] for k, v in self.income_per_cat.items(): info.extend([k, v]) for k, v in self.fixed_expenses_per_cat.items(): info.extend([k, v]) for k, v in self.variable_expenses_per_cat.items(): info.extend([k, v]) p = """ \t\t\t\t{0} Report Income\t\t\tFixed Expenses\t\tVariable Expenses {1}\t{2}\t{11}\t\t{12}\t{25}\t\t{26} {3}\t{4}\t{13}\t{14}\t{27}\t{28} {5}\t{6}\t{15}\t{16}\t{29}\t{30} {7}\t{8}\t{17}\t\t{18}\t{31}\t{32} {9}\t{10}\t{19}\t\t{20}\t{33}\t{34} \t\t\t{21}\t\t{22}\t{35}\t\t{36} \t\t\t{23}\t{24}\t{37}\t\t{38} \t\t\t\t\t\t{39}\t\t{40} \t\t\t\t\t\t{41}\t\t{42} \t\t\t\t\t\t{43}\t\t{44} \t\t\t\t\t\t{45}\t\t{46} \t\t\t\t\t\t{47}\t{48} \t\t\t\t\t\t{49}\t\t{50} \t\t\t\t\t\t{51}\t{52} \t\t{53}\t\t\t{54}\t\t\t{55} Expenses:\t{56} Net:\t\t{57} """.format( self.month.strftime("%B"), *info, self.income(), self.fixed_expenses(), self.variable_expenses(), self.expenses(), self.income() - self.expenses(), ) return p def get_transactions(csvfile): with open(csvfile, newline="") as fp: reader = csv.reader(fp, delimiter="\t") transactions = [] for transaction in reader: try: # date = datetime.datetime.strptime(transaction[0], "%Y-%m-%d") date = datetime.datetime.strptime(transaction[0], "%d/%m/%Y") description = transaction[1] value = Decimal(transaction[2]) category = transaction[3] transactions.append(Transaction(date, description, value, category)) except Exception as e: print(repr(e)) print(transaction) sys.exit(2) return transactions def reorder_transactions(transactions): return sorted(transactions, key=lambda transaction: transaction.date) def write_transactions(csvfile, transactions): with open(csvfile, "w", newline="") as fp: writer = csv.writer(fp, delimiter="\t") for t in transactions: writer.writerow([t.date.date(), t.description, t.value, t.category]) def get_month_transactions(transactions, month): month_transactions = [] for transaction in transactions: if transaction.date.month == month: month_transactions.append(transaction) return month_transactions def get_value_per_category(transactions): categories = dict() for transaction in transactions: try: categories[transaction.category] += transaction.value except KeyError: categories[transaction.category] = transaction.value return categories def split_income_expenses(value_per_category): income = dict() expenses = dict() for category, value in value_per_category.items(): if category.startswith("Income"): income[category] = -value elif category == "Investment": pass else: expenses[category] = value return income, expenses if __name__ == "__main__": transactions = get_transactions("transactions.csv") transactions = reorder_transactions(transactions) write_transactions("transactions_ordered.csv", transactions) monthly_transactions = list() for month in range(1, 7): month_transactions = MonthlyTransactions( month, get_month_transactions(transactions, month) ) monthly_transactions.append(month_transactions) print(month_transactions) x = range(1, 7) y_income = [float(month.income()) for month in monthly_transactions] y_fixed_expenses = [float(month.fixed_expenses()) for month in monthly_transactions] y_variable_expenses = [ float(month.variable_expenses()) for month in monthly_transactions ] y = [] labels = monthly_transactions[0].expense_categories for label in labels: category = [] for month in monthly_transactions: category.append(float(month.expenses_per_cat[label])) y.append(category) print(y) no_negatives = False while not no_negatives: no_negatives = True for category in y: for month in range(0, 6): if category[month] < 0: category[month - 1] += category[month] category[month] = 0 no_negatives = False print(y) print(labels) plt.plot(x, y_income, label="Income") plt.stackplot(x, y, labels=labels) plt.legend(loc="upper left") plt.show() total_income = sum(month.income() for month in monthly_transactions) total_expenses = sum(month.expenses() for month in monthly_transactions) if total_income - total_expenses > 0: print(f"\nWe're {total_income - total_expenses} richer!") else: print(f"We're {total_expenses - total_income} poorer :(")