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:
2025-08-24 16:09:51 +10:00
parent 4b5b713c20
commit f3b828b051

View File

@@ -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