Compare commits

...

2 Commits

5 changed files with 138 additions and 27 deletions

8
TODO
View File

@@ -3,14 +3,10 @@ CALC:
differently, but hard to calc - slides around with tax brackets in future differently, but hard to calc - slides around with tax brackets in future
UI: UI:
* maybe a help/note/set of links somehow, to common things: e.g. macquarie int rate page, inflation source, etc. * 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)
- also then could effectively take those "notes" out of db.py and incorporate, so that when I update stuff it gets done right then the question mark can be more prominent -> use an <alert> that suggests updating as per ?
- 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
For bills: For bills:
* if we change certain items on the finance page, need to re-do bills, e.g.
- change D # pays to quit, or Car lease/buy out
* might need to be able to mark a specific bill as an outlier: * might need to be able to mark a specific bill as an outlier:
- so we ignore the data somehow (think Gas is messing with my bills) - so we ignore the data somehow (think Gas is messing with my bills)
- and even electricity, water, etc. for when we were away in Europe but mostly gas/elec - and even electricity, water, etc. for when we were away in Europe but mostly gas/elec

102
bills.py
View File

@@ -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 from defines import END_YEAR
import datetime import datetime
from datetime import date, timedelta 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_ann_growth_avg = {row["id"]: row["ann_growth_avg"] for row in bt}
bt_id_name = {row["id"]: row["name"] 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) # 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} 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 # 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: 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 continue
add_missing_bills_for_yr( bill_type, bill_info, yr ) 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_car_bills( key_dates, future_car_bills, bill_info )
deal_with_future_D_quit_bills( key_dates, future_D_quit_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}" new_date=f"{yr}-{car_mmdd}"
# if we dont already have an annual bill for this year (all car bills are annual) # if we dont already have an annual bill for this year (all car bills are annual)
if yr not in bill_info[bt]['year']: 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 ) new_estimated_bill( bill_info, yr, fb['bill_type'], amt, new_date )
amt += amt * bill_info[bt]['growth']/100 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}" new_date=f"{yr}-{dq_mm}-{dq_dd}"
# if we dont already have an annual bill for this year # if we dont already have an annual bill for this year
if not find_this_bill( bt, bill_info, new_date ): 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 ) new_estimated_bill( bill_info, yr, bt, amt, new_date )
amt += amt * bill_info[bt]['growth']/100 amt += amt * bill_info[bt]['growth']/100
elif bill_info[bt]['num_ann_bills'] == 12: elif bill_info[bt]['num_ann_bills'] == 12:
print( f"should be adding monthly future bill" )
# do rest of this year, then next years # do rest of this year, then next years
for m in range( int(dq_mm), 13): for m in range( int(dq_mm), 13):
new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}" new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}"
if not find_this_bill( bt, bill_info, new_date ): 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 ) new_estimated_bill( bill_info, yr, bt, amt, new_date )
for yr in range( int(D_quit_yr)+1, END_YEAR ): for yr in range( int(D_quit_yr)+1, END_YEAR ):
amt += amt * bill_info[bt]['growth']/100 amt += amt * bill_info[bt]['growth']/100
for m in range( 1, 13): for m in range( 1, 13):
new_date=f"{yr}-{m:02d}-{dq_dd}" new_date=f"{yr}-{m:02d}-{dq_dd}"
if not find_this_bill( bt, bill_info, new_date ): 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 ) 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 # terms of min/avg/max - uses qtr data for qtrly bills, or just normal totals
# for other bill types # 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) # just do up to now so we stop earlier than looking at other estimated (just an optimisation)
now_yr = datetime.date.today().year 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 # had to round to 2 decimal here to get sensible totals
total[bt['id']][yr] = round( total[bt['id']][yr], 2 ) total[bt['id']][yr] = round( total[bt['id']][yr], 2 )
return total 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

17
db.py
View File

@@ -119,15 +119,6 @@ def init_db():
# Check if table is empty, if so insert default values # Check if table is empty, if so insert default values
cur.execute('SELECT COUNT(*) FROM finance') cur.execute('SELECT COUNT(*) FROM finance')
if cur.fetchone()[0] == 0: 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, 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) 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
@@ -397,6 +388,14 @@ def save_ui(data):
return 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(): def delete_estimated_bills():
conn = connect_db(False) conn = connect_db(False)
cur = conn.cursor() cur = conn.cursor()

