Created
July 14, 2015 05:48
-
-
Save acutesoftware/3f70f952d51c161f335d to your computer and use it in GitHub Desktop.
Combine bank statements and map to tax categories to simplify Australian Tax returns
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
#!/usr/bin/python3 | |
# -*- coding: utf-8 -*- | |
# tax.py written by Duncan Murray 14/7/2015 | |
# | |
# Step 1 - download all your bank statements in CSV format | |
# then update the raw_files dictionary below | |
# | |
# Step 2 - create a mappings.csv file which maps the start of a | |
# a details column to a mapping, e.g. | |
# Details_string Maps_to Deductible | |
# cc t'fer Bank Fees N | |
# VISA-ASIC Business Renewal Y | |
# Wdl ATM CBA ATM Cash Withdrawal N | |
# MSY TECHNOLOGY SA Computer Y | |
# VISA-STEAMGAMES.COM Entertainment N | |
# Debit Adjustment Fee N | |
# | |
# Step 3 - run this program which concatenates your bank statements | |
# and adds categories for each mapping line | |
raw_files = [ | |
{'name':'Comm_keycard', 'src':'Comm', 'fname':'CSVData_Comm_Keycard.csv'}, | |
{'name':'Comm_mastercard', 'src':'Comm', 'fname':'CSVData_Comm_Mastercard.csv'}, | |
{'name':'CPS_S1', 'src':'CPS', 'fname':'StatementCsv_S1.csv'}, | |
{'name':'CPS_S2', 'src':'CPS', 'fname':'StatementCsv_S2.csv'}, | |
] | |
def main(): | |
map = read_mappings('mappings.csv') | |
dat = read_csv(map) | |
save_data(dat) | |
def save_data(dat): | |
""" | |
save the combined files as CSV | |
""" | |
with open('bank_statements_2015_2016.csv', 'w') as f: | |
f.write('src,date,amount,details,balance,type,category,is_deductible,is_income\n') | |
for row in dat: | |
for col in row: | |
if type(col) is str: | |
f.write(col + ', ') | |
else: | |
f.write(str(col) + ', ') | |
f.write('\n') | |
def read_csv(m): | |
lst = [] | |
for fd in raw_files: | |
with open(fd['fname'], 'r') as f: | |
for num, line in enumerate(f): | |
if line[0:28] == 'Effective Date,Entered Date,': | |
pass | |
else: | |
dte, amt, det, cat = extract_cols(fd, line) | |
cat1, cat2, is_ded, is_inc = get_tax_cat(line, amt, det, m) | |
lst.append([fd['src'], dte, amt, det, cat, cat1, cat2, is_ded, is_inc]) | |
return lst | |
def get_tax_cat(line, amount, det, m): | |
""" | |
takes a list of mappings 'm' and uses this to | |
get a category | |
""" | |
cat_broad = 'Expense' | |
cat_narrow = '' | |
is_deductable = False | |
is_income = False | |
if amount > 0: | |
is_income = True | |
cat_broad = 'Income' | |
for map_line in m: | |
map_str = map_line[0][0].strip() + ' ' | |
tst_str = det[0:len(map_str)].strip() + ' ' | |
if tst_str == map_str: | |
cat_narrow = map_line[0][1] | |
return cat_broad, cat_narrow, is_deductable, is_income | |
def extract_cols(fd, line): | |
""" | |
parses the columns from 2 sources into a standard form | |
You will need to modify this if your bank has different | |
formats. | |
Column Headers: | |
CSVData_Comm_Keycard = date,amount,details | |
CSVData_Comm_Mastercard = date,amount,details | |
StatementCsv_S1 = Effective Date,Entered Date,Transaction Description,Amount,Balance | |
StatementCsv_S2 = Effective Date,Entered Date,Transaction Description,Amount,Balance | |
""" | |
dte = '' | |
amt = 0 | |
det = '' | |
cat = '' | |
cols = line.split(',') | |
if fd['src'] == 'Comm': | |
dte = cols[0].strip('"').strip() | |
amt = float(cols[1].strip('"').strip() ) | |
det = cols[2].strip('"').strip() | |
cat = cols[3].strip('"').strip() | |
elif fd['src'] == 'CPS': | |
dte = cols[1].strip('"').strip() | |
amt = float(cols[3].strip('"').strip()) | |
det = cols[2].strip('"').strip() | |
return dte, amt, det, cat | |
def read_mappings(fname): | |
""" | |
read a mapping file to see how to map details. Format is | |
Details_string Maps_to Deductible | |
cc t'fer Bank Fees N | |
VISA-ASIC Business Renewal Y | |
Wdl ATM CBA ATM Cash Withdrawal N | |
""" | |
maps = [] | |
with open(fname, 'r') as f: | |
for line in f: | |
if line.strip() != ',,': | |
cols = line.split(',') | |
maps.append([cols]) | |
return maps | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment