Initial commit. Lots of messy functions but eventually each file will do different things for apple shortcuts to then send me a text.

This commit is contained in:
norm
2025-11-05 14:43:11 -05:00
commit b1d6aab9e3
5 changed files with 548 additions and 0 deletions

30
actual_bank_sync.py Normal file
View File

@ -0,0 +1,30 @@
from actual import Actual
import os
from dotenv import load_dotenv
load_dotenv()
def main():
synced_accounts = os.getenv('SYNCED_ACCOUNTS')
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'),
cert=False
) as actual:
for account in synced_accounts:
try:
sync_test = actual.run_bank_sync(account=account, run_rules=True)
except Exception as e:
print("**************")
print(f"An exception occurred! \n {e}")
print("**************")
else:
for transaction in sync_test:
print(f"Added of modified {transaction}")
finally:
actual.commit()
if __name__ == "__main__":
main()

195
last_month.py Normal file
View File

@ -0,0 +1,195 @@
from actual import Actual
import os
from dotenv import load_dotenv
from actual.queries import get_budgets, get_categories, get_category, get_transactions
from datetime import datetime, timedelta
from moneyed import Money, USD
from moneyed.l10n import format_money
from dateutil.relativedelta import relativedelta
import json
import re
import pprint
pp=pprint.PrettyPrinter(indent=4, sort_dicts=False)
load_dotenv()
THISMONTH = datetime.now()
MONTHMINUSONE = THISMONTH - relativedelta(months=1)
MONTHMINUSTWO = THISMONTH - relativedelta(months=2)
ONBUDGETACCOUNTS = os.getenv('ONBUDGETACCOUNTS')
def compare_months(actual):
budget_one = main(actual, MONTHMINUSONE)
budget_two = main(actual, MONTHMINUSTWO)
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)
def find_percent_diff(lastmonth, twomonths):
diffs = []
for k, v in lastmonth.items():
for y, x in twomonths.items():
if k == y:
absolute = v['amount_spent'] - x['amount_spent']
average = (v['amount_spent'] + x['amount_spent'])/2
diff = round(absolute/average, 2)
diffs.append(( k, diff) )
sort = sorted(diffs, reverse=True, key=lambda item: item[1])
return sort
def simple_track(actual):
main_dict = {}
main_list = []
budget = get_budgets(actual.session)
for b in budget:
if b.month == int(MONTH_DAY_ONE) and b.amount > 0:
cats = get_categories(actual.session)
for cat in cats:
if cat.id == b.category_id:
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)}
category_transcations(sesh=actual.session, main_list=main_dict, origin="simple")
def main(actual, month):
intmonth = month.strftime("%Y%m")
main_dict = {}
main_list = []
budget = get_budgets(actual.session)
for b in budget:
if b.month == int(intmonth) and 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": ""}
returns = category_transcations(sesh=actual.session, main_list=main_dict, origin="all_cats", month=month)
return returns
def category_transcations(sesh, main_list, origin, month):
first_day_month = month.replace(day=1)
first_day_next_month = (month.replace(day=1) + timedelta(days=32)).replace(day=1)
if origin == "simple":
this_monhth_income = get_transactions(sesh, category='Income', start_date=MONTHMINUSONE.replace(day=1), end_date=THISMONTH.replace(day=1))
income_sum = Money(0, USD)
for tit in this_monhth_income:
print(tit.amount, tit.payee.name, tit.date)
income = str(tit.amount)[:-2] + "." + str(tit.amount)[-2:]
total_income = Money(income, USD)
income_sum += total_income
this_month_trans = get_transactions(sesh, account='onbudget', start_date=first_day_month, end_date=first_day_next_month)
curr_sum = Money(0, USD)
count = 0
for ta in this_month_trans:
if ta.category_id != '3c1699a5-522a-435e-86dc-93d900a14f0e' and ta.account.id in ONBUDGETACCOUNTS:
tamount = str(ta.amount)[:-2] + "." + str(ta.amount)[-2:]
total_sum = Money(tamount, USD)
curr_sum += total_sum
count += 1
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']['Total Spent'] = curr_sum
else:
for keys, vals in main_list.items():
this_month_trans = get_transactions(sesh, category=keys, start_date=first_day_month, end_date=first_day_next_month)
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)
if origin == "simple":
x = format_dicts(main_list)
simple_notifications(x)
else:
return main_list
# all_budgets_for_notifications(main_list)
def simple_notifications(x):
print(f"""Here's where you stand last month!\n {x}""")
def all_budgets_for_notifications(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)[10:]
# 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,5})', lambda x: x[1][:-3]+','+x[1][-3:], p)
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'),
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:
# main(actual)
compare_months(actual)
# simple_track(actual)

