regluit/not_maintained/ola_premiums.py

267 lines
12 KiB
Python

"""
external library imports
"""
import sys
from collections import defaultdict
from decimal import Decimal as D
"""
django imports
"""
import django
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.db.models import Q, F, Count, Sum, Max
"""
regluit imports
"""
from regluit.core.models import Campaign
from regluit.experimental.gutenberg import unicode_csv
from regluit.payment.models import Transaction
from regluit.payment.manager import PaymentManager
# http://stackoverflow.com/questions/2348317/how-to-write-a-pager-for-python-iterators/2350904#2350904
def grouper(iterable, page_size):
page= []
for item in iterable:
page.append( item )
if len(page) == page_size:
yield page
page= []
if len(page):
yield page
# compare to https://unglue.it/work/81724/acks/
# possible options -- these are options that are relevant in how we would determine what we ask people and what they get
# no implementation of these options however
# should transactions be No premium selected be given nothing by default or maximum benefits for their amount by default?
KEEP_NULL_PREMIUM = False
# should people who explicitly picked a lesser premium be given that premium or the maximum one by default?
KEEP_LESSER_PREMIUM = False
# anonymous by default?
ANONYMOUS_BY_DEFAULT = False
# list of OLA premium ids and pledge amount required for that premium
OLA_PREMIUMS_AND_AMOUNTS = ((1L, D('1')), (97L, D('7')), (2L, D('25')), (98L, D('40')), (3L, D('50')), (99L, D('75')), (4L, D('100')), (15L, D('200')), (18L, D('500')), (65L, D('1000')))
# just the premium ids
OLA_PREMIUM_IDS = tuple([x[0] for x in OLA_PREMIUMS_AND_AMOUNTS])
# just the premium amount levels
LEVELS = tuple([x[1] for x in OLA_PREMIUMS_AND_AMOUNTS])
# mapping of premium amount to id -- assumes only one premium can have the same amount
OLA_PREMIUM_FOR_AMOUNT = dict([(n,m) for (m,n) in OLA_PREMIUMS_AND_AMOUNTS])
# each premium is composed of "material benefits" and acknowledgement levels
# mapping of the material benefits for each premium
MATERIAL_FOR_PREMIUM = dict([(1L, (1,)),
(97L, (1, 2)),
(2L, (1, 2)),
(98L , (1,3)),
(3L, (1,3)),
(99L, (1,4)),
(4L, (1,4)),
(15L, (1,4,5,6)),
(18L, (1,4,5,7,8)),
(65L, (1,4,7,9,10,11))
])
# acknowledgement level for each premium
ACK_FOR_PREMIUM = dict([
(1L, ()), (97L, ()), (2L, ('A',)), (98L, ('A',)), (3L, ('B',)), (99L, ('B',)), (4L,('C',)), (15L,('C','D')), (18L, ('C','D')), (65L, ('C', 'D'))
])
# the material premiums cluster in the following way -- it makes sense to be able to have 0 or 1 of the specific material
# premiums in a cluster since within a cluster, they are essentially versions of different quality
# 1
# 2, 3, 4
# 5, 9
# 6, 8 , 11
# 7
# 10
MATERIAL_CLUSTERS = ((1,), (2,3,4), (5,9), (6, 8, 11), (7,), (10,))
MATERIAL_PREMIUMS = dict([(1, "The unglued ebook delivered to your inbox."),
(2, "You will have the choice of one free digital edition selected from our list of published titles. (Offer valid until 30 September 2012)"),
(3, "You may select one free paperback edition or three free digital editions from our list of published titles. (Offer valid until 30 September 2012.)"),
(4, "You may select one free hardback edition, or two free paperback editions, or six free digital editions from our list of published titles. (Offer valid until 30 September 2012.)"),
(5, "A free printed paperback edition with personalised message from the author acknowledging your support."),
(6, "10% discount on any orders made for printed or digital editions of this, or any other OBP title, from the Open Book Publishers website for 12 months. "),
(7, "Opportunity to hold either a personal conversation or a webinar with Dr. Mark Turin, Director of the World Oral Literature Project to discuss the project and broader issues surrounding protecting oral literature globally."),
(8, "20% discount on any orders made for printed or digital editions of this, or any other OBP title, from the Open Book Publishers website for 12 months."),
(9, "Creation of a special personal benefactor's hardback edition of the title with personalised cover and front page and author's inscription. You will also be able to order additional copies of your personalised edition directly from us at $35 per copy plus shipping."),
(10, "Free registration and two nights' accommodation to attend the World Oral Literature Project Workshop in Cambridge, England (29-30 June) or a similar future event."),
(11, "30% discount on any orders made for printed or digital editions of this, or any other OBP title, from the Open Book Publishers website for 12 months.")])
ACKNOWLEDGEMENT_LEVELS = dict([
('A', {'label':"""Your username under "supporters" in the acknowledgements section.""", 'parts':('username',)}),
('B', {'label':'Your username and profile URL of your choice under "benefactors"', 'parts':('username', 'home_url')}),
('C', {'label':'Your username, profile URL, and profile tagline under "bibliophiles"', 'parts':('username', 'home_url', 'tagline')}),
('D', {'label':"Your name recorded on a special benefactors' page in all printed and digital editions of the work.", 'parts':('username', 'home_url', 'tagline')})
])
ALTERNATE_ACK = dict(
[("jmo",{'username':'John and Mary Mark Ockerbloom'})]
)
def supporters_for_campaign(c):
for k in c.transaction_set.filter(status='Complete').values_list('user',flat=True).order_by('-amount'):
yield User.objects.get(id=k)
def highest_eligible_premium(amount, levels):
return max(filter(lambda x: x <= amount, levels))
def premiums_and_acks(c):
""" calculate charts of premiums and acknowledgements owed"""
for (i, u) in enumerate(supporters_for_campaign(c)):
try:
t = c.transaction_set.get(user=u, status='Complete')
max_premium_amount = highest_eligible_premium(t.amount, LEVELS)
max_premium_id = OLA_PREMIUM_FOR_AMOUNT[max_premium_amount]
row = {
"id":t.id,
"username": t.user.username,
"email": t.user.email,
"home_url": t.user.profile.home_url,
"tagline": t.user.profile.tagline,
"amount": t.amount,
"premium_id": t.premium.id if t.premium is not None else None,
"premium_amount": t.premium.amount if t.premium is not None else None,
"max_premium_id": max_premium_id,
"max_premium_amount": max_premium_amount,
"material_premium": ",".join([str(x) for x in MATERIAL_FOR_PREMIUM[max_premium_id]]),
"anonymous": t.anonymous,
"ack": ",".join(ACK_FOR_PREMIUM[max_premium_id]) if not t.anonymous else ""
}
yield row
except Exception, e:
print "problem: ", i, u, e
def out_csv(c, out_fname = "/Users/raymondyee/Downloads/ola_fulfill.csv"):
out_headers = ["id",
"username",
"email",
"home_url",
"tagline",
"amount",
"premium_id",
"premium_amount",
"max_premium_id",
"max_premium_amount",
"material_premium",
"anonymous",
"ack"
]
f = open(out_fname, "wb")
f = unicode_csv.output_to_csv(f, out_headers, premiums_and_acks(c))
def benefits_acks_by_category(c):
"""loop through all the transactions and classify into buckets for material premiums + acks"""
material_benefits_recipients = defaultdict(list)
ack_recipients = defaultdict(list)
for p in premiums_and_acks(c):
material_premium = [int(s) for s in p["material_premium"].split(",")]
ack = p["ack"].split(",")
for mp in material_premium:
material_benefits_recipients[mp].append(p)
for a in ack:
ack_recipients[a].append(p)
# now print out the materials categories working through the material clusters
for cluster in MATERIAL_CLUSTERS:
for mp in cluster:
print "[{0}]".format(mp), MATERIAL_PREMIUMS[mp], "({0})".format( len(material_benefits_recipients[mp]))
for p in material_benefits_recipients[mp]:
print p["email"]
print
print
for a in sorted(ACKNOWLEDGEMENT_LEVELS.keys()):
""" for various levels of acknowledgements, write out the different pieces"""
print "[{0}]".format(a), ACKNOWLEDGEMENT_LEVELS[a]['label'], "({0})".format(len(ack_recipients[a]))
for p in ack_recipients[a]:
# if the username is in ALTERNATE_ACK, overwrite with the alternative info
if ALTERNATE_ACK.has_key(p["username"]):
p1 = p.copy()
p1.update(ALTERNATE_ACK[p["username"]])
print "\t".join([p1[e] for e in ACKNOWLEDGEMENT_LEVELS[a]["parts"]])
else:
print "\t".join([p[e] for e in ACKNOWLEDGEMENT_LEVELS[a]["parts"]])
print
print
# compare with Eric's calculation of various ack levels
ungluers = c.ungluers()
print
print
print "supporters match?", set([u.username for u in ungluers["supporters"]]) == set([p['username'] for p in ack_recipients['A']])
print "patrons match?", set([u.username for u in ungluers["patrons"]]) == set([p['username'] for p in ack_recipients['B']])
print "bibliophiles match?", set([u.username for u in ungluers["bibliophiles"]]) == set([p['username'] for p in ack_recipients['C']])
# list the emails for various acks-- A, B, C-D, D
for pledgers in (('A', [p["email"] for p in ack_recipients['A']]),
('B', [p["email"] for p in ack_recipients['B']]),
('C-D', set([p["email"] for p in ack_recipients['C']]) - set([p["email"] for p in ack_recipients['D']])),
('D', [p["email"] for p in ack_recipients['D']])):
print pledgers[0], len(pledgers[1]), "\n"
for page in grouper(pledgers[1], 40):
for p in page:
print p
print
print
print
def validate_c3(c3):
# enumerate the various premiums and how many people have chosen them
# confirm that there are the same premiums as assumed in operation
assert (c3.name == 'Oral Literature in Africa')
assert set([p.id for p in c3.effective_premiums()]) == set(OLA_PREMIUM_IDS)
assert set([(p.id, p.amount) for p in c3.effective_premiums()]) == set(OLA_PREMIUMS_AND_AMOUNTS)
# confirm that no one has a premium valued greater than transaction
assert c3.transaction_set.filter(Q(status='Complete') & Q(premium__isnull=False)).filter(amount__lt = F('premium__amount')).count() == 0
def overall_stats(c3):
print "total number of pledges completed", c3.transaction_set.filter(status='Complete').count()
print "number of pledgers that did not pick any premium", c3.transaction_set.filter(status='Complete').filter(premium__isnull=True).count()
for p in c3.transaction_set.filter(Q(status='Complete') & Q(premium__isnull=False)).order_by('premium__amount').values_list('premium', 'premium__amount').annotate(count_premium=Count('premium')):
print p[0], p[1], p[2]
# who deliberately picked premiums at a value level equal to the pledge amount
print "number of transactions with amount equal to premium amount", c3.transaction_set.filter(Q(status='Complete') & Q(premium__isnull=False)).filter(amount = F('premium__amount')).count()
class Command(BaseCommand):
help = "Displays data about how related to collecting information about premiums for Oral Literature in Africa"
# args = "<filename> <username>"
def handle(self, **options):
# this is meant specifically for OLA -- check whether such a compaign is in the db
c3 = Campaign.objects.get(id=3)
validate_c3(c3)
overall_stats(c3)
out_csv(c3)
benefits_acks_by_category(c3)