change to col-auto everywhere, with some more forcing of width maximums to make the page more consistent, redid defaults to incorporate updated per fortnight lease cost, and updated inflation figure/notes/references. Updated default Living expenses to 84000 to match latest view of data, fixed up compare_to to now work, shows a graph of data to compare with, allows drop-down to be compare_to nothing or a saved set, only 1 hand saved for now. Annotations on graph for large changes in savings now work and are legible - had to allow overlap and do some overly complex left/right up/down offsetting to make them all sensible
This commit is contained in:
26
calc.py
26
calc.py
@@ -1,9 +1,16 @@
|
|||||||
# calc.py
|
# calc.py
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
def add_annotation(finance, dt, amt, text):
|
def add_annotation(finance, dt, total, delta, text):
|
||||||
|
# dont add an annotation for small changes (jic)
|
||||||
|
if abs(delta) < 5000:
|
||||||
|
return
|
||||||
tm = dt.timestamp() * 1000
|
tm = dt.timestamp() * 1000
|
||||||
finance['annotations'].append( { 'label': text, 'x': tm, 'y': amt } )
|
if delta > 0:
|
||||||
|
text += f": ${int(abs(delta))}"
|
||||||
|
else:
|
||||||
|
text += f": -${int(abs(delta))}"
|
||||||
|
finance['annotations'].append( { 'label': text, 'x': tm, 'y': total } )
|
||||||
return
|
return
|
||||||
|
|
||||||
def calculate_savings_depletion(finance):
|
def calculate_savings_depletion(finance):
|
||||||
@@ -98,6 +105,7 @@ def calculate_savings_depletion(finance):
|
|||||||
if D_Num_fortnights_pay == 0 and D_leave_after_tax > 0:
|
if D_Num_fortnights_pay == 0 and D_leave_after_tax > 0:
|
||||||
print(f"D has resigned {current_date}: get paid out my 9 weeks leave and lose 45% to tax - ${D_leave_after_tax}" )
|
print(f"D has resigned {current_date}: get paid out my 9 weeks leave and lose 45% to tax - ${D_leave_after_tax}" )
|
||||||
current_savings += D_leave_after_tax
|
current_savings += D_leave_after_tax
|
||||||
|
add_annotation(finance, current_date, current_savings, D_leave_after_tax, "D quit" )
|
||||||
D_leave_after_tax = 0
|
D_leave_after_tax = 0
|
||||||
|
|
||||||
if fortnight_income:
|
if fortnight_income:
|
||||||
@@ -125,28 +133,28 @@ def calculate_savings_depletion(finance):
|
|||||||
|
|
||||||
if current_date.date() == school_fees_date.date():
|
if current_date.date() == school_fees_date.date():
|
||||||
current_savings -= School_Fees
|
current_savings -= School_Fees
|
||||||
add_annotation(finance, current_date, current_savings, f"Pay School Fees: ${School_Fees}")
|
add_annotation(finance, current_date, current_savings, -School_Fees, "School Fees")
|
||||||
|
|
||||||
if current_date.date() == car_balloon_date.date():
|
if current_date.date() == car_balloon_date.date():
|
||||||
current_savings -= Car_balloon
|
current_savings -= Car_balloon
|
||||||
add_annotation(finance, current_date, current_savings, f"car balloon paid: ${Car_balloon}" )
|
add_annotation(finance, current_date, current_savings, -Car_balloon, "car balloon")
|
||||||
|
|
||||||
# Anniversary of Car balloon so pay insurance/rego
|
# Anniversary of Car balloon so pay insurance/rego
|
||||||
if current_date.year >= car_balloon_date.year and current_date.month == car_balloon_date.month and current_date.day == car_balloon_date.day:
|
if current_date.year >= car_balloon_date.year and current_date.month == car_balloon_date.month and current_date.day == car_balloon_date.day:
|
||||||
current_savings -= post_lease_car_costs
|
current_savings -= post_lease_car_costs
|
||||||
add_annotation(finance, current_date, current_savings, f"IONIQ 6 ins/rego: ${post_lease_car_costs}" )
|
add_annotation(finance, current_date, current_savings, -post_lease_car_costs, "IONIQ 6 ins/rego" )
|
||||||
|
|
||||||
if current_date.date() == overseas_trip_date.date():
|
if current_date.date() == overseas_trip_date.date():
|
||||||
current_savings -= Overseas_trip
|
current_savings -= Overseas_trip
|
||||||
add_annotation(finance, current_date, current_savings, f"Overseas trip: ${Overseas_trip}")
|
add_annotation(finance, current_date, current_savings, -Overseas_trip, "O/S trip")
|
||||||
|
|
||||||
if current_date.date() == mich_present_date.date():
|
if current_date.date() == mich_present_date.date():
|
||||||
current_savings -= Mich_present
|
current_savings -= Mich_present
|
||||||
add_annotation(finance, current_date, current_savings, f"Michelle's present: ${Mich_present}")
|
add_annotation(finance, current_date, current_savings, -Mich_present, "Mich's present" )
|
||||||
|
|
||||||
if current_date.date() == mark_reno_date.date():
|
if current_date.date() == mark_reno_date.date():
|
||||||
current_savings -= Mark_reno
|
current_savings -= Mark_reno
|
||||||
add_annotation(finance, current_date, current_savings, f"Mark/reno costs: ${Mark_reno}")
|
add_annotation(finance, current_date, current_savings, -Mark_reno, "Mark/reno" )
|
||||||
|
|
||||||
if current_savings < 0:
|
if current_savings < 0:
|
||||||
depletion_date = current_date
|
depletion_date = current_date
|
||||||
@@ -177,7 +185,7 @@ def calculate_savings_depletion(finance):
|
|||||||
Sell_shares -= 1
|
Sell_shares -= 1
|
||||||
|
|
||||||
current_savings += actual_sell
|
current_savings += actual_sell
|
||||||
add_annotation(finance, current_date, current_savings, f"Selling shares: ${int(actual_sell)}" )
|
add_annotation(finance, current_date, current_savings, actual_sell, "Sell shares" )
|
||||||
|
|
||||||
|
|
||||||
current_date += timedelta(days=1)
|
current_date += timedelta(days=1)
|
||||||
|
|||||||
44
db.py
44
db.py
@@ -34,7 +34,8 @@ def init_db():
|
|||||||
CBA_price REAL,
|
CBA_price REAL,
|
||||||
Overseas_trip_date STRING,
|
Overseas_trip_date STRING,
|
||||||
Mark_reno_date STRING,
|
Mark_reno_date STRING,
|
||||||
Sell_shares INTEGER
|
Sell_shares INTEGER,
|
||||||
|
compare_to INTEGER
|
||||||
)''')
|
)''')
|
||||||
|
|
||||||
cur.execute('''CREATE TABLE IF NOT EXISTS comparison_set (
|
cur.execute('''CREATE TABLE IF NOT EXISTS comparison_set (
|
||||||
@@ -82,14 +83,15 @@ def init_db():
|
|||||||
# Interest rate
|
# Interest rate
|
||||||
# D_leave_owed_in_days
|
# D_leave_owed_in_days
|
||||||
# maybe quarterly update Inflation? (this is harder to appreciate, seems much lower officialy than Savings, but which inflation:
|
# maybe quarterly update Inflation? (this is harder to appreciate, seems much lower officialy than Savings, but which inflation:
|
||||||
# I've decided to consider food only or overall, whichever is worse - but only found a rabobank reference - THIS IS A MATERIAL ISSUE WITH PROJECTING OUT)
|
# I've decided to consider food only or overall, whichever is worse - using this: https://tradingeconomics.com/australia/food-inflation
|
||||||
|
# FOR NOW inf=3 THIS IS A MATERIAL ISSUE WITH PROJECTING OUT)
|
||||||
###
|
###
|
||||||
cur.execute('''INSERT INTO finance (D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay, Car_loan, Car_balloon, 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, 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, Sell_shares)
|
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, Sell_shares, compare_to)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
|
||||||
(4762.29, 6, 22000, 620, 1001.12, 45824.68, 83000, 416670.67, 5.0, 3.9, 10000, 32000, 10000, 93.64, 1000, 750, 1095, 3.94, 158.57, '2025-06-01', '2025-09-01', 6))
|
(4762.29, 6, 22000, 620, 1017.99, 45824.68, 84000, 416670.67, 5.0, 3.0, 10000, 32000, 10000, 93.64, 1000, 750, 1095, 3.92, 167.03, '2025-06-01', '2025-09-01', 6, 0))
|
||||||
# NOTE: 1001.12 car-pay -- is 1017.99 (actual rate) - 16.87 (car park)
|
# NOTE: 1017.99 car-lease (NEED TO VERIFY)
|
||||||
# NOTE: o/s trip. ~ $4kpp flights x3, then ~$3k / week in barcelona accom, $100pp/pd for food ($2k), + spending money
|
# NOTE: o/s trip. ~ $4kpp flights x3, then ~$3k / week in barcelona accom, $100pp/pd for food ($2k), + spending money (could be less)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -112,8 +114,8 @@ def get_budget_data(finance_data):
|
|||||||
return BUDGET
|
return BUDGET
|
||||||
|
|
||||||
def update_finance(data):
|
def update_finance(data):
|
||||||
|
print(data)
|
||||||
conn = connect_db()
|
conn = connect_db(False)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute('''UPDATE finance SET
|
cur.execute('''UPDATE finance SET
|
||||||
D_Salary = ?,
|
D_Salary = ?,
|
||||||
@@ -137,14 +139,15 @@ def update_finance(data):
|
|||||||
CBA_price = ?,
|
CBA_price = ?,
|
||||||
Overseas_trip_date = ?,
|
Overseas_trip_date = ?,
|
||||||
Mark_reno_date = ?,
|
Mark_reno_date = ?,
|
||||||
Sell_shares = ?
|
Sell_shares = ?,
|
||||||
|
compare_to = ?
|
||||||
WHERE id = 1''', data)
|
WHERE id = 1''', data)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def insert_cset( data ):
|
def insert_cset( data ):
|
||||||
conn = connect_db()
|
conn = connect_db(False)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute('''INSERT INTO comparison_set (
|
cur.execute('''INSERT INTO comparison_set (
|
||||||
name, D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay,
|
name, D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay,
|
||||||
@@ -181,12 +184,12 @@ def get_comp_set_data(cset_id):
|
|||||||
conn = connect_db(True)
|
conn = connect_db(True)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
# HARDCODED FOR NOW
|
|
||||||
cset_id = 1
|
|
||||||
|
|
||||||
# get saved finance data for this comparison set
|
# get saved finance data for this comparison set
|
||||||
cur.execute( f"select * from comparison_set where id = {cset_id}" )
|
cur.execute( f"select * from comparison_set where id = {cset_id}" )
|
||||||
COMP['vars']= dict(cur.fetchone())
|
res=cur.fetchone()
|
||||||
|
if not res:
|
||||||
|
return None
|
||||||
|
COMP['vars']= dict(res)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# open new connection so we get rows back as basic array
|
# open new connection so we get rows back as basic array
|
||||||
@@ -201,3 +204,14 @@ def get_comp_set_data(cset_id):
|
|||||||
COMP['date'], COMP['amount'] = COMP['savings_data'][-1]
|
COMP['date'], COMP['amount'] = COMP['savings_data'][-1]
|
||||||
return COMP
|
return COMP
|
||||||
|
|
||||||
|
|
||||||
|
def get_comp_set_options(finance):
|
||||||
|
finance['COMP_SETS']=[ ( 0, 'Nothing' ) ]
|
||||||
|
# get comp data from DB (as object so dict conversion works below)
|
||||||
|
conn = connect_db(False)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# get saved finance data for this comparison set
|
||||||
|
cur.execute( f"select id, name from comparison_set order by id" )
|
||||||
|
finance['COMP_SETS'].extend( cur.fetchall() )
|
||||||
|
return
|
||||||
|
|||||||
2
disp.py
2
disp.py
@@ -22,7 +22,7 @@ class FP_VAR(FP):
|
|||||||
dot-notation of fields
|
dot-notation of fields
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, label, varname, display='', cl='col-2', datevarname=''):
|
def __init__(self, label, varname, display='', cl='col-auto', datevarname=''):
|
||||||
### Var Attributes -- note, simple class, no methods ###
|
### Var Attributes -- note, simple class, no methods ###
|
||||||
self.label=label
|
self.label=label
|
||||||
self.varname=varname
|
self.varname=varname
|
||||||
|
|||||||
22
main.py
22
main.py
@@ -1,7 +1,7 @@
|
|||||||
# main.py
|
# main.py
|
||||||
from flask import Flask, render_template, request, redirect, url_for, Response
|
from flask import Flask, render_template, request, redirect, url_for, Response
|
||||||
from calc import calculate_savings_depletion
|
from calc import calculate_savings_depletion
|
||||||
from db import init_db, get_finance_data, update_finance, get_budget_data, get_comp_set_data
|
from db import init_db, get_finance_data, update_finance, get_budget_data, get_comp_set_data, get_comp_set_options
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import csv
|
import csv
|
||||||
@@ -16,18 +16,15 @@ init_db()
|
|||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
finance_data = get_finance_data()
|
finance_data = get_finance_data()
|
||||||
|
get_comp_set_options(finance_data)
|
||||||
depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data)
|
depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data)
|
||||||
BUDGET=get_budget_data(finance_data)
|
BUDGET=get_budget_data(finance_data)
|
||||||
|
|
||||||
if depletion_date:
|
if depletion_date:
|
||||||
depletion_date=depletion_date.date(); # just show date
|
depletion_date=depletion_date.date(); # just show date
|
||||||
|
|
||||||
#HARDCODE HACK:
|
# if we are comparing...(compare_to will be 0 / None to start with, and then COMP will be None
|
||||||
finance_data['comp_set']=1
|
COMP=get_comp_set_data(finance_data['compare_to'])
|
||||||
|
|
||||||
# we are comparing...
|
|
||||||
if finance_data['comp_set'] >= 1:
|
|
||||||
COMP=get_comp_set_data(finance_data['comp_set'])
|
|
||||||
|
|
||||||
DISP=[]
|
DISP=[]
|
||||||
# Row 1
|
# Row 1
|
||||||
@@ -36,7 +33,7 @@ def index():
|
|||||||
r.append( FP_VAR( 'Savings', 'Savings' ) )
|
r.append( FP_VAR( 'Savings', 'Savings' ) )
|
||||||
r.append( FP_VAR( 'Car Loan via Pay', 'Car_loan_via_pay', 'readonly' ) )
|
r.append( FP_VAR( 'Car Loan via Pay', 'Car_loan_via_pay', 'readonly' ) )
|
||||||
r.append( FP_VAR( 'Living Expenses', 'Living_Expenses' ) )
|
r.append( FP_VAR( 'Living Expenses', 'Living_Expenses' ) )
|
||||||
r.append( FP_VAR( 'Overseas Trip', 'Overseas_trip', 'date', 'col-4', 'Overseas_trip_date' ) )
|
r.append( FP_VAR( 'Overseas Trip', 'Overseas_trip', 'date', 'col-auto', 'Overseas_trip_date' ) )
|
||||||
DISP.append(r)
|
DISP.append(r)
|
||||||
|
|
||||||
# Row 2
|
# Row 2
|
||||||
@@ -45,12 +42,12 @@ def index():
|
|||||||
r.append( FP_VAR( 'Interest Rate', 'Interest_Rate' ) )
|
r.append( FP_VAR( 'Interest Rate', 'Interest_Rate' ) )
|
||||||
r.append( FP_VAR( 'Car Loan', 'Car_loan', 'readonly' ) )
|
r.append( FP_VAR( 'Car Loan', 'Car_loan', 'readonly' ) )
|
||||||
r.append( FP_VAR( 'Inflation', 'Inflation' ) )
|
r.append( FP_VAR( 'Inflation', 'Inflation' ) )
|
||||||
r.append( FP_VAR( 'Reno Costs', 'Mark_reno', 'date', 'col-4', 'Mark_reno_date' ) )
|
r.append( FP_VAR( 'Reno Costs', 'Mark_reno', 'date', 'col-auto', 'Mark_reno_date' ) )
|
||||||
DISP.append(r)
|
DISP.append(r)
|
||||||
|
|
||||||
# Row 2
|
# Row 2
|
||||||
r=[]
|
r=[]
|
||||||
r.append( FP_VAR( 'D leave owed (in days)', 'D_leave_owed_in_days' ) )
|
r.append( FP_VAR( 'D # days leave', 'D_leave_owed_in_days' ) )
|
||||||
r.append( FP_VAR( 'M TLS amount', 'M_TLS_shares' ) )
|
r.append( FP_VAR( 'M TLS amount', 'M_TLS_shares' ) )
|
||||||
r.append( FP_VAR( 'Car Balloon', 'Car_balloon', 'readonly' ) )
|
r.append( FP_VAR( 'Car Balloon', 'Car_balloon', 'readonly' ) )
|
||||||
DISP.append(r)
|
DISP.append(r)
|
||||||
@@ -60,7 +57,7 @@ def index():
|
|||||||
r.append( FP_VAR( 'D CBA amount', 'D_CBA_shares' ) )
|
r.append( FP_VAR( 'D CBA amount', 'D_CBA_shares' ) )
|
||||||
r.append( FP_VAR( 'D TLS amount', 'D_TLS_shares' ) )
|
r.append( FP_VAR( 'D TLS amount', 'D_TLS_shares' ) )
|
||||||
r.append( FP_VAR( 'Mich Present', 'Mich_present', 'readonly' ) )
|
r.append( FP_VAR( 'Mich Present', 'Mich_present', 'readonly' ) )
|
||||||
r.append( FP_VAR( 'Sell Shares for:', 'Sell_shares', 'select', 'offset-2 col-2' ) )
|
r.append( FP_VAR( 'Sell Shares for:', 'Sell_shares', 'select', 'col-auto' ) )
|
||||||
DISP.append(r)
|
DISP.append(r)
|
||||||
|
|
||||||
# Row 4
|
# Row 4
|
||||||
@@ -100,6 +97,7 @@ def update():
|
|||||||
request.form['Overseas_trip_date'],
|
request.form['Overseas_trip_date'],
|
||||||
request.form['Mark_reno_date'],
|
request.form['Mark_reno_date'],
|
||||||
request.form['Sell_shares'],
|
request.form['Sell_shares'],
|
||||||
|
request.form['compare_to']
|
||||||
)
|
)
|
||||||
|
|
||||||
update_finance(finance_data)
|
update_finance(finance_data)
|
||||||
@@ -110,7 +108,7 @@ def update():
|
|||||||
def download_csv():
|
def download_csv():
|
||||||
|
|
||||||
finance_data = get_finance_data()
|
finance_data = get_finance_data()
|
||||||
depletion_date, savings_per_fortnight, final_savings, TLS, CBA = calculate_savings_depletion(finance_data)
|
depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data)
|
||||||
BUDGET=get_budget_data(finance_data)
|
BUDGET=get_budget_data(finance_data)
|
||||||
|
|
||||||
# Group data by year
|
# Group data by year
|
||||||
|
|||||||
@@ -8,38 +8,34 @@
|
|||||||
<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.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/highcharts.js"></script>
|
||||||
<script src="https://code.highcharts.com/modules/annotations.js"></script>
|
<script src="https://code.highcharts.com/modules/annotations.js"></script>
|
||||||
|
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.col-form-label {
|
.col-form-label { width:140px; }
|
||||||
width:170px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="containerfluid">
|
||||||
<h3 align="center">Finance Tracker</h3>
|
<h3 align="center">Finance Tracker</h3>
|
||||||
|
|
||||||
<form id="vals_form" class="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 %}
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
{% for el in r %}
|
{% for el in r %}
|
||||||
<div class="{{el.cl}}">
|
<div class="{{el.cl}}">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
{% if el.display=="select" %}
|
{% if el.display=="select" %}
|
||||||
<label for="{{el.varname}}" class="input-group-text">{{el.label}}</label>
|
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
|
||||||
<select class="form-select border border-primary text-primary" id="{{el.varname}}" name="{{el.varname}}" onchange="this.form.submit()">
|
<select class="form-select border border-primary text-primary" id="{{el.varname}}" name="{{el.varname}}" style="width: 120px;" onchange="this.form.submit()">
|
||||||
<option value="0">Never</option>
|
<option value="0">Never</option>
|
||||||
<option value="1">1 years</option>
|
{% for el in range( 1,7 ) %}
|
||||||
<option value="2">2 years</option>
|
<option value="{{el}}">{{el}} years</option>
|
||||||
<option value="3">3 years</option>
|
{% endfor %}
|
||||||
<option value="4">4 years</option>
|
|
||||||
<option value="5">5 years</option>
|
|
||||||
<option value="6">6 years</option>
|
|
||||||
</select>
|
</select>
|
||||||
{% elif el.display=="date" %}
|
{% elif el.display=="date" %}
|
||||||
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
|
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
|
||||||
<input type="number" step="any" class="form-control text-end float-end border border-primary" onchange="this.form.submit()"
|
<input type="number" step="any" class="form-control text-end float-end border border-primary" onchange="this.form.submit()" style="max-width: 120px;"
|
||||||
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
|
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
|
||||||
<input type="date" class="form-control text-end float-end border border-primary" id="{{el.datevarname}}"
|
<input type="date" class="form-control text-end float-end border border-primary" id="{{el.datevarname}}" style="max-width: 150px;"
|
||||||
name="{{el.datevarname}}" value="{{ finance[el.datevarname] }}" onchange="this.form.submit()">
|
name="{{el.datevarname}}" value="{{ finance[el.datevarname] }}" onchange="this.form.submit()">
|
||||||
{% else %}
|
{% else %}
|
||||||
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
|
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
|
||||||
@@ -50,7 +46,7 @@
|
|||||||
{% set bg="" %}
|
{% set bg="" %}
|
||||||
{% set bd="border-1 border-primary" %}
|
{% set bd="border-1 border-primary" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="number" step="any" class="{{bg}} form-control text-end float-end {{bd}}" onchange="this.form.submit()"
|
<input type="number" step="any" class="{{bg}} form-control text-end float-end {{bd}}" onchange="this.form.submit()" style="max-width: 120px;"
|
||||||
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
|
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -58,24 +54,23 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
<h5 align="center" class="mt-4">Fortnighthly Savings data:
|
<h5 align="center" class="mt-4">Fortnighthly Savings data:
|
||||||
{% if COMP %}
|
{% if COMP %}
|
||||||
<font color="blue"> Note: value in blue below is value we should have been at when comparing to saved values</font>
|
{# get comparison date so we can use it below in loop to know when to print it out #}
|
||||||
|
{% set comp_yr=COMP['date'][:4] %}
|
||||||
|
{% set comp_mon=COMP['date'][5:7] %}
|
||||||
|
{% set comp_day=COMP['date'][8:10 ] %}
|
||||||
|
{% set comp_done=namespace( val=0 ) %}
|
||||||
|
{% else %}
|
||||||
|
{# we dont need to do a comparison, so consider it done before we begin #}
|
||||||
|
{% set comp_done=namespace( val=1 ) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center" style="background:lemonchiffon">2025</div>
|
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center" style="background:lemonchiffon">2025</div>
|
||||||
|
|
||||||
{# get comparison date so we can use it below in loop to know when to print it out #}
|
|
||||||
{% set comp_yr=COMP['date'][:4] %}
|
|
||||||
{% set comp_mon=COMP['date'][5:7] %}
|
|
||||||
{% set comp_day=COMP['date'][8:10 ] %}
|
|
||||||
{% set comp_done=namespace( val=0 ) %}
|
|
||||||
|
|
||||||
{% set first_yr=2025 %}
|
{% set first_yr=2025 %}
|
||||||
{% for date, dollars in savings %}
|
{% for date, dollars in savings %}
|
||||||
{% set yr=date[:4] %}
|
{% set yr=date[:4] %}
|
||||||
@@ -103,6 +98,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-success">Super kicks in!!!</div>
|
<div class="alert alert-success">Super kicks in!!!</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if COMP %}
|
||||||
|
<div class="alert alert-info">Note: value in blue<br>above is value we<br>should have been<br>at when comparing<br> to saved values</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
@@ -115,18 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button type="submit" class="btn btn-primary" onClick="$('#vals_form').submit()">Update</button>
|
<button type="button" class="btn btn-primary" onClick="alert('not yet'); return false">Save</button>
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<div class="input-group">
|
|
||||||
<button type="submit" class="disabled btn btn-primary" onClick="$('#vals_form').submit() disabled">Compare with:</button>
|
|
||||||
<select class="form-select border border-primary text-primary" id="comp_set" name="comp_set" onchange="">
|
|
||||||
<option value="0">None</option>
|
|
||||||
<option value="1">something</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" class="btn btn-primary" onClick="
|
<button type="button" class="btn btn-primary" onClick="
|
||||||
$.ajax( { type: 'GET', url: '/download_csv', xhrFields: { responseType: 'blob' },
|
$.ajax( { type: 'GET', url: '/download_csv', xhrFields: { responseType: 'blob' },
|
||||||
success: function(res){
|
success: function(res){
|
||||||
@@ -142,24 +129,42 @@
|
|||||||
console.log('done') } })
|
console.log('done') } })
|
||||||
"> Export to CSV </button>
|
"> Export to CSV </button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="input-group">
|
||||||
|
<button type="submit" class="disabled btn btn-primary" onClick="$('#vals_form').submit() disabled">Compare to:</button>
|
||||||
|
<select class="form-select border border-primary text-primary" id="compare_to" name="compare_to" onchange="$('#vals_form').submit()">
|
||||||
|
{% for el in finance['COMP_SETS'] %}
|
||||||
|
<option value="{{el[0]}}">{{el[1]}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div class="row mt-4" id="container" style="width:100%; height:400px;"></div>
|
<div class="row mt-4" id="container" style="width:100%; height:400px;"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
document.getElementById("Sell_shares").value = {{finance['Sell_shares']}};
|
$('#Sell_shares').val( {{finance['Sell_shares']}} )
|
||||||
};
|
$('#compare_to').val( {{finance['compare_to']}} )
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// Parse the savings_data from Flask
|
// Parse the savings_data from Flask
|
||||||
const savingsData = JSON.parse('{{ COMP['savings_data'] | tojson }}');
|
const savingsData = JSON.parse('{{ savings | tojson }}');
|
||||||
const chartData = savingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
|
const chartData = savingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
|
||||||
|
{% if COMP %}
|
||||||
|
const compSavingsData = JSON.parse('{{ COMP['savings_data'] | tojson }}');
|
||||||
|
const compChartData = compSavingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
// Get the legend name and vars for the tooltip
|
// Get the legend name and vars for the tooltip
|
||||||
const legendName = '{{ COMP["vars"]["name"] }}';
|
const legendName = 'Savings';
|
||||||
const vars = JSON.parse('{{ COMP["vars"] | tojson }}');
|
const vars = JSON.parse('{{ finance | tojson }}');
|
||||||
|
|
||||||
// Tooltip content from vars
|
// Tooltip content from vars
|
||||||
const tooltipContent = Object.entries(vars).map(([key, value]) => `${key}: ${value}`).join('<br>');
|
const tooltipContent = Object.entries(vars).map(([key, value]) => `${key}: ${value}`).join('<br>');
|
||||||
|
console.log(tooltipContent)
|
||||||
|
|
||||||
// Calculate plot bands for each year with alternating background colors
|
// Calculate plot bands for each year with alternating background colors
|
||||||
const plotBands = [];
|
const plotBands = [];
|
||||||
@@ -188,63 +193,58 @@
|
|||||||
color: year % 2 === 0 ? 'white' : 'lemonchiffon'
|
color: year % 2 === 0 ? 'white' : 'lemonchiffon'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add annotations for changes greater than 5000
|
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
{% for a in finance['annotations'] %}
|
var offset=13
|
||||||
console.log( "{{a['x']}}" )
|
var al='left'
|
||||||
console.log( "{{a['y']}}" )
|
var x=-130
|
||||||
console.log( "{{a['label']}}" )
|
var done=0
|
||||||
annotations.push({
|
{% if not COMP %}
|
||||||
labels: [{
|
// Add annotations for changes greater than 5000
|
||||||
point: {
|
{% for a in finance['annotations'] %}
|
||||||
x: {{a['x']}},
|
annotations.push({
|
||||||
y: {{a['y']}},
|
labels: [{
|
||||||
xAxis: 0,
|
point: {
|
||||||
yAxis: 0
|
x: {{a['x']}},
|
||||||
},
|
y: {{a['y']}},
|
||||||
text: '{{a['label']}}'
|
crop: true,
|
||||||
}]
|
xAxis: 0,
|
||||||
});
|
yAxis: 0
|
||||||
{% endfor %}
|
},
|
||||||
|
x: x,
|
||||||
|
y: offset,
|
||||||
|
align: al,
|
||||||
|
text: '{{a['label']}}'
|
||||||
|
}], labelOptions: { allowOverlap: true }
|
||||||
|
});
|
||||||
|
if( offset == 150 ) { offset=100 } else { offset=150 }
|
||||||
|
if( done == 2 ) {
|
||||||
|
if( al=='right' ) { console.log('change to left'); al='left'; x=-130 } else { console.log('change to right'); al='right'; x=130 }
|
||||||
|
done=0
|
||||||
|
}
|
||||||
|
done++
|
||||||
|
{% endfor %}
|
||||||
|
document.keep = annotations
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
// Highcharts configuration
|
// Highcharts configuration
|
||||||
Highcharts.chart('container', {
|
Highcharts.chart('container', {
|
||||||
chart: {
|
chart: { type: 'line' },
|
||||||
type: 'line'
|
title: { text: 'Savings Over Time' },
|
||||||
},
|
|
||||||
title: {
|
|
||||||
text: 'Savings Over Time'
|
|
||||||
},
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
title: {
|
title: { text: 'Date' },
|
||||||
text: 'Date'
|
|
||||||
},
|
|
||||||
plotBands: plotBands // Alternating background for years
|
plotBands: plotBands // Alternating background for years
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: { title: { text: 'Amount ($)' } },
|
||||||
title: {
|
legend: { labelFormatter: function () { return `<span title="${tooltipContent}">${legendName}</span>`; } },
|
||||||
text: 'Amount ($)'
|
tooltip: { pointFormat: '{point.x:%Y-%m-%d}: <b>{point.y:.2f}</b>' },
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
labelFormatter: function () {
|
|
||||||
return `<span title="${tooltipContent}">${legendName}</span>`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
pointFormat: '{point.x:%Y-%m-%d}: <b>{point.y:.2f}</b>'
|
|
||||||
},
|
|
||||||
annotations: annotations, // Add annotations
|
annotations: annotations, // Add annotations
|
||||||
series: [{
|
series: [
|
||||||
name: legendName,
|
{ name: legendName, data: chartData, marker: { radius: 2 } }
|
||||||
data: chartData,
|
{% if COMP %}
|
||||||
marker: {
|
,{ name: "TEST", data: compChartData, marker: { radius: 2 } }
|
||||||
radius: 2, // Smaller points (default is 4)
|
{% endif %}
|
||||||
lineWidth: 0, // Optional: thinner border
|
]
|
||||||
symbol: 'circle' // Optional: shape of the points (default is 'circle')
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user