Skip to content

Commit

Permalink
Merge pull request #796 from hyanwong/error-squash
Browse files Browse the repository at this point in the history
Rework price setting
  • Loading branch information
hyanwong authored Apr 16, 2024
2 parents fb6a70a + 462da47 commit 851d19b
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 40 deletions.
66 changes: 36 additions & 30 deletions controllers/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def SHOW_SPONSOR_SUMS():
cols['grouped_otts'],
cols['sum_paid'],
cols['count'],
groupby=groupby, orderby= cols['sum_paid'] + " DESC")
groupby=groupby, orderby=cols['sum_paid'] + " DESC")
return dict(rows = rows, cols=cols)

@OZfunc.require_https_if_nonlocal()
Expand Down Expand Up @@ -793,7 +793,14 @@ def SET_PRICES():
"""
# Take the initial prices from the previous prices in the DB
bands = {string.ascii_uppercase[i]: row for i, row in enumerate(db().select(db.prices.ALL))}
bands = {
string.ascii_uppercase[i]: row
for i, row in enumerate(db().select(
db.prices.ALL,
orderby="ISNULL(quantile), quantile, ISNULL(price), price"
))
}
max_band=max((b for b in bands if bands[b].price is not None), default=None)
n_leaves = db(db.ordered_leaves).count()

bespoke_spp = [ #these are in order highest to lowest
Expand All @@ -806,8 +813,10 @@ def SET_PRICES():
# Make a form with all the rows in it
other_fields = [f for f in db.prices if f.name not in ('id', 'quantile', 'n_leaves')]
fields = []
for i, (band, row) in enumerate(sorted(bands.items(), reverse=True)):
if i > 0:
for band, row in bands.items():
if band == max_band or row.price is None:
pass # Don't get quantiles for the max band or the unsponsorable
else:
field = db.prices.quantile
# Don't have a max popularity cutoff for the highest price
fields.append(Field("_".join([field.name, band]), type = field.type, default=row[field.name], requires=IS_NOT_EMPTY()))
Expand All @@ -821,7 +830,7 @@ def SET_PRICES():
cutoffs = {}
other_vars = {}
prev = None
for band in sorted(bands.keys()):
for band in sorted(bands):
if prev is None:
# Cheapest category
quantile = float(form.vars["_".join(["quantile", band])])
Expand All @@ -836,18 +845,22 @@ def SET_PRICES():
(db.ordered_leaves.popularity_rank <= cutoffs[prev][1]) &
(db.ordered_leaves.popularity_rank > cutoffs[band][1]))
else:
#this is the last one, which does not have a defined top price
queries[band] = db(
(db.ordered_leaves.popularity_rank <= cutoffs[prev][1]))
if bands[band].price is not None:
#this is the last quantile: no upper cutoff
queries[band] = db(
(db.ordered_leaves.popularity_rank <= cutoffs[prev][1]))
other_vars[band] = {
f.name: form.vars["_".join([f.name, band])]
for f in other_fields
}
prev = band

if prev is not None:
assert bands[prev].price is None # Check the last has no price set
queries[prev] = db(db.banned.ott) # And set that to the banned list

#save the results
db.prices.truncate()
for band in sorted(bands.keys()):
for band in sorted(bands):
num=queries[band].count()
db.prices.insert(
quantile=(float(cutoffs[band][0]) if band in cutoffs else None),
Expand All @@ -857,34 +870,27 @@ def SET_PRICES():

output = []
revenue = 0
#also None means 'call us'
for band in sorted(bands):
num = queries[band].count()
pc = 0 if n_leaves==0 else (100*num/n_leaves)
price = other_vars[band]['price']
cnt=queries[band].update(price=price)
num=queries[band].count()
revenue += num*price/100
output.append(f"{OZfunc.fmt_pounds(pence=price)}: {num:>8} species ({100*num/n_leaves:.2f}%) - {cnt} changed")
price_str = OZfunc.fmt_pounds(pence=price)
# Now set all the prices for this band
count = 0 if num == 0 else queries[band].update(price=price)
revenue += num*(price or 0)/100
output.append(f"{price_str}: {num:>8} species ({pc:.2f}%): {count} changed")
output.append(BR())


response.flash = DIV(
f"SET THE FOLLOWING DEFAULT PRICE STRUCTURE for {n_leaves} species:", BR(),PRE(*output),
f"Total revenue: {OZfunc.fmt_pounds(revenue)}!\nNow overriding the following special exclusions (and setting banned):", BR(),
f"{bespoke_spp}"
)

#override with the bespoke ones
target_band = 0
for band in sorted(bands, reverse=True):
db(db.ordered_leaves.name.belongs(bespoke_spp[target_band])).update(price=other_vars[band]['price'])
target_band+=1
if target_band>=len(bespoke_spp):
break

#make sure the banned ones are NULLified
rows = db().select(db.banned.ott)
for ban in rows:
db(db.ordered_leaves.ott == ban.ott).update(price=None)
#override bespoke species prices: these are ordered by reverse price tag
rev_price_bands = [b for b in sorted(bands, reverse=True) if other_vars[b]['price'] is not None]
for band, spp in zip(rev_price_bands, bespoke_spp):
db(db.ordered_leaves.name.belongs(spp)).update(price=other_vars[band]['price'])

elif form.errors:
response.flash = 'form has errors'
Expand Down Expand Up @@ -920,9 +926,9 @@ def SET_PRICES():
pop_max=db(db.ordered_leaves).select(mx)
return dict(
form=form,
bands={b: bands[b].price for b in sorted(bands.keys())},
bands={b: bands[b].price for b in sorted(bands)},
other_fields=other_fields,
max_band=max(bands.keys()) if len(bands) else None,
max_band=max_band,
pop_min=pop_min[0][mn],
pop_max=pop_max[0][mx],
example_spp=sorted(example_spp, key=lambda tup: tup[1]),
Expand Down
4 changes: 2 additions & 2 deletions models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,8 +649,8 @@

# this table defines the current pricing cutoff points
db.define_table('prices',
Field('price', type='integer', unique=True, requires=IS_NOT_EMPTY()),
Field('perpetuity_price', type='integer', requires=IS_NOT_EMPTY()),
Field('price', type='integer', unique=True),
Field('perpetuity_price', type='integer'),
# Map the "normal" 4 year price to the in-perpetuity price
Field('quantile', type='double'),
Field('n_leaves', type='integer'),
Expand Down
8 changes: 5 additions & 3 deletions modules/OZfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ def otts2ids(ottIntegers):
return {"nodes": {}, "leaves": {}, "names": {}}

def fmt_pounds(pounds=0, pence=0):
p = pence / 100 + pounds
return '£{:.0f}'.format(p) if float(p).is_integer() else '£{:.2f}'.format(p)

try:
p = pence / 100 + pounds
return '£{:.0f}'.format(p) if float(p).is_integer() else '£{:.2f}'.format(p)
except:
return current.T('Undefined price')
2 changes: 1 addition & 1 deletion views/default/list_sponsorable_children.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Alphabetical list of species in this group</h2>
{{if sponsorship_enabled:}}
&pound;{{if s.price is None:}}{{T('contact us')}}{{else:}}{{=f"{s.price/100:.2f}"}}{{pass}}{{T('or more to sponsor.')}}
{{else:}}
{{=T(price_levels_pence.get(s.price, "click for sponsorship details"))}}
{{=T(price_levels_pence.get(s.price) or "click for sponsorship details"))}}
{{pass}}</li>
{{pass}}
</ul>
Expand Down
15 changes: 11 additions & 4 deletions views/manage/SET_PRICES.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ <h2>set cutoffs:</h2>
</tr>
{{for b, price in bands.items():}}
<tr>
{{ if b != max_band:}}
<th style="text-align: left">{{=b}} (currently {{=fmt_pounds(pence=price)}})</th><td style="background-color: yellow; border-right: double">{{=form.custom.widget['_'.join(['quantile', b])]}}</td>
{{ else:}}
{{ if b == max_band:}}
<th style="text-align: left">{{=b}} (currently {{=fmt_pounds(pence=price)}})</th><td style="border-right: double">remainder</td>
{{elif price is None:}}
<th style="text-align: left">{{=b}} (currently {{=fmt_pounds(pence=price)}})</th><td style="border-right: double">{{=price}}</td>
{{ else:}}
<th style="text-align: left">{{=b}} (currently {{=fmt_pounds(pence=price)}})</th><td style="background-color: yellow; border-right: double">{{=form.custom.widget['_'.join(['quantile', b])]}}</td>
{{ pass}}
{{ for i, field in enumerate(other_fields):}}
<td style="border: none; {{if i==0:}}padding-left: 4em;{{pass}}">{{=form.custom.widget["_".join([field.name, b])]}}</td>
Expand All @@ -51,6 +53,11 @@ <h2>set cutoffs:</h2>
</tr>
</table>
Click here to set all the prices in the database (may take some time): {{=form.custom.submit}}
{{=form.custom.end}}<h2>Submitted variables</h2>
{{=form.custom.end}}
<br />
{{if request.vars:}}
<details><summary>Click to see submitted variables</summary>
{{=BEAUTIFY(request.vars)}}
{{pass}}
</details>
</div></div>

0 comments on commit 851d19b

Please sign in to comment.