| | from solverforge_legacy.solver.test import ConstraintVerifier |
| |
|
| | from order_picking.domain import ( |
| | Product, Order, OrderItem, Trolley, TrolleyStep, OrderPickingSolution |
| | ) |
| | from order_picking.warehouse import WarehouseLocation, Side, new_shelving_id, Column, Row |
| | from order_picking.constraints import ( |
| | define_constraints, |
| | required_number_of_buckets, |
| | minimize_order_split_by_trolley, |
| | minimize_distance_from_previous_step, |
| | minimize_distance_from_last_step_to_origin, |
| | ) |
| |
|
| |
|
| | |
| | LOCATION_A1_LEFT_5 = WarehouseLocation( |
| | shelving_id=new_shelving_id(Column.COL_A, Row.ROW_1), |
| | side=Side.LEFT, |
| | row=5 |
| | ) |
| | LOCATION_A1_LEFT_8 = WarehouseLocation( |
| | shelving_id=new_shelving_id(Column.COL_A, Row.ROW_1), |
| | side=Side.LEFT, |
| | row=8 |
| | ) |
| | LOCATION_B1_LEFT_3 = WarehouseLocation( |
| | shelving_id=new_shelving_id(Column.COL_B, Row.ROW_1), |
| | side=Side.LEFT, |
| | row=3 |
| | ) |
| | LOCATION_E3_RIGHT_9 = WarehouseLocation( |
| | shelving_id=new_shelving_id(Column.COL_E, Row.ROW_3), |
| | side=Side.RIGHT, |
| | row=9 |
| | ) |
| |
|
| | |
| | BUCKET_CAPACITY = 48000 |
| |
|
| |
|
| | constraint_verifier = ConstraintVerifier.build( |
| | define_constraints, OrderPickingSolution, Trolley, TrolleyStep |
| | ) |
| |
|
| |
|
| | def create_product(id: str, volume: int, location: WarehouseLocation) -> Product: |
| | return Product(id=id, name=f"Product {id}", volume=volume, location=location) |
| |
|
| |
|
| | def create_order_with_items(order_id: str, products: list[Product]) -> tuple[Order, list[OrderItem]]: |
| | order = Order(id=order_id, items=[]) |
| | items = [] |
| | for i, product in enumerate(products): |
| | item = OrderItem(id=f"{order_id}-{i}", order=order, product=product) |
| | order.items.append(item) |
| | items.append(item) |
| | return order, items |
| |
|
| |
|
| | def connect(trolley: Trolley, *steps: TrolleyStep): |
| | """Set up trolley-step relationships.""" |
| | trolley.steps = list(steps) |
| | for i, step in enumerate(steps): |
| | step.trolley = trolley |
| | step.previous_step = steps[i - 1] if i > 0 else None |
| | step.next_step = steps[i + 1] if i < len(steps) - 1 else None |
| |
|
| |
|
| | class TestRequiredNumberOfBuckets: |
| | def test_not_penalized_when_under_capacity(self): |
| | """Trolley with enough buckets should not be penalized.""" |
| | trolley = Trolley( |
| | id="1", |
| | bucket_count=4, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | product = create_product("p1", 10000, LOCATION_B1_LEFT_3) |
| | order, items = create_order_with_items("o1", [product]) |
| | step = TrolleyStep(id="s1", order_item=items[0]) |
| |
|
| | connect(trolley, step) |
| |
|
| | constraint_verifier.verify_that(required_number_of_buckets).given( |
| | trolley, step |
| | ).penalizes_by(0) |
| |
|
| | def test_penalized_when_over_bucket_count(self): |
| | """Trolley with too few buckets should be penalized.""" |
| | trolley = Trolley( |
| | id="1", |
| | bucket_count=1, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | |
| | product1 = create_product("p1", 40000, LOCATION_B1_LEFT_3) |
| | product2 = create_product("p2", 40000, LOCATION_A1_LEFT_8) |
| | order, items = create_order_with_items("o1", [product1, product2]) |
| | step1 = TrolleyStep(id="s1", order_item=items[0]) |
| | step2 = TrolleyStep(id="s2", order_item=items[1]) |
| |
|
| | connect(trolley, step1, step2) |
| |
|
| | |
| | |
| | |
| | constraint_verifier.verify_that(required_number_of_buckets).given( |
| | trolley, step1, step2 |
| | ).penalizes_by(1) |
| |
|
| |
|
| | class TestMinimizeOrderSplitByTrolley: |
| | def test_single_trolley_per_order(self): |
| | """Order on single trolley should be minimally penalized.""" |
| | trolley = Trolley( |
| | id="1", |
| | bucket_count=4, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | product = create_product("p1", 10000, LOCATION_B1_LEFT_3) |
| | order, items = create_order_with_items("o1", [product]) |
| | step = TrolleyStep(id="s1", order_item=items[0]) |
| |
|
| | connect(trolley, step) |
| |
|
| | |
| | constraint_verifier.verify_that(minimize_order_split_by_trolley).given( |
| | trolley, step |
| | ).penalizes_by(1000) |
| |
|
| | def test_order_split_across_trolleys(self): |
| | """Order split across trolleys should be penalized more.""" |
| | trolley1 = Trolley( |
| | id="1", |
| | bucket_count=4, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | trolley2 = Trolley( |
| | id="2", |
| | bucket_count=4, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | product1 = create_product("p1", 10000, LOCATION_B1_LEFT_3) |
| | product2 = create_product("p2", 10000, LOCATION_A1_LEFT_8) |
| | order, items = create_order_with_items("o1", [product1, product2]) |
| | step1 = TrolleyStep(id="s1", order_item=items[0]) |
| | step2 = TrolleyStep(id="s2", order_item=items[1]) |
| |
|
| | connect(trolley1, step1) |
| | connect(trolley2, step2) |
| |
|
| | |
| | constraint_verifier.verify_that(minimize_order_split_by_trolley).given( |
| | trolley1, trolley2, step1, step2 |
| | ).penalizes_by(2000) |
| |
|
| |
|
| | class TestMinimizeDistanceFromPreviousStep: |
| | def test_distance_from_trolley_start(self): |
| | """Distance from trolley start location to first step.""" |
| | trolley = Trolley( |
| | id="1", |
| | bucket_count=4, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | product = create_product("p1", 10000, LOCATION_A1_LEFT_8) |
| | order, items = create_order_with_items("o1", [product]) |
| | step = TrolleyStep(id="s1", order_item=items[0]) |
| |
|
| | connect(trolley, step) |
| |
|
| | |
| | constraint_verifier.verify_that(minimize_distance_from_previous_step).given( |
| | trolley, step |
| | ).penalizes_by(3) |
| |
|
| |
|
| | class TestMinimizeDistanceFromLastStepToOrigin: |
| | def test_distance_back_to_origin(self): |
| | """Distance from last step back to trolley start location.""" |
| | trolley = Trolley( |
| | id="1", |
| | bucket_count=4, |
| | bucket_capacity=BUCKET_CAPACITY, |
| | location=LOCATION_A1_LEFT_5 |
| | ) |
| | product = create_product("p1", 10000, LOCATION_A1_LEFT_8) |
| | order, items = create_order_with_items("o1", [product]) |
| | step = TrolleyStep(id="s1", order_item=items[0]) |
| |
|
| | connect(trolley, step) |
| |
|
| | |
| | constraint_verifier.verify_that(minimize_distance_from_last_step_to_origin).given( |
| | trolley, step |
| | ).penalizes_by(3) |
| |
|