Created
July 22, 2014 12:28
-
-
Save HeinrichHartmann/59f1b3464a2085ee9514 to your computer and use it in GitHub Desktop.
Bank Account Analytics for Sparda-Bank csv files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Computes a summary of (unique) bank transactions from CSV files | |
USAGE: | |
python account-summary.py account_statment1.csv account_statement2.csv ... | |
OUTPUT: | |
CSV printed to stdout with the following colums | |
ACCOUNT, | |
DATE(Wertstellungstag), | |
DESCRIPTION(Verwendungszweck), | |
VALUE(Umsatz), | |
SALDO | |
(C) Heinrich Hartmann, 2014 | |
""" | |
from __future__ import print_function | |
from datetime import datetime | |
import re | |
import sys | |
import functools as ft | |
ERR = ft.partial(print, file=sys.stderr) | |
PRINT = ft.partial(print, file=sys.stdout) | |
# | |
# Account Management | |
# | |
class Transaction(object): | |
""" | |
Bank Transaction class | |
Stores value as int in cents | |
""" | |
# Attributes | |
date = None # : datetime | |
description = None # : str | |
value_cnt = None # : int in CNT | |
def __init__(self, date, description, value_eur): | |
""" | |
Create transaction object | |
Arguments: | |
date: datetime -- date of transaction | |
descripiton: str -- textual description | |
value_eur: float -- value of transaction in EUR | |
""" | |
self.date = type_check(datetime, date) | |
self.description = type_check(str, description) | |
self.value_cnt = int(100 * type_check(float, value_eur)) | |
def get_date(self, pattern = "%Y-%m-%d"): | |
return self.date.strftime("%Y-%m-%d") | |
def get_datetime(self): | |
return self.date | |
def get_cnt(self): | |
return self.value_cnt | |
def get_eur(self): | |
return float(self.value_cnt) / 100 | |
def get_description(self): | |
return self.description | |
def __str__(self): | |
return "".join([ | |
"Transaction: ", | |
self.get_date() + "\t", | |
self.get_description() + "\t", | |
str(self.get_eur()) + " EUR" | |
]) | |
def uid(self): | |
return self.__str__() | |
class Account(object): | |
""" | |
Stores a list of transactions. | |
Checks for unicity on add. | |
""" | |
T_set = set([]) | |
T_list = [] | |
def __init__(self, name): | |
self.name = name | |
def add_transaction(self, transaction): | |
""" | |
Add (unique) transaction to account. | |
""" | |
assert_type(Transaction, transaction) | |
if transaction.uid() in self.T_set: | |
ERR("Transaction allready added", transaction) | |
return | |
self.T_set.add(transaction.uid()) | |
self.T_list.append(transaction) | |
def print_transactions(self, stream=sys.stdout): | |
""" | |
Prints all transactions ot stream = sys.stdout | |
""" | |
# sort by date | |
self.T_list.sort(key=lambda t : t.get_datetime()) | |
saldo_cnt = 0 | |
for transaction in self.T_list: | |
saldo_cnt += transaction.get_cnt() | |
print("\t".join( | |
[ | |
transaction.get_date(), | |
str(transaction.get_eur()), | |
str(saldo_cnt/100.), | |
transaction.get_description() | |
] | |
), file=stream) | |
# | |
# Sparda Format Conversion | |
# | |
class SpardaFileParser(object): | |
""" | |
Parses a file downloaded from Sparda Web Interface | |
""" | |
file_handle = None # : file -- file handle | |
def __init__(self, file_handle): | |
self.file_handle = file_handle | |
def __iter__(self): | |
self.file_handle.seek(0) | |
for i, line in enumerate(self.file_handle): | |
if i < 6: # 5 line headder | |
continue | |
if line.startswith("*"): # remark in the last row | |
break | |
yield self.parse_sparda_row(line) | |
@classmethod | |
def parse_date(cls, r_date): | |
day, month, year = re.findall("(\d{2}).(\d{2}).(\d{4})", r_date)[0] | |
return datetime(int(year), int(month), int(day)) | |
@classmethod | |
def parse_sparda_row(cls, line): | |
""" | |
Arguments: | |
line: string -- transation row in sparda fromat | |
Returns: | |
date: datetime -- date of transaction | |
descr: string -- textual description | |
value: int -- value in EUR-cnts | |
""" | |
fields = line.split("\t") | |
fields[4] = fields[4].strip("\n") | |
r_date, _, r_description, r_value, _ = fields | |
value = float(r_value.replace(",",".")) | |
date = cls.parse_date(r_date) | |
return Transaction(date, r_description, value) | |
# | |
# Typing Helper | |
# | |
def assert_type(type_obj, obj): | |
"Raises Exception if obj is not of type type_obj." | |
if not type(obj) is type_obj: | |
raise Exception("Type Mismatch") | |
# TODO: raise proper exception | |
def type_check(type_obj, obj): | |
"Returns obj if type matches type_obj." | |
assert_type(type_obj, obj) | |
return obj | |
# | |
# Control Flow | |
# | |
if __name__ == "__main__": | |
account = Account("") | |
for path in sys.argv[1:]: | |
ERR("Inserting file", path) | |
for transaction in SpardaFileParser(open(path)): | |
account.add_transaction(transaction) | |
account.print_transactions() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment