Compare commits

..

7 Commits

9 changed files with 457 additions and 100 deletions

2
BUGS
View File

@@ -1,2 +1,4 @@
* kayo bills are wrong in between normal bills
* added an electricity bill by accident for 2018, that kills lots :( * added an electricity bill by accident for 2018, that kills lots :(
- something to do with missing year of data in quarterly bills - still an issue - something to do with missing year of data in quarterly bills - still an issue

25
TODO
View File

@@ -1,19 +1,24 @@
bills html, and growth types are very lame... could I do something more like:
{% for gt in growth %}
{% if gt.name == 'Min' %}
<option value='min'
{% if bt.which_growth == gt.name %}selected{% endif %}
>{{'%.2f'|format(bt.ann_growth_min)}}% {{gt.name}}</option>
CALC: CALC:
* if I quit at different times of the financial year, technically the amount I earn will be taxed * if I quit at different times of the financial year, technically the amount I earn will be taxed
differently, but hard to cal - slides around with tax brackets in future differently, but hard to calc - slides around with tax brackets in future
UI: UI:
* have a comparison management capability - somehow, at least be able to delete csets (can I put an x inside the menu? OR, have a manage button and pop-up?) * to allow <yr> total on bill summary, need to re-work annual growth to a drop-down
- [DONE] mock-up
* maybe a help/note/set of links somehow, to common things: e.g. macquarie int rate page, inflation source, etc. - need to change 'which growth' to accommodate new growth models (cpi, override*)
- also then could effectively take those "notes" out of db.py and incorporate, so that when I update stuff it gets done right - [DONE] do this in DB with new table - then don't do crazy pattern matching/making up overrides in the UI code
- in fact, I *COULD* have a basic date check/workflow, e.g. its been more than X days since Y was updated, click here - get UI to use bill_growth_types table
and it walks me through the manual update steps and when I finish its datestamped until next time - get UseGrowth() to use bill_growth_types table
For bills: For bills:
* quick hack, Force Re-estimate (delete estimates and reload bill page)
* 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

121
bills.py
View File

@@ -1,6 +1,8 @@
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
import re
from datetime import date, timedelta from datetime import date, timedelta
@@ -237,6 +239,8 @@ def actually_add_estimated_new_quarter_bill_forced( bill_type, bill_info, yr, q
# NOTE: ALWAYS called for first year - don't always add bills/see below # NOTE: ALWAYS called for first year - don't always add bills/see below
def add_missing_monthly_bills_in_yr( bill_type, bill_info, yr ): def add_missing_monthly_bills_in_yr( bill_type, bill_info, yr ):
print( f"add_missing_monthly_bills_in_yr for ( bt={bill_type} -- yr={yr} )" )
# start date arithmetic from first bill (this is possibly an issue if monthly is not # start date arithmetic from first bill (this is possibly an issue if monthly is not
# really perfectly the same each month, but its only for an estimate so should be ok # really perfectly the same each month, but its only for an estimate so should be ok
dd = bill_info[bill_type]['first_bill']['bill_date'][8:] dd = bill_info[bill_type]['first_bill']['bill_date'][8:]
@@ -294,8 +298,18 @@ def get_growth_value( bt, bill_type ):
return el['ann_growth_min'] return el['ann_growth_min']
elif which == 'simple': elif which == 'simple':
return el['ann_growth_simple'] return el['ann_growth_simple']
else: elif which == 'max':
return el['ann_growth_max'] return el['ann_growth_max']
elif which == 'cpi':
finance_data = get_finance_data()
return finance_data['Inflation']
else:
match = re.match("flat-(\d+)", which )
if match:
return int(match.group(1))
else:
print( f"FAILED TO GET_GROWTH_VALUE --> which={which}" )
return 0
################################################################################ ################################################################################
@@ -310,7 +324,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 +399,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 )
@@ -405,11 +418,10 @@ def deal_with_future_car_bills( key_dates, future_car_bills, bill_info ):
amt=fb['amount'] amt=fb['amount']
bt=fb['bill_type'] bt=fb['bill_type']
# factor in growth for next bill # factor in growth for next bill
for yr in range( int(car_yr), END_YEAR ): for yr in range( int(car_yr), END_YEAR+1 ):
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
@@ -429,27 +441,23 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ):
bt=fb['bill_type'] bt=fb['bill_type']
if bill_info[bt]['num_ann_bills'] == 1: if bill_info[bt]['num_ann_bills'] == 1:
# factor in growth for next bill # factor in growth for next bill
for yr in range( int(D_quit_yr), END_YEAR ): for yr in range( int(D_quit_yr), END_YEAR+1 ):
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+1 ):
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 +500,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 +594,90 @@ 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 )
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+1 ):
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+1 ):
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+1 ):
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

