mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			213 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python2.7
 | 
						|
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")
 |