now allows downloading, defaults include health care and better leave calc. This is probably good enough now, so I have also saved a snapshot/csv of the data
This commit is contained in:
6
README
6
README
@@ -1,7 +1,5 @@
|
|||||||
NEED to confirm car future
|
TODO:
|
||||||
|
* need to consider whether we pay GMHBA / HCF at what cadence and include it in calcs better
|
||||||
ALLOW SAVE / export, so I can compare this when we get a chance :)
|
|
||||||
|
|
||||||
|
|
||||||
to run:
|
to run:
|
||||||
|
|
||||||
|
|||||||
9
calc.py
9
calc.py
@@ -24,10 +24,11 @@ def calculate_savings_depletion(finance):
|
|||||||
TLS_price = finance['TLS_price']
|
TLS_price = finance['TLS_price']
|
||||||
CBA_price = finance['CBA_price']
|
CBA_price = finance['CBA_price']
|
||||||
|
|
||||||
# leave in days, 10 business days to a fortnight, salary is per fortnight, and we lose 37% to tax
|
# leave in days, 10 business days to a fortnight,
|
||||||
# (assuming we don't earn over $180k)
|
# paid before tax I earn $7830.42 / fortnight. Tax on that will be at 37% or $4933.16 after tax
|
||||||
# if we could stretch this to July 2026, then would be more (due to less tax)
|
# if we could stretch this to July 2026, then would be more (due to less tax)
|
||||||
D_leave_after_tax = (D_leave_owed_in_days/10) * D_Salary;
|
after_tax_extra_leave_per_fortnight = 4933.16
|
||||||
|
D_leave_after_tax = (D_leave_owed_in_days/10) * after_tax_extra_leave_per_fortnight;
|
||||||
|
|
||||||
# Constants for interest calculations
|
# Constants for interest calculations
|
||||||
annual_interest_rate = Interest_Rate / 100.0
|
annual_interest_rate = Interest_Rate / 100.0
|
||||||
|
|||||||
12
db.py
12
db.py
@@ -41,7 +41,7 @@ def init_db():
|
|||||||
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)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
|
||||||
(4762.29, 6, 22000, 620, 1001.12, 45824.68, 78000, 412000, 5.0, 3.9, 10000, 32000, 10000, 90.6, 1000, 750, 1095, 3.99, 160.61, '2025-06-01', '2025-09-01', 5))
|
(4762.29, 6, 22000, 620, 1001.12, 45824.68, 83000, 412000, 5.0, 3.9, 10000, 32000, 10000, 90.6, 1000, 750, 1095, 3.99, 160.61, '2025-06-01', '2025-09-01', 6))
|
||||||
# NOTE: 1001.12 car-pay -- is 1017.99 (actual rate) - 16.87 (car park)
|
# NOTE: 1001.12 car-pay -- is 1017.99 (actual rate) - 16.87 (car park)
|
||||||
# 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
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -55,6 +55,16 @@ def get_finance_data():
|
|||||||
conn.close()
|
conn.close()
|
||||||
return dict(finance)
|
return dict(finance)
|
||||||
|
|
||||||
|
def get_budget_data(finance_data, CBA, TLS):
|
||||||
|
# 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( ('Monthly budget', f"${((finance_data['Living_Expenses']-12000) / 12):,.2f}" ) )
|
||||||
|
BUDGET.append( ('Weekly budget', f"${((finance_data['Living_Expenses']-12000) / 52):,.2f}" ) )
|
||||||
|
return BUDGET
|
||||||
|
|
||||||
def update_finance(data):
|
def update_finance(data):
|
||||||
|
|
||||||
conn = connect_db()
|
conn = connect_db()
|
||||||
|
|||||||
BIN
finance.db
BIN
finance.db
Binary file not shown.
29
finance_data_snapshot-31-01-2025.csv
Normal file
29
finance_data_snapshot-31-01-2025.csv
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
2025-Date,2025-Value,2026-Date,2026-Value,2027-Date,2027-Value,2028-Date,2028-Value,2029-Date,2029-Value,2030-Date,2030-Value,2031-Date,2031-Value
|
||||||
|
2025-02-05,414887.73,2026-01-07,374779.5,2027-01-06,262498.3,2028-01-05,219760.58,2029-01-03,171153.2,2030-01-02,116169.68,2031-01-01,32136.61,,Bills,"$19,330.00",,Variable,Value
|
||||||
|
2025-02-19,415836.12,2026-01-21,370468.42,2027-01-20,259056.92,2028-01-19,216182.55,2029-01-17,167433.11,2030-01-16,112301.89,2031-01-15,28269.95,,Buffer,$0.00,,D_Salary,4762.29
|
||||||
|
2025-03-05,418367.1,2026-02-04,367735.83,2027-02-03,256717.17,2028-02-02,213523.9,2029-01-31,163713.02,2030-01-30,108434.09,2031-01-29,24248.59,,Monthly
|
||||||
|
budget,"$5,916.67",,D_Num_fortnights_pay,6
|
||||||
|
2025-03-19,419305.1,2026-02-18,363413.99,2027-02-17,253264.6,2028-02-16,209934.25,2029-02-14,160692.72,2030-02-13,105031.25,2031-02-12,20334.57,,Weekly
|
||||||
|
budget,"$1,365.38",,School_Fees,22000
|
||||||
|
2025-04-02,422012.43,2026-03-04,360488.77,2027-03-03,250783.27,2028-03-01,206344.6,2029-02-28,156960.54,2030-02-27,101150.88,2031-02-26,16300.14,,,,,Car_loan_via_pay,620
|
||||||
|
2025-04-16,467253.33,2026-03-18,356156.14,2027-03-17,247319.48,2028-03-15,203577.26,2029-03-14,153831.92,2030-03-13,97659.91,2031-03-12,12329.53,,,,,Car_loan,1001.12
|
||||||
|
2025-04-30,463037.5,2026-04-01,351823.5,2027-03-31,243855.69,2028-03-29,199975.94,2029-03-28,150087.61,2030-03-27,93766.93,2031-03-26,8281.98,,,,,Car_balloon,45824.68
|
||||||
|
2025-05-14,460631.46,2026-04-15,348996.24,2027-04-14,241431.71,2028-04-12,197227.75,2029-04-11,146984.46,2030-04-10,90275.82,2031-04-09,4273.14,,,,,Living_Expenses,83000
|
||||||
|
2025-05-28,456405.19,2026-04-29,344652.78,2027-04-28,237956.66,2028-04-26,193614.73,2029-04-25,143227.98,2030-04-24,86370.19,,,,,,,Savings,412000
|
||||||
|
2025-06-11,422125.83,2026-05-13,341734.41,2027-05-12,235462.37,2028-05-10,190799.91,2029-05-09,140062.51,2030-05-08,82821.76,,,,,,,Interest_Rate,5.0
|
||||||
|
2025-06-25,417889.08,2026-05-27,337380.09,2027-05-26,231976.03,2028-05-24,187175.14,2029-05-23,136293.82,2030-05-22,78903.44,,,,,,,Inflation,3.9
|
||||||
|
2025-07-09,451750.54,2026-06-10,334466.85,2027-06-09,229477.84,2028-06-07,184347.87,2029-06-06,133106.4,2030-06-05,75322.5,,,,,,,Mich_present,10000
|
||||||
|
2025-07-23,447503.27,2026-06-24,330101.63,2027-06-23,225980.16,2028-06-21,180711.33,2029-06-20,129325.47,2030-06-19,71391.44,,,,,,,Overseas_trip,32000
|
||||||
|
2025-08-06,445162.62,2026-07-08,363397.17,2027-07-07,259710.84,2028-07-05,214116.72,2029-07-04,162375.12,2030-07-03,82530.95,,,,,,,Mark_reno,10000
|
||||||
|
2025-08-20,440904.8,2026-07-22,359021.02,2027-07-21,256201.8,2028-07-19,210468.35,2029-07-18,158581.9,2030-07-17,78587.12,,,,,,,D_leave_owed_in_days,90.6
|
||||||
|
2025-09-03,428524.06,2026-08-05,356175.3,2027-08-04,253782.55,2028-08-02,207715.12,2029-08-01,154788.67,2030-07-31,74643.29,,,,,,,D_TLS_shares,1000
|
||||||
|
2025-09-17,424255.66,2026-08-19,351788.17,2027-08-18,250262.11,2028-08-16,204054.9,2029-08-15,151657.7,2030-08-14,71021.28,,,,,,,M_TLS_shares,750
|
||||||
|
2025-10-01,419987.25,2026-09-02,348899.23,2027-09-01,246741.66,2028-08-30,200394.68,2029-08-29,147852.15,2030-08-28,67064.63,,,,,,,D_CBA_shares,1095
|
||||||
|
2025-10-15,417454.7,2026-09-16,344501.1,2027-09-15,244273.6,2028-09-13,197589.68,2029-09-12,144678.6,2030-09-11,63396.8,,,,,,,TLS_price,3.99
|
||||||
|
2025-10-29,413175.68,2026-09-30,340102.97,2027-09-29,240741.72,2028-09-27,193917.56,2029-09-26,140860.68,2030-09-25,59427.29,,,,,,,CBA_price,160.61
|
||||||
|
2025-11-12,410660.56,2026-10-14,337111.91,2027-10-13,238202.3,2028-10-11,191045.16,2029-10-10,137624.31,2030-10-09,55704.53,,,,,,,Overseas_trip_date,2025-06-01
|
||||||
|
2025-11-26,406370.89,2026-10-28,322702.74,2027-10-27,234658.94,2028-10-25,187361.11,2029-10-24,133793.99,2030-10-23,51722.12,,,,,,,Mark_reno_date,2025-09-01
|
||||||
|
2025-12-10,381758.47,2026-11-11,319692.08,2027-11-10,232115.41,2028-11-08,184475.65,2029-11-07,130534.62,2030-11-06,47961.98,,,,,,,Sell_shares,6
|
||||||
|
2025-12-24,377458.1,2026-11-25,270448.27,2027-11-24,228560.53,2028-11-22,180779.63,2029-11-21,126691.85,2030-11-20,43966.63,,,,,,
|
||||||
|
,,2026-12-09,268232.01,2027-12-08,225946.99,2028-12-06,177828.21,2029-12-05,123371.23,2030-12-04,40153.28,,,,,,
|
||||||
|
,,2026-12-23,264801.78,2027-12-22,222380.56,2028-12-20,174120.17,2029-12-19,119515.97,2030-12-18,36144.95,,,,,,
|
||||||
|
34
main.py
34
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
|
from db import init_db, get_finance_data, update_finance, get_budget_data
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import csv
|
import csv
|
||||||
@@ -16,16 +16,10 @@ init_db()
|
|||||||
def index():
|
def index():
|
||||||
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, TLS, CBA = calculate_savings_depletion(finance_data)
|
||||||
|
BUDGET=get_budget_data(finance_data, CBA, TLS)
|
||||||
if depletion_date:
|
if depletion_date:
|
||||||
depletion_date=depletion_date.date(); # just show date
|
depletion_date=depletion_date.date(); # just show date
|
||||||
|
|
||||||
# annual bills - 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 = 14330
|
|
||||||
BUDGET=[]
|
|
||||||
BUDGET.append( ('Bills', f"${bills:,.2f}") )
|
|
||||||
BUDGET.append( ('Buffer', f"${CBA*finance_data['CBA_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 render_template('index.html', finance=finance_data, depletion_date=depletion_date, savings=savings_per_fortnight, TLS=TLS, CBA=CBA, BUDGET=BUDGET)
|
return render_template('index.html', finance=finance_data, depletion_date=depletion_date, savings=savings_per_fortnight, TLS=TLS, CBA=CBA, BUDGET=BUDGET)
|
||||||
|
|
||||||
@app.route('/update', methods=['POST'])
|
@app.route('/update', methods=['POST'])
|
||||||
@@ -65,6 +59,7 @@ 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, TLS, CBA = calculate_savings_depletion(finance_data)
|
||||||
|
BUDGET=get_budget_data(finance_data, CBA, TLS)
|
||||||
|
|
||||||
# Group data by year
|
# Group data by year
|
||||||
data_by_year = defaultdict(list)
|
data_by_year = defaultdict(list)
|
||||||
@@ -78,6 +73,10 @@ def download_csv():
|
|||||||
# Sort years for column ordering
|
# Sort years for column ordering
|
||||||
years = sorted(data_by_year.keys())
|
years = sorted(data_by_year.keys())
|
||||||
|
|
||||||
|
# get finance data into a list for spitting out during csv last column dump
|
||||||
|
fd_lst = list(finance_data.items())
|
||||||
|
fd_lst[0]=('Variable', 'Value')
|
||||||
|
|
||||||
# Create an in-memory output file
|
# Create an in-memory output file
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
|
|
||||||
@@ -91,6 +90,7 @@ def download_csv():
|
|||||||
writer.writerow(header)
|
writer.writerow(header)
|
||||||
|
|
||||||
# Write the data rows
|
# Write the data rows
|
||||||
|
cnt=0
|
||||||
for i in range(max_entries_per_year):
|
for i in range(max_entries_per_year):
|
||||||
row = []
|
row = []
|
||||||
for year in years:
|
for year in years:
|
||||||
@@ -100,8 +100,26 @@ def download_csv():
|
|||||||
row.extend([date, value])
|
row.extend([date, value])
|
||||||
else:
|
else:
|
||||||
row.extend(["", ""]) # If no data for this year, leave blank cells
|
row.extend(["", ""]) # If no data for this year, leave blank cells
|
||||||
|
|
||||||
|
# spacer
|
||||||
|
row.extend([""])
|
||||||
|
|
||||||
|
# now add budget data
|
||||||
|
if cnt < len(BUDGET):
|
||||||
|
row.extend( BUDGET[cnt] )
|
||||||
|
else:
|
||||||
|
row.extend(["", ""]) # If no data for this year, leave blank cells
|
||||||
|
|
||||||
|
# spacer
|
||||||
|
row.extend([""])
|
||||||
|
|
||||||
|
# now add reference data
|
||||||
|
if cnt < len(fd_lst):
|
||||||
|
row.extend(fd_lst[cnt])
|
||||||
writer.writerow(row)
|
writer.writerow(row)
|
||||||
|
|
||||||
|
cnt += 1
|
||||||
|
|
||||||
csv_data = output.getvalue()
|
csv_data = output.getvalue()
|
||||||
|
|
||||||
# Create a Flask Response object, with CSV mime type and downloadable as a file
|
# Create a Flask Response object, with CSV mime type and downloadable as a file
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<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-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<title>Finance Form</title>
|
<title>Finance Form</title>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@@ -167,6 +168,22 @@
|
|||||||
<input type="number" class="form-control text-end float-end bg-light" id="TLS_shares" value="{{TLS}}" readonly>
|
<input type="number" class="form-control text-end float-end bg-light" id="TLS_shares" value="{{TLS}}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<button type="button" class="btn btn-primary" onClick="
|
||||||
|
$.ajax( { type: 'GET', url: '/download_csv', xhrFields: { responseType: 'blob' },
|
||||||
|
success: function(res){
|
||||||
|
// Create a link element
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const url = window.URL.createObjectURL(res);
|
||||||
|
link.href = url;
|
||||||
|
link.download = 'finance_data.csv'; // Set the file name
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url); // Clean up the object URL
|
||||||
|
console.log('done') } })
|
||||||
|
"> Export to CSV </button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user