Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Implement pachinko in payday; #449
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Apr 18, 2013
1 parent 2dd0449 commit da5cae6
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 2 deletions.
62 changes: 62 additions & 0 deletions gittip/billing/payday.py
Expand Up @@ -25,6 +25,7 @@
from aspen import log
from aspen.utils import typecheck
from gittip.participant import Participant
from gittip.models.participant import Participant as ORMParticipant
from psycopg2 import IntegrityError
from psycopg2.extras import RealDictRow

Expand Down Expand Up @@ -130,6 +131,8 @@ def run(self):
self.zero_out_pending(ts_start)

self.payin(ts_start, self.genparticipants(ts_start, ts_start))
self.move_pending_to_balance_for_open_groups()
self.pachinko(ts_start, self.genparticipants(ts_start, ts_start))
self.clear_pending_to_balance()
self.payout(ts_start, self.genparticipants(ts_start, False))

Expand Down Expand Up @@ -205,6 +208,7 @@ def get_participants(self, ts_start):
, balanced_account_uri
, stripe_customer_id
, is_suspicious
, type
FROM participants
WHERE claimed_time IS NOT NULL
AND claimed_time < %s
Expand All @@ -228,6 +232,43 @@ def payin(self, ts_start, participants):
log("Did payin for %d participants." % i)


def pachinko(self, ts_start, participants):
for i, (participant, foo, bar) in enumerate(participants, start=1):
if i % 100 == 0:
log("Pachinko done for %d participants." % i)
if participant['type'] != 'open group':
continue
p = ORMParticipant.query.get(participant['username'])
nanswers, threshold, split = p.compute_split()
if nanswers < threshold:
continue
split.reverse()
top_receiver = split.pop()

total = p.balance
given = 0
log("Pachinko $%s out from %s." % (total, p.username))

def tip(member, amount):
tip = {}
tip['tipper'] = p.username
tip['tippee'] = member['username']
tip['amount'] = amount
tip['claimed_time'] = ts_start
self.tip({"username": p.username}, tip, ts_start)
return tip['amount']

for member in split:
amount = p.balance * member['weight']
amount = amount.quantize(Decimal('0.00'), rounding=ROUND_UP)
given += tip(member, amount)

remainder = total - given
tip(top_receiver, remainder)

log("Did pachinko for %d participants." % i)


def payout(self, ts_start, participants):
"""Given a datetime and an iterator, do the payout side of Payday.
"""
Expand Down Expand Up @@ -270,6 +311,26 @@ def charge_and_or_transfer(self, ts_start, participant, tips, total):
self.mark_participant(nsuccessful_tips)


def move_pending_to_balance_for_open_groups(self):
"""Transfer pending into balance for open groups.
We do this because debit_participant operates against balance, not
pending. This is because credit card charges go directly into balance
on the first (payin) loop.
"""
self.db.execute("""\
UPDATE participants
SET balance = (balance + pending)
, pending = 0
WHERE type='open group'
""")
# "Moved" instead of "cleared" because we don't also set to null.
log("Moved pending to balance for open groups. Ready for pachinko.")


def clear_pending_to_balance(self):
"""Transfer pending into balance, setting pending to NULL.
Expand All @@ -290,6 +351,7 @@ def clear_pending_to_balance(self):
WHERE pending IS NOT NULL
""")
# "Cleared" instead of "moved because we also set to null.
log("Cleared pending to balance. Ready for payouts.")


Expand Down
1 change: 1 addition & 0 deletions gittip/cli.py
Expand Up @@ -6,6 +6,7 @@
def payday():
db = wireup.db()
wireup.billing()
wireup.nanswers()


# Lazily import the billing module.
Expand Down
6 changes: 4 additions & 2 deletions gittip/models/participant.py
Expand Up @@ -313,9 +313,11 @@ def compute_split(self):
splitmap[row['member']] += row['weight']
total += row['weight']

total = float(total)
total = Decimal(total)
for username, weight in splitmap.items():
split.append({"username": username, "weight": weight / total})
split.append({ "username": username
, "weight": Decimal(weight) / total
})

split.sort(key=lambda r: r['weight'], reverse=True)

Expand Down

0 comments on commit da5cae6

Please sign in to comment.