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.
This commit is contained in:
BIN
__pycache__/last_month.cpython-313.pyc
Normal file
BIN
__pycache__/last_month.cpython-313.pyc
Normal file
Binary file not shown.
@ -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):
|
||||
|
||||
15
min_test.py
Normal file
15
min_test.py
Normal file
@ -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)
|
||||
49
new_month_check_transactions.py
Normal file
49
new_month_check_transactions.py
Normal file
@ -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)
|
||||
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
@ -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 = []
|
||||
@ -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
|
||||
|
||||
128
scrubbed_main.py
128
scrubbed_main.py
@ -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()
|
||||
@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user