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 # 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 = {} subtotals = {}
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0))) lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
for tax, total in lines.values_list('tax', 'totals'): 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 subtotal += total
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2)) subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
return subtotals return subtotals

View file

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

View file

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.utils.python import AttrDict from orchestra.utils.python import AttrDict
def _compute(rates, metric): def _compute_steps(rates, metric):
value = 0 value = 0
num = len(rates) num = len(rates)
accumulated = 0 accumulated = 0
@ -65,18 +65,20 @@ def _prepend_missing(rates):
def step_price(rates, metric): 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 # Step price
group = [] group = []
minimal = (sys.maxsize, []) minimal = (sys.maxsize, [])
for plan, rates in rates.group_by('plan').items(): for plan, rates in rates.group_by('plan').items():
rates = _prepend_missing(rates) rates = _prepend_missing(rates)
value, steps = _compute(rates, metric) value, steps = _compute_steps(rates, metric)
if plan.is_combinable: if plan.is_combinable:
group.append(steps) group.append(steps)
else: else:
minimal = min(minimal, (value, steps), key=lambda v: v[0]) minimal = min(minimal, (value, steps), key=lambda v: v[0])
if len(group) == 1: 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]) minimal = min(minimal, (value, steps), key=lambda v: v[0])
elif len(group) > 1: elif len(group) > 1:
# Merge # 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): def match_price(rates, metric):
if rates.query.order_by != ['plan', 'quantity']:
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
candidates = [] candidates = []
selected = False selected = False
prev = None 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): def best_price(rates, metric):
if rates.query.order_by != ['plan', 'quantity']:
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
candidates = [] candidates = []
selected = False for plan, rates in rates.group_by('plan').items():
prev = None rates = _prepend_missing(rates)
rates = _prepend_missing(rates.distinct()) plan_candidates = []
for rate in rates: for rate in rates:
if prev: if rate.quantity > metric:
if prev.plan != rate.plan: break
if not selected and prev.quantity <= metric: if plan_candidates:
candidates.append(prev) plan_candidates[-1].barrier = rate.quantity
selected = False plan_candidates.append(AttrDict(
if not selected and rate.quantity > metric: price=rate.price,
if prev.quantity <= metric: barrier=metric,
candidates.append(prev) ))
selected = True candidates.extend(plan_candidates)
prev = rate results = []
if not selected and prev.quantity <= metric: accumulated = 0
candidates.append(prev) for candidate in sorted(candidates, key=lambda c: c.price):
candidates.sort(key=lambda r: r.price) if accumulated+candidate.barrier > metric:
if candidates: quantity = metric - accumulated
return [AttrDict(**{ else:
'quantity': metric, quantity = candidate.barrier
'price': candidates[0].price, if quantity:
})] if results and results[-1].price == candidate.price:
return None results[-1].quantity += quantity
else:
results.append(AttrDict(**{
'quantity': quantity,
'price': candidate.price
}))
return results
best_price.verbose_name = _("Best price") best_price.verbose_name = _("Best price")
best_price.help_text = _("Produces the best possible price given all active rating lines.") 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): def get_billing_point(self, order, bp=None, **options):
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point') not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
if not_cachable or bp is None: 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'): if not options.get('fixed_point'):
msg = ("Support for '%s' period and '%s' point is not implemented" msg = ("Support for '%s' period and '%s' point is not implemented"
% (self.get_billing_period_display(), self.get_billing_point_display())) % (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: if charged is None:
charged = metric charged = metric
size = self.get_price_size(cini, cend) 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 new_metric += metric
size = self.get_price_size(rini, bp) size = self.get_price_size(rini, bp)
old_price = self.get_price(account, charged) * size old_price = self.get_price(account, charged) * size

View file

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

View file

@ -16,7 +16,7 @@ class Order(object):
last_id = 0 last_id = 0
def __init__(self, **kwargs): 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.billed_until = kwargs.get('billed_until', None)
self.cancelled_on = kwargs.get('cancelled_on', None) self.cancelled_on = kwargs.get('cancelled_on', None)
type(self).last_id += 1 type(self).last_id += 1

View file

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