FA-94986 / Warehouse slotting and bin packing / Open access
Pallet build: rotated layer pattern never tried · case 01
Pallets are built with fewer cases per layer than a 90-degree rotated pattern allows.
ROOT CAUSE
Cases per layer only evaluates the case footprint in its listed orientation.
VERIFIED REPAIR
Take the better of the two upright orientations of the case footprint.
Unsuccessful approach: Area division ignores that cases are rigid rectangles and promises layers that cannot be built.
Case contract
case [l, w, h, kg], pallet [L, W]; cases stay upright but may rotate 90 degrees; all layers share one orientation. Pallet base adds 15 height and 25 kg tare. Cases per pallet are bounded by whole layers within max_h and by max_kg (a partial top layer is allowed). Return [per_layer, layers used, cases, height, weight].
Why this case matters
Pallet build limits govern reserve storage heights and floor loading.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(case, pallet, max_h, max_kg):
l, w, h, kg = case
L, W = pallet
per_layer = (L // l) * (W // w)
if per_layer == 0:
return [0, 0, 0, 15, 25]
layers = (max_h - 15) // h
by_weight = (max_kg - 25) // kg
cases = min(per_layer * layers, by_weight)
if cases <= 0:
return [per_layer, 0, 0, 15, 25]
full = -(-cases // per_layer)
return [per_layer, full, cases, 15 + full * h, 25 + cases * kg]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['generated mix 8', [[40, 15, 30, 8], [120, 80], 100, 800], [16, 2, 32, 75, 281]]], [['generated mix 12', [[40, 15, 30, 12], [120, 80], 140, 1200], [16, 4, 64, 135, 793]], ['generated mix 10', [[30, 15, 20, 5], [120, 80], 190, 1025], [20, 8, 160, 175, 825]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['generated mix 9', [[50, 20, 25, 12], [120, 100], 115, 1200], [12, 4, 48, 115, 601]], ['generated mix 19', [[30, 20, 20, 5], [120, 100], 190, 800], [20, 8, 155, 175, 800]]], [['generated mix 33', [[40, 30, 10, 20], [120, 80], 165, 800], [8, 5, 38, 65, 785]], ['generated mix 16', [[25, 20, 30, 5], [120, 80], 140, 800], [18, 4, 72, 135, 385]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['generated mix 21', [[25, 20, 30, 2], [120, 80], 140, 300], [18, 4, 72, 135, 169]], ['generated mix 29', [[50, 30, 30, 20], [120, 100], 190, 300], [8, 2, 13, 75, 285]]], [['generated mix 43', [[25, 20, 20, 12], [120, 100], 140, 1200], [24, 5, 97, 115, 1189]], ['generated mix 25', [[25, 25, 25, 20], [120, 80], 190, 1200], [12, 5, 58, 140, 1185]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['generated mix 32', [[40, 30, 10, 8], [120, 100], 165, 300], [9, 4, 34, 55, 297]], ['generated mix 40', [[20, 15, 30, 20], [120, 80], 190, 500], [32, 1, 23, 45, 485]]], [['generated mix 55', [[50, 30, 30, 5], [120, 100], 190, 1200], [8, 5, 40, 165, 225]], ['generated mix 41', [[25, 25, 10, 20], [120, 100], 165, 1200], [16, 4, 58, 55, 1185]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['generated mix 43', [[25, 20, 20, 12], [120, 100], 140, 1200], [24, 5, 97, 115, 1189]], ['generated mix 51', [[25, 15, 10, 8], [120, 80], 165, 500], [24, 3, 59, 45, 497]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| rotation wins | [6, 5, 30, 115, 175] | [8, 5, 40, 115, 225] | Failed |
| weight bound partial layer | [9, 3, 20, 90, 425] | [9, 3, 20, 90, 425] | Passed |
| listed orientation wins | [20, 5, 100, 115, 525] | [20, 5, 100, 115, 525] | Passed |
| too big | [0, 0, 0, 15, 25] | [0, 0, 0, 15, 25] | Passed |
| square | [12, 5, 60, 165, 325] | [12, 5, 60, 165, 325] | Passed |
| height exact | [9, 5, 45, 140, 115] | [9, 5, 45, 140, 115] | Passed |
| too heavy for one | [9, 0, 0, 15, 25] | [9, 0, 0, 15, 25] | Passed |
| generated mix 8 | [15, 2, 30, 75, 265] | [16, 2, 32, 75, 281] | Failed |
SHA-256 / 78f5767aeaaa59a9ae3fb5c625721317b8c84758a3e14c4a700d294fbf037836
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(case, pallet, max_h, max_kg):
l, w, h, kg = case
L, W = pallet
per_layer = (L * W) // (l * w)
if per_layer == 0:
return [0, 0, 0, 15, 25]
layers = (max_h - 15) // h
by_weight = (max_kg - 25) // kg
cases = min(per_layer * layers, by_weight)
if cases <= 0:
return [per_layer, 0, 0, 15, 25]
full = -(-cases // per_layer)
return [per_layer, full, cases, 15 + full * h, 25 + cases * kg]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['generated mix 8', [[40, 15, 30, 8], [120, 80], 100, 800], [16, 2, 32, 75, 281]]], [['generated mix 12', [[40, 15, 30, 12], [120, 80], 140, 1200], [16, 4, 64, 135, 793]], ['generated mix 10', [[30, 15, 20, 5], [120, 80], 190, 1025], [20, 8, 160, 175, 825]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['generated mix 9', [[50, 20, 25, 12], [120, 100], 115, 1200], [12, 4, 48, 115, 601]], ['generated mix 19', [[30, 20, 20, 5], [120, 100], 190, 800], [20, 8, 155, 175, 800]]], [['generated mix 33', [[40, 30, 10, 20], [120, 80], 165, 800], [8, 5, 38, 65, 785]], ['generated mix 16', [[25, 20, 30, 5], [120, 80], 140, 800], [18, 4, 72, 135, 385]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['generated mix 21', [[25, 20, 30, 2], [120, 80], 140, 300], [18, 4, 72, 135, 169]], ['generated mix 29', [[50, 30, 30, 20], [120, 100], 190, 300], [8, 2, 13, 75, 285]]], [['generated mix 43', [[25, 20, 20, 12], [120, 100], 140, 1200], [24, 5, 97, 115, 1189]], ['generated mix 25', [[25, 25, 25, 20], [120, 80], 190, 1200], [12, 5, 58, 140, 1185]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['generated mix 32', [[40, 30, 10, 8], [120, 100], 165, 300], [9, 4, 34, 55, 297]], ['generated mix 40', [[20, 15, 30, 20], [120, 80], 190, 500], [32, 1, 23, 45, 485]]], [['generated mix 55', [[50, 30, 30, 5], [120, 100], 190, 1200], [8, 5, 40, 165, 225]], ['generated mix 41', [[25, 25, 10, 20], [120, 100], 165, 1200], [16, 4, 58, 55, 1185]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['generated mix 43', [[25, 20, 20, 12], [120, 100], 140, 1200], [24, 5, 97, 115, 1189]], ['generated mix 51', [[25, 15, 10, 8], [120, 80], 165, 500], [24, 3, 59, 45, 497]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| rotation wins | [8, 5, 40, 115, 225] | [8, 5, 40, 115, 225] | Passed |
| weight bound partial layer | [10, 2, 20, 65, 425] | [9, 3, 20, 90, 425] | Failed |
| listed orientation wins | [20, 5, 100, 115, 525] | [20, 5, 100, 115, 525] | Passed |
| too big | [3, 7, 21, 190, 67] | [0, 0, 0, 15, 25] | Failed |
| square | [15, 5, 75, 165, 400] | [12, 5, 60, 165, 325] | Failed |
| height exact | [10, 5, 50, 140, 125] | [9, 5, 45, 140, 115] | Failed |
| too heavy for one | [10, 0, 0, 15, 25] | [9, 0, 0, 15, 25] | Failed |
| generated mix 8 | [16, 2, 32, 75, 281] | [16, 2, 32, 75, 281] | Passed |
SHA-256 / 07867fdc0a341126fcc7768d40816ec2c4d738afa055146f0cd4e2820588f155
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(case, pallet, max_h, max_kg):
l, w, h, kg = case
L, W = pallet
per_layer = max((L // l) * (W // w), (L // w) * (W // l))
if per_layer == 0:
return [0, 0, 0, 15, 25]
layers = (max_h - 15) // h
by_weight = (max_kg - 25) // kg
cases = min(per_layer * layers, by_weight)
if cases <= 0:
return [per_layer, 0, 0, 15, 25]
full = -(-cases // per_layer)
return [per_layer, full, cases, 15 + full * h, 25 + cases * kg]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['generated mix 8', [[40, 15, 30, 8], [120, 80], 100, 800], [16, 2, 32, 75, 281]]], [['generated mix 12', [[40, 15, 30, 12], [120, 80], 140, 1200], [16, 4, 64, 135, 793]], ['generated mix 10', [[30, 15, 20, 5], [120, 80], 190, 1025], [20, 8, 160, 175, 825]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['generated mix 9', [[50, 20, 25, 12], [120, 100], 115, 1200], [12, 4, 48, 115, 601]], ['generated mix 19', [[30, 20, 20, 5], [120, 100], 190, 800], [20, 8, 155, 175, 800]]], [['generated mix 33', [[40, 30, 10, 20], [120, 80], 165, 800], [8, 5, 38, 65, 785]], ['generated mix 16', [[25, 20, 30, 5], [120, 80], 140, 800], [18, 4, 72, 135, 385]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['weight bound partial layer', [[40, 30, 25, 20], [120, 100], 190, 425], [9, 3, 20, 90, 425]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['generated mix 21', [[25, 20, 30, 2], [120, 80], 140, 300], [18, 4, 72, 135, 169]], ['generated mix 29', [[50, 30, 30, 20], [120, 100], 190, 300], [8, 2, 13, 75, 285]]], [['generated mix 43', [[25, 20, 20, 12], [120, 100], 140, 1200], [24, 5, 97, 115, 1189]], ['generated mix 25', [[25, 25, 25, 20], [120, 80], 190, 1200], [12, 5, 58, 140, 1185]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['square', [[25, 25, 30, 5], [120, 80], 165, 900], [12, 5, 60, 165, 325]], ['rotation wins', [[50, 30, 20, 5], [120, 100], 115, 1000], [8, 5, 40, 115, 225]], ['generated mix 32', [[40, 30, 10, 8], [120, 100], 165, 300], [9, 4, 34, 55, 297]], ['generated mix 40', [[20, 15, 30, 20], [120, 80], 190, 500], [32, 1, 23, 45, 485]]], [['generated mix 55', [[50, 30, 30, 5], [120, 100], 190, 1200], [8, 5, 40, 165, 225]], ['generated mix 41', [[25, 25, 10, 20], [120, 100], 165, 1200], [16, 4, 58, 55, 1185]], ['too heavy for one', [[40, 30, 25, 500], [120, 100], 190, 425], [9, 0, 0, 15, 25]], ['listed orientation wins', [[30, 20, 20, 5], [120, 100], 115, 1000], [20, 5, 100, 115, 525]], ['height exact', [[40, 30, 25, 2], [120, 100], 140, 2000], [9, 5, 45, 140, 115]], ['too big', [[130, 30, 25, 2], [120, 100], 190, 425], [0, 0, 0, 15, 25]], ['generated mix 43', [[25, 20, 20, 12], [120, 100], 140, 1200], [24, 5, 97, 115, 1189]], ['generated mix 51', [[25, 15, 10, 8], [120, 80], 165, 500], [24, 3, 59, 45, 497]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| rotation wins | [8, 5, 40, 115, 225] | [8, 5, 40, 115, 225] | Passed |
| weight bound partial layer | [9, 3, 20, 90, 425] | [9, 3, 20, 90, 425] | Passed |
| listed orientation wins | [20, 5, 100, 115, 525] | [20, 5, 100, 115, 525] | Passed |
| too big | [0, 0, 0, 15, 25] | [0, 0, 0, 15, 25] | Passed |
| square | [12, 5, 60, 165, 325] | [12, 5, 60, 165, 325] | Passed |
| height exact | [9, 5, 45, 140, 115] | [9, 5, 45, 140, 115] | Passed |
| too heavy for one | [9, 0, 0, 15, 25] | [9, 0, 0, 15, 25] | Passed |
| generated mix 8 | [16, 2, 32, 75, 281] | [16, 2, 32, 75, 281] | Passed |
SHA-256 / 4329fa3b41ec833dd048b73fc8d45b5f6c050c4c6c356b6afe6813e5c7e90286
Verification & scope
Uniform-case, single-orientation layer model; no interlocking patterns. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:52:09.501660+00:00.
Case digest / b9f27a6dda2f220b8a232f52654252fb3d24acabb065ea96022a5d6ae93fb45b