Every check lands in the hub. Bills get paid first. What is left gets pushed out to the spokes by fixed rules, so no single place holds everything.
Every bill gets turned into a per-check amount. Bills that hit rarely, like insurance, get held back a little each check so the money is there when the bill lands.
# Rule 0: turn every bill into a per-check amount (26 checks a year)
PER_CHECK = {"day": 14, "week": 2, "month": 12 / 26,
"six_months": 2 / 26, "year": 1 / 26}
def bills_per_check(bill_list):
return sum(amount * PER_CHECK[how_often]
for name, amount, how_often in bill_list)
# rare bills (insurance) stay in the hub until due
def split(pay, travel, bills, base, floor_target, floor_now):
income = pay - travel # travel money repays what you already spent
out = {"travel": travel}
# Rule 1: bills come first, always
if income <= bills:
out["bills"] = income
out["draw_from_floor"] = bills - income
return out # every spoke pauses this check
out["bills"] = bills
left = income - bills
# Rule 2: the normal part of the check uses fixed shares
core = min(left, base - bills)
extra = left - core
room = max(0, floor_target - floor_now)
floor_cut = core * 0.375
to_floor = min(floor_cut, room)
spill = floor_cut - to_floor # floor full? its share moves on
out["floor"] = to_floor
out["growth"] = core * 0.3125 + spill * 0.5
out["earner"] = core * 0.1875 + spill * 0.5
out["protect"] = core * 0.0625
out["pocket"] = core * 0.0625
# Rule 3: overtime above a normal check
if extra > 0:
out["pocket"] += extra * 0.10
rest = extra * 0.90
room -= to_floor
fill = min(rest, room) # finish the floor first
out["floor"] += fill
rest -= fill
out["growth"] += rest * 5 / 9
out["earner"] += rest * 4 / 9
return out