10
main.py
View File

@@ -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_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_ui, save_ui, delete_cset
from db import get_bill_types, insert_bill_type, update_bill_type, delete_bill_type, use_growth 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 defines import END_YEAR
from collections import defaultdict, Counter from collections import defaultdict, Counter
from datetime import datetime, date from datetime import datetime, date
@@ -113,6 +113,8 @@ def save():
@app.route('/update', methods=['POST']) @app.route('/update', methods=['POST'])
def update(): def update():
old_finance_data = get_finance_data()
finance_data = ( finance_data = (
request.form['D_Salary'], request.form['D_Salary'],
request.form['D_Num_fortnights_pay'], request.form['D_Num_fortnights_pay'],
@@ -142,7 +144,11 @@ def update():
request.form['Ioniq6_future'] request.form['Ioniq6_future']
) )
update_finance(finance_data) 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')) return redirect(url_for('index'))
@app.route('/bills') @app.route('/bills')

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<title>Finance Form</title> <title>Finance Form</title>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@@ -19,7 +21,30 @@
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<h3 align="center">Finance Tracker (go to <a href="bills">Bills</a> or <a href="cset">Comparison Sets</a>)</h3> <div class="d-flex align-items-center justify-content-center position-relative">
<h3 align="center">Finance Tracker (go to <a href="bills">Bills</a> or <a href="cset">Comparison Sets</a>)</h3>
<!-- Clickable Question Mark Icon -->
<a href="#" tabindex="0"
class="position-absolute end-0 me-3"
data-bs-toggle="popover"
data-bs-trigger="click"
data-bs-placement="right"
data-bs-content="For now manually update the itmes below on day aftter original pay shcedule to compare saved version vs. our reality:
<ul>
<li>Savings (<a href='https://online.macquarie.com.au/personal/#/login'>Macquarie</a>
+<a href='https://ib.mebank.com.au/authR5/ib/login.jsp'>ME bank</a>
+<a href='https://ib.nab.com.au/login'>NAB</a>) -- noting ME bank is: $2001.19</li>
<li><a href='https://www.google.com/search?q=asx+tls'>TLS</a>/<a href='https://www.google.com/search?q=asx+cba'>CBA</a> prices</li>
<li>Macq <a href='https://www.macquarie.com.au/everyday-banking/savings-account.html'>Interest rate</a></li>
<li><a href='https://deakinpeople.deakin.edu.au/psc/HCMP/EMPLOYEE/HRMS/c/NUI_FRAMEWORK.PT_AGSTARTPAGE_NUI.GBL?CONTEXTIDPARAMS=TEMPLATE_ID%3aPTPPNAVCOL&scname=ADMN_LEAVE&PTPPB_GROUPLET_ID=DU_LEAVE&CRefName=ADMN_NAVCOLL_3'>D_leave_owed_in_days</a> by: {{key_dates['D_quit_date']}}</li>
<li>update Inflation - using <a href='https://tradingeconomics.com/australia/core-inflation-rate'>RBA Trimmed Mean CPI YoY</a></li></li>
</ul>"
data-bs-html="true">
<i class="bi bi-question-circle" style="font-size: 2.0rem;"></i>
</a>
</div>
</div>
<form id="vals_form" class="ms-3 mt-3" action="/update" method="POST"> <form id="vals_form" class="ms-3 mt-3" action="/update" method="POST">
{% for r in DISP %} {% for r in DISP %}
@@ -198,6 +223,7 @@
const savingsData = JSON.parse('{{ savings | tojson }}'); const savingsData = JSON.parse('{{ savings | tojson }}');
const vars = JSON.parse('{{ finance | tojson }}'); const vars = JSON.parse('{{ finance | tojson }}');
$(function() { $('[data-bs-toggle="popover"]').popover(); });
window.onload = function() { window.onload = function() {
$('#Sell_shares').val( {{finance['Sell_shares']}} ) $('#Sell_shares').val( {{finance['Sell_shares']}} )
$('#compare_to').val( {{finance['compare_to']}} ) $('#compare_to').val( {{finance['compare_to']}} )