27
requirements.txt Normal file
View File

@ -0,0 +1,27 @@
actualpy==0.16.0
annotated-types==0.7.0
babel==2.17.0
certifi==2025.10.5
cffi==2.0.0
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
pycparser==2.23
pydantic==2.12.3
pydantic-core==2.41.4
python-dateutil==2.9.0.post0
python-dotenv==1.2.1
pytz==2025.2
requests==2.32.5
six==1.17.0
sqlalchemy==2.0.44
sqlmodel==0.0.27
typing-extensions==4.15.0
typing-inspection==0.4.2
tzdata==2025.2
urllib3==2.5.0

128
scrubbed_main.py Normal file
View File

@ -0,0 +1,128 @@
"""
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()

168
this_month.py Normal file
View File

@ -0,0 +1,168 @@
from actual import Actual
import os
from dotenv import load_dotenv
from actual.queries import get_budgets, get_categories, get_category, get_transactions
from datetime import datetime
from moneyed import Money, USD
from moneyed.l10n import format_money
import json
import re
import pprint
load_dotenv()
pp=pprint.PrettyPrinter(indent=4)
TODAY = datetime.now()
MONTH_DAY = TODAY.strftime("%Y%m")
MONTHSTR = TODAY.strftime("%B")
MONTHDAYDATE = datetime.strptime(MONTH_DAY, "%Y%m")
ONBUDGETACCOUNTS = os.getenv('ONBUDGETACCOUNTS')
def simple_track(actual):
main_dict = {}
main_list = []
budget = get_budgets(actual.session)
for b in budget:
if b.month == int(MONTH_DAY) and b.amount > 0:
cats = get_categories(actual.session)
for cat in cats:
if cat.id == b.category_id:
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)}
category_transcations(sesh=actual.session, main_list=main_dict, origin="simple")
def main(actual):
main_dict = {}
main_list = []
budget = get_budgets(actual.session)
for b in budget:
if b.month == int(MONTH_DAY) and 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": ""}
print(main_dict)
category_transcations(sesh=actual.session, main_list=main_dict, origin="all_cats")
def category_transcations(sesh, main_list, origin):
if origin == "simple":
this_monhth_income = get_transactions(sesh, category='Income', start_date=MONTHDAYDATE)
income_sum = Money(0, USD)
for ti in this_monhth_income:
income = str(ti.amount)[:-2] + "." + str(ti.amount)[-2:]
total_income = Money(income, USD)
income_sum += total_income
this_month_trans = get_transactions(sesh, account='onbudget', start_date=MONTHDAYDATE)
curr_sum = Money(0, USD)
count = 0
for ta in this_month_trans:
if ta.category_id != '3c1699a5-522a-435e-86dc-93d900a14f0e' and ta.account.id in ONBUDGETACCOUNTS:
tamount = str(ta.amount)[:-2] + "." + str(ta.amount)[-2:]
total_sum = Money(tamount, USD)
curr_sum += total_sum
count += 1
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']['Total Spent'] = 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)
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)
if origin == "simple":
x = format_dicts(main_list)
simple_notifications(x)
else:
all_budgets_for_notifications(main_list)
def simple_notifications(x):
print(f"""Here's where you stand this month!\n {x}""")
def all_budgets_for_notifications(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)[10:]
# 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,5})', lambda x: x[1][:-3]+','+x[1][-3:], p)
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'),
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:
# main(actual)
simple_track(actual)