diff --git a/TODO b/TODO index d7c1e04..179f46f 100644 --- a/TODO +++ b/TODO @@ -3,10 +3,8 @@ CALC: differently, but hard to calc - slides around with tax brackets in future UI: - * maybe a help/note/set of links somehow, to common things: e.g. macquarie int rate page, inflation source, etc. - - also then could effectively take those "notes" out of db.py and incorporate, so that when I update stuff it gets done right - - in fact, I *COULD* have a basic date check/workflow, e.g. its been more than X days since Y was updated, click here - and it walks me through the manual update steps and when I finish its datestamped until next time + * I *COULD* have a basic date check/workflow, e.g. it now>last_update+14 (and its more than a paydate <- need to think that bit through) + then the question mark can be more prominent -> use an that suggests updating as per ? For bills: * if we change certain items on the finance page, need to re-do bills, e.g. diff --git a/bills.py b/bills.py index 3e70ff5..5e8a5a9 100644 --- a/bills.py +++ b/bills.py @@ -1,4 +1,5 @@ -from db import set_bill_type_growth, new_bill +from db import set_bill_type_growth, new_bill, deleteFutureEstimates, get_finance_data, get_bill_data, get_bill_types, get_bill_freqs +from calc import calc_key_dates from defines import END_YEAR import datetime from datetime import date, timedelta @@ -310,7 +311,6 @@ def process_bill_data(bd, bt, bf, key_dates): bt_id_ann_growth_avg = {row["id"]: row["ann_growth_avg"] for row in bt} bt_id_name = {row["id"]: row["name"] for row in bt} - # this maps freq to bills per annum (e.g. id=2 to 4 bills per annum) bf_id_num = {row["id"]: row["num_bills_per_annum"] for row in bf} # and allows me a way to see if the bill is quarterly but also fixed or seasonal @@ -386,7 +386,7 @@ def process_bill_data(bd, bt, bf, key_dates): if yr in bill_info[bill_type]['year'] and len(bill_info[bill_type]['year'][yr]) >= bill_info[bill_type]['num_ann_bills'] and bill_info[bill_type]['num_ann_bills'] !=4: continue add_missing_bills_for_yr( bill_type, bill_info, yr ) - derive_ann_growth( bill_type, bill_info, key_dates, future_car_bills, future_D_quit_bills ) + derive_ann_growth( bill_type, bill_info, key_dates ) deal_with_future_car_bills( key_dates, future_car_bills, bill_info ) deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ) @@ -409,7 +409,6 @@ def deal_with_future_car_bills( key_dates, future_car_bills, bill_info ): new_date=f"{yr}-{car_mmdd}" # if we dont already have an annual bill for this year (all car bills are annual) if yr not in bill_info[bt]['year']: - print( f"amt of a car fb is: {amt}, car_yr={car_yr}, yr={yr}, nd={new_date}") new_estimated_bill( bill_info, yr, fb['bill_type'], amt, new_date ) amt += amt * bill_info[bt]['growth']/100 @@ -433,23 +432,19 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ): new_date=f"{yr}-{dq_mm}-{dq_dd}" # if we dont already have an annual bill for this year if not find_this_bill( bt, bill_info, new_date ): - print( f"amt of a D_quit fb is: {amt}, dq_yr={D_quit_yr}, yr={yr}, nd={new_date}") new_estimated_bill( bill_info, yr, bt, amt, new_date ) amt += amt * bill_info[bt]['growth']/100 elif bill_info[bt]['num_ann_bills'] == 12: - print( f"should be adding monthly future bill" ) # do rest of this year, then next years for m in range( int(dq_mm), 13): new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}" if not find_this_bill( bt, bill_info, new_date ): - print( f"amt of a D_quit fb is: {amt}, dq_yr={D_quit_yr}, nd={new_date}") new_estimated_bill( bill_info, yr, bt, amt, new_date ) for yr in range( int(D_quit_yr)+1, END_YEAR ): amt += amt * bill_info[bt]['growth']/100 for m in range( 1, 13): new_date=f"{yr}-{m:02d}-{dq_dd}" if not find_this_bill( bt, bill_info, new_date ): - print( f"amt of a D_quit fb is: {amt}, dq_yr={D_quit_yr}, yr={yr}, nd={new_date}") new_estimated_bill( bill_info, yr, bt, amt, new_date ) ################################################################################ @@ -492,7 +487,7 @@ def ProportionQtrlyData( bill_type, bill_info ): # terms of min/avg/max - uses qtr data for qtrly bills, or just normal totals # for other bill types ################################################################################ -def derive_ann_growth( bill_type, bill_info, key_dates, future_car_bills, future_D_quit_bills ): +def derive_ann_growth( bill_type, bill_info, key_dates ): # just do up to now so we stop earlier than looking at other estimated (just an optimisation) now_yr = datetime.date.today().year @@ -586,3 +581,92 @@ def calc_future_totals(bill_info, bill_types): # had to round to 2 decimal here to get sensible totals total[bt['id']][yr] = round( total[bt['id']][yr], 2 ) return total + + +################################################################################ +# When we change the day D_quits, or we buyout the car, then future bills need +# to change/rebuild estimates, convenience routine used to find future bills - +# rather than go through them as we render /bills +################################################################################ +def getFutureBills(bd,bt,future_car_bills, future_D_quit_bills): + # this maps a bill id to a name + bt_id_name = {row["id"]: row["name"] for row in bt} + + for bill in bd: + bill_type = bill['bill_type'] + if bill['bill_date'] == 'future': + # Future bills, deal with them at the end - they have dynamic start dates + if 'Hyundai' in bt_id_name[bill_type]: + future_car_bills.insert( 0, bill ) + else: + future_D_quit_bills.insert( 0, bill ) + return + + +################################################################################ +# When we change the day D_quits, or we buyout the car, then future bills need +# to change/rebuild estimates, convenience routine used to handle this +################################################################################ +def recalcFutureBills(): + future_car_bills=[] + future_D_quit_bills=[] + + print("Recalculating future bills as we changed a key date" ) + finance_data = get_finance_data() + key_dates = calc_key_dates( finance_data ) + finance_data = get_finance_data() + bill_data = get_bill_data("order_by_date_only") + bill_types = get_bill_types() + bill_freqs = get_bill_freqs() + + bt_id_freq = {row["id"]: row["freq"] for row in bill_types} + # this maps freq to bills per annum (e.g. id=2 to 4 bills per annum) + bf_id_num = {row["id"]: row["num_bills_per_annum"] for row in bill_freqs} + + + getFutureBills(bill_data, bill_types, future_car_bills, future_D_quit_bills) + deleteFutureEstimates() + # deal with future car bills + car_yr=key_dates['D_hyundai_owned'][0:4] + car_mmdd=key_dates['D_hyundai_owned'][5:] + for fb in future_car_bills: + amt=fb['amount'] + bt=fb['bill_type'] + # only can use simple growth as its a future bill + growth=bill_types[bt]['ann_growth_simple'] + # factor in growth for next bills + for yr in range( int(car_yr), END_YEAR ): + new_date=f"{yr}-{car_mmdd}" + new_bill( fb['bill_type'], amt, new_date, 1 ) + amt += amt * growth/100 + + # deal with future D_Quit bills + D_quit_yr = key_dates['D_quit_date'][0:4] + dq_mm=key_dates['D_quit_date'][5:7] + dq_dd=key_dates['D_quit_date'][8:] + # avoid feb 29+ :) + if int(dq_dd) > 28: dq_dd=28 + for fb in future_D_quit_bills: + # deal with future bills due to their starting dates being dynamic + amt=fb['amount'] + bt=fb['bill_type'] + growth=bill_types[bt]['ann_growth_simple'] + num_ann_bills= bf_id_num[bt_id_freq[bt]] + if num_ann_bills == 1: + # factor in growth for next bill + for yr in range( int(D_quit_yr), END_YEAR ): + new_date=f"{yr}-{dq_mm}-{dq_dd}" + # if we dont already have an annual bill for this year + new_bill( fb['bill_type'], amt, new_date, 1 ) + amt += amt * growth/100 + elif num_ann_bills == 12: + # do rest of this year, then next years + for m in range( int(dq_mm), 13): + new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}" + new_bill( fb['bill_type'], amt, new_date, 1 ) + for yr in range( int(D_quit_yr)+1, END_YEAR ): + amt += amt * growth/100 + for m in range( 1, 13): + new_date=f"{yr}-{m:02d}-{dq_dd}" + new_bill( fb['bill_type'], amt, new_date, 1 ) + return diff --git a/db.py b/db.py index c65cb6d..0edfa44 100644 --- a/db.py +++ b/db.py @@ -119,15 +119,6 @@ def init_db(): # Check if table is empty, if so insert default values cur.execute('SELECT COUNT(*) FROM finance') if cur.fetchone()[0] == 0: - ### - # For now manually update below on the fortnight of the original pay shcedule to compare saved version vs. our reality. Update: - # Savings (Macq+me bank) -- noting ME bank is: $2001.19, nab is -5200 - # TLS/CBA prices - # Interest rate - # D_leave_owed_in_days - # maybe quarterly update Inflation? (this is harder to appreciate, seems much lower officialy than Savings, but which inflation: - # I've decided to use RBA Trimmed Mean CPI YoY -- https://tradingeconomics.com/australia/inflation-cpi - ### cur.execute('''INSERT INTO finance (D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay, Car_loan, Car_balloon, Car_buyout, Living_Expenses, Savings, Interest_Rate, Inflation, Mich_present, Overseas_trip, Mark_reno, D_leave_owed_in_days, D_TLS_shares, M_TLS_shares, D_CBA_shares, TLS_price, CBA_price, Overseas_trip_date, Mark_reno_date, Car_buyout_date, Sell_shares, compare_to, Ioniq6_future) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', @@ -397,6 +388,14 @@ def save_ui(data): return +def deleteFutureEstimates(): + conn = connect_db(False) + cur = conn.cursor() + cur.execute( "delete from bill_data where bill_date != 'future' and bill_type in ( select bill_type from bill_data where bill_date='future')" ) + conn.commit() + conn.close() + return + def delete_estimated_bills(): conn = connect_db(False) cur = conn.cursor() diff --git a/main.py b/main.py index 3fc08c0..9db9efb 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ from db import init_db, get_finance_data, update_finance, get_budget_data, inser from db import get_bill_data, new_bill, update_bill_data, delete_bill, delete_estimated_bills from db import get_bill_ui, save_ui, delete_cset from db import get_bill_types, insert_bill_type, update_bill_type, delete_bill_type, use_growth -from bills import process_bill_data, calc_future_totals, set_bill_type_growth +from bills import process_bill_data, calc_future_totals, set_bill_type_growth, recalcFutureBills from defines import END_YEAR from collections import defaultdict, Counter from datetime import datetime, date @@ -113,6 +113,8 @@ def save(): @app.route('/update', methods=['POST']) def update(): + old_finance_data = get_finance_data() + finance_data = ( request.form['D_Salary'], request.form['D_Num_fortnights_pay'], @@ -142,7 +144,11 @@ def update(): request.form['Ioniq6_future'] ) update_finance(finance_data) - # FIXME: need code here to delete/rebuild future bills if we change "D # Pays to quit " + new_finance_data = get_finance_data() + # changed Ioniq6_future, Car_buyout_date or D_Num_fortnights_pay, so lets force recalc key_dates, and therefore estimated bills + if old_finance_data['D_Num_fortnights_pay'] != new_finance_data['D_Num_fortnights_pay'] or old_finance_data['Ioniq6_future'] != new_finance_data['Ioniq6_future'] or old_finance_data['Car_buyout_date'] != new_finance_data['Car_buyout_date']: + recalcFutureBills() + return redirect(url_for('index')) @app.route('/bills') diff --git a/templates/index.html b/templates/index.html index a1030f7..1d24dde 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,6 +4,8 @@ + + Finance Form @@ -19,7 +21,30 @@
-

Finance Tracker (go to Bills or Comparison Sets)

+
+

Finance Tracker (go to Bills or Comparison Sets)

+ + + + +
+
+
{% for r in DISP %} @@ -198,6 +223,7 @@ const savingsData = JSON.parse('{{ savings | tojson }}'); const vars = JSON.parse('{{ finance | tojson }}'); + $(function() { $('[data-bs-toggle="popover"]').popover(); }); window.onload = function() { $('#Sell_shares').val( {{finance['Sell_shares']}} ) $('#compare_to').val( {{finance['compare_to']}} )