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 db import get_bill_data, get_bill_types, get_bill_freqs, set_bill_type_growth, new_bill
|
||||||
from defines import END_YEAR
|
from defines import END_YEAR
|
||||||
import datetime
|
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)
|
# give a bill dat in format YYYY-MM-DD, return quarter (1-4)
|
||||||
def qtr(d):
|
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 ):
|
for yr in range( wanted_year, bill_info[bill_type]['last_bill_year']+1 ):
|
||||||
# start with bills in the year wanted (if any)
|
# start with bills in the year wanted (if any)
|
||||||
if yr in bill_info[bill_type]['year']:
|
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]:
|
for bill in bill_info[bill_type]['year'][yr][::-1]:
|
||||||
bill_mm = int(bill['bill_date'][5:7])
|
bill_mm = int(bill['bill_date'][5:7])
|
||||||
# if bill is in this year but later OR its a later year, return this bill
|
# 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)
|
# 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.
|
# 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']):
|
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.
|
# range of this year with -1, does not return anything, so force this year.
|
||||||
yr_range=[ wanted_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,
|
# 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
|
# just return the last one in this year as its the most recent
|
||||||
if wanted_year > yr:
|
if wanted_year > yr:
|
||||||
return bill_info[bill_type]['year'][yr][-1]
|
return bill_info[bill_type]['year'][yr][0]
|
||||||
else:
|
else:
|
||||||
# lets go through the newest to oldest of these bills
|
# 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])
|
bill_mm = int(bill['bill_date'][5:7])
|
||||||
# reversing the bills, means we start with the 'most recent' in this year to the oldest
|
# 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 the month we want is after the bill, we are done
|
||||||
if wanted_mm > bill_mm:
|
if wanted_mm > bill_mm:
|
||||||
return bill
|
return bill
|
||||||
|
|
||||||
return None
|
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 ):
|
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)
|
# just do up to now so we stop earlier than looking at other estimated (just an optimisation)
|
||||||
now_yr = datetime.date.today().year
|
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={}
|
total={}
|
||||||
for yr in range( bill_info[bill_type]['first_bill_year'], now_yr+1):
|
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)
|
# 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
|
total[yr] = 0
|
||||||
for b in bill_info[bill_type]['year'][yr]:
|
for b in bill_info[bill_type]['year'][yr]:
|
||||||
total[yr] += b['amount']
|
total[yr] += b['amount']
|
||||||
if bill_type == 3:
|
#crazily we have more bills in this year than expected, so work out qtrly costs
|
||||||
print( f"total[{yr}]={total[yr]}" )
|
|
||||||
|
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:
|
# once we have all yr totals:
|
||||||
growth = {}
|
growth = {}
|
||||||
@@ -295,6 +372,8 @@ def derive_ann_growth( bill_type, bill_info ):
|
|||||||
if growth > max_growth:
|
if growth > max_growth:
|
||||||
max_growth = growth
|
max_growth = growth
|
||||||
if count:
|
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 min_growth< 0: min_growth=0
|
||||||
if avg_growth< 0 or avg_growth > 10: avg_growth = 3*count
|
if avg_growth< 0 or avg_growth > 10: avg_growth = 3*count
|
||||||
if max_growth>10 : max_growth = 9.99
|
if max_growth>10 : max_growth = 9.99
|
||||||
|
|||||||
Reference in New Issue
Block a user