diff --git a/BUGS b/BUGS index a432fad..47270da 100644 --- a/BUGS +++ b/BUGS @@ -1,2 +1,4 @@ +* kayo bills are wrong in between normal bills + * 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 diff --git a/TODO b/TODO index bf03801..530024d 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,22 @@ +bills html, and growth types are very lame... could I do something more like: + {% for gt in growth %} + {% if gt.name == 'Min' %} + + + CALC: * if I quit at different times of the financial year, technically the amount I earn will be taxed differently, but hard to calc - slides around with tax brackets in future UI: - * allow changing comparison when in tab with graph of comparisons - remember to force redraw page and then come to graph again - * 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 ? + * to allow total on bill summary, need to re-work annual growth to a drop-down + - [DONE] mock-up + - need to change 'which growth' to accommodate new growth models (cpi, override*) + - [DONE] do this in DB with new table - then don't do crazy pattern matching/making up overrides in the UI code + - get UI to use bill_growth_types table + - get UseGrowth() to use bill_growth_types table For bills: * might need to be able to mark a specific bill as an outlier: diff --git a/bills.py b/bills.py index 5e8a5a9..a6b38fe 100644 --- a/bills.py +++ b/bills.py @@ -2,6 +2,7 @@ from db import set_bill_type_growth, new_bill, deleteFutureEstimates, get_financ from calc import calc_key_dates from defines import END_YEAR import datetime +import re from datetime import date, timedelta @@ -238,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 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 # 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:] @@ -295,8 +298,18 @@ def get_growth_value( bt, bill_type ): return el['ann_growth_min'] elif which == 'simple': return el['ann_growth_simple'] - else: + elif which == '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 ################################################################################ @@ -405,7 +418,7 @@ def deal_with_future_car_bills( key_dates, future_car_bills, bill_info ): amt=fb['amount'] bt=fb['bill_type'] # 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}" # if we dont already have an annual bill for this year (all car bills are annual) if yr not in bill_info[bt]['year']: @@ -428,7 +441,7 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ): bt=fb['bill_type'] if bill_info[bt]['num_ann_bills'] == 1: # 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}" # if we dont already have an annual bill for this year if not find_this_bill( bt, bill_info, new_date ): @@ -440,7 +453,7 @@ def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ): new_date=f"{D_quit_yr}-{m:02d}-{dq_dd}" if not find_this_bill( bt, bill_info, 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 for m in range( 1, 13): new_date=f"{yr}-{m:02d}-{dq_dd}" @@ -614,7 +627,6 @@ def recalcFutureBills(): 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() @@ -623,7 +635,6 @@ def recalcFutureBills(): # 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 @@ -635,7 +646,7 @@ def recalcFutureBills(): # 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 ): + 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 @@ -654,7 +665,7 @@ def recalcFutureBills(): 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 ): + 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 ) @@ -664,7 +675,7 @@ def recalcFutureBills(): 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 ): + 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}" diff --git a/db.py b/db.py index 0edfa44..bfe35a3 100644 --- a/db.py +++ b/db.py @@ -84,6 +84,12 @@ def init_db(): 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 ( id INTEGER PRIMARY KEY AUTOINCREMENT, freq INTEGER, @@ -95,12 +101,6 @@ def init_db(): 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 ( id INTEGER PRIMARY KEY AUTOINCREMENT, bill_type INTEGER, @@ -116,19 +116,56 @@ def init_db(): 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') if cur.fetchone()[0] == 0: 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', (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 ( 2, 'Quarterly', 4 )" ) cur.execute( "INSERT INTO bill_freq values ( 3, 'Quarterly (forced)', 4 )" ) cur.execute( "INSERT INTO bill_freq values ( 4, 'Monthly', 12 )" ) # 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 )" ) + + # 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.close() @@ -367,6 +404,14 @@ def set_bill_type_growth( id, min_g, avg_g, max_g, simple_g ): conn.close() 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(): conn = connect_db(True) cur = conn.cursor() @@ -404,6 +449,14 @@ def delete_estimated_bills(): conn.close() 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() diff --git a/main.py b/main.py index bf9c1fe..502f5b7 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,11 @@ # main.py from flask import Flask, render_template, request, redirect, url_for, Response, jsonify 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 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 init_db, get_finance_data, update_finance, get_budget_data +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_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, recalcFutureBills from defines import END_YEAR @@ -11,6 +13,7 @@ from collections import defaultdict, Counter from datetime import datetime, date import csv import io +import requests from disp import FP_VAR app = Flask(__name__) @@ -148,6 +151,21 @@ def update(): # 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')) @@ -160,13 +178,14 @@ def DisplayBillData(): bill_types = get_bill_types() bill_freqs = get_bill_freqs() 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, 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) total=calc_future_totals(bill_info, bill_types) # update/re-get bill_data now that new estimated bills have been added 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']) def InsertBillType(): @@ -224,6 +243,7 @@ def SaveUI(): @app.route('/force_recalc_bills', methods=['POST']) def force_recalc_bills(): delete_estimated_bills() + recalcFutureBills() return "200" @app.route('/cset') diff --git a/templates/bills.html b/templates/bills.html index a822882..9a01d23 100644 --- a/templates/bills.html +++ b/templates/bills.html @@ -36,7 +36,7 @@ {% endfor %} #} -
+
Bill Type
Frequency
@@ -55,66 +55,92 @@
-
-
-
-
-
+
+
+
+ {% for yr in range( 2025, 2032 ) %} +
+ {% endfor %} +
+ {# spacer to get header line right now we don't use forced col widths #} +
{% for bt in bill_types %}
-
+
-
{% for bf in bill_freqs %} {% endfor %}
-
+
- - - - - - - - +
-
- - - - + {% for yr in range( 2025, 2032 ) %} +
+ {% endfor %} + + + +
{% endfor %} - {% for yr in range( this_year, END_YEAR) %} +
+
+
+
+ {% for yr in range( this_year, END_YEAR+1) %} {% set tot=namespace( sum=0 ) %} {% for bt in bill_types %} {% if bt.id in total %} {% set tot.sum = tot.sum + total[bt.id][yr] %} {% endif %} {% endfor %} +
+ {# {% set markup="h5" %} {% if yr == this_year %} {% set markup="h4 pt-4" %} @@ -127,12 +153,16 @@ ${{'%.2f'|format(tot.sum)}}
+ #} {% endfor %} +
+
+
-
+
Bill Type
Date
@@ -186,8 +216,9 @@
-
-
+
+
+
{% endif %} {% if bd.bill_type == bt.id %} @@ -203,15 +234,24 @@
{% else %}
+ {% endif %} -
+
{% if bd.estimated == 0 %} - - - - + + + + + {% else %} +
+
{% endif %}
{% endif %} @@ -403,10 +443,11 @@ 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', - 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 ) @@ -472,6 +513,13 @@ $.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() ) + } + } +*/ diff --git a/templates/index.html b/templates/index.html index 1d24dde..573dfdf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -33,7 +33,7 @@