diff --git a/bills.py b/bills.py index 1456704..3e70ff5 100644 --- a/bills.py +++ b/bills.py @@ -88,6 +88,18 @@ def find_next_bill( bill_type, bill_info, bill_date ): return None +# see if this bill exists (used to prevent adding more than once in future +# estimated bills) +def find_this_bill( bill_type, bill_info, bill_date ): + yr = int(bill_date[:4]) + if not bill_type in bill_info or not 'year' in bill_info[bill_type] or not yr in bill_info[bill_type]['year']: + return None + for b in bill_info[bill_type]['year'][yr]: + if bill_type == b['bill_type'] and bill_date == b['bill_date']: + return b + return None + + # find the bill just before the date given def find_previous_bill( bill_type, bill_info, bill_date ): wanted_year = int(bill_date[:4]) @@ -139,6 +151,7 @@ def new_estimated_bill( bill_info, yr, bill_type, amt, new_date ): bill={} bill['bill_date']=new_date bill['amount']=amt + bill['bill_type']=bill_type bill['estimated']=1 # need to insert(0,) to add this "newest" bill to start of the data for {yr} so that find_previous_bill can work - only need the above 3 fields bill_info[bill_type]['year'][yr].insert(0,bill) @@ -295,6 +308,8 @@ def process_bill_data(bd, bt, bf, key_dates): # this maps a bill id to a freq id (e.g. bill #34 - has a frequency of #2 (which might be quarterly) bt_id_freq = {row["id"]: row["freq"] 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} + # 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} @@ -304,11 +319,23 @@ def process_bill_data(bd, bt, bf, key_dates): # want to proces all bill data into easier to maniuplate structure, so make # a bill_info[bill_id] with first_bill, last_bill, [yr] with matching bills to process bill_info={} - + future_car_bills=[] + future_D_quit_bills=[] + for bill in bd: bill_type = bill['bill_type'] if bill['bill_date'] == 'future': - print("Having a future data - skip this one") + # 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 ) + bill_info[bill_type]={} + bill_info[bill_type]['future'] = 1 + bill_info[bill_type]['freq'] = bf_id_name[bt_id_freq[bill_type]] + bill_info[bill_type]['growth'] = get_growth_value( bt, bill_type ) + bill_info[bill_type]['num_ann_bills'] = bf_id_num[bt_id_freq[bill_type]] + bill_info[bill_type]['year']={} continue yr= int(bill['bill_date'][:4]) @@ -339,6 +366,8 @@ def process_bill_data(bd, bt, bf, key_dates): # now process the bill_info from yr of first bill to yr of last bill for bill_type in bill_info: + if 'future' in bill_info[bill_type]: + continue # find freq id based on bill_type id, then use that to find num bills by freq id num = bf_id_num[bt_id_freq[bill_type]] @@ -357,9 +386,72 @@ 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: continue add_missing_bills_for_yr( bill_type, bill_info, yr ) - derive_ann_growth( bill_type, bill_info ) + derive_ann_growth( bill_type, bill_info, key_dates, future_car_bills, future_D_quit_bills ) + + 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 ) + return bill_info +################################################################################ +# deal_with_future_car_bills - just add these estimate bills based on when we +# own the car (data can change if I buy it out) +################################################################################ +def deal_with_future_car_bills( key_dates, future_car_bills, bill_info ): + car_yr=key_dates['D_hyundai_owned'][0:4] + car_mmdd=key_dates['D_hyundai_owned'][5:] + for fb in future_car_bills: + # deal with future bills due to their starting dates being dynamic + amt=fb['amount'] + bt=fb['bill_type'] + # factor in growth for next bill + for yr in range( int(car_yr), END_YEAR ): + 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']: + 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 ) + amt += amt * bill_info[bt]['growth']/100 + + +################################################################################ +# deal_with_future_D_quit_bills - just add these estimate bills based on when I +# quit +################################################################################ +def deal_with_future_D_quit_bills( key_dates, future_D_quit_bills, bill_info ): + 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:] + 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'] + if bill_info[bt]['num_ann_bills'] == 1: + # factor in growth for next bill + for yr in range( int(D_quit_yr), END_YEAR ): + 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 ): + 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 ) + amt += amt * bill_info[bt]['growth']/100 + elif bill_info[bt]['num_ann_bills'] == 12: + print( f"should be adding monthly future bill" ) + # 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}" + 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 ) + for yr in range( int(D_quit_yr)+1, END_YEAR ): + amt += amt * bill_info[bt]['growth']/100 + for m in range( 1, 13): + new_date=f"{yr}-{m:02d}-{dq_dd}" + 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 ) + ################################################################################ # add_missing_bills_for_yr -- wrapper to call right func based on bill freq ################################################################################ @@ -400,7 +492,7 @@ def ProportionQtrlyData( bill_type, bill_info ): # terms of min/avg/max - uses qtr data for qtrly bills, or just normal totals # for other bill types ################################################################################ -def derive_ann_growth( bill_type, bill_info ): +def derive_ann_growth( bill_type, bill_info, key_dates, future_car_bills, future_D_quit_bills ): # just do up to now so we stop earlier than looking at other estimated (just an optimisation) now_yr = datetime.date.today().year @@ -433,7 +525,6 @@ def derive_ann_growth( bill_type, bill_info ): # use new derived qtr, slightly more accurate total[yr]=tot - # once we have all yr totals: growth = {} min_growth = 999 @@ -495,4 +586,3 @@ def calc_future_totals(bill_info, bill_types): # had to round to 2 decimal here to get sensible totals total[bt['id']][yr] = round( total[bt['id']][yr], 2 ) return total - diff --git a/calc.py b/calc.py index fb460da..8e1f04b 100644 --- a/calc.py +++ b/calc.py @@ -117,35 +117,15 @@ def calculate_savings_depletion(finance, bill_data, bill_type): # main loop range -- start from now, and simulate till D is 60 (April 2031) current_date = datetime.today() - # work out which bill_types relate to future bills - for bt in bill_type: - if 'Hyundai' in bt['name']: - if 'Car Ins' in bt['name']: - ioniq6_ins_bt = bt - if 'Car Rego' in bt['name']: - ioniq6_rego_bt = bt - if 'Health Ins' in bt['name']: - health_ins_bt = bt - if 'Phone' in bt['name'] and 'Damien' in bt['name']: - phone_d_bt = bt # TODO: need to refactor Living_Expenses to exclude bills total=0 yr=str(current_date.year) for b in bill_data: - if b['bill_type'] == ioniq6_rego_bt['id']: - ioniq6_rego = b['amount'] - if b['bill_type'] == ioniq6_ins_bt['id']: - ioniq6_ins = b['amount'] - if b['bill_type'] == health_ins_bt['id']: - health_ins = b['amount'] - if b['bill_type'] == phone_d_bt['id']: - phone_d = b['amount'] - if yr in b['bill_date']: total += b['amount'] - print( f"this yr={current_date.year} - total={total} -- hi={health_ins}, phone_d={phone_d}" ) + print( f"this yr={current_date.year} - total={total}" ) Living_Expenses -= total print( f"LE is now={Living_Expenses}" ) @@ -187,19 +167,6 @@ def calculate_savings_depletion(finance, bill_data, bill_type): # if we have a bill for today, pay for it current_savings -= bill_amount_today( finance, current_date, bill_data, bt_id_name, current_savings ) - # KLUDGE for future bills for now: - # if I have quit, pay monthly future bills - assume 1st day of month for health ins - if D_has_quit and current_date.day == 1: - health_ins = health_ins * 1+((health_ins_bt['ann_growth_simple'])/100)*12 - current_savings -= health_ins - - # once a year from when I quit, pay the phone bill (year I quit comes below) - if D_has_quit and current_date.month == D_quit_date.month and current_date.day == D_quit_date.day: - amt=phone_d - for y in range( D_quit_date.year, current_date.year): - amt = amt * (1+phone_d_bt['ann_growth_simple']/100) - current_savings -= amt - # Calculate daily interest but apply at the end of the month monthly_interest += current_savings * daily_interest_rate @@ -230,8 +197,6 @@ def calculate_savings_depletion(finance, bill_data, bill_type): current_savings += D_leave_after_tax add_annotation(finance, current_date, current_savings, D_leave_after_tax, "D quit" ) D_leave_after_tax = 0 - # pay for 1st year of phone for Damien - current_savings -= phone_d # its end of 'next' fin year, if tax_diff > 0, then ddp quit after new tax year and gets back the overpaid tax if current_date > new_fin_year_26 and claim_tax_on_leave: @@ -281,30 +246,6 @@ def calculate_savings_depletion(finance, bill_data, bill_type): add_annotation(finance, current_date, current_savings, -Car_buyout, "car buyout") print(f"{current_date}: car buyout - ${Car_buyout}" ) - # Anniversary of Car purchase/balloon so potentially insurance/rego - # when I quit, the if we haven't paid the car outright, then need to add rego, but not insurance - # if we pay-out the car, then add insurace and rego - if current_date.month == car_balloon_date.month and current_date.day == car_balloon_date.day: - # staying with the lease (0), if I have quit, then pay monthly rego only up to lease date, but full cost after car balloon date - if Ioniq6_future == LEASE: - if current_date.year >= car_balloon_date.year: - ins_amt=ioniq6_ins - for y in range( car_balloon_date.year, current_date.year): - ins_amt = ins_amt * (1+ioniq6_ins_bt['ann_growth_simple']/100) - rego_amt=ioniq6_rego - for y in range( car_balloon_date.year, current_date.year): - rego_amt = rego_amt * (1+ioniq6_rego_bt['ann_growth_simple']/100) - current_savings -= (ins_amt+rego_amt) - add_annotation(finance, current_date, current_savings, -(ins_amt+rego_amt), "IONIQ 6 ins/rego" ) - # if we buy car outright, then as long as this anniversary is after buyout date, pay ins and rego - elif current_date.year >= car_buyout_date.year: - current_savings -= (ioniq6_ins + ioniq6_rego) - add_annotation(finance, current_date, current_savings, -(ioniq6_ins+ioniq6_rego), "IONIQ 6 ins/rego" ) - -# if current_date.date() == overseas_trip_date.date(): -# current_savings -= Overseas_trip -# add_annotation(finance, current_date, current_savings, -Overseas_trip, "O/S trip") - if current_date.date() == mich_present_date.date(): current_savings -= Mich_present add_annotation(finance, current_date, current_savings, -Mich_present, "Mich's present" ) @@ -358,7 +299,6 @@ def calculate_savings_depletion(finance, bill_data, bill_type): finance['CBA']=D_CBA_shares finance['TLS']=D_TLS_shares+M_TLS_shares - return depletion_date, savings_per_fortnight, current_savings ################################################################################