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.
This commit is contained in:
Luís Murta 2020-12-03 22:39:55 +00:00
commit 1619ee8561
Signed by: satprog
GPG Key ID: DDF2EFC6179009DC
2 changed files with 405 additions and 0 deletions

150
.gitignore vendored Normal file
View File

@ -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

255
reader.py Normal file
View File

@ -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 :(")