def shipping_optimization(order): """ finds the cheapest means of shipping an order @arg order : a dictionary -- see example for details @returns : a models.Order object -- see example for details @raises : AssertionError : Exception (generic with custom messages) """ # pull zipcode zipcode = str(order['zip'])[:5] try: assert re.match(r'\d{5}', zipcode) except AssertionError: bad_zip = Order() box = Box() for item in order['items']: # box.items.append(Item(**item)) if 'qty' in item: qty = int(item['qty']) del item['qty'] for _ in range(qty): print item box.items.append(Item(**item)) else: box.items.append(Item(**item)) box.shipping_cost = 0 box.shipping_method = 'UPS Mail Innovations' bad_zip.boxes.append(box) return bad_zip # pull items items = order['items'] try: assert isinstance(items, (list, tuple)) except AssertionError: raise Exception("items is not an iterable structure / array") # assert well formed items for item in items: # uid try: assert len(str(item['uid'])) > 0 except (KeyError, AssertionError): raise Exception("malformed item - uid missing: '%s'" % str(item)) # weight try: assert item['weight'] > 0 assert item['weight'] <= 2400 except KeyError: raise Exception("malformed item - weight missing: '%s'" % str(item)) except (ValueError, AssertionError): raise Exception("malformed item - weight must be a positive number and less than 2400 oz: '%s'" % str(item)) # qty if 'qty' in item: try: assert item['qty'] > 0 except (ValueError, AssertionError): raise Exception("malformed item - if qty is used it must be > 0: '%s'" % str(item)) # short out system in # of items > 9 -- force ground in 1 box num_items = 0 for item in items: if 'qty' in item: num_items += item['qty'] else: num_items += 1 if num_items > 9: shorted = Box() for item in items: try: qty = item['qty'] del item['qty'] except KeyError: qty = 1 for _ in xrange(qty): shorted.items.append(Item(**item)) shorted.shipping_cost, shorted.shipping_method = get_irregular_box_price(zipcode, shorted.weight) order = Order() order.boxes.append(shorted) return order # build lists of items regular_items = [] irregular_items = [] for item in items: try: qty = item['qty'] del item['qty'] except KeyError: qty = 1 # loop over quantity to create Item objects for _ in xrange(qty): if 'force_ground' in item.keys(): if item['force_ground']: irregular_items.append(Item(**item)) else: regular_items.append(Item(**item)) else: regular_items.append(Item(**item)) # get prices for irregular items irregular_boxes = [] for item in irregular_items: box = Box() box.items.append(item) box.shipping_cost, box.shipping_method = get_irregular_box_price(zipcode, box.weight) box.shipping_cost += LABOR_COST + PACKAGING_COST irregular_boxes.append(box) # make orders of boxes of regular items orders = [] for combination in partitions(regular_items): order = Order() for partition in combination: box = Box() box.items.extend(partition) box.shipping_cost, box.shipping_method = get_regular_box_price(zipcode, box.weight) box.shipping_cost += LABOR_COST + PACKAGING_COST order.boxes.append(box) orders.append(order) # find cheapest order cheapest = min(orders, key=lambda o: o.shipping_cost) # add irregular boxes to order cheapest.boxes.extend(irregular_boxes) # return cheapest order return cheapest
def optimize_shipping(): """ """ def make_combinations(items): """ makes all possible combinations to the length of the origin input """ def inner(items, r): """ recursively yields partitioned remainders of original partition lists """ items = set(items) if not len(items): yield () return first = next(iter(items)) remainder = items.difference((first, )) for combination in combinations(remainder, r-1): first_subset = (first, ) + combination for partition in inner(remainder.difference(combination), r): yield (first_subset, ) + partition def outter(items, r): """ combines partition lists """ items = set(items) for i in range(len(items), -1, -r): if i == 0: for partition in inner(items, r): yield partition elif i != r: for combination in combinations(items, i): for partition in inner(items.difference(combination), r): yield partition + (combination, ) # step through length of origin combination partitions to ensure full list for i in range(1, len(items)): gen = outter(items, i) for row in gen: yield row # get posted json data = json.loads(request.data) # pull zipcode zipcode = data['zip'] # create items regular, irregular = [], [] for item in data['items']: qty = item['qty'] del item['qty'] for __ in xrange(qty): if 'is_irregular' in item: if item['is_irregular']: irregular.append(Item(**item)) else: regular.append(Item(**item)) else: regular.append(Item(**item)) # process irregular items irregular_boxes = [] for item in irregular: box = Box() box.items.append(item) cost = get_irregular_price(zipcode, box.weight) box.shipping_method = 'UPS Ground' box.shipping_cost = cost irregular_boxes.append(box) # process regular items if len(regular): if len(regular) == 1: box = Box() box.items.extend(regular) method, cost = get_cheapest_option(zipcode, box.weight) box.shipping_method = method box.shipping_cost = cost order = Order() order.boxes.append(box) # add irregular boxes to order order.boxes.extend(irregular_boxes) return str(order.to_json()) else: # create orders/bundles from items combinations orders = [] for combination in make_combinations(regular): # full tuple of tuples of items. ex: ( (one,two), (three,) ) order = Order() for grouping in combination: # tuple of items. ex (one,two) from above box = Box() # add items to the box box.items.extend(grouping) # get cheapest shipping option for this box method, cost = get_cheapest_option(zipcode, box.weight) box.shipping_method = method box.shipping_cost = cost # add box to order order.boxes.append(box) # add irregular boxes to order order.boxes.extend(irregular_boxes) # add order to list of all possible combinations orders.append(order) # get the cheapest order combination cheapest = min(orders, key=lambda o: o.shipping_cost) cheapest.shipping_cost = round(cheapest.shipping_cost, 2) # respond return str(cheapest.to_json()) # no regular items: else: order = Order() order.boxes.extend(irregular_boxes) return str(order.to_json())