-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtransactions.py
221 lines (186 loc) · 9.94 KB
/
transactions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#!/home/mice/anaconda3/envs/wealthtracker/bin/python
import psycopg2 as psql
import pandas as pd
import configparser
import requests
import os
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import numpy as np
def chart_cashflows(cfs):
plt.bar(cfs.keys(), cfs.values())
plt.show()
class Cashflows:
def __init__(self, configfile):
self.accounts_cols = ['account_id', 'account_type', 'bank', 'status', 'joint']
self.transactions_cols = ['id', 'account_id', 'transaction_date', 'category', 'description', 'amount']
self.expense_types_cols = ['id', 'category', 'target']
psql_config = configparser.ConfigParser()
psql_config.read(configfile)
local_ip = psql_config['myip']['localip']
current_ip = requests.get('https://api.ipify.org/').text
location = 'outside' if local_ip != current_ip else ''
conn_location = ''.join(['postgresql', location])
self.conn = psql.connect(host=psql_config[conn_location]['host'],
port=psql_config[conn_location]['port'],
database=psql_config[conn_location]['database'],
user=psql_config[conn_location]['user'],
password=psql_config[conn_location]['password'])
def execute_command(self, command):
with self.conn:
with self.conn.cursor() as curs:
curs.execute(command)
return curs.fetchall()
def get_table_range(self, table, date_cat, fro, to):
command = f"""SELECT * from {table}
WHERE {date_cat} BETWEEN '{fro}'::date AND '{to}'::date
ORDER BY {date_cat}"""
return self.execute_command(command)
def get_cashflows(self, fro=None, to=None):
tz = 'America/Edmonton'
# Convert fro and to to datetime
fro = pd.Timestamp('2021-04-01', tz=tz) if fro is None else pd.Timestamp(fro, tz=tz)
to = pd.Timestamp(datetime.now().date(), tz=tz) if to is None else pd.Timestamp(to, tz=tz)
# We need to put the start and end dates to first and last of each respective month,
# not where indicated by fro and to. Manipulate the dates to get those dates
monthly = pd.date_range(start=fro, end=to, freq='M', tz=tz)
dates_start = []
dates_end = []
# Append the first day of the month of 'fro', in case 'fro' was entered in the middle
dates_start.append(fro - timedelta(days=(fro.day - 1)))
for date in monthly:
dates_end.append(date)
dates_start.append(date + timedelta(days=1))
# Append the last day of the month of 'to', not 'to' itself. This will prevent incomplete cashflow calculations
dates_end.append(pd.date_range(start=to, end=to + timedelta(days=(32 - to.day)),
freq='M', tz=tz)[0])
# Retrieve transaction data based on calculated dates and input to DataFrame
df = pd.DataFrame(self.get_table_range('transactions', 'transaction_date', dates_start[0], dates_end[-1]),
columns=self.transactions_cols)
df.drop(columns='id', inplace=True) # Drop id, it's useless
# Covert dates to datetime type with local tz
df['transaction_date'] = pd.to_datetime(df['transaction_date']).dt.tz_localize(tz)
# # Self Generated Histogram
# cashflows = {}
# for start, end in zip(dates_start, dates_end):
# # We want to mask by dates, and filter out expense_types: credit_payments, admin, ignore
# mask = (df['transaction_date'] >= start) & (df['transaction_date'] <= end) & \
# (df['category'] != 'admin') & \
# (df['category'] != 'credit_payments') & \
# (df['category'] != 'ignore')
#
# # Create dict entry by Month Year and net cashflow
# cashflows[start.strftime('%B %Y')] = df[mask]
# Generate Histogram
hist_bin = dates_start + [dates_end[-1]] # Generate bins for histogram
# Generate Masks and get raw data in one dataframe
mask = (df['category'] != 'admin') & \
(df['category'] != 'credit_payments') & \
(df['category'] != 'ignore')
return df[mask], hist_bin
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.conn.close()
class Transactions:
def __init__(self, configfile):
self.expense_types = None
self.expense_specifics = None
self.cat_path = "categories.cfg"
self.refresh_categories()
self.accounts_cols = ['account_id', 'account_type', 'bank', 'status', 'joint']
self.transactions_cols = ['id', 'account_id', 'transaction_date', 'category', 'description', 'amount']
self.expense_types_cols = ['id', 'category', 'target']
psql_config = configparser.ConfigParser()
psql_config.read(configfile)
local_ip = psql_config['myip']['localip']
current_ip = requests.get('https://api.ipify.org/').text
location = 'outside' if local_ip != current_ip else ''
conn_location = ''.join(['postgresql', location])
self.conn = psql.connect(host=psql_config[conn_location]['host'],
port=psql_config[conn_location]['port'],
database=psql_config[conn_location]['database'],
user=psql_config[conn_location]['user'],
password=psql_config[conn_location]['password'])
def refresh_categories(self):
cat_cfg = configparser.ConfigParser()
cat_cfg.read(self.cat_path)
self.expense_types = list(cat_cfg['Types'])
self.expense_specifics = dict(cat_cfg['Specifics'])
def update_categories(self, line):
with open(self.cat_path, 'a') as file:
file.write(line + '\n')
def execute_command(self, command):
with self.conn:
with self.conn.cursor() as curs:
curs.execute(command)
return curs.fetchall()
def resolve_uncategorized(self):
""" User to resolve any uncategorized Transactions in Database """
try:
command = "SELECT * FROM transactions WHERE category = 'uncategorized';"
curr_uncat = self.execute_command(command)
for trans in curr_uncat:
chosen_cat = input(f"Choose for {trans[-2]} -> {trans[-1]}\nFrom {self.expense_types}\nInput -> ")
new_cat = [categ for categ in self.expense_types if categ in chosen_cat]
command = f"UPDATE transactions SET category = '{new_cat[0]}' WHERE id = {trans[0]} RETURNING *"
self.execute_command(command)
to_update = input(f"Choose to update categories? [Y/N]")
if 'y' in to_update.lower(): # Need to update list
chosen_keyword = input("Choose Keyword to add: ")
self.update_categories(f"{chosen_keyword} = {chosen_cat}")
self.refresh_categories()
except Exception as e:
print(e)
def insert_transactions(self):
""" Insert Transactions into Database using CSV in transaction_records folder
Name of CSV shall be:
'AccountNumber'.csv
Structure of CSV shall be:
Date | Description | Debit Amount | Credit Amount
No header line in CSV
"""
try:
all_files = os.listdir("transaction_records")
for each_file in all_files:
file_path = os.path.join("transaction_records", each_file)
with open(file_path, 'r') as file:
for line in file.readlines():
transaction = [itm for itm in line.strip().split(',')]
transaction[1] = transaction[1].replace("'", "").strip() # Cleanup
account_id = each_file.split('.')[0]
transaction_date = datetime.strptime(transaction[0], '%m/%d/%Y')
index = [cat for cat in self.expense_specifics.keys() if cat in transaction[1].lower()]
category = self.expense_specifics[index[0]] if len(index) > 0 else 'uncategorized'
description = transaction[1]
if len(transaction[2]) > 0: # Debit, use negative number
amount = -1 * float(transaction[2])
elif len(transaction[3]) > 0: # Credit, use positive number
amount = float(transaction[3])
else:
amount = 0.0
command = f"""INSERT INTO transactions (account_id,transaction_date,category,description,amount)
VALUES ('{account_id}','{transaction_date}','{category}','{description}','{amount}')
RETURNING *"""
self.execute_command(command)
# Delete transaction file as its no longer needed
os.remove(file_path)
# Update last_updated date in table accounts
command = f"""SELECT transaction_date from transactions WHERE account_id = '{account_id}'
ORDER BY transaction_date desc limit 1"""
last_date, = self.execute_command(command).pop()
command = f"""UPDATE accounts
SET last_updated = '{last_date}'::date
WHERE account_id = '{account_id}'
RETURNING *"""
self.execute_command(command)
except Exception as e:
print(e)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.conn.close()
def main():
pass
if __name__ == '__main__':
main()