From b70fcbb18572f7454eadbd9be5e5f6b7e15a9c06 Mon Sep 17 00:00:00 2001 From: norm Date: Tue, 11 Nov 2025 09:47:47 -0500 Subject: [PATCH] Finalized the this month script, which will be sent weekly via text to me and necessary parties. The last month and new_month_check transactions are also cleaned up and working. The next tasks are to sunset the import of moneyed as reading the actual-py docs, I found that I can do transactions.get_amount() which will return a decimal formatted number instead of needing to change the strings, etc. Will also need to clean up the files so they are more organized for public release. --- README.md | 0 __pycache__/last_month.cpython-313.pyc | Bin 0 -> 12718 bytes last_month.py | 37 ++++++- min_test.py | 15 +++ new_month_check_transactions.py | 49 ++++++++++ pyproject.toml | 7 ++ requirements.txt | 1 - scrubbed_main.py | 128 ------------------------- this_month.py | 26 ++--- uv.lock | 8 ++ 10 files changed, 119 insertions(+), 152 deletions(-) create mode 100644 README.md create mode 100644 __pycache__/last_month.cpython-313.pyc create mode 100644 min_test.py create mode 100644 new_month_check_transactions.py create mode 100644 pyproject.toml delete mode 100644 scrubbed_main.py create mode 100644 uv.lock 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 0000000000000000000000000000000000000000..ef9d9e3e08acc495552fcfc12d58ee11dccace88 GIT binary patch literal 12718 zcmd5?d2k!odEZ?ufW-|01aDExOC%^BqDac3EGyu$_NO6BxC}h7N8_D zmV-Om-d$g)RJ>lx8)I-#aX%`~2=(srgwg`rhu_x9|PF>peD^^aNawUbY4Qy`CU`jRoamvYE#`MG)^19Kn%C zi2;%)StJae>Qz{cC8JayF0+ofj}@`Y0B;2jeVSCcZtd|FL#I!@0S zx+uPQg03byvrQ~9p?B@d^C`JJ2VXi-I$?wUM$XhlHp6`IW!V~YuBIIBvRCPwPpgTE zGS1Qo^Me<^?Q6Np*Kt)&Y=?f7>t(6a1_Gn7c9L?HlJZqtK^ehUb2gwgoE>N_R|vF@ za{%4J6#=d1ih(w8B|x1Mja=!zF@o?OC5Q>~8P6(Hp7FJ#DOWf+>T7u_ay;9*Xo}#8 ziE4Nzq13{cr0`FfC6<_`rl~5TiD<~ayC2?oieicF1Pg!D)Jv-E#1!eQe1PpvQq*+& zV)NeFVaOV0BVND9ABhFRXPvZYm=44|lktsaVm0}G&S4AyYJA! z(eB>f5%=&Y=R;2A(n3ZBzsH5rz8lZAxry=YN+SqmHpAqGb{vg#<=Uxnyq|Q9*>bh2 zah*~-ZcuV=(q&Te`Bd3S99afmvWrr_YnWbl4~xR@rk`~Wr7z+Jom(Mcbt@6pxYf_< zX(tZvAc%Q*|GD#S?RY`%c??IZp`hHkg9_{18kb#Z&!=vk!ow0nu=EVD*2W!5yIZ5Q za%xUP6JdQT!D)|^VT0QM&)%MW_CJpoDLq^zN-m#P6KnX=@x^>@Wo6^SV-Fh>@8Qdx}(M~I)gZ)7`jl&xj3@!?M;m0;0?-6bQYMc^| zLRw2WRl_{Ch(>QHG9Qk4qI0lNZxA;~kq*WJp(rjeR@~j{jt2N>YZ#V$YhT1S9|Epu zE9@57d!D%k&m12)-8$=yN?Rc|(=xXpGW%z}p-I2@fXMnHp*b%fz-VBS8j|&=Gaaar_~<}PqTRpVyE~>h{ZW$rFq$>*Ds#c>$}5#c1F2FY$m|U zH(1y2Trf7nV#{Q3dO85zgJB;Z@J0iW$vODH;hT#ps1PYW0GatoQOgJBX1%_EsGj44;h575e9}@9_3~VL08exI z*tv*Eo)`5~K_1o@K1ww50sp)&;Fn9fSm1ojgAWzSph&*Xqf8fdGB)G5fL(f?v}#3F zWEKV#4xEdcfsfsA@fFzBym7(u2cfbQe$j2P9pc1Vfg>^Xekfhg6d%}-XG)WeX{IgS z4Ylu1C11U_tvR)=IlZkl)p;^i^lG|b96H#G->26#rAv0L+E%NCl8&^!Gd`HH+Tuqt z){1-9>XfzmuC?|ey+mdTZ0`;(7c3u0Rqaj}whPvdC3S`=xW|;Fn3AO9F0=hhHBnIa zgdqwF6GyI?lV_G~g0(h&M6{oe58f|iFEUH2wZfvS{qOWAqshQZ&5C2SG+p@OTe>e* zgrn-AhS2FR=`QLLHHpbIQwlFY>mCw>

3I00cB7N2pOuMyX*Mk`ekCCd2e`Obny+ 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 literal 0 HcmV?d00001 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 = "." }