def testSingletonDefaultDict(self): i = Inventory() i.clear() i2 = Inventory() self.assertIs(i.stock, i2.stock, 'singleton test fails') self.assertEqual(len(i.stock), 0, 'inventory SBE empty, found {} items'\ .format(i.stock.keys()))
def testExceptions(self): i = Inventory() i.clear() try: i.stock = dict() raise ValueError('stock rack_no should have raised exception') except ValueError as v: print(v) sys.exit(-1) except Exception as e: pass
def testUpdateStock(self): r_no = 3 i_no = 3 location = Bin.Bin_Location(rack_no=r_no, side='a', bin_no=1) i = Inventory() i.clear() i.update_bin(item_no=i_no, location=location, qty=10) self.assertEqual(Inventory.__repr__(Inventory()), str(Inventory()), '__repr__ != __string__') self.assertEqual(len(i.stock), 1, 'inventory SBE empty, found {} items'\ .format(len(i.stock)))
def testStockingRackBins(self): Inventory.clear() Warehouse.clear() wh = Warehouse(5, 5) for i, (r, s, bn) in enumerate( product(range(1, 5 + 1, 1), list('ab'), range(1, 5 + 1, 1))): if s == 'a': b = wh.racks[r - 1].bins_a[bn] else: # s == 'b' b = wh.racks[r - 1].bins_b[bn] wh.update_stock(i + 1, (i + 1) * 10, b.location) _, q = wh.get_stock_qty(i + 1) self.assertEqual( q, (i + 1) * 10, 'inventory qty for item {} item SBE {}, is {}'.format( i, (i + 1) * 10, q))
def testAddItem(self): b = Bin(rack_no=1, side='b', bin_no=2) itm_no = 3 qty = 30 i = Inventory() i.clear() i.update_bin(location=b.location, item_no=itm_no, qty=qty) self.assertEqual(i.get_stock_qty(location=b.location), (itm_no, qty), \ "item {} at {} SBE qty {}, is {}".format(itm_no, qty, b.location, i.get_stock_qty(location=b.location))) b = Bin.get_bin_by_location(b.location) self.assertEqual(b.item, itm_no, \ 'location ({} item/qty SBE {}, is {}'.format(b.location, (itm_no, qty), (b.item, b.count)))
def testClear(self): b = Bin(rack_no=1, side='a', bin_no=1) b.__count = 0 i = Inventory() i.clear() Inventory.update_bin(location=b.location, item_no=1, qty=10) self.assertEqual( len(i.stock), 1, 'SBE only 1 item in inventory stock, is {}'.format(len(i.stock))) i.clear() self.assertEqual( len(i.stock), 0, 'SBE no1 items in inventory stock after clear, is {}'.format( len(i.stock)))
def testStockingRackBins(self): Inventory.clear() r = Rack(rack_no=1, bin_count=5) for b in r.bins_a.values(): b.stock_bin(item_no=b.bin_no, item_count=b.bin_no * 10) for b in r.bins_b.values(): b.stock_bin(item_no=b.bin_no, item_count=b.bin_no * 20) for b in r.bins_a.values(): self.assertEqual( b.item, b.bin_no, 'bin @ {} item SBE {}, is {}'.format(b.location, b.bin_no, b.item)) self.assertEqual( b.count, b.bin_no * 10, 'bin @ {} count SBE {}, is {}'.format(b.location, b.bin_no * 10, b.count)) for b in r.bins_b.values(): self.assertEqual( b.item, b.bin_no, 'bin @ {} item SBE {}, is {}'.format(b.location, b.bin_no, b.item)) self.assertEqual( b.count, b.bin_no * 20, 'bin @ {} count SBE {}, is {}'.format(b.location, b.bin_no * 20, b.count)) inv = Inventory() # Inventory.__stock[item_no][location] for b in r.bins_a.values(): (i, q) = inv.get_stock_qty(location=b.location) self.assertEqual( (i, q), (b.item, b.count), 'inventory item/qty for bin @ {} item SBE {}, is {}'.format( b.location, (b.item, b.count), (i, q))) for b in r.bins_b.values(): (i, q) = inv.get_stock_qty(location=b.location) self.assertEqual( (i, q), (b.item, b.count), 'inventory qty for bin @ {} item SBE {}, is {}'.format( b.location, (b.item, b.count), (i, q)))
class Test(unittest.TestCase): Warehouse.clear() wh = Warehouse(5, 5) rack_count, bin_count = wh.racks_bins bins_total = rack_count * bin_count * 2 inv = Inventory() num_items = bins_total o = None order_lines = 5 setup_calls = 0 def setUp(self): Test.setup_calls += 1 # print('setup call number {}'.format(Test.setup_calls)) np.random.seed(42) Test.wh = Test.wh.reset(5, 5) self.wh = Test.wh Test.inv.clear() rsb = [(r, s, b) for r, s, b in product(range(1, 5 + 1, 1), list('ab'), range(1, 5 + 1, 1))] np.random.seed(42) np.random.shuffle(rsb) for i, (r, s, bn) in enumerate(rsb): if s == 'a': b = self.wh.racks[r - 1].bins_a[bn] else: # s == 'b' b = self.wh.racks[r - 1].bins_b[bn] self.wh.update_stock(i + 1, (i + 1) * 10, b.location) np.random.seed(42) Test.o = Order() np.random.seed(42) for i, q in zip(np.random.choice(range(1, Test.num_items + 1), size=Test.order_lines, replace=False), np.random.randint(1, Test.num_items + 1, size=Test.order_lines)): Test.o.add_line(item_no=int(i), qty=int(q)) racks = self.wh.racks b = racks[0].bins_a[1] self.assertEqual(b.item, 26, 'rack 1, bin @ {}, item SBE 26, is {}'.format(b.location, b.item)) # print(b) b = racks[1].bins_a[1] self.assertEqual(b.item, 41, 'rack 1, bin @ {}, item SBE 41, is {}'.format(b.location, b.item)) # print(b) b = racks[1].bins_b[1] self.assertEqual(b.item, 20, 'rack 1, bin @ {}, item SBE 29, is {}'.format(b.location, b.item)) # print(b) def tearDown(self): pass def testInit(self): # print('testInit') pr = PickRoute(Test.wh, self.o) self.assertEqual(len(pr.wh.racks), 5, 'warehouse should have 5 racks, is {}'.format(len(pr.wh.racks))) self.assertEqual(len(self.o.lines), Test.order_lines, 'order lines SBE {}, is {}'.format(Test.order_lines, len(self.o.lines))) self.assertEqual(len(pr.pick_bins), Test.order_lines, \ 'order pick_bins SBE {}, is {}'.format(Test.order_lines, len(pr.pick_bins))) def testBinsDistance(self): # print('testBinsDistance') b1 = Bin(rack_no=1, side='b', bin_no=1) b2 = Bin(rack_no=1, side='a', bin_no=5) pr = PickRoute(Test.wh, self.o) d = pr.bin_to_bin_distance(b1, b2) print('from {} to {} dist: {}'.format(b1.location, b2.location, d)) #expected = 16 #self.assertEqual(d, expected, '{} to {} SBE [], is {}'\ # .format(b1.location, b2.location, expected, d)) b1 = Bin(rack_no=1, side='b', bin_no=1) b2 = Bin(rack_no=2, side='a', bin_no=1) pr = PickRoute(Test.wh, self.o) d = pr.bin_to_bin_distance(b1, b2) print('from {} to {} dist: {}'.format(b1.location, b2.location, d)) #expected = 16 #self.assertEqual(d, expected, '{} to {} SBE [], is {}'\ # .format(b1.location, b2.location, expected, d)) b1 = Bin(rack_no=1, side='a', bin_no=4) b2 = Bin(rack_no=4, side='b', bin_no=5) pr = PickRoute(Test.wh, self.o) d = pr.bin_to_bin_distance(b1, b2) print('from {} to {} dist: {}'.format(b1.location, b2.location, d)) #expected = 16 #self.assertEqual(d, expected, '{} to {} SBE [], is {}'\ # .format(b1.location, b2.location, expected, d)) b1 = Bin(rack_no=3, side='a', bin_no=0) b2 = Bin(rack_no=4, side='b', bin_no=5) pr = PickRoute(Test.wh, self.o) d = pr.bin_to_bin_distance(b1, b2) print('from dock {} to {} dist: {}'.format(b1.location, b2.location, d)) #expected = 16 #self.assertEqual(d, expected, '{} to {} SBE [], is {}'\ # .format(b1.location, b2.location, expected, d)) def testRoute(self): # print('testRoute') np.random.seed(42) pr = PickRoute(Test.wh, self.o) expected_steps = 46 if pr.route_distance != expected_steps: print('route distance {:d} SBE {} {}'.format(pr.route_distance, expected_steps, 'maybe next time?' \ if pr.route_distance != expected_steps \ else '')) # print(os.path.curdir) # I'm missing a random.seed somewhere self.assertEqual(pr.route_distance, expected_steps, 'route_distince SBE {}, is {}'\ .format(expected_steps, pr.route_distance))
class Warehouse: ''' A warehouse has inventory, racks, and a dock. Racks contain bins. Warehouse assigns a lat / long to bins to allow computation of distance traveled from one bin to another. The init args are: racks, int > 0 for how many racks are in warehouse bins, int > 0 for how many bins are in each rack Assumptions: coordinates of warehouse's lower-left corner is (0, 0) dock is single location with lat == 0 and long centered WRT number of racks bins_a[1] and bins_[1] are for bin 1 shortest route from one bin to another goes through the rack end (cap) closest to the destination bin ''' __instance = None __inventory = Inventory() @classmethod def clear(cls): Inventory.clear() @classmethod def reset(cls, racks, bins): cls.__instance = None # clear __instance so __init__ doen't fail cls.__instance = Warehouse( racks=racks, bins=bins) # Now the initialization can be called Inventory.clear() return cls.__instance def __init__(self, racks=None, bins=None): if type(self).__instance is None: # Initialization assert isinstance(racks, int) and racks > 0, \ 'number of racks must be int > 0' assert isinstance(bins, int) and bins > 0, \ 'number of bins must be int > 0' type(self).__instance = self dock_rack = round((racks / 2) + .1) # friggin "Banker's rounding! self.__dock = Bin(rack_no=dock_rack, side='a', bin_no=0) self.__dock.nearest_cap = self.__dock self.__dock.nearest_cap_distance = 0 self.__racks_bins = (racks, bins) self.__racks = [ Rack(rack_no=x, bin_count=bins) for x in range(1, racks + 1, 1) ] self.__bins = [ list(r.bins_a.values()) + list(r.bins_b.values()) for r in self.__racks ][0] else: self.__dict__ = Warehouse.__instance.__dict__ def __repr__(self): return '''racks: {}, dock (lat, long): {}, inventory has {:,d} item_no, {:,d} quantities'''\ .format(len(self.__racks), (self.__dock.lat, self.__dock.long), len(Warehouse.__inventory.stock), sum(b.count for b in Bin.bin_locations.values())) @classmethod def update_stock(cls, item_no, qty, location): ''' Use Inventory.update_stock if called w/o location or with location == None so qty will be divided among bins holding item Use Inventory.update_bin if called with location ''' assert isinstance( location, Bin.Bin_Location ), 'update_stock SBE called with nlocation = None or instance of Bin_Location' cls.__inventory.update_bin(location, item_no, qty) def get_stock_qty(self, item_no): return Warehouse.__inventory.get_stock_qty(item_no) @property def racks(self): return self.__racks @racks.setter def racks(self, *args): assert 1 == 0, 'racks attribute set by init only' return @property def racks_bins(self): return self.__racks_bins @racks_bins.setter def racks_bins(self, args): assert 0 == 1, 'number of racks and bins fixed at instantiation' @property def dock(self): return self.__dock @dock.setter def dock(self, *arg): assert 1 == 0, 'dock lat/long set by init only' @property def stock(self): return Warehouse.__inventory.stock @property def inventory(self): return self.__inventory @inventory.setter def inventory(self, args): raise RuntimeError('inventory attribute set by init')
def reset(cls, racks, bins): cls.__instance = None # clear __instance so __init__ doen't fail cls.__instance = Warehouse( racks=racks, bins=bins) # Now the initialization can be called Inventory.clear() return cls.__instance
def clear(cls): Inventory.clear()
class Rack: ''' Instantiation: Rack(rack_no = int > 0, bin_count = int > 0) Method Notes: bins_a and bins_b are dicts of bins and accessed by bin_no as key ''' _inventory = Inventory() def __init__(self, rack_no, bin_count): assert isinstance(rack_no, int) and rack_no > 0,\ 'rack_no must be int > 0' assert isinstance(bin_count, int) and bin_count > 0,\ 'bin_count must be int > 0' self.__rack_no = rack_no self.__bin_count = bin_count self.__top_cap_lat = bin_count + 2 # always with fixed # of bins self.__bottom_cap_lat = 1 # always self.__bins_a = dict() # access via bin_no self.__bins_b = dict() # access via bin_no for bin_no in range(1, bin_count + 1, 1): b = Bin(rack_no=self.rack_no, side='a', bin_no=bin_no) b.nearest_cap = self.nearest_cap(b) b.nearest_cap_distance = int( abs(b.lat_long - b.nearest_cap.lat_long).sum()) self.__bins_a[bin_no] = b b = Bin(rack_no=self.rack_no, side='b', bin_no=bin_no) b.nearest_cap = self.nearest_cap(b) b.nearest_cap_distance = int( abs(b.lat_long - b.nearest_cap.lat_long).sum()) self.__bins_b[bin_no] = b def __repr__(self): return 'rack_no {}, bin_count: {}, top_cap_lat: {:2d}, bottom_cap_lat: {}'\ .format(self.__rack_no, self.__bin_count, \ self.__top_cap_lat, self.__bottom_cap_lat) def nearest_cap(self, b): (dist_bottom, dist_top) = (b.bin_no, self.bin_count - b.bin_no + 1) if dist_bottom > dist_top: bin_no = self.bin_count + 1 else: bin_no = 0 return Bin(rack_no=self.rack_no, side=b.bin_side, bin_no=bin_no) @property def rack_no(self): return self.__rack_no @rack_no.setter def rack_no(self, *arg, **varg): raise RuntimeError('rack_no is {} and can not be reset'.format( self.rack_no)) @property def bins_a(self): return self.__bins_a @bins_a.setter def bins_a(self, *arg): raise RuntimeError('setting bin arrays by init') @property def bins_b(self): return self.__bins_b @bins_b.setter def bins_b(self, *arg): raise RuntimeError('setting bin arrays by init') @property def bin_count(self): return self.__bin_count @bin_count.setter def bin_count(self, *argv): raise RuntimeError('bin_count only gets set when adding bin(s)')
class Order: """ Order contains order_lines which contain order_items Assumptions: It is permissable to add items to order that are not in Inventory """ class OrderItem: ''' OrderItem is class for contents of order line item ''' def __init__(self, line_no, item_no, qty, status='ordered'): self.__line_no = line_no self.__item_no = item_no self.__qty = qty self.__status = status def __repr__(self): return 'line_no: {}, item_no: {}, qty: {}, line status: {}'.format( self.line_no, self.item_no, self.qty, self.status) @property def line_no(self): return self.__line_no @property def item_no(self): return self.__item_no @property def qty(self): return self.__qty @property def status(self): return self.__status @status.setter def status(self, stat): self.__status = stat ############################## # Order class variabiles __last_order_no = 0 __inventory = Inventory() @classmethod def clear(cls): ''' clear needed to keep unit tests independent ''' cls.__last_order_no = 0 pass # for debugging @classmethod def last_order_no(cls): return cls.__last_order_no def __init__(self): Order.__last_order_no += 1 self.__order_no__ = 0 + Order.__last_order_no self.__lines = [] self.__last_line_no = 0 def add_line(self, item_no=None, qty=None): assert item_no is not None,\ 'Order Line SBE instantiated with item_no and qty' assert isinstance(item_no, int) and item_no > 0,\ 'item_no must be int > 0, is {}'.format(item_no) assert isinstance(qty, int) and qty > 0,\ 'qty must be int > 0, is {}'.format(qty) # not sure I'm ready for this assert: # assert item_no in Order.__inventory, 'attemted to add item not in inventory: {}'.format(item_no) self.__last_line_no += 1 line = Order.OrderItem(0 + self.__last_line_no, item_no, qty, 'ordered') self.__lines.append((line)) def __repr__(self): return ('order_no: {}, {} lines'.format(self.__order_no__, len(self.__lines))) @property def order_no(self): return self.__order_no__ @property def lines(self): return self.__lines
def testReprStr(self): i = Inventory() r = i.__repr__() s = i.__str__() self.assertEqual(r, s, 'repr "{}" not eq str "{}"'.format(r, s))
def testUpdateStockSeparateSides(self): b = Bin(rack_no=1, side='a', bin_no=1) b.__count = 0 i = Inventory() i.clear() Inventory.update_bin(location=b.location, item_no=1, qty=10) self.assertEqual(Inventory.__repr__(Inventory()), str(Inventory()), '__repr__ != __string__') self.assertEqual( len(i.stock), 1, 'inventory SBE empty, found {} items'.format(len(i.stock))) self.assertEqual(Bin.get_bin_by_location(b.location).count, 10, 'location ({} item SBE 10, is {}'\ .format(b.location, Bin.get_bin_by_location(b.location))) b = Bin(rack_no=1, side='b', bin_no=2) location = b.location i.update_bin(location=location, item_no=1, qty=20) (itm, q) = i.get_stock_qty(location=location) self.assertEqual( itm, 1, 'item at location {} SBE 1, is {}'.format(location, itm)) self.assertEqual( q, 20, "item {} at {} SBE qty 20, is {}".format(1, location, q))
def setUp(self): Inventory.clear()