92
db.py
View File

@@ -84,6 +84,12 @@ def init_db():
FOREIGN KEY(comparison_set_id) REFERENCES comparison_set(id) FOREIGN KEY(comparison_set_id) REFERENCES comparison_set(id)
)''') )''')
cur.execute('''CREATE TABLE IF NOT EXISTS bill_freq (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name STRING,
num_bills_per_annum INTEGER
)''')
cur.execute('''CREATE TABLE IF NOT EXISTS bill_type ( cur.execute('''CREATE TABLE IF NOT EXISTS bill_type (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
freq INTEGER, freq INTEGER,
@@ -95,12 +101,6 @@ def init_db():
FOREIGN KEY(freq) REFERENCES bill_freq(id) FOREIGN KEY(freq) REFERENCES bill_freq(id)
)''') )''')
cur.execute('''CREATE TABLE IF NOT EXISTS bill_freq (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name STRING,
num_bills_per_annum INTEGER
)''')
cur.execute('''CREATE TABLE IF NOT EXISTS bill_data ( cur.execute('''CREATE TABLE IF NOT EXISTS bill_data (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
bill_type INTEGER, bill_type INTEGER,
@@ -116,28 +116,56 @@ def init_db():
show_estimated INTEGER show_estimated INTEGER
)''') )''')
# Check if table is empty, if so insert default values cur.execute('''CREATE TABLE IF NOT EXISTS bill_growth_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name string,
value INTEGER
)''')
# Check if finance 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
(4762.29, 10, 24000, 620, 2412, 45824.68, 83738.74, 80000, 424875.26, 4.75, 2.4, 10000, 50000, 10000, 76.85, 1000, 750, 1111, 4.52, 163.32, '2025-06-01', '2025-09-01', '2025-02-20', 4, 0, 0)) (4762.29, 10, 24000, 620, 2412, 45824.68, 83738.74, 80000, 424875.26, 4.75, 2.4, 10000, 50000, 10000, 76.85, 1000, 750, 1111, 4.52, 163.32, '2025-06-01', '2025-09-01', '2025-02-20', 4, 0, 0))
# Check if bill_freq table is empty, if so insert default values
cur.execute('SELECT COUNT(*) FROM bill_freq')
if cur.fetchone()[0] == 0:
cur.execute( "INSERT INTO bill_freq values ( 1, 'Annual', 1 )" ) cur.execute( "INSERT INTO bill_freq values ( 1, 'Annual', 1 )" )
cur.execute( "INSERT INTO bill_freq values ( 2, 'Quarterly', 4 )" ) cur.execute( "INSERT INTO bill_freq values ( 2, 'Quarterly', 4 )" )
cur.execute( "INSERT INTO bill_freq values ( 3, 'Quarterly (forced)', 4 )" ) cur.execute( "INSERT INTO bill_freq values ( 3, 'Quarterly (forced)', 4 )" )
cur.execute( "INSERT INTO bill_freq values ( 4, 'Monthly', 12 )" ) cur.execute( "INSERT INTO bill_freq values ( 4, 'Monthly', 12 )" )
# start with no specific Tab/bill_type to show, and dont show_estimated # start with no specific Tab/bill_type to show, and dont show_estimated
# Check if bill_ui table is empty, if so insert default values
cur.execute('SELECT COUNT(*) FROM bill_ui')
if cur.fetchone()[0] == 0:
cur.execute( "INSERT INTO bill_ui values ( 1, null, 0 )" ) cur.execute( "INSERT INTO bill_ui values ( 1, null, 0 )" )
# Check if bill_growth_types table is empty, if so insert default values
cur.execute('SELECT COUNT(*) FROM bill_growth_types')
if cur.fetchone()[0] == 0:
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Min', 0 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Avg', 0 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Max', 0 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Simple', 0 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'CPI', 0 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 0', 0 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 1', 1 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 2', 2 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 3', 3 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 4', 4 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 5', 5 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 6', 6 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 7', 7 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 8', 8 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 9', 9 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 10', 10 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 12', 12 )" )
cur.execute( "INSERT INTO bill_growth_types ( name, value ) values ( 'Flat 15', 15 )" )
conn.commit() conn.commit()
conn.close() conn.close()
@@ -376,6 +404,14 @@ def set_bill_type_growth( id, min_g, avg_g, max_g, simple_g ):
conn.close() conn.close()
return return
def get_bill_growth_types():
conn = connect_db(True)
cur = conn.cursor()
cur.execute('SELECT * FROM bill_growth_types')
growth = cur.fetchall()
conn.close()
return growth
def get_bill_ui(): def get_bill_ui():
conn = connect_db(True) conn = connect_db(True)
cur = conn.cursor() cur = conn.cursor()
@@ -397,6 +433,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()
@@ -404,3 +448,19 @@ def delete_estimated_bills():
conn.commit() conn.commit()
conn.close() conn.close()
return return
def delete_estimated_bills_for(bt_id):
conn = connect_db(False)
cur = conn.cursor()
cur.execute( f"delete from bill_data where estimated=1 and bill_type = {bt_id}" )
conn.commit()
conn.close()
return
def delete_cset(id):
conn = connect_db(False)
cur = conn.cursor()
cur.execute( f"delete from comparison_set where id = '{id}'" )
conn.commit()
conn.close()
return

67
main.py
View File

@@ -1,16 +1,19 @@
# main.py # main.py
from flask import Flask, render_template, request, redirect, url_for, Response, jsonify from flask import Flask, render_template, request, redirect, url_for, Response, jsonify
from calc import calculate_savings_depletion, calc_key_dates from calc import calculate_savings_depletion, calc_key_dates
from db import init_db, get_finance_data, update_finance, get_budget_data, insert_cset, get_comp_set_data, get_comp_set_options, get_bill_freqs from db import init_db, get_finance_data, update_finance, get_budget_data
from db import get_bill_data, new_bill, update_bill_data, delete_bill, delete_estimated_bills from db import insert_cset, get_comp_set_data, get_comp_set_options, delete_cset
from db import get_bill_freqs, get_bill_growth_types
from db import get_bill_data, new_bill, update_bill_data, delete_bill, delete_estimated_bills, delete_estimated_bills_for
from db import get_bill_ui, save_ui from db import get_bill_ui, save_ui
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
import csv import csv
import io import io
import requests
from disp import FP_VAR from disp import FP_VAR
app = Flask(__name__) app = Flask(__name__)
@@ -113,6 +116,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 +147,26 @@ 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()
if old_finance_data['Inflation'] != new_finance_data['Inflation']:
# need to check if any bill type is using CPI, if so, force those future bills to be recalculated
bill_types = get_bill_types()
for bt in bill_types:
if bt['which_growth'] == 'cpi':
print( f"OK, changed inflation and need to redo bills for bt_id={bt['id']}" )
delete_estimated_bills_for( bt['id'] )
#recalc_estimated_bills_for( bt['id'] )
# okay, now go through code to recalc bills...
base=request.url_root
response = requests.get(f"{base}/bills")
if response.status_code == 200:
print("ALL GOOD")
else:
print("FFS")
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/bills') @app.route('/bills')
@@ -154,13 +178,14 @@ def DisplayBillData():
bill_types = get_bill_types() bill_types = get_bill_types()
bill_freqs = get_bill_freqs() bill_freqs = get_bill_freqs()
bill_ui = get_bill_ui() bill_ui = get_bill_ui()
bill_growth_types = get_bill_growth_types()
# take bill data, AND work out estimated future bills - process this into the bill_info array, # take bill data, AND work out estimated future bills - process this into the bill_info array,
bill_info=process_bill_data(bill_data, bill_types, bill_freqs, key_dates) bill_info=process_bill_data(bill_data, bill_types, bill_freqs, key_dates)
# get an array of the total costs of bills each year - purely cosmetic (using bill_info) # get an array of the total costs of bills each year - purely cosmetic (using bill_info)
total=calc_future_totals(bill_info, bill_types) total=calc_future_totals(bill_info, bill_types)
# update/re-get bill_data now that new estimated bills have been added # update/re-get bill_data now that new estimated bills have been added
bill_data = get_bill_data("order_by_bill_type_then_date") bill_data = get_bill_data("order_by_bill_type_then_date")
return render_template('bills.html', bill_data=bill_data, bill_types=bill_types, bill_freqs=bill_freqs, bill_ui=bill_ui, this_year=datetime.today().year, END_YEAR=END_YEAR, total=total, key_dates=key_dates ) return render_template('bills.html', bill_data=bill_data, bill_types=bill_types, bill_freqs=bill_freqs, bill_ui=bill_ui, this_year=datetime.today().year, END_YEAR=END_YEAR, total=total, key_dates=key_dates, growth=bill_growth_types, cpi=finance_data['Inflation'] )
@app.route('/newbilltype', methods=['POST']) @app.route('/newbilltype', methods=['POST'])
def InsertBillType(): def InsertBillType():
@@ -218,8 +243,40 @@ def SaveUI():
@app.route('/force_recalc_bills', methods=['POST']) @app.route('/force_recalc_bills', methods=['POST'])
def force_recalc_bills(): def force_recalc_bills():
delete_estimated_bills() delete_estimated_bills()
recalcFutureBills()
return "200" return "200"
@app.route('/cset')
def cset():
finance_data = get_finance_data()
get_comp_set_options(finance_data)
comp_data={}
for el in finance_data['COMP_SETS']:
comp_data[el[0]] = get_comp_set_data( el[0] )
# delete items not that helpful (same for all, not that interesting)
if el[0]:
del comp_data[el[0]]['vars']['Car_loan_via_pay']
del comp_data[el[0]]['vars']['Mark_reno']
del comp_data[el[0]]['vars']['Mark_reno_date']
del comp_data[el[0]]['vars']['Overseas_trip_date']
del comp_data[el[0]]['vars']['Car_balloon']
del comp_data[el[0]]['vars']['Mich_present']
del comp_data[el[0]]['vars']['D_TLS_shares']
del comp_data[el[0]]['vars']['M_TLS_shares']
return render_template('cset.html', finance=finance_data, comp_data=comp_data )
@app.route('/delcset', methods=['POST'])
def DeleteCSet():
data = request.get_json()
delete_cset( data['id'] )
return "200"
# quick health route so traefik knows we are up
@app.route('/health')
def health():
return {"status": "ok"}, 200
# Main program # Main program
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True)

View File

@@ -8,4 +8,4 @@ pysqlite3
Werkzeug Werkzeug
flask-compress flask-compress
gunicorn gunicorn
requests

View File

@@ -36,7 +36,7 @@
{% endfor %} {% endfor %}
</table> </table>
#} #}
<div class="col-7"> <div class="col-8">
<div class="row"> <div class="row">
<div class="col-2 form-control-inline d-none new-bill-type-class">Bill Type</div> <div class="col-2 form-control-inline d-none new-bill-type-class">Bill Type</div>
<div class="col-2 form-control-inline d-none new-bill-type-class">Frequency</div> <div class="col-2 form-control-inline d-none new-bill-type-class">Frequency</div>
@@ -55,66 +55,92 @@
<button id="recalc-bills" class="mt-4 col-2 offset-3 btn btn-warning bg-warning-subtle text-warning" onClick="ForceRecalcBills()"><span class="bi bi-repeat"> Recalculate</span></button> <button id="recalc-bills" class="mt-4 col-2 offset-3 btn btn-warning bg-warning-subtle text-warning" onClick="ForceRecalcBills()"><span class="bi bi-repeat"> Recalculate</span></button>
</div> </div>
<div class="row"> <div class="row">
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div> <div class="px-0 col"><label class="form-control d-flex
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Frequency</ ></div> align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div>
<div class="px-0 col-4"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Annual Growth Est (min/avg/max/simple)</ ></div> <div class="px-0 col"><label class="form-control d-flex
<div class="px-0 col-1"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">{{this_year}} Total</ ></div> align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Frequency</ ></div>
<div class="px-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div> <div class="px-0 col"><label class="form-control d-flex
align-items-end justify-content-center h-100 border-0 fw-bold
bg-body-tertiary rounded-0">Annual Growth</ ></div>
{% for yr in range( 2025, 2032 ) %}
<div class="px-0 col"><label class="form-control d-flex
align-items-end justify-content-center h-100 border-0
fw-bold bg-body-tertiary rounded-0">{{yr}} Total</ ></div>
{% endfor %}
<div class="px-0 col"><label class="form-control d-flex
align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div>
{# spacer to get header line right now we don't use forced col widths #}
<div class="px-0 col"><label class="form-control d-flex
align-items-end justify-content-center h-100 border-0 fw-bold bg-body-tertiary rounded-0"> </ ></div>
</div> </div>
{% for bt in bill_types %} {% for bt in bill_types %}
<div class="row"> <div class="row">
<div class="px-0 col-2"><input type="text" class="bill-type-{{bt.id}} form-control text-center" id="bill-type-name-{{bt.id}}" value="{{ bt.name }}" disabled> </div> <div class="px-0 col-1"><input type="text" class="bill-type-{{bt.id}} form-control text-center" id="bill-type-name-{{bt.id}}" value="{{ bt.name }}" disabled> </div>
<!-- bind Enter to save this bill-type --> <!-- bind Enter to save this bill-type -->
<script>$("#bill-type-name-{{bt.id}}").keyup(function(event){ if(event.which == 13){ $('#bill-type-save-{{bt.id}}').click(); } event.preventDefault(); });</script> <script>$("#bill-type-name-{{bt.id}}").keyup(function(event){ if(event.which == 13){ $('#bill-type-save-{{bt.id}}').click(); } event.preventDefault(); });</script>
<div class="px-0 col-2"><select id="bill-type-freq-{{bt.id}}" class="bill-type-{{bt.id}} form-select text-center" disabled> <div class="px-0 col-1"><select id="bill-type-freq-{{bt.id}}" class="bill-type-{{bt.id}} form-select text-center" disabled>
{% for bf in bill_freqs %} {% for bf in bill_freqs %}
<option value={{bf.id}}>{{bf.name}}</option> <option value={{bf.id}}>{{bf.name}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<script>$('#bill-type-freq-{{bt.id}}').val( {{bt.freq}} );</script> <script>$('#bill-type-freq-{{bt.id}}').val( {{bt.freq}} );</script>
<div class="px-0 col-4"> <div class="px-0 col">
<div class="btn-group w-100" role="group"> <div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="min-{{bt.id}}" autocomplete="off" <select id="{{bt.id}}_growth" class="form-select col" onChange="UseGrowth({{bt.id}})">
onChange="UseGrowth({{bt.id}}, 'min')" {% if bt.which_growth == 'min' %}checked{% endif %}> {% for gt in growth %}
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="min-{{bt.id}}" style="width: 6ch;"> {% if gt.name == 'Min' %}
{% if bt.ann_growth_min> 0 and bt.ann_growth_min < 10 %} &nbsp; {% endif %} <option value='min'
{{'%.2f'|format(bt.ann_growth_min)}} {% if bt.which_growth == 'min' %}selected{% endif %}
</label> >{{'%.2f'|format(bt.ann_growth_min)}}% {{gt.name}}</option>
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="avg-{{bt.id}}" autocomplete="off" {% elif gt.name == 'Avg' %}
onChange="UseGrowth({{bt.id}}, 'avg')" {% if bt.which_growth == 'avg' %}checked{% endif %}> <option value='avg'
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="avg-{{bt.id}}" style="width: 6ch;"> {% if bt.which_growth == 'avg' %}selected{% endif %}
{% if bt.ann_growth_avg < 10 %} &nbsp; {% endif %} >{{'%.2f'|format(bt.ann_growth_avg)}}% {{gt.name}}</option>
{{'%.2f'|format(bt.ann_growth_avg)}} {% elif gt.name == 'Max' %}
</label> <option value='max'
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="max-{{bt.id}}" autocomplete="off" {% if bt.which_growth == 'max' %}selected{% endif %}
onChange="UseGrowth({{bt.id}}, 'max')" {% if bt.which_growth == 'max' %}checked{% endif %}> >{{'%.2f'|format(bt.ann_growth_max)}}% {{gt.name}}</option>
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="max-{{bt.id}}" style="width: 6ch;"> {% elif gt.name == 'Simple' %}
{% if bt.ann_growth_max < 10 %} &nbsp; {% endif %} <option value='simple'
{{'%.2f'|format(bt.ann_growth_max)}} {% if bt.which_growth == 'simple' %}selected{% endif %}
</label> >{{'%.2f'|format(bt.ann_growth_simple)}}% {{gt.name}}</option>
<input type="radio" class="btn-check" name="growth-{{bt.id}}" id="simple-{{bt.id}}" autocomplete="off" {% elif gt.name == 'CPI' %}
onChange="UseGrowth({{bt.id}}, 'simple')" {% if bt.which_growth == 'simple' %}checked{% endif %}> <option value='cpi'
<label class="btn btn-outline-secondary font-monospace d-inline-block text-end" for="simple-{{bt.id}}" style="width: 6ch;"> {% if bt.which_growth == 'cpi' %}selected{% endif %}
{% if bt.ann_growth_simple < 10 %} &nbsp; {% endif %} >{{'%.2f'|format(cpi)}}% {{gt.name}}</option>
{{'%.2f'|format(bt.ann_growth_simple)}} {% else %}
</label> <option value='flat-{{gt.value}}'
{% if bt.which_growth == 'flat-'+gt.value|string %}selected{% endif %}
>{{'%.2f'|format(gt.value)}}% {{gt.name}}
</option>
{% endif %}
{% endfor %}
</select>
</div> </div>
</div> </div>
<div class="px-0 col-1"><input type="text" class="bill-type-total-{{bt.id}} form-control text-center" id="bill-type-total-{{bt.id}}" value="${{'%.2f'|format(total[bt.id][this_year])}}" disabled> </div> {% for yr in range( 2025, 2032 ) %}
<button id="bill-type-chg-{{bt.id}}" class="px-0 col-1 btn btn-success bg-success-subtle text-success" onClick="StartUpdateBillType( {{bt.id}} )"><span class="bi bi-pencil-square"> Change</button> <div class="px-0 col"><input type="text" class="bill-type-total-{{bt.id}} form-control text-center" id="bill-type-total-{{bt.id}}" value="${{'%.2f'|format(total[bt.id][yr])}}" disabled> </div>
<button id="bill-type-del-{{bt.id}}" class="px-0 col-1 btn btn-danger bg-danger-subtle text-danger" onClick="DelBillType({{bt.id}})"><span class="bi bi-trash3"> Delete</button> {% endfor %}
<button id="bill-type-save-{{bt.id}}" class="px-0 col-1 btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBillType( {{bt.id}} )"><spam class="bi bi-floppy"> Save</button> <button id="bill-type-chg-{{bt.id}}" class="px-0 col btn btn-success bg-success-subtle text-success" onClick="StartUpdateBillType( {{bt.id}} )"><span class="bi bi-pencil-square"> Change</button>
<button id="bill-type-canc-{{bt.id}}" class="px-0 col-1 btn btn-danger bg-danger-subtle text-danger d-none" onClick="CancelUpdateBillType({{bt.id}}, '{{bt.name}}')"><span class="bi bi-x"> Cancel</button> <button id="bill-type-del-{{bt.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger" onClick="DelBillType({{bt.id}})"><span class="bi bi-trash3"> Delete</button>
<button id="bill-type-save-{{bt.id}}" class="px-0 col btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBillType( {{bt.id}} )"><spam class="bi bi-floppy"> Save</button>
<button id="bill-type-canc-{{bt.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger d-none" onClick="CancelUpdateBillType({{bt.id}}, '{{bt.name}}')"><span class="bi bi-x"> Cancel</button>
</div> </div>
{% endfor %} {% endfor %}
{% for yr in range( this_year, END_YEAR) %} <div class="row">
<div class="px-0 col"></div>
<div class="px-0 col"></div>
<div class="px-0 col"><input type="text" class="form-control text-end text-primary fs-5" value="TOTAL:"></div>
{% for yr in range( this_year, END_YEAR+1) %}
{% set tot=namespace( sum=0 ) %} {% set tot=namespace( sum=0 ) %}
{% for bt in bill_types %} {% for bt in bill_types %}
{% if bt.id in total %} {% if bt.id in total %}
{% set tot.sum = tot.sum + total[bt.id][yr] %} {% set tot.sum = tot.sum + total[bt.id][yr] %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="px-0 col"><input type="text" class="form-control text-center text-primary bg-dark fs-5" value="${{'%.2f'|format(tot.sum)}}" disabled></div>
{#
{% set markup="h5" %} {% set markup="h5" %}
{% if yr == this_year %} {% if yr == this_year %}
{% set markup="h4 pt-4" %} {% set markup="h4 pt-4" %}
@@ -127,12 +153,16 @@
${{'%.2f'|format(tot.sum)}} ${{'%.2f'|format(tot.sum)}}
</div> </div>
</div> </div>
#}
{% endfor %} {% endfor %}
<div class="px-0 col"></div>
<div class="px-0 col"></div>
</div>
</div> </div>
<!-- right-hand-side, bill types (e.g. gas, phone, etc.) --> <!-- right-hand-side, bill types (e.g. gas, phone, etc.) -->
<div class="col-5"> <div class="col-4">
<div class="row"> <div class="row">
<div class="col-2 form-control-inline d-none new-bill-data-class">Bill Type</div> <div class="col-2 form-control-inline d-none new-bill-data-class">Bill Type</div>
<div id="new-bill-data-date-label" class="col-4 form-control-inline d-none new-bill-data-class">Date</div> <div id="new-bill-data-date-label" class="col-4 form-control-inline d-none new-bill-data-class">Date</div>
@@ -186,8 +216,9 @@
<div class="row pt-2"> <div class="row pt-2">
<div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div> <div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Name</ ></div>
<div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Date</ ></div> <div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Date</ ></div>
<div class="p-0 col-2"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Amount</ ></div> <div class="p-0 col"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Amount</ ></div>
<div class="px-0 col-4"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div> <div class="px-0 col"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0">Actions</ ></div>
<div class="px-0 col"><label class="form-control text-center border-0 fw-bold bg-body-tertiary rounded-0 h-100"></ ></div>
</div> </div>
{% endif %} {% endif %}
{% if bd.bill_type == bt.id %} {% if bd.bill_type == bt.id %}
@@ -203,15 +234,24 @@
<div class="px-0 col-2"> <input type="text" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div> <div class="px-0 col-2"> <input type="text" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div>
{% else %} {% else %}
<div class="px-0 col-2"> <input type="date" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div> <div class="px-0 col-2"> <input type="date" class="{{classes}}" id="bill-data-date-{{bd.id}}" value="{{ bd.bill_date }}" disabled> </div>
<script>
if( typeof future_id !== 'undefined' && future_id>0) {
first_col_id={{bd.id}}
future_id=0
}
</script>
{% endif %} {% endif %}
<div class="px-0 col-2"> <input type="number" class="{{classes}}" id="bill-data-amount-{{bd.id}}" value="{{ bd.amount }}" disabled> </div> <div class="px-0 col"> <input type="number" class="{{classes}}" id="bill-data-amount-{{bd.id}}" value="{{ bd.amount }}" disabled> </div>
{% if bd.estimated == 0 %} {% if bd.estimated == 0 %}
<button id="bill-data-chg-{{bd.id}}" class="px-0 col-2 btn btn-success bg-success-subtle text-success" onClick="StartUpdateBill( {{bd.id}} )"><span class="bi bi-pencil-square"> Change</button> <button id="bill-data-chg-{{bd.id}}" class="px-0 col btn btn-success bg-success-subtle text-success" onClick="StartUpdateBill( {{bd.id}} )"><span class="bi bi-pencil-square"> Change</button>
<button id="bill-data-del-{{bd.id}}" class="px-0 col-2 btn btn-danger bg-danger-subtle text-danger" onClick="DeleteBill( {{bd.id }} )"><span class="bi bi-trash3"> Delete <button id="bill-data-del-{{bd.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger" onClick="DeleteBill( {{bd.id }} )"><span class="bi bi-trash3"> Delete
<button id="bill-data-save-{{bd.id}}" class="px-0 col-2 btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBill( {{bd.id}} )"><span class="bi bi-floppy"> Save</button> <button id="bill-data-save-{{bd.id}}" class="px-0 col btn btn-success bg-success-subtle text-success d-none" onClick="UpdateBill( {{bd.id}} )"><span class="bi bi-floppy"> Save</button>
<button id="bill-data-canc-{{bd.id}}" class="px-0 col-2 btn btn-danger bg-danger-subtle text-danger d-none" <button id="bill-data-canc-{{bd.id}}" class="px-0 col btn btn-danger bg-danger-subtle text-danger d-none"
onClick="CancelUpdateBill({{bd.id}}, '{{bd.name}}', '{{bd.bill_date}}', '{{bd.amount}}')"> <span class="bi bi-x"> Cancel</button> onClick="CancelUpdateBill({{bd.id}}, '{{bd.name}}', '{{bd.bill_date}}', '{{bd.amount}}')"> <span class="bi bi-x"> Cancel</button>
</button> </button>
{% else %}
<div class="px-0 col"></div>
<div class="px-0 col"></div>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -403,10 +443,11 @@
data: JSON.stringify( { 'id': id } ), success: function() { window.location='bills' } } ) data: JSON.stringify( { 'id': id } ), success: function() { window.location='bills' } } )
} }
function UseGrowth( bt, which ) function UseGrowth( bt_id )
{ {
which = $('#'+bt_id+ '_growth option:selected').val()
$.ajax( { type: 'POST', url: '/usegrowth', contentType: 'application/json', $.ajax( { type: 'POST', url: '/usegrowth', contentType: 'application/json',
data: JSON.stringify( { 'bill_type': bt, 'which_growth': which } ), success: function() { window.location='bills' } } ) data: JSON.stringify( { 'bill_type': bt_id, 'which_growth': which } ), success: function() { window.location='bills' } } )
} }
function SaveTab( last_tab ) function SaveTab( last_tab )
@@ -472,7 +513,13 @@
$.ajax( { type: 'POST', url: '/force_recalc_bills', contentType: 'application/json', success: function() { window.location='bills' } } ) $.ajax( { type: 'POST', url: '/force_recalc_bills', contentType: 'application/json', success: function() { window.location='bills' } } )
} }
/*
$(document.ready() {
for( bt in future_ids ) {
$('#bill-data-date-'+future_ids[bt]).width( $('#bill-data-date-'+first_col_id[bt]).width() )
}
}
*/
</script> </script>
</body> </body>
</html> </html>

66
templates/cset.html Normal file
View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<title>Finance Form (Comparison Sets)</title>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/annotations.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
<style>
.col-form-label { width:140px; }
html { font-size: 80%; }
</style>
</head>
<body>
<div class="pt-2 mx-2 container-fluid row">
<h3 align="center">Comparison Sets (go to <a href="/">Finance Tracker</a>)</h3>
<div class="row">
</div>
<table class="table table-sm table-dark table-striped">
<thead>
<tr>
<th class="text-center">Action</th>
{% for d in comp_data[finance['COMP_SETS'][1][0]]['vars'] %}
{% if d != 'id' %}
<th class="text-center">{{ d|replace('_', '<br>')|safe }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
{% for c in finance['COMP_SETS'] %}
{% if c[0] != 0 %}
<tr>
<td><button class="btn btn-danger bg-danger-subtle text-danger"
onClick="DelCSet({{c[0]}})"><span class="bi bi-trash3"> Delete</button></td>
{% for d in comp_data[c[0]]['vars'] %}
{% if d != 'id' %}
{% if d == 'name' %}
<td class="align-middle">{{comp_data[c[0]]['vars'][d]}}</td>
{% else %}
<td class="text-center align-middle">{{comp_data[c[0]]['vars'][d]}}</td>
{% endif %}
{% endif %}
{% endfor %}
</tr>
{% endif %}
{% endfor %}
</table>
<script>
function DelCSet( id )
{
// POST to a delete, success should just reload this page
$.ajax( { type: 'POST', url: '/delcset', contentType: 'application/json', data: JSON.stringify( { 'id': id } ), success: function() { window.location='cset' } } )
}
</script>
</body>
</html>

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>)</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: $1000</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 %}
@@ -105,7 +130,7 @@
</h5> </h5>
<div class="row"> <div class="row">
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">2025</div> <div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center bg-secondary text-light">{{savings[0][0][:4]}}</div>
{# inside started if below, we add blank lines to the start of the year so the dates line up #} {# inside started if below, we add blank lines to the start of the year so the dates line up #}
{% for _ in range( 0, padding ) %} {% for _ in range( 0, padding ) %}
@@ -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']}} )
@@ -389,4 +415,3 @@
</div> </div>
</body> </body>
</html> </html>