Esempio n. 1
0
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
Esempio n. 2
0
File: app.py Progetto: muniu/sirius
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())