diff --git a/bills.py b/bills.py index 808e6dd..6ca1ffa 100644 --- a/bills.py +++ b/bills.py @@ -1,6 +1,55 @@ from db import get_bill_data, get_bill_types, get_bill_freqs, set_bill_type_growth, new_bill from defines import END_YEAR import datetime +from datetime import date, timedelta + + +################################################################################ + +# this finds start and end dates of a quarter for a given date +def quarter_bounds(d): + q = (d.month-1)//3 + start = date(d.year, 3*q+1, 1) + # last day of quarter = first day of next quarter minus 1 day + if q == 3: + next_start = date(d.year+1, 1, 1) + else: + next_start = date(d.year, 3*q+4, 1) + end = next_start - timedelta(days=1) + return start, end + +# this needs tweaking to be used to add to our total - but is close +def allocate_by_quarter( bill_info, bill_type, yr, prev_bill_date, curr_bill_date, amount, include_start=False, include_end=True): + start = prev_bill_date if include_start else prev_bill_date + timedelta(days=1) + end = curr_bill_date if include_end else curr_bill_date - timedelta(days=1) + if end < start: + return {} + if not 'qtr' in bill_info[bill_type]: + bill_info[bill_type]['qtr'] = {} + + q_start, q_end = quarter_bounds(start) + cur = q_start + # walk quarters that might overlap - start from the quarter of `start`, iterate until past `end` + while cur <= end: + q_start, q_end = quarter_bounds(cur) + overlap_start = max(start, q_start) + overlap_end = min(end, q_end) + # only add qtr total for yr being calc'd + if overlap_end >= overlap_start: + days = (overlap_end - overlap_start).days + 1 + q = (q_start.month-1)//3 + 1 + # NEED LOGIC TO INIT IF ITS NOT HERE YET + if q_start.year not in bill_info[bill_type]['qtr']: + bill_info[bill_type]['qtr'][q_start.year] = {} + for i in range(1,5): + bill_info[bill_type]['qtr'][q_start.year][i]=0 +# print( f"^^^^^^ adding {days*amount} into q={q}, yr={q_start.year}, with pbd={prev_bill_date}, cbd={curr_bill_date}, cba={amount}" ) + bill_info[bill_type]['qtr'][q_start.year][q] += days*amount + # next quarter + cur = q_end + timedelta(days=1) + return + +################################################################################ # give a bill dat in format YYYY-MM-DD, return quarter (1-4) def qtr(d): @@ -17,6 +66,7 @@ def find_next_bill( bill_type, bill_info, bill_date ): for yr in range( wanted_year, bill_info[bill_type]['last_bill_year']+1 ): # start with bills in the year wanted (if any) if yr in bill_info[bill_type]['year']: + # reverse this list so we can q1 bills before q4 for bill in bill_info[bill_type]['year'][yr][::-1]: bill_mm = int(bill['bill_date'][5:7]) # if bill is in this year but later OR its a later year, return this bill @@ -35,7 +85,7 @@ def find_previous_bill( bill_type, bill_info, bill_date ): # start loop from bill_date, go backwards and find which one it is (same year, should be month-based) # earlier year, then just last one from the year. - yr_range=range( wanted_year, bill_info[bill_type]['first_bill_year'], -1 ) + yr_range=range( wanted_year, bill_info[bill_type]['first_bill_year']-1, -1 ) if wanted_year == int(bill_info[bill_type]['first_bill_year']): # range of this year with -1, does not return anything, so force this year. yr_range=[ wanted_year ] @@ -47,16 +97,15 @@ def find_previous_bill( bill_type, bill_info, bill_date ): # okay, we have the previous billing year, and we wanted one for a year in the future, # just return the last one in this year as its the most recent if wanted_year > yr: - return bill_info[bill_type]['year'][yr][-1] + return bill_info[bill_type]['year'][yr][0] else: # lets go through the newest to oldest of these bills - for bill in bill_info[bill_type]['year'][yr][::-1]: + for bill in bill_info[bill_type]['year'][yr]: bill_mm = int(bill['bill_date'][5:7]) # reversing the bills, means we start with the 'most recent' in this year to the oldest # if the month we want is after the bill, we are done if wanted_mm > bill_mm: return bill - return None @@ -256,6 +305,25 @@ def add_missing_bills_for_yr( bill_type, bill_info, yr ): def derive_ann_growth( bill_type, bill_info ): # just do up to now so we stop earlier than looking at other estimated (just an optimisation) now_yr = datetime.date.today().year + + # FIX UP CRAPPY QUARTERLY BILLING PROPORTIONS (only useful as some gas bills are 6 / year!) + if bill_info[bill_type]['num_ann_bills']==4: + for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1): + for b in bill_info[bill_type]['year'][yr]: + pb = find_previous_bill( bill_type, bill_info, b['bill_date'] ) + if not pb: + continue + + pb_d=pb['bill_date'] + b_d=b['bill_date'] + date1 = date( int(pb_d[:4]), int(pb_d[5:7]), int(pb_d[8:])) + date2 = date( int(b_d[:4]), int(b_d[5:7]), int(b_d[8:])) + + time_difference = date2 - date1 + days = time_difference.days + cost_per_day = b['amount']/days + allocate_by_quarter( bill_info, bill_type, yr, date1, date2, cost_per_day ) + total={} for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1): # if not enough bills in this year (or none), then try next year (first year might have not enough bills) @@ -274,8 +342,17 @@ def derive_ann_growth( bill_type, bill_info ): total[yr] = 0 for b in bill_info[bill_type]['year'][yr]: total[yr] += b['amount'] - if bill_type == 3: - print( f"total[{yr}]={total[yr]}" ) + #crazily we have more bills in this year than expected, so work out qtrly costs + + for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1): + if 'qtr' in bill_info[bill_type] and yr in bill_info[bill_type]['qtr']: + tot=0 + for q in range( 1,5 ): + tot += bill_info[bill_type]['qtr'][yr][q] + if yr in total: + # use new derived qtr, slightly more accurate + total[yr]=tot + # once we have all yr totals: growth = {} @@ -295,6 +372,8 @@ def derive_ann_growth( bill_type, bill_info ): if growth > max_growth: max_growth = growth if count: + print( f"Before sanity check, min={min_growth}, avg={avg_growth/count}, max_growth={max_growth}" ) + # HACK FOR SANITY SAKE NOW - bills wont decrease normally, and 10% is unlikely for sustained growth if min_growth< 0: min_growth=0 if avg_growth< 0 or avg_growth > 10: avg_growth = 3*count if max_growth>10 : max_growth = 9.99