diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__pycache__/last_month.cpython-313.pyc b/__pycache__/last_month.cpython-313.pyc new file mode 100644 index 0000000..ef9d9e3 Binary files /dev/null and b/__pycache__/last_month.cpython-313.pyc differ diff --git a/last_month.py b/last_month.py index 37f50e7..11e3d4d 100644 --- a/last_month.py +++ b/last_month.py @@ -13,9 +13,14 @@ import pprint pp=pprint.PrettyPrinter(indent=4, sort_dicts=False) load_dotenv() THISMONTH = datetime.now() +# datestring= "2025-08-06 13:57:57" +# format = '%Y-%m-%d %H:%M:%S' +# THISMONTH = datetime.strptime(datestring, format) MONTHMINUSONE = THISMONTH - relativedelta(months=1) MONTHMINUSTWO = THISMONTH - relativedelta(months=2) -ONBUDGETACCOUNTS = os.getenv('ONBUDGETACCOUNTS') +LASTM_STRING = datetime.strftime(MONTHMINUSONE, '%B') +TWOM_STRING = datetime.strftime(MONTHMINUSTWO, '%B') +ONBUDGETACCOUNTS = list(os.getenv('ONBUDGETACCOUNTS')) def compare_months(actual): budget_one = main(actual, MONTHMINUSONE) @@ -23,7 +28,18 @@ def compare_months(actual): x = find_percent_diff(budget_one, budget_two) first_sort = dict(sorted(budget_one.items(), key=lambda item: item[1]['amount_spent'])) reduced_sort = dict(list(first_sort.items())[:5]) - print(x) + text_list = [] + for i, k in reduced_sort.items(): + items = (i, format_money(k['amount_spent'])) + text_list.append(items) + + formatted = str(text_list)[1:-1] + replacements = [('\\(', ''), ('\\)',''), ("'", ''), ('"',''), ('(\\d)\\,\\s\\b', '\\1\\n'), ('\\b, (\\-)', ': \\1')] + for old, new in replacements: + formatted = re.sub(fr"{old}", fr"{new}", formatted) + + x = x.replace('"','') + print(f"""**This is an automated message!**\nHere's your budget status. Here are your top 5 spending categories from last month:\n\n{formatted}\n\nAnd here are the top 5 categories with the biggest increase in spending between last month and the previous month:\n\n{x}""") def find_percent_diff(lastmonth, twomonths): @@ -34,10 +50,23 @@ def find_percent_diff(lastmonth, twomonths): absolute = v['amount_spent'] - x['amount_spent'] average = (v['amount_spent'] + x['amount_spent'])/2 diff = round(absolute/average, 2) - diffs.append(( k, diff) ) + diffs.append((k, diff, (LASTM_STRING, v['amount_spent']), (TWOM_STRING, x['amount_spent']))) sort = sorted(diffs, reverse=True, key=lambda item: item[1]) - return sort + first = sort[:5] + second = [] + for t in first: + t1 = str(f"{float(t[1])}%") + t2 = (t[2][0], format_money(t[2][1])) + t3 = (t[3][0], format_money(t[3][1])) + newlist = f"{t[0]}, {t1}, {t2}, {t3}" + second.append(newlist) + formatted = str(second)[1:-1] + replacements = [('\\(', ''), ('\\)',''), ("'", ''), ('", ','\\n'), ('(\\%), ','\\1 --> '), ('(\\d)\\,\\s\\b', '\\1 --> '), ('([a-z]),', '\\1:')] + for old, new in replacements: + formatted = re.sub(fr"{old}", fr"{new}", formatted) + + return formatted def simple_track(actual): diff --git a/min_test.py b/min_test.py new file mode 100644 index 0000000..894112b --- /dev/null +++ b/min_test.py @@ -0,0 +1,15 @@ +from dotenv import load_dotenv +from actual import Actual +import os + +load_dotenv() + + +print("hold onto your butts") +with Actual( + base_url=os.getenv('BASEURL'), + password=os.getenv('PASSWORD'), + encryption_password=None, # Optional: Password for the file encryption. Will not use it if set to None. + file=os.getenv('FILE') +) as actual: + print(actual) diff --git a/new_month_check_transactions.py b/new_month_check_transactions.py new file mode 100644 index 0000000..f01b771 --- /dev/null +++ b/new_month_check_transactions.py @@ -0,0 +1,49 @@ +from actual import Actual, Transactions +import os +from dotenv import load_dotenv +from actual.queries import get_transactions +from datetime import datetime, timedelta +from moneyed import Money, USD +from moneyed.l10n import format_money +from last_month import compare_months +import last_month +from dateutil.relativedelta import relativedelta +import json +import re +import pprint + +pp=pprint.PrettyPrinter(indent=4, sort_dicts=False) +load_dotenv() +THISMONTH = datetime.now() +# datestring= "2025-08-06 13:57:57" +# format = '%Y-%m-%d %H:%M:%S' +# THISMONTH = datetime.strptime(datestring, format) +MONTHMINUSONE = THISMONTH - relativedelta(months=1) +MONTHMINUSTWO = THISMONTH - relativedelta(months=2) +ONBUDGETACCOUNTS = os.getenv('ONBUDGETACCOUNTS').split() + +def main(actual, month): + accounts_with_curr_month = 0 + monthdaydate = datetime.strftime(month, "%Y%m") + print(monthdaydate) + first_day_month = month.replace(day=1) + for account in ONBUDGETACCOUNTS: + latest_trans = get_transactions(actual.session, account=account, start_date=first_day_month) + # for acctrans in latest_trans: + if f"date={monthdaydate}" in str(latest_trans):# and not run_compare_months: + accounts_with_curr_month += 1 + if accounts_with_curr_month == 3: + print("Hold onto your butts! All 3 accounts have this month's data!") + last_month.compare_months(actual) + else: + print(f"No current month transactions for {account}") + + +if __name__ == "__main__": + with Actual( + base_url=os.getenv('BASEURL'), + password=os.getenv('PASSWORD'), + encryption_password=None, + file=os.getenv('FILE') + ) as actual: + main(actual, THISMONTH) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1648abc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "actual-py-proj" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [] diff --git a/requirements.txt b/requirements.txt index da4d58e..30aafc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,6 @@ charset-normalizer==3.4.4 cryptography==46.0.3 idna==3.11 numpy==2.3.4 -pandas==2.3.3 proto-plus==1.26.1 protobuf==6.33.0 py-moneyed==3.0 diff --git a/scrubbed_main.py b/scrubbed_main.py deleted file mode 100644 index fe603b0..0000000 --- a/scrubbed_main.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -The output of this script should be in a format that can be sent as a text or added to a note. -In Apple Shortcuts, do the following: -* Create new shortcut -* Add Shell Script action -* In the text box put: -source ~/Documents/tmp/.actualpy/bin/activate -python ~/Documents/tmp/main.py - -Select the following options: -Shell: Bash -Input: Input -Pass Input: to stdin -Run as Admin: unchecked - -Then add the other actions that you need. In my case, I did "Send {Shell Script Result} to {Contact}" -""" - - -from actual import Actual -from actual.queries import get_budgets, get_categories, get_category, get_transactions -from dotenv import load_dotenv -from datetime import datetime -from moneyed import Money, USD -from moneyed.l10n import format_money -import json -import re -import pprint - -pp=pprint.PrettyPrinter(indent=4) -load_dotenv() -TODAY = datetime.now() -MONTH_DAY = TODAY.strftime("%Y%m") -MONTHSTR = TODAY.strftime("%B") -MONTHDAYDATE = datetime.strptime(MONTH_DAY, "%Y%m") - -def main(): - main_dict = {} - main_list = [] - with Actual( - base_url=os.getenv('BASEURL') - password=os.getenv('PASSWORD') - encryption_password=None, # Optional: Password for the file encryption. Will not use it if set to None. - file=os.getenv('FILE') - ) as actual: - budget = get_budgets(actual.session) - for b in budget: - if b.month == int(MONTH_DAY): - if b.amount > 0: - cats = get_categories(actual.session) - for cat in cats: - if cat.id == b.category_id: - # if cat.name == "Kid's Activities": - formatted_amount = str(b.amount)[:-2] + "." + str(b.amount)[-2:] - budgeted_amount = Money(formatted_amount, USD) - main_dict[cat.name] = {"budgeted_amount": budgeted_amount, "amount_spent": "", "amount_left": ""} - # main_dict = {"category": cat.name, "month": MONTHSTR, "budgeted_amount": budgeted_amount, "amount_spent": "", "amount_left": ""} - category_transcations(sesh=actual.session, main_list=main_dict) - - -def category_transcations(sesh, main_list): - for keys, vals in main_list.items(): - this_month_trans = get_transactions(sesh, category=keys, start_date=MONTHDAYDATE) - curr_sum = Money(0, USD) - for ta in this_month_trans: - tamount = str(ta.amount)[:-2] + "." + str(ta.amount)[-2:] - total_sum = Money(tamount, USD) - curr_sum += total_sum - # print(f"curr_sum: {curr_sum} PLUS total_sum: {total_sum}") - amount_list = curr_sum + vals["budgeted_amount"] - vals["budgeted_amount"] = vals['budgeted_amount'] - vals["amount_spent"] = curr_sum - vals["amount_left"] = amount_list - - # sorted_items = sorted(main_list.items(), key=lambda item: item[1]['amount_left']) - # sorted_nested_dict = dict(sorted_items) - # pp.pprint(sorted_nested_dict) - # pp.pprint(sorted_key_val) - # sorted_data = sorted(main_list, key=lambda x: main_list[x]['amount_left']) - # pp.pprint(sorted_data) - # sort_budgets_for_notification(sorted_data) - sort_budgets_for_notification(main_list) - -def sort_budgets_for_notification(sorted_data): - negative_budget = {} - no_budget_left = {} - some_budget_left = {} - final_tuple = () - for categories, values in sorted_data.items(): - # for budget_type, amount in values.items(): - intmoney = values['amount_left'].amount - if intmoney == 0: - # no_budget_left.append((categories, values) ) - no_budget_left[categories] = values - elif intmoney < 0: - # negative_budget.append((categories, values)) - negative_budget[categories] = values - else: - # some_budget_left.append(( categories, values) ) - some_budget_left[categories] = values - - final_tuple = (format_dicts(some_budget_left), format_dicts(negative_budget), format_dicts(no_budget_left)) - print(f"""**This is an automated message!** Here's your spending status so far for this month.\nFirst, here are the categories where you've overspent:\n{final_tuple[1]}.\n\nHere is where you still have some budget left:\n{final_tuple[0]}.\n\nAnd finally, here is where you're exactly where you need to be. $0 left.\n {final_tuple[2]}""") - - -def format_dicts(budgets_sorted): - x = str(budgets_sorted) - # y = re.search(r"(\'\w*\':)|(\'\w{1,9}\s\w{1,9})|(\d*.\d{2})", x).group() - y = (x - .replace("{'", "") - .replace("}", "") - .replace("Money('", "") - .replace("'", "") - .replace(", USD", "") - .replace(")","") - .replace("(","") - .replace('"', '') - ) - z = re.sub(r"((([A-Z][a-z]*( | & )){1,2}[A-Z][a-z]*:)|([A-Z][a-z]*)|529 Contrib)", '\n\\1', y) - t = re.sub(r", \n", '\n', z) - h = re.sub(r'([a-z]*_[a-z]*)',lambda x: x[1].replace('_', ' ').capitalize(), t) - p = re.sub(r'(\d{1,5}.\d{2})', '$\\1', h) - o = re.sub(r'(\d{4})', lambda x: x[1][:1]+','+x[1][1:], p) - return o - - -if __name__ == "__main__": - main() diff --git a/this_month.py b/this_month.py index f3cc7de..15977e1 100644 --- a/this_month.py +++ b/this_month.py @@ -29,7 +29,7 @@ def simple_track(actual): if cat.name == "Income": formatted_amount = str(b.amount)[:-2] + "." + str(b.amount)[-2:] expected_income = Money(formatted_amount, USD) - main_dict[cat.name] = {"Expected Income": expected_income, "Actual Income": Money(0, USD), "Total Spent": Money(0, USD), "Amount Left Against Budget": Money(0, USD)} + main_dict[cat.name] = {"Expected Income": expected_income, "Actual Income": Money(0, USD), "Total Spent": Money(0, USD), "Left Against Budget": Money(0, USD), "Left Against Actual": Money(0, USD)} category_transcations(sesh=actual.session, main_list=main_dict, origin="simple") def main(actual): @@ -72,10 +72,14 @@ def category_transcations(sesh, main_list, origin): amount_left = curr_sum + main_list['Income']['Expected Income'] main_list['Income']['Expected Income'] = main_list['Income']['Expected Income'] main_list['Income']['Actual Income'] = income_sum - main_list['Income']['Amount Left Against Budget'] = amount_left + main_list['Income']['Left Against Budget'] = amount_left main_list['Income']['Total Spent'] = curr_sum + xx = curr_sum.amount + if xx.is_zero(): + main_list['Income']['Left Against Actual'] = income_sum+curr_sum + else: + main_list['Income']['Left Against Actual'] = income_sum+curr_sum - # print(f"curr_sum: {curr_sum} PLUS total_sum: {total_sum}") else: for keys, vals in main_list.items(): this_month_trans = get_transactions(sesh, category=keys, start_date=MONTHDAYDATE) @@ -84,20 +88,11 @@ def category_transcations(sesh, main_list, origin): tamount = str(ta.amount)[:-2] + "." + str(ta.amount)[-2:] total_sum = Money(tamount, USD) curr_sum += total_sum - # print(f"curr_sum: {curr_sum} PLUS total_sum: {total_sum}") amount_list = curr_sum + vals["budgeted_amount"] vals["budgeted_amount"] = vals['budgeted_amount'] vals["amount_spent"] = curr_sum vals["amount_left"] = amount_list - # sorted_items = sorted(main_list.items(), key=lambda item: item[1]['amount_left']) - # sorted_nested_dict = dict(sorted_items) - # pp.pprint(sorted_nested_dict) - # pp.pprint(sorted_key_val) - # sorted_data = sorted(main_list, key=lambda x: main_list[x]['amount_left']) - # pp.pprint(sorted_data) - # sort_budgets_for_notification(sorted_data) - if origin == "simple": x = format_dicts(main_list) simple_notifications(x) @@ -150,13 +145,6 @@ def format_dicts(budgets_sorted): return o - # categies = str([x['category'] for x in negative_budget ])[1:-1].replace('"','').replace("'",'') - # categies2 = str([x['category'] for x in some_budget_left ])[1:-1].replace('"','').replace("'",'') - # categies3 = str([x['category'] for x in no_budget_left ])[1:-1].replace('"','').replace("'",'') - # print(f"""Here's your spending status so far for this month. First, here are the categories where you've overspent:\n- {str(categies)}.\n\nHere is where you still have some budget left:\n- {str(categies2)}.\n\nAnd finally, here is where you're exactly where you need to be. $0 left.\n- {str(categies3)}""") - - - if __name__ == "__main__": with Actual( base_url=os.getenv('BASEURL'), diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..fc1cab5 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "actual-py-proj" +version = "0.1.0" +source = { virtual = "." }