added best_price rating method

This commit is contained in:
Marc Aymerich 2015-05-12 14:04:20 +00:00
parent b5ede03858
commit 8fdec05908
8 changed files with 58 additions and 37 deletions

View file

@ -355,4 +355,9 @@ resorce monitoring more efficient, less mem an better queries for calc current d
# best_price rating method
# select contact with one result: redirect
# bill this https://orchestra.pangea.org/admin/orders/order/8236/ should be already billed, <= vs <
# Convert rating method from function to PluginClass
# Tests can not run because django.db.utils.ProgrammingError: relation "accounts_account" does not exist

View file

@ -233,7 +233,7 @@ class Bill(models.Model):
subtotals = {}
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
for tax, total in lines.values_list('tax', 'totals'):
subtotal, taxes = subtotals.get(tax, (0, 0))
subtotal, taxes = subtotals.get(tax) or (0, 0)
subtotal += total
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
return subtotals

View file

@ -146,8 +146,8 @@ def execute(scripts, serialize=False, async=None):
def collect(instance, action, **kwargs):
""" collect operations """
operations = kwargs.get('operations', OrderedSet())
route_cache = kwargs.get('route_cache', {})
operations = kwargs.get('operations') or OrderedSet()
route_cache = kwargs.get('route_cache') or {}
for backend_cls in ServiceBackend.get_backends():
# Check if there exists a related instance to be executed for this backend and action
instances = []

View file

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.utils.python import AttrDict
def _compute(rates, metric):
def _compute_steps(rates, metric):
value = 0
num = len(rates)
accumulated = 0
@ -65,18 +65,20 @@ def _prepend_missing(rates):
def step_price(rates, metric):
if rates.query.order_by != ['plan', 'quantity']:
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
# Step price
group = []
minimal = (sys.maxsize, [])
for plan, rates in rates.group_by('plan').items():
rates = _prepend_missing(rates)
value, steps = _compute(rates, metric)
value, steps = _compute_steps(rates, metric)
if plan.is_combinable:
group.append(steps)
else:
minimal = min(minimal, (value, steps), key=lambda v: v[0])
if len(group) == 1:
value, steps = _compute(rates, metric)
value, steps = _compute_steps(rates, metric)
minimal = min(minimal, (value, steps), key=lambda v: v[0])
elif len(group) > 1:
# Merge
@ -125,6 +127,8 @@ step_price.help_text = _("All rates with a quantity lower than the metric are ap
def match_price(rates, metric):
if rates.query.order_by != ['plan', 'quantity']:
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
candidates = []
selected = False
prev = None
@ -155,29 +159,37 @@ match_price.help_text = _("Only <b>the rate</b> with a) inmediate inferior metri
def best_price(rates, metric):
if rates.query.order_by != ['plan', 'quantity']:
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
candidates = []
selected = False
prev = None
rates = _prepend_missing(rates.distinct())
for plan, rates in rates.group_by('plan').items():
rates = _prepend_missing(rates)
plan_candidates = []
for rate in rates:
if prev:
if prev.plan != rate.plan:
if not selected and prev.quantity <= metric:
candidates.append(prev)
selected = False
if not selected and rate.quantity > metric:
if prev.quantity <= metric:
candidates.append(prev)
selected = True
prev = rate
if not selected and prev.quantity <= metric:
candidates.append(prev)
candidates.sort(key=lambda r: r.price)
if candidates:
return [AttrDict(**{
'quantity': metric,
'price': candidates[0].price,
})]
return None
if rate.quantity > metric:
break
if plan_candidates:
plan_candidates[-1].barrier = rate.quantity
plan_candidates.append(AttrDict(
price=rate.price,
barrier=metric,
))
candidates.extend(plan_candidates)
results = []
accumulated = 0
for candidate in sorted(candidates, key=lambda c: c.price):
if accumulated+candidate.barrier > metric:
quantity = metric - accumulated
else:
quantity = candidate.barrier
if quantity:
if results and results[-1].price == candidate.price:
results[-1].quantity += quantity
else:
results.append(AttrDict(**{
'quantity': quantity,
'price': candidate.price
}))
return results
best_price.verbose_name = _("Best price")
best_price.help_text = _("Produces the best possible price given all active rating lines.")

View file

@ -138,7 +138,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
def get_billing_point(self, order, bp=None, **options):
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
if not_cachable or bp is None:
bp = options.get('billing_point', timezone.now().date())
bp = options.get('billing_point') or timezone.now().date()
if not options.get('fixed_point'):
msg = ("Support for '%s' period and '%s' point is not implemented"
% (self.get_billing_period_display(), self.get_billing_point_display()))
@ -501,7 +501,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
if charged is None:
charged = metric
size = self.get_price_size(cini, cend)
new_price += self.get_price(order, metric) * size
new_price += self.get_price(account, metric) * size
new_metric += metric
size = self.get_price_size(rini, bp)
old_price = self.get_price(account, charged) * size

View file

@ -228,7 +228,11 @@ class Service(models.Model):
return self.rates.by_account(account)
if not hasattr(self, '__cached_rates'):
self.__cached_rates = {}
rates = self.__cached_rates.get(account.id, self.rates.by_account(account))
try:
return self.__cached_rates[account.id]
except KeyError:
rates = self.rates.by_account(account)
self.__cached_rates[account.id] = rates
return rates
@property

View file

@ -16,7 +16,7 @@ class Order(object):
last_id = 0
def __init__(self, **kwargs):
self.registered_on = kwargs.get('registered_on', timezone.now().date())
self.registered_on = kwargs.get('registered_on') or timezone.now().date()
self.billed_until = kwargs.get('billed_until', None)
self.cancelled_on = kwargs.get('cancelled_on', None)
type(self).last_id += 1

View file

@ -27,7 +27,7 @@ def get_request_cache():
class RequestCacheMiddleware(object):
def process_request(self, request):
cache = _request_cache.get(currentThread(), RequestCache())
cache = _request_cache.get(currentThread()) or RequestCache()
_request_cache[currentThread()] = cache
cache.clear()