well, calculated proportioned per quarter values - not making much difference on growth numbers, BUT, will now be able to project future estimates based on full quarter cost, not just a bill in that quarter that may only cover 2 months
This commit is contained in:
91
bills.py
91
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
|
||||
|
||||
Reference in New Issue
Block a user