Remove finbot from Zulip repository.

It's been split into its own repository,
https://github.com/zulip/finbot.
This commit is contained in:
Tim Abbott 2016-07-08 16:04:50 -07:00
parent 12028339a3
commit b13eeae24c
3 changed files with 0 additions and 367 deletions

View File

@ -1,214 +0,0 @@
#!/usr/bin/env python
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")

View File

@ -1,151 +0,0 @@
"""monthdelta
Date calculation with months: MonthDelta class and monthmod() function.
"""
__all__ = ['MonthDelta', 'monthmod']
from datetime import date, timedelta
class MonthDelta(object):
"""Number of months offset from a date or datetime.
MonthDeltas allow date calculation without regard to the different lengths
of different months. A MonthDelta value added to a date produces another
date that has the same day-of-the-month, regardless of the lengths of the
intervening months. If the resulting date is in too short a month, the
last day in that month will result:
date(2008,1,30) + MonthDelta(1) -> date(2008,2,29)
MonthDeltas may be added, subtracted, multiplied, and floor-divided
similarly to timedeltas. They may not be added to timedeltas directly, as
both classes are intended to be used directly with dates and datetimes.
Only ints may be passed to the constructor. MonthDeltas are immutable.
NOTE: in calculations involving the 29th, 30th, and 31st days of the
month, MonthDeltas are not necessarily invertible [i.e., the result above
would not imply that date(2008,2,29) - MonthDelta(1) -> date(2008,1,30)].
"""
__slots__ = ('__months',)
def __init__(self, months=1):
if not isinstance(months, int):
raise TypeError('months must be an integer')
self.__months = months
def months(self):
return self.__months
months = property(months)
def __repr__(self):
try:
return 'MonthDelta({0})'.format(self.__months)
except AttributeError:
return 'MonthDelta(' + str(self.__months) + ')'
def __str__(self):
return str(self.__months) + ' month' + ((abs(self.__months) != 1
and 's') or '')
def __hash__(self):
return hash(self.__months)
def __eq__(self, other):
if isinstance(other, MonthDelta):
return (self.__months == other.months)
return False
def __ne__(self, other):
if isinstance(other, MonthDelta):
return (self.__months != other.months)
return True
def __lt__(self, other):
if isinstance(other, MonthDelta):
return (self.__months < other.months)
return NotImplemented
def __le__(self, other):
if isinstance(other, MonthDelta):
return (self.__months <= other.months)
return NotImplemented
def __gt__(self, other):
if isinstance(other, MonthDelta):
return (self.__months > other.months)
return NotImplemented
def __ge__(self, other):
if isinstance(other, MonthDelta):
return (self.__months >= other.months)
return NotImplemented
def __add__(self, other):
if isinstance(other, MonthDelta):
return MonthDelta(self.__months + other.months)
if isinstance(other, date):
day = other.day
# subract one because months are not zero-based
month = other.month + self.__months - 1
year = other.year + month // 12
# now add it back
month = month % 12 + 1
if month == 2:
if day >= 29 and not year%4 and (year%100 or not year%400):
day = 29
elif day > 28:
day = 28
elif month in (4, 6, 9, 11) and day > 30:
day = 30
try:
return other.replace(year, month, day)
except ValueError:
raise OverflowError('date value out of range')
return NotImplemented
def __sub__(self, other):
if isinstance(other, MonthDelta):
return MonthDelta(self.__months - other.months)
return NotImplemented
def __mul__(self, other):
if isinstance(other, int):
return MonthDelta(self.__months * other)
return NotImplemented
def __floordiv__(self, other):
# MonthDelta // MonthDelta -> int
if isinstance(other, MonthDelta):
return self.__months // other.months
if isinstance(other, int):
return MonthDelta(self.__months // other)
return NotImplemented
def __radd__(self, other):
return self + other
def __rsub__(self, other):
return -self + other
def __rmul__(self, other):
return self * other
def __ifloordiv__(self, other):
# in-place division by a MonthDelta (which will change the variable's
# type) is almost certainly a bug -- raising this error is the reason
# we don't just fall back on __floordiv__
if isinstance(other, MonthDelta):
raise TypeError('in-place division of a MonthDelta requires an '
'integer divisor')
if isinstance(other, int):
return MonthDelta(self.__months // other)
return NotImplemented
def __neg__(self):
return MonthDelta(-self.__months)
def __pos__(self):
return MonthDelta(+self.__months)
def __abs__(self):
return MonthDelta(abs(self.__months))
def __bool__(self):
return bool(self.__months)
__nonzero__ = __bool__
def monthmod(start, end):
"""Months between dates, plus leftover time.
Distribute the interim between start and end dates into MonthDelta and
timedelta portions. If and only if start is after end, returned MonthDelta
will be negative. Returned timedelta is always non-negative, and is always
smaller than the month in which the end date occurs.
Invariant: dt + monthmod(dt, dt+td)[0] + monthmod(dt, dt+td)[1] = dt + td
"""
if not (isinstance(start, date) and isinstance(end, date)):
raise TypeError('start and end must be dates')
md = MonthDelta(12*(end.year - start.year) + end.month - start.month -
int(start.day > end.day))
# will overflow (underflow?) for end near date.min
return md, end - (start + md)

View File

@ -24,8 +24,6 @@ api/integrations/trac/zulip_trac_config.py
api/integrations/trac/zulip_trac.py
bots/jabber_mirror_backend.py
bots/zephyr_mirror_backend.py
tools/deprecated/finbot/monthdelta.py
tools/deprecated/finbot/money.py
tools/deprecated/generate-activity-metrics.py
zproject/settings.py
zproject/test_settings.py