converted over to using a class in disp.py that defines data about each variable in the form, and use an array of these objects to format the form in templates/index.html -- provides consistency of bootstrap classes and makes it far easier to move items around columns/rows in the future, e.g. when I resign. Also, now retrieve actual values form comparison set data (still have hardcoded cset_id for now), but also stopped using separate vars for items like CBA/TLS, and buried them into the finance_data

This commit is contained in:
2025-02-12 17:25:23 +11:00
parent af38b45034
commit 65ed02812a
5 changed files with 171 additions and 173 deletions

11
calc.py
View File

@@ -1,6 +1,11 @@
# calc.py
from datetime import datetime, timedelta
# this somehow needs to connect the annotation, so maybe store it via date in an annotations array?
# not sure I need amount, etc.
def add_annotation(label,amount):
return
def calculate_savings_depletion(finance):
# Extract all the financial data from the database
D_Salary = finance['D_Salary']
@@ -174,5 +179,9 @@ def calculate_savings_depletion(finance):
current_date += timedelta(days=1)
days_count += 1
return depletion_date, savings_per_fortnight, current_savings, D_TLS_shares+M_TLS_shares, D_CBA_shares
finance['CBA']=D_CBA_shares
finance['TLS']=D_TLS_shares+M_TLS_shares
return depletion_date, savings_per_fortnight, current_savings

49
db.py
View File

@@ -1,13 +1,15 @@
# db.py
import sqlite3
import pprint
def connect_db():
def connect_db(as_object):
conn = sqlite3.connect('finance.db')
if as_object:
conn.row_factory = sqlite3.Row # This allows us to access columns by name
return conn
def init_db():
conn = connect_db()
conn = connect_db(True)
cur = conn.cursor()
cur.execute('''CREATE TABLE IF NOT EXISTS finance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -92,19 +94,19 @@ def init_db():
conn.close()
def get_finance_data():
conn = connect_db()
conn = connect_db(True)
cur = conn.cursor()
cur.execute('SELECT * FROM finance WHERE id = 1') # Assuming a single record for simplicity
finance = cur.fetchone()
conn.close()
return dict(finance)
def get_budget_data(finance_data, CBA, TLS):
def get_budget_data(finance_data):
# annual bills - health ins (5k), rates (2.4), electricity (1.5), gas (2), internet (1.6), car insurance (.7), rego (.8), house insurance (2.4), GFC (2.2), phones (.5), melb. pollen (.03), nabu casa (.1), eweka (.1) --- noting phone is elevated presuming I also go onto Aldi plan, but that there is no family discount
bills = 19330
BUDGET=[]
BUDGET.append( ('Bills', f"${bills:,.2f}") )
BUDGET.append( ('Buffer', f"${CBA*finance_data['CBA_price']+TLS*finance_data['TLS_price']:,.2f}") )
BUDGET.append( ('Buffer', f"${finance_data['CBA']*finance_data['CBA_price']+finance_data['TLS']*finance_data['TLS_price']:,.2f}") )
BUDGET.append( ('Monthly budget', f"${((finance_data['Living_Expenses']-12000) / 12):,.2f}" ) )
BUDGET.append( ('Weekly budget', f"${((finance_data['Living_Expenses']-12000) / 52):,.2f}" ) )
return BUDGET
@@ -166,11 +168,36 @@ def insert_cset( data ):
conn.close()
return cset_id
def last_cset_savings_data(cset_id):
conn = connect_db()
################################################################################
#
# gets comparison set data from the DB based on cset_id passed in (via drop-down in GUI).
# returns a DICT vars pointing to all the var values used for the ARRAY savings_data of saved fortnightly $'s
# also sets date / amount for the last set of $'s for easy display in the GUI
#
################################################################################
def get_comp_set_data(cset_id):
COMP={}
# get comp data from DB (as object so dict conversion works below)
conn = connect_db(True)
cur = conn.cursor()
cur.execute('''select name, value from comparison_savings_data
where name = ( select max(name) from comparison_savings_data ) ''' )
name, value = cur.fetchone() # fetchone() fetches a single row
# HARDCODED FOR NOW
cset_id = 1
# get saved finance data for this comparison set
cur.execute( f"select * from comparison_set where id = {cset_id}" )
COMP['vars']= dict(cur.fetchone())
conn.close()
return name, value
# open new connection so we get rows back as basic array
conn = connect_db(False)
cur = conn.cursor()
# get data for this comparison set
cur.execute( f"select name, value from comparison_savings_data where comparison_set_id = {cset_id}" )
COMP['savings_data']=cur.fetchall()
# do this for convenience in printing single last cset data point
COMP['date'], COMP['amount'] = COMP['savings_data'][-1]
return COMP

32
disp.py Normal file
View File

@@ -0,0 +1,32 @@
################################################################################
class FP:
def __repr__(self):
str=f"<{self.__class__.__name__}("
for k, v in self.__dict__.items():
if isinstance(v, (bytes, bytearray)):
str += f"{k}=<bytes>, "
elif k == "thumbnail":
str += f"{k}=<base64>, "
# skip internal state
elif k == "_sa_instance_state":
continue
else:
str += f"{k}={v!r}, "
str=str.rstrip(", ") + ")>"
return str
class FP_VAR(FP):
""" Var class is a simple 'struct' to keep data per variable to display
Created just to avoid using python list/dicts intermixed, and be able to consistently use
dot-notation of fields
"""
def __init__(self, label, varname, display='', cl='col-2', datevarname=''):
### Var Attributes -- note, simple class, no methods ###
self.label=label
self.varname=varname
self.display=display
self.cl=cl
self.datevarname=datevarname
return

68
main.py
View File

@@ -1,11 +1,12 @@
# main.py
from flask import Flask, render_template, request, redirect, url_for, Response
from calc import calculate_savings_depletion
from db import init_db, get_finance_data, update_finance, get_budget_data, last_cset_savings_data
from db import init_db, get_finance_data, update_finance, get_budget_data, get_comp_set_data
from collections import defaultdict
from datetime import datetime
import csv
import io
from disp import FP_VAR
app = Flask(__name__)
@@ -15,20 +16,63 @@ init_db()
@app.route('/')
def index():
finance_data = get_finance_data()
depletion_date, savings_per_fortnight, final_savings, TLS, CBA = calculate_savings_depletion(finance_data)
BUDGET=get_budget_data(finance_data, CBA, TLS)
depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data)
BUDGET=get_budget_data(finance_data)
if depletion_date:
depletion_date=depletion_date.date(); # just show date
# HARDCODED FOR NOW
cset_id = 1
# work out comparison func, but hardcode for now:
COMP={}
COMP['date'], COMP['amount'] = last_cset_savings_data( cset_id )
COMP['vars']=finance_data
COMP['savings_data']=savings_per_fortnight
#HARDCODE HACK:
finance_data['comp_set']=1
return render_template('index.html', finance=finance_data, depletion_date=depletion_date, savings=savings_per_fortnight, TLS=TLS, CBA=CBA, BUDGET=BUDGET, COMP=COMP)
# we are comparing...
if finance_data['comp_set'] >= 1:
COMP=get_comp_set_data(finance_data['comp_set'])
DISP=[]
# Row 1
r=[]
r.append( FP_VAR( 'D Salary', 'D_Salary' ) )
r.append( FP_VAR( 'Savings', 'Savings' ) )
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( 'Overseas Trip', 'Overseas_trip', 'date', 'col-4', 'Overseas_trip_date' ) )
DISP.append(r)
# Row 2
r=[]
r.append( FP_VAR( 'D # Fortnights pay', 'D_Num_fortnights_pay' ) )
r.append( FP_VAR( 'Interest Rate', 'Interest_Rate' ) )
r.append( FP_VAR( 'Car Loan', 'Car_loan', 'readonly' ) )
r.append( FP_VAR( 'Inflation', 'Inflation' ) )
r.append( FP_VAR( 'Reno Costs', 'Mark_reno', 'date', 'col-4', 'Mark_reno_date' ) )
DISP.append(r)
# Row 2
r=[]
r.append( FP_VAR( 'D leave owed (in days)', 'D_leave_owed_in_days' ) )
r.append( FP_VAR( 'M TLS amount', 'M_TLS_shares' ) )
r.append( FP_VAR( 'Car Balloon', 'Car_balloon', 'readonly' ) )
DISP.append(r)
# Row 3
r=[]
r.append( FP_VAR( 'D CBA amount', 'D_CBA_shares' ) )
r.append( FP_VAR( 'D TLS amount', 'D_TLS_shares' ) )
r.append( FP_VAR( 'Mich Present', 'Mich_present', 'readonly' ) )
r.append( FP_VAR( 'Sell Shares for:', 'Sell_shares', 'select', 'offset-2 col-2' ) )
DISP.append(r)
# Row 4
r=[]
r.append( FP_VAR( 'CBA price', 'CBA_price' ) )
r.append( FP_VAR( 'TLS price', 'TLS_price' ) )
r.append( FP_VAR( 'School Fees', 'School_Fees', 'readonly' ) )
r.append( FP_VAR( 'FINAL # of CBA', 'CBA', 'readonly' ) )
r.append( FP_VAR( 'FINAL # of TLS', 'TLS', 'readonly' ) )
DISP.append(r)
return render_template('index.html', finance=finance_data, depletion_date=depletion_date, savings=savings_per_fortnight, BUDGET=BUDGET, COMP=COMP, DISP=DISP)
@app.route('/update', methods=['POST'])
def update():
@@ -67,7 +111,7 @@ def download_csv():
finance_data = get_finance_data()
depletion_date, savings_per_fortnight, final_savings, TLS, CBA = calculate_savings_depletion(finance_data)
BUDGET=get_budget_data(finance_data, CBA, TLS)
BUDGET=get_budget_data(finance_data)
# Group data by year
data_by_year = defaultdict(list)

View File

@@ -19,115 +19,14 @@
<h3 align="center">Finance Tracker</h3>
<form id="vals_form" class="mt-3" action="/update" method="POST">
{% for r in DISP %}
<div class="row align-items-center">
<div class="col">
{% for el in r %}
<div class="{{el.cl}}">
<div class="input-group">
<label for="D_Salary" class="col-form-label me-2 text-end float-end">D Salary</label>
<input type="number" step="any" class="form-control text-end float-end" id="D_Salary" name="D_Salary" value="{{ finance['D_Salary'] }}">
</div>
</div>
<div class="col">
<div class="input-group">
<label for="Savings" class="col-form-label me-2 text-end float-end">Savings</label>
<input type="number" step="any" class="form-control text-end float-end " id="Savings" name="Savings" value="{{ finance['Savings'] }}">
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="Car_loan_via_pay" class="col-form-label me-2 text-end float-end">Car Loan via Pay</label>
<input type="number" step="any" class="bg-light form-control text-end float-end" id="Car_loan_via_pay" name="Car_loan_via_pay" value="{{ finance['Car_loan_via_pay'] }}" readonly>
</div>
</div>
<div class="col">
<div class="input-group">
<label for="Living_Expenses" class="col-form-label me-2 text-end float-end">Living Expenses</label>
<input type="number" step="any" class="form-control text-end float-end " id="Living_Expenses" name="Living_Expenses" value="{{ finance['Living_Expenses'] }}">
</div>
</div>
<div class="col-4">
<div class="input-group">
<label for="Overseas_trip" class="col-form-label me-2 text-end float-end">Overseas Trip</label>
<input type="number" step="any" class="form-control text-end float-end" id="Overseas_trip" name="Overseas_trip" value="{{ finance['Overseas_trip'] }}">
<input type="date" class="form-control text-end float-end" id="Overseas_trip_date" name="Overseas_trip_date" value="{{ finance['Overseas_trip_date'] }}" onchange="this.form.submit()">
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="input-group">
<label for="D_fortnights_pay" class="col-form-label me-2 text-end float-end">D # Fortnights pay</label>
<input type="number" class="form-control text-end float-end " id="D_Num_fortnights_pay" name="D_Num_fortnights_pay" value="{{ finance['D_Num_fortnights_pay'] }}">
</div>
</div>
<div class="col">
<div class="input-group">
<label for="Interest_Rate" class="col-form-label me-2 text-end float-end">Interest Rate (%)</label>
<input type="number" step="any" class="form-control text-end float-end" id="Interest_Rate" name="Interest_Rate" value="{{ finance['Interest_Rate'] }}">
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="Car_loan" class="col-form-label me-2 text-end float-end">Car Loan</label>
<input type="number" step="any" class="bg-light form-control text-end float-end" id="Car_loan" name="Car_loan" value="{{ finance['Car_loan'] }}" readonly>
</div>
</div>
<div class="col">
<div class="input-group">
<label for="Inflation" class="col-form-label me-2 text-end float-end">Inflation (%)</label>
<input type="number" step="any" class="form-control text-end float-end" id="Inflation" name="Inflation" value="{{ finance['Inflation'] }}">
</div>
</div>
<div class="col-4">
<div class="input-group">
<label for="Mark_reno" class="col-form-label me-2 text-end float-end">Reno costs</label>
<input type="number" step="any" class="form-control text-end float-end " id="Mark_reno" name="Mark_reno" value="{{ finance['Mark_reno'] }}" >
<input type="date" class="form-control text-end float-end " id="Mark_reno_date" name="Mark_reno_date" value="{{ finance['Mark_reno_date'] }}" onchange="this.form.submit()">
</div>
</div>
</div class="row">
<div class="row align-items-center">
<div class="col-2">
<div class="input-group">
<label for="D_leave_owed_in_days" class="col-form-label me-2 text-end float-end">D leave owed (in days)</label>
<input type="number" step="any" class="form-control text-end float-end" id="D_leave_owed_in_days" name="D_leave_owed_in_days" value="{{ finance['D_leave_owed_in_days'] }}" >
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="M_TLS_shares" class="col-form-label me-2 text-end float-end">M TLS amount</label>
<input type="number" step="any" class="form-control text-end float-end" id="M_TLS_shares" name="M_TLS_shares" value="{{ finance['M_TLS_shares'] }}" >
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="Car_balloon" class="col-form-label me-2 text-end float-end">Car Balloon</label>
<input type="number" step="any" class="bg-light form-control text-end float-end" id="Car_balloon" name="Car_balloon" value="{{ finance['Car_balloon'] }}" readonly>
</div>
</div>
</div class="row">
<div class="row align-items-center">
<div class="col-2">
<div class="input-group">
<label for="D_CBA_shares" class="col-form-label me-2 text-end float-end">D CBA amount</label>
<input type="number" step="any" class="form-control text-end float-end" id="D_CBA_shares" name="D_CBA_shares" value="{{ finance['D_CBA_shares'] }}" >
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="D_TLS_shares" class="col-form-label me-2 text-end float-end">D TLS amount</label>
<input type="number" step="any" class="form-control text-end float-end" id="D_TLS_shares" name="D_TLS_shares" value="{{ finance['D_TLS_shares'] }}" >
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="Mich_present" class="col-form-label me-2 text-end float-end float-end">Mich Present</label>
<input type="number" step="any" class="form-control text-end float-end bg-light" id="Mich_present" name="Mich_present" value="{{ finance['Mich_present'] }}" readonly>
</div>
</div>
<div class="offset-2 col-2">
<div class="input-group">
<label class="input-group-text" for="Sell_shares">Sell Shares for:</label>
<select class="form-select border border-primary text-primary" id="Sell_shares" name="Sell_shares" onchange="this.form.submit()">
{% if el.display=="select" %}
<label for="{{el.varname}}" class="input-group-text">{{el.label}}</label>
<select class="form-select border border-primary text-primary" id="{{el.varname}}" name="{{el.varname}}" onchange="this.form.submit()">
<option value="0">Never</option>
<option value="1">1 years</option>
<option value="2">2 years</option>
@@ -136,45 +35,32 @@
<option value="5">5 years</option>
<option value="6">6 years</option>
</select>
{% elif el.display=="date" %}
<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()"
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}}"
name="{{el.datevarname}}" value="{{ finance[el.datevarname] }}" onchange="this.form.submit()">
{% else %}
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
{% if el.display== "readonly" %}
{% set bg="bg-light" %}
{% set bd="" %}
{% else %}
{% set bg="" %}
{% set bd="border-1 border-primary" %}
{% endif %}
<input type="number" step="any" class="{{bg}} form-control text-end float-end {{bd}}" onchange="this.form.submit()"
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
{% endif %}
</div>
</div>
</div class="row">
<div class="row align-items-center">
<div class="col-2">
<div class="input-group">
<label for="CBA_price" class="col-form-label me-2 text-end float-end">CBA price</label>
<input type="number" step="any" class="form-control text-end float-end" id="CBA_price" name="CBA_price" value="{{ finance['CBA_price'] }}" >
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="TLS_price" class="col-form-label me-2 text-end float-end">TLS price</label>
<input type="number" step="any" class="form-control text-end float-end" id="TLS_price" name="TLS_price" value="{{ finance['TLS_price'] }}" >
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="School_Fees" class="col-form-label me-2 text-end float-end">School Fees</label>
<input type="number" step="any" class="bg-light form-control text-end float-end" id="School_Fees" name="School_Fees" value="{{ finance['School_Fees'] }}" readonly>
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="CBA_shares" class="col-form-label me-2 text-end float-end">FINAL # of CBA</label>
<input type="number" class="form-control text-end float-end bg-light" id="CBA_shares" value="{{CBA}}" readonly>
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="TLS_shares" class="offset-1 col-form-label me-2 text-end float-end">FINAL # of TLS</label>
<input type="number" class="form-control text-end float-end bg-light" id="TLS_shares" value="{{TLS}}" readonly>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</form>
<h5 align="center" class="mt-4">Fortnighthly Savings data:
{% if COMP %}
<font color="blue">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Note: value in blue below is value we should have been at when comparing to saved values</font>