commit 1619ee85616935b439137d0c7072df94a63505e3 Author: Luís Murta Date: Thu Dec 3 22:39:55 2020 +0000 Initial commit Working csv reader, which reorders the transactions on transaction.csv based on date. Then it aggregates the information per month based on the used categories, and prints that information. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69cfeb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,150 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# profiling data +.prof + +# End of https://www.toptal.com/developers/gitignore/api/python + +### Default user directories +data/ +raw/ +*.pickle +transactions.csv diff --git a/reader.py b/reader.py new file mode 100644 index 0000000..e506973 --- /dev/null +++ b/reader.py @@ -0,0 +1,255 @@ +from decimal import Decimal +import csv +import datetime +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.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) + + 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() + monthly_categories = list() + incomes = list() + expenses = list() + for month in range(1, 7): + month_transactions = MonthlyTransactions( + month, get_month_transactions(transactions, month) + ) + monthly_transactions.append(month_transactions) + + print(month_transactions) + + 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 :(")