From 7aa2260bfde8b637bdf5aeb652740138f99f974d Mon Sep 17 00:00:00 2001 From: John Lamb Date: Mon, 24 Feb 2025 21:57:36 -0600 Subject: [PATCH] initial --- .gitignore | 6 ++++ process_csv.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ tx_categories.py | 85 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 .gitignore create mode 100644 process_csv.py create mode 100644 tx_categories.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e5c1fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*/.DS_Store/* +.env +*/.idea/* +CapitalOne.csv +Chase.csv +*/__pychache__/* diff --git a/process_csv.py b/process_csv.py new file mode 100644 index 0000000..c4b1bf5 --- /dev/null +++ b/process_csv.py @@ -0,0 +1,90 @@ +import logging +import os +import csv +import subprocess + +from datetime import date, datetime +from collections import namedtuple + +from tx_categories import TX_TO_CATEGORY, Category + +logging.basicConfig(level=logging.INFO) +# poetry run python process_csv.py +# DESIRED_MONTH = date.today().month +DESIRED_MONTH = 2 +CSV_TX = namedtuple('CSV_TX', ['TX_DATE', 'POST_DATE', 'DESCRIPTION', 'CATEGORY', 'TYPE', 'AMOUNT', 'MEMO']) + +class Transaction: + def __init__(self, date, payee, type, amount): + # self.date = date.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('America/Chicago')) + self.date = date + self.payee = payee + self.type = type + self.amount = amount + self.category = self.get_category() + + def get_category(self): + category = None + for agent, cat in TX_TO_CATEGORY.items(): + if agent.lower() in self.payee.lower(): + category = cat + + return category or Category.OTHER + +def load_chase_csv(): + working_dir = os.path.abspath(os.getcwd()) + filenames = os.listdir(working_dir) + tx_filename = None + for filename in filenames: + if filename.lower().startswith("chase"): + tx_filename = os.path.join(working_dir, filename) + break + + if not tx_filename: + exit("Could not find tx file") + + with open(tx_filename) as csvfile: + reader = csv.reader(csvfile, delimiter=',', quotechar='|') + output = list(reader) + + all_transactions = [] + for raw_tx in output[1:]: + all_transactions.append(CSV_TX(*raw_tx)) + + return all_transactions + +def get_transactions(all_transactions): + formatted_transactions = [] + for tx in all_transactions: + tx_post_date = datetime.strptime(tx.POST_DATE, '%m/%d/%Y') + if tx_post_date.month != DESIRED_MONTH or tx_post_date.year != date.today().year: + continue + cc_trans = Transaction( + date=tx_post_date, + payee=tx.DESCRIPTION, + type=tx.TYPE, + amount=tx.AMOUNT + ) + + formatted_transactions.append(cc_trans) + + sorted_transactions = sorted(formatted_transactions, key=lambda x: x.date, reverse=False) + return sorted_transactions + +def print_transactions(transactions): + for tx in transactions: + print(f"{tx.date} - {tx.payee} - {tx.amount} - {tx.category}") + +def convert_tx_to_csv(transactions): + output = "" + for tx in transactions: + output += f"{tx.date.strftime('%m/%d/%Y')},{tx.payee},{tx.amount},{tx.category.value}\n" + return output + +if __name__ == "__main__": + all_transactions = load_chase_csv() + transactions = get_transactions(all_transactions) + print_transactions(transactions) + output = convert_tx_to_csv(transactions) + subprocess.run("pbcopy", universal_newlines=True, input=output) + print("TXs copied to clipboard!") diff --git a/tx_categories.py b/tx_categories.py new file mode 100644 index 0000000..a331a48 --- /dev/null +++ b/tx_categories.py @@ -0,0 +1,85 @@ +from enum import Enum + +class Category(Enum): + RECURRING = "Recurring" + FOOD = "Food" + GROCERY = "Groceries" + CLOTHING = "Clothing" + OTHER = "Other" + TRAVEL = "Vacation" + GAS = "Gas" + CAR = "Car" + EXCLUDED = "N/A" + MEDICAL = "Medical" + UTILITIES = "Utilities" + COUNSELING = "Counseling" + +TX_TO_CATEGORY = { + "NYTimes": Category.RECURRING, + "Easy Tiger": Category.FOOD, + "Doordash": Category.FOOD, + "H-E-B": Category.GROCERY, + "HEB": Category.GROCERY, + "its organic": Category.GROCERY, + "Poshmark": Category.CLOTHING, + "Apple": Category.RECURRING, + "AMZN": Category.OTHER, + "farmbox delivery": Category.GROCERY, + "central market": Category.GROCERY, + "black star": Category.FOOD, + "Southwes": Category.TRAVEL, + "Honest marys": Category.FOOD, + "Cabo Bobs": Category.FOOD, + "disneyplus": Category.RECURRING, + "peloton": Category.RECURRING, + "ATT*Bill": Category.UTILITIES, + "google *fiber": Category.UTILITIES, + "Sunrise citgo mini mart": Category.OTHER, + "celis": Category.FOOD, + "barrett?s": Category.FOOD, + "cava": Category.FOOD, + "shell oil": Category.GAS, + "American eagle": Category.CLOTHING, + "turnstile": Category.FOOD, + "turo": Category.TRAVEL, + "o'reilly auto parts": Category.CAR, + "modern market": Category.FOOD, + "enchiladas-y-mas": Category.FOOD, + "spotify": Category.RECURRING, + "automatic payment": Category.EXCLUDED, + "fresh plus": Category.GROCERY, + "fwb hancock": Category.FOOD, + "vuori": Category.CLOTHING, + "american ai": Category.TRAVEL, + "titayas": Category.FOOD, + "central machine work": Category.FOOD, + "lazarus brewing": Category.FOOD, + "nervous charlies": Category.FOOD, + "trader joe": Category.GROCERY, + "peached tortilla": Category.FOOD, + "that burger": Category.FOOD, + "BROTZMAN SPORTS MEDICINE": Category.MEDICAL, + "Jack allens kitchen": Category.FOOD, + "airbnb": Category.TRAVEL, + "buddys burgers": Category.TRAVEL, + "SHELLEY GAUNTT MOELLER": Category.COUNSELING, + "marine layer": Category.CLOTHING, + "disney plus": Category.RECURRING, + "tylers": Category.CLOTHING, + "state farm insurance": Category.RECURRING, + "lululemon": Category.CLOTHING, + "chick-fil-a": Category.FOOD, + "going.com": Category.RECURRING, + "progressive ins": Category.RECURRING, + "VERACRUZALLNATURA.COM": Category.FOOD, + "epoch coffee": Category.FOOD, + "Mondo sports": Category.MEDICAL, + "tri county practice": Category.MEDICAL, + "aspire fertility": Category.MEDICAL, + "juiceland": Category.FOOD, + "MONDOSPORTSTHERAPY": Category.MEDICAL, + "chatgpt": Category.RECURRING, + "WWW.PERPLEXITY.AI": Category.RECURRING, + "payment thank you": Category.EXCLUDED, + "public storage": Category.RECURRING, +}