mirror of
https://github.com/zulip/zulip.git
synced 2025-11-11 17:36:27 +00:00
215 lines
7.6 KiB
Python
Executable File
215 lines
7.6 KiB
Python
Executable File
#!/usr/bin/env python2.7
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
import datetime
|
|
import monthdelta
|
|
|
|
def parse_date(date_str):
|
|
return datetime.datetime.strptime(date_str, "%Y-%m-%d")
|
|
|
|
def unparse_date(date_obj):
|
|
return date_obj.strftime("%Y-%m-%d")
|
|
|
|
class Company(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.flows = []
|
|
self.verbose = False
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def add_flow(self, flow):
|
|
self.flows.append(flow)
|
|
|
|
def cash_at_date_internal(self, start_date, end_date):
|
|
cash = 0
|
|
for flow in self.flows:
|
|
delta = flow.cashflow(start_date, end_date, (end_date - start_date).days)
|
|
cash += delta
|
|
if self.verbose:
|
|
print(flow.name, round(delta, 2))
|
|
return round(cash, 2)
|
|
|
|
def cash_at_date(self, start, end):
|
|
start_date = parse_date(start)
|
|
end_date = parse_date(end)
|
|
return self.cash_at_date_internal(start_date, end_date)
|
|
|
|
def cash_monthly_summary(self, start, end):
|
|
start_date = parse_date(start)
|
|
cur_date = parse_date(start)
|
|
end_date = parse_date(end)
|
|
while cur_date <= end_date:
|
|
print(cur_date, self.cash_at_date_internal(start_date, cur_date))
|
|
cur_date += monthdelta.MonthDelta(1)
|
|
if self.verbose:
|
|
print()
|
|
|
|
# CashFlow objects fundamentally just provide a function that says how
|
|
# much cash has been spent by that source at each time
|
|
#
|
|
# The API is that one needs to define a function .cashflow(date)
|
|
class CashFlow(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
class FixedCost(CashFlow):
|
|
def __init__(self, name, amount):
|
|
super(FixedCost, self).__init__(name)
|
|
self.cost = -amount
|
|
|
|
def cashflow(self, start, end, days):
|
|
return self.cost
|
|
|
|
class ConstantCost(CashFlow):
|
|
def __init__(self, name, amount):
|
|
super(ConstantCost, self).__init__(name)
|
|
self.rate = -amount
|
|
|
|
def cashflow(self, start, end, days):
|
|
return self.rate * days / 365.
|
|
|
|
class PeriodicCost(CashFlow):
|
|
def __init__(self, name, amount, start, interval):
|
|
super(PeriodicCost, self).__init__(name)
|
|
self.amount = -amount
|
|
self.start = parse_date(start)
|
|
self.interval = interval
|
|
|
|
def cashflow(self, start, end, days):
|
|
cur = self.start
|
|
delta = 0
|
|
while (cur <= end):
|
|
if cur >= start:
|
|
delta += self.amount
|
|
cur += datetime.timedelta(days=self.interval)
|
|
return delta
|
|
|
|
class MonthlyCost(CashFlow):
|
|
def __init__(self, name, amount, start):
|
|
super(MonthlyCost, self).__init__(name)
|
|
self.amount = -amount
|
|
self.start = parse_date(start)
|
|
|
|
def cashflow(self, start, end, days):
|
|
cur = self.start
|
|
delta = 0
|
|
while (cur <= end):
|
|
if cur >= start:
|
|
delta += self.amount
|
|
cur += monthdelta.MonthDelta(1)
|
|
return delta
|
|
|
|
class TotalCost(CashFlow):
|
|
def __init__(self, name, *args):
|
|
self.name = name
|
|
self.flows = args
|
|
|
|
def cashflow(self, start, end, days):
|
|
return sum(cost.cashflow(start, end, days) for cost in self.flows)
|
|
|
|
class SemiMonthlyCost(TotalCost):
|
|
def __init__(self, name, amount, start1, start2 = None):
|
|
if start2 is None:
|
|
start2 = unparse_date(parse_date(start1) + datetime.timedelta(days=14))
|
|
super(SemiMonthlyCost, self).__init__(name,
|
|
MonthlyCost(name, amount, start1),
|
|
MonthlyCost(name, amount, start2)
|
|
)
|
|
|
|
class SemiMonthlyWagesNoTax(SemiMonthlyCost):
|
|
def __init__(self, name, wage, start):
|
|
super(SemiMonthlyWagesNoTax, self).__init__(name, self.compute_wage(wage), start)
|
|
|
|
def compute_wage(self, wage):
|
|
return wage / 24.
|
|
|
|
class SemiMonthlyWages(SemiMonthlyWagesNoTax):
|
|
def compute_wage(self, wage):
|
|
fica_tax = min(wage, 110100) * 0.062 + wage * 0.0145
|
|
unemp_tax = 450
|
|
return (wage + fica_tax + unemp_tax) / 24.
|
|
|
|
def __init__(self, name, wage, start):
|
|
super(SemiMonthlyWages, self).__init__(name, wage, start)
|
|
|
|
class DelayedCost(CashFlow):
|
|
def __init__(self, start, base_model):
|
|
super(DelayedCost, self).__init__("Delayed")
|
|
self.base_model = base_model
|
|
self.start = parse_date(start)
|
|
|
|
def cashflow(self, start, end, days):
|
|
start = max(start, self.start)
|
|
if start > end:
|
|
return 0
|
|
time_delta = (end-start).days
|
|
return self.base_model.cashflow(start, end, time_delta)
|
|
|
|
class BiweeklyWagesNoTax(PeriodicCost):
|
|
def __init__(self, name, wage, start):
|
|
super(BiweeklyWagesNoTax, self).__init__(name, self.compute_wage(wage), start, 14)
|
|
|
|
def compute_wage(self, wage):
|
|
# You would think this calculation would be (wage * 14 /
|
|
# 365.24), but you'd be wrong -- companies paying biweekly
|
|
# wages overpay by about 0.34% by doing the math this way
|
|
return wage / 26.
|
|
|
|
class BiweeklyWages(BiweeklyWagesNoTax):
|
|
def compute_wage(self, wage):
|
|
fica_tax = min(wage, 110100) * 0.062 + wage * 0.0145
|
|
unemp_tax = 450
|
|
# You would think this calculation would be (wage * 14 /
|
|
# 365.24), but you'd be wrong -- companies paying biweekly
|
|
# wages overpay by about 0.34% by doing the math this way
|
|
return (wage + fica_tax + unemp_tax) / 26.
|
|
|
|
def __init__(self, name, wage, start):
|
|
super(BiweeklyWages, self).__init__(name, wage, start)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Tests
|
|
c = Company("Example Inc")
|
|
c.add_flow(FixedCost("Initial Cash", -500000))
|
|
c.add_flow(FixedCost("Incorporation", 500))
|
|
assert(c.cash_at_date("2012-01-01", "2012-03-01") == 500000 - 500)
|
|
c.add_flow(FixedCost("Incorporation", -500))
|
|
|
|
c.add_flow(ConstantCost("Office", 50000))
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-02") == 500000 - round(50000*1/365., 2))
|
|
c.add_flow(ConstantCost("Office", -50000))
|
|
|
|
c.add_flow(PeriodicCost("Payroll", 4000, "2012-01-05", 14))
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-02") == 500000)
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-06") == 500000 - 4000)
|
|
c.add_flow(PeriodicCost("Payroll", -4000, "2012-01-05", 14))
|
|
|
|
c.add_flow(DelayedCost("2012-02-01", ConstantCost("Office", 50000)))
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-05") == 500000)
|
|
assert(c.cash_at_date("2012-01-01", "2012-02-05") == 500000 - round(50000*4/365., 2))
|
|
c.add_flow(DelayedCost("2012-02-01", ConstantCost("Office", -50000)))
|
|
|
|
c.add_flow(DelayedCost("2012-02-01", FixedCost("Financing", 50000)))
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-15") == 500000)
|
|
c.add_flow(DelayedCost("2012-02-01", FixedCost("Financing", -50000)))
|
|
|
|
c.add_flow(SemiMonthlyCost("Payroll", 4000, "2012-01-01"))
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-01") == 500000 - 4000)
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-14") == 500000 - 4000)
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-15") == 500000 - 4000 * 2)
|
|
assert(c.cash_at_date("2012-01-01", "2012-01-31") == 500000 - 4000 * 2)
|
|
assert(c.cash_at_date("2012-01-01", "2012-02-01") == 500000 - 4000 * 3)
|
|
assert(c.cash_at_date("2012-01-01", "2012-02-15") == 500000 - 4000 * 4)
|
|
c.add_flow(SemiMonthlyCost("Payroll", -4000, "2012-01-01"))
|
|
|
|
c.add_flow(SemiMonthlyWages("Payroll", 4000, "2012-01-01"))
|
|
assert(c.cash_at_date("2012-01-01", "2012-02-15") == 499207.33)
|
|
c.add_flow(SemiMonthlyWages("Payroll", -4000, "2012-01-01"))
|
|
|
|
print(c)
|
|
c.cash_monthly_summary("2012-01-01", "2012-07-01")
|