mirror of https://github.com/zulip/zulip.git
Remove finbot from Zulip repository.
It's been split into its own repository, https://github.com/zulip/finbot.
This commit is contained in:
parent
12028339a3
commit
b13eeae24c
|
@ -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")
|
|
|
@ -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)
|
|
|
@ -24,8 +24,6 @@ api/integrations/trac/zulip_trac_config.py
|
||||||
api/integrations/trac/zulip_trac.py
|
api/integrations/trac/zulip_trac.py
|
||||||
bots/jabber_mirror_backend.py
|
bots/jabber_mirror_backend.py
|
||||||
bots/zephyr_mirror_backend.py
|
bots/zephyr_mirror_backend.py
|
||||||
tools/deprecated/finbot/monthdelta.py
|
|
||||||
tools/deprecated/finbot/money.py
|
|
||||||
tools/deprecated/generate-activity-metrics.py
|
tools/deprecated/generate-activity-metrics.py
|
||||||
zproject/settings.py
|
zproject/settings.py
|
||||||
zproject/test_settings.py
|
zproject/test_settings.py
|
||||||
|
|
Loading…
Reference in New Issue