FAILURE MAP
← Case archive

FA-95056 / Warehouse slotting and bin packing / Open access

FEFO putaway: largest empty bin chosen · case 01

Small receipts consume the largest empty bins far from the pick face.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Empty bins are ranked by capacity descending instead of distance.

VERIFIED REPAIR

Choose the nearest empty bin that can hold the receipt, then bin id.

Unsuccessful approach: Ranking empty bins by id alone ignores travel distance.

Case contract

bins [[bin, sku|None, lot, expiry_day, qty, capacity, distance]]; receipt [sku, lot, expiry_day, qty]. A bin is empty when sku is None or qty is 0. Preference: (1) non-empty bin with same sku and lot and room (capacity-qty >= receipt qty), nearest then id; (2) same sku, different lot, expiry within 30 days either way, room; smallest expiry gap, nearest, id; (3) empty bin with capacity >= qty, nearest then id; else "HOLD".

Why this case matters

FEFO storage keeps lots with similar expiry together so the earliest expiring stock ships first.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(bins, receipt):
    sku, lot, exp, q = receipt
    def room(b):
        return b[5] - b[4] >= q
    def empty(b):
        return b[1] is None or b[4] == 0
    same_lot = [b for b in bins if not empty(b) and b[1] == sku and b[2] == lot and room(b)]
    if same_lot:
        return min(same_lot, key=lambda b: (b[6], b[0]))[0]
    mixed = [b for b in bins if not empty(b) and b[1] == sku and b[2] != lot and abs(b[3] - exp) <= 30 and room(b)]
    if mixed:
        return min(mixed, key=lambda b: (abs(b[3] - exp), b[6], b[0]))[0]
    free = [b for b in bins if empty(b) and b[5] >= q]
    if free:
        return max(free, key=lambda b: (b[5], b[0]))[0]
    return 'HOLD'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['generated mix 5', [[['B0', 'S1', 'L2', 131, 5, 20, 3], ['B1', None, None, 0, 0, 20, 3], ['B2', None, None, 0, 0, 40, 3], ['B3', None, None, 0, 0, 40, 1], ['B4', None, None, 0, 0, 40, 8], ['B5', None, None, 0, 0, 20, 3], ['B6', 'S2', 'L3', 100, 10, 40, 7]], ['S2', 'L2', 150, 20]], 'B3'], ['generated mix 3', [[['B0', None, None, 0, 0, 60, 5], ['B1', None, None, 0, 0, 60, 4], ['B2', 'S1', 'L1', 145, 30, 60, 2], ['B3', 'S2', 'L2', 100, 40, 20, 1], ['B4', 'S2', 'L1', 131, 30, 40, 2], ['B5', 'S1', 'L1', 145, 30, 40, 7]], ['S1', 'L4', 100, 10]], 'B1'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 8', [[['B0', 'S1', 'L2', 100, 30, 20, 6], ['B1', 'S2', 'L3', 131, 40, 40, 8], ['B2', None, None, 0, 0, 20, 4], ['B3', 'S2', 'L1', 145, 30, 40, 1], ['B4', 'S2', 'L3', 130, 0, 60, 6]], ['S1', 'L1', 130, 20]], 'B2']], [['generated mix 31', [[['B0', 'S1', 'L1', 145, 5, 60, 4], ['B1', None, None, 0, 0, 60, 7], ['B2', 'S1', 'L3', 160, 10, 60, 7], ['B3', 'S1', 'L1', 160, 0, 60, 8]], ['S1', 'L2', 100, 10]], 'B1'], ['generated mix 24', [[['B0', 'S1', 'L1', 131, 10, 40, 3], ['B1', 'S1', 'L2', 131, 5, 40, 9], ['B2', None, None, 0, 0, 60, 9], ['B3', 'S2', 'L1', 120, 40, 40, 8], ['B4', None, None, 0, 0, 60, 8], ['B5', 'S1', 'L3', 160, 0, 40, 6]], ['S2', 'L1', 130, 30]], 'B5'], ['expiry 31 days', [[['A', 'S1', 'L2', 131, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'E'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 9', [[['B0', 'S1', 'L2', 160, 40, 40, 1], ['B1', 'S1', 'L1', 131, 40, 40, 1], ['B2', 'S1', 'L3', 145, 0, 60, 1], ['B3', 'S1', 'L2', 100, 10, 20, 7], ['B4', 'S2', 'L1', 100, 30, 60, 5]], ['S1', 'L4', 150, 20]], 'B2'], ['generated mix 17', [[['B0', 'S2', 'L2', 120, 0, 20, 8], ['B1', 'S1', 'L2', 131, 5, 40, 3], ['B2', 'S2', 'L2', 100, 10, 20, 8], ['B3', 'S2', 'L2', 100, 10, 60, 6], ['B4', 'S1', 'L3', 100, 30, 40, 3], ['B5', 'S1', 'L1', 145, 10, 60, 5]], ['S1', 'L4', 150, 20]], 'B5']], [['generated mix 8', [[['B0', 'S1', 'L2', 100, 30, 20, 6], ['B1', 'S2', 'L3', 131, 40, 40, 8], ['B2', None, None, 0, 0, 20, 4], ['B3', 'S2', 'L1', 145, 30, 40, 1], ['B4', 'S2', 'L3', 130, 0, 60, 6]], ['S1', 'L1', 130, 20]], 'B2'], ['generated mix 71', [[['B0', 'S1', 'L3', 160, 5, 40, 5], ['B1', 'S1', 'L1', 130, 0, 60, 7], ['B2', 'S1', 'L3', 120, 0, 60, 2]], ['S2', 'L1', 100, 10]], 'B2'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 31 days', [[['A', 'S1', 'L2', 131, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'E'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['generated mix 21', [[['B0', 'S1', 'L3', 130, 40, 60, 3], ['B1', 'S1', 'L3', 145, 30, 40, 7], ['B2', 'S1', 'L2', 130, 40, 60, 4], ['B3', 'S2', 'L2', 120, 30, 40, 3], ['B4', 'S1', 'L3', 160, 30, 60, 2], ['B5', 'S1', 'L3', 160, 40, 40, 7]], ['S2', 'L4', 150, 10]], 'B3'], ['generated mix 29', [[['B0', None, None, 0, 0, 60, 5], ['B1', 'S1', 'L3', 100, 0, 60, 7], ['B2', 'S2', 'L2', 160, 40, 60, 7], ['B3', 'S1', 'L3', 131, 30, 60, 3], ['B4', 'S1', 'L1', 145, 10, 20, 4], ['B5', 'S2', 'L1', 130, 5, 40, 3], ['B6', 'S2', 'L2', 160, 40, 60, 5]], ['S1', 'L2', 100, 30]], 'B0']], [['generated mix 34', [[['B0', 'S1', 'L3', 100, 40, 20, 7], ['B1', None, None, 0, 0, 60, 1], ['B2', 'S2', 'L3', 100, 0, 20, 9], ['B3', 'S2', 'L2', 160, 0, 40, 6], ['B4', None, None, 0, 0, 20, 5], ['B5', 'S1', 'L1', 130, 0, 60, 9]], ['S1', 'L2', 130, 10]], 'B1'], ['generated mix 20', [[['B0', None, None, 0, 0, 20, 8], ['B1', 'S1', 'L1', 131, 30, 40, 6], ['B2', None, None, 0, 0, 20, 6], ['B3', 'S1', 'L3', 160, 0, 40, 6], ['B4', 'S1', 'L3', 160, 10, 20, 4], ['B5', 'S1', 'L3', 131, 30, 40, 9], ['B6', None, None, 0, 0, 60, 5]], ['S1', 'L4', 130, 20]], 'B6'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 32', [[['B0', 'S1', 'L3', 145, 40, 40, 7], ['B1', None, None, 0, 0, 40, 3]], ['S2', 'L4', 150, 20]], 'B1'], ['generated mix 41', [[['B0', 'S1', 'L2', 160, 30, 60, 7], ['B1', 'S1', 'L2', 160, 40, 60, 3], ['B2', 'S1', 'L3', 100, 0, 60, 7], ['B3', 'S2', 'L2', 130, 10, 60, 9], ['B4', 'S1', 'L2', 160, 0, 20, 4], ['B5', 'S2', 'L2', 100, 30, 40, 4]], ['S2', 'L1', 130, 30]], 'B3']], [['generated mix 14', [[['B0', 'S2', 'L1', 130, 5, 60, 3], ['B1', 'S2', 'L1', 120, 0, 60, 8], ['B2', None, None, 0, 0, 60, 9]], ['S1', 'L1', 100, 30]], 'B1'], ['generated mix 67', [[['B0', 'S1', 'L2', 145, 0, 20, 8], ['B1', None, None, 0, 0, 40, 5], ['B2', 'S1', 'L2', 160, 40, 20, 4], ['B3', 'S2', 'L3', 160, 5, 60, 4], ['B4', None, None, 0, 0, 20, 8], ['B5', 'S1', 'L3', 145, 30, 40, 1], ['B6', 'S2', 'L2', 131, 5, 20, 6]], ['S1', 'L4', 130, 20]], 'B1'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['generated mix 43', [[['B0', 'S1', 'L1', 120, 30, 40, 4], ['B1', None, None, 0, 0, 20, 9]], ['S1', 'L4', 130, 10]], 'B0'], ['generated mix 51', [[['B0', 'S2', 'L3', 130, 40, 60, 6], ['B1', 'S1', 'L1', 130, 40, 40, 1], ['B2', 'S1', 'L1', 100, 30, 40, 8], ['B3', 'S1', 'L1', 145, 0, 20, 2]], ['S2', 'L1', 100, 30]], 'HOLD']]]
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 fixtureActualExpectedOutcome
generated mix 5B4B3Failed
generated mix 3B1B1Passed
same lot preferredAAPassed
expiry 30 days laterAAPassed
residual label emptyRRPassed
nothingHOLDHOLDPassed
room exactAAPassed
generated mix 8B4B2Failed

SHA-256 / 516357c9f72156763dccd9d299650a8fc6926994e868b387b4fd44a312e43c92

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(bins, receipt):
    sku, lot, exp, q = receipt
    def room(b):
        return b[5] - b[4] >= q
    def empty(b):
        return b[1] is None or b[4] == 0
    same_lot = [b for b in bins if not empty(b) and b[1] == sku and b[2] == lot and room(b)]
    if same_lot:
        return min(same_lot, key=lambda b: (b[6], b[0]))[0]
    mixed = [b for b in bins if not empty(b) and b[1] == sku and b[2] != lot and abs(b[3] - exp) <= 30 and room(b)]
    if mixed:
        return min(mixed, key=lambda b: (abs(b[3] - exp), b[6], b[0]))[0]
    free = [b for b in bins if empty(b) and b[5] >= q]
    if free:
        return min(free, key=lambda b: b[0])[0]
    return 'HOLD'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['generated mix 5', [[['B0', 'S1', 'L2', 131, 5, 20, 3], ['B1', None, None, 0, 0, 20, 3], ['B2', None, None, 0, 0, 40, 3], ['B3', None, None, 0, 0, 40, 1], ['B4', None, None, 0, 0, 40, 8], ['B5', None, None, 0, 0, 20, 3], ['B6', 'S2', 'L3', 100, 10, 40, 7]], ['S2', 'L2', 150, 20]], 'B3'], ['generated mix 3', [[['B0', None, None, 0, 0, 60, 5], ['B1', None, None, 0, 0, 60, 4], ['B2', 'S1', 'L1', 145, 30, 60, 2], ['B3', 'S2', 'L2', 100, 40, 20, 1], ['B4', 'S2', 'L1', 131, 30, 40, 2], ['B5', 'S1', 'L1', 145, 30, 40, 7]], ['S1', 'L4', 100, 10]], 'B1'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 8', [[['B0', 'S1', 'L2', 100, 30, 20, 6], ['B1', 'S2', 'L3', 131, 40, 40, 8], ['B2', None, None, 0, 0, 20, 4], ['B3', 'S2', 'L1', 145, 30, 40, 1], ['B4', 'S2', 'L3', 130, 0, 60, 6]], ['S1', 'L1', 130, 20]], 'B2']], [['generated mix 31', [[['B0', 'S1', 'L1', 145, 5, 60, 4], ['B1', None, None, 0, 0, 60, 7], ['B2', 'S1', 'L3', 160, 10, 60, 7], ['B3', 'S1', 'L1', 160, 0, 60, 8]], ['S1', 'L2', 100, 10]], 'B1'], ['generated mix 24', [[['B0', 'S1', 'L1', 131, 10, 40, 3], ['B1', 'S1', 'L2', 131, 5, 40, 9], ['B2', None, None, 0, 0, 60, 9], ['B3', 'S2', 'L1', 120, 40, 40, 8], ['B4', None, None, 0, 0, 60, 8], ['B5', 'S1', 'L3', 160, 0, 40, 6]], ['S2', 'L1', 130, 30]], 'B5'], ['expiry 31 days', [[['A', 'S1', 'L2', 131, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'E'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 9', [[['B0', 'S1', 'L2', 160, 40, 40, 1], ['B1', 'S1', 'L1', 131, 40, 40, 1], ['B2', 'S1', 'L3', 145, 0, 60, 1], ['B3', 'S1', 'L2', 100, 10, 20, 7], ['B4', 'S2', 'L1', 100, 30, 60, 5]], ['S1', 'L4', 150, 20]], 'B2'], ['generated mix 17', [[['B0', 'S2', 'L2', 120, 0, 20, 8], ['B1', 'S1', 'L2', 131, 5, 40, 3], ['B2', 'S2', 'L2', 100, 10, 20, 8], ['B3', 'S2', 'L2', 100, 10, 60, 6], ['B4', 'S1', 'L3', 100, 30, 40, 3], ['B5', 'S1', 'L1', 145, 10, 60, 5]], ['S1', 'L4', 150, 20]], 'B5']], [['generated mix 8', [[['B0', 'S1', 'L2', 100, 30, 20, 6], ['B1', 'S2', 'L3', 131, 40, 40, 8], ['B2', None, None, 0, 0, 20, 4], ['B3', 'S2', 'L1', 145, 30, 40, 1], ['B4', 'S2', 'L3', 130, 0, 60, 6]], ['S1', 'L1', 130, 20]], 'B2'], ['generated mix 71', [[['B0', 'S1', 'L3', 160, 5, 40, 5], ['B1', 'S1', 'L1', 130, 0, 60, 7], ['B2', 'S1', 'L3', 120, 0, 60, 2]], ['S2', 'L1', 100, 10]], 'B2'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 31 days', [[['A', 'S1', 'L2', 131, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'E'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['generated mix 21', [[['B0', 'S1', 'L3', 130, 40, 60, 3], ['B1', 'S1', 'L3', 145, 30, 40, 7], ['B2', 'S1', 'L2', 130, 40, 60, 4], ['B3', 'S2', 'L2', 120, 30, 40, 3], ['B4', 'S1', 'L3', 160, 30, 60, 2], ['B5', 'S1', 'L3', 160, 40, 40, 7]], ['S2', 'L4', 150, 10]], 'B3'], ['generated mix 29', [[['B0', None, None, 0, 0, 60, 5], ['B1', 'S1', 'L3', 100, 0, 60, 7], ['B2', 'S2', 'L2', 160, 40, 60, 7], ['B3', 'S1', 'L3', 131, 30, 60, 3], ['B4', 'S1', 'L1', 145, 10, 20, 4], ['B5', 'S2', 'L1', 130, 5, 40, 3], ['B6', 'S2', 'L2', 160, 40, 60, 5]], ['S1', 'L2', 100, 30]], 'B0']], [['generated mix 34', [[['B0', 'S1', 'L3', 100, 40, 20, 7], ['B1', None, None, 0, 0, 60, 1], ['B2', 'S2', 'L3', 100, 0, 20, 9], ['B3', 'S2', 'L2', 160, 0, 40, 6], ['B4', None, None, 0, 0, 20, 5], ['B5', 'S1', 'L1', 130, 0, 60, 9]], ['S1', 'L2', 130, 10]], 'B1'], ['generated mix 20', [[['B0', None, None, 0, 0, 20, 8], ['B1', 'S1', 'L1', 131, 30, 40, 6], ['B2', None, None, 0, 0, 20, 6], ['B3', 'S1', 'L3', 160, 0, 40, 6], ['B4', 'S1', 'L3', 160, 10, 20, 4], ['B5', 'S1', 'L3', 131, 30, 40, 9], ['B6', None, None, 0, 0, 60, 5]], ['S1', 'L4', 130, 20]], 'B6'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 32', [[['B0', 'S1', 'L3', 145, 40, 40, 7], ['B1', None, None, 0, 0, 40, 3]], ['S2', 'L4', 150, 20]], 'B1'], ['generated mix 41', [[['B0', 'S1', 'L2', 160, 30, 60, 7], ['B1', 'S1', 'L2', 160, 40, 60, 3], ['B2', 'S1', 'L3', 100, 0, 60, 7], ['B3', 'S2', 'L2', 130, 10, 60, 9], ['B4', 'S1', 'L2', 160, 0, 20, 4], ['B5', 'S2', 'L2', 100, 30, 40, 4]], ['S2', 'L1', 130, 30]], 'B3']], [['generated mix 14', [[['B0', 'S2', 'L1', 130, 5, 60, 3], ['B1', 'S2', 'L1', 120, 0, 60, 8], ['B2', None, None, 0, 0, 60, 9]], ['S1', 'L1', 100, 30]], 'B1'], ['generated mix 67', [[['B0', 'S1', 'L2', 145, 0, 20, 8], ['B1', None, None, 0, 0, 40, 5], ['B2', 'S1', 'L2', 160, 40, 20, 4], ['B3', 'S2', 'L3', 160, 5, 60, 4], ['B4', None, None, 0, 0, 20, 8], ['B5', 'S1', 'L3', 145, 30, 40, 1], ['B6', 'S2', 'L2', 131, 5, 20, 6]], ['S1', 'L4', 130, 20]], 'B1'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['generated mix 43', [[['B0', 'S1', 'L1', 120, 30, 40, 4], ['B1', None, None, 0, 0, 20, 9]], ['S1', 'L4', 130, 10]], 'B0'], ['generated mix 51', [[['B0', 'S2', 'L3', 130, 40, 60, 6], ['B1', 'S1', 'L1', 130, 40, 40, 1], ['B2', 'S1', 'L1', 100, 30, 40, 8], ['B3', 'S1', 'L1', 145, 0, 20, 2]], ['S2', 'L1', 100, 30]], 'HOLD']]]
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 fixtureActualExpectedOutcome
generated mix 5B1B3Failed
generated mix 3B0B1Failed
same lot preferredAAPassed
expiry 30 days laterAAPassed
residual label emptyERFailed
nothingHOLDHOLDPassed
room exactAAPassed
generated mix 8B2B2Passed

SHA-256 / cd86faac03c5ec58f74e15b7a8ba05fd6654e56fafa8eefbc53c17731d8a97c7

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(bins, receipt):
    sku, lot, exp, q = receipt
    def room(b):
        return b[5] - b[4] >= q
    def empty(b):
        return b[1] is None or b[4] == 0
    same_lot = [b for b in bins if not empty(b) and b[1] == sku and b[2] == lot and room(b)]
    if same_lot:
        return min(same_lot, key=lambda b: (b[6], b[0]))[0]
    mixed = [b for b in bins if not empty(b) and b[1] == sku and b[2] != lot and abs(b[3] - exp) <= 30 and room(b)]
    if mixed:
        return min(mixed, key=lambda b: (abs(b[3] - exp), b[6], b[0]))[0]
    free = [b for b in bins if empty(b) and b[5] >= q]
    if free:
        return min(free, key=lambda b: (b[6], b[0]))[0]
    return 'HOLD'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['generated mix 5', [[['B0', 'S1', 'L2', 131, 5, 20, 3], ['B1', None, None, 0, 0, 20, 3], ['B2', None, None, 0, 0, 40, 3], ['B3', None, None, 0, 0, 40, 1], ['B4', None, None, 0, 0, 40, 8], ['B5', None, None, 0, 0, 20, 3], ['B6', 'S2', 'L3', 100, 10, 40, 7]], ['S2', 'L2', 150, 20]], 'B3'], ['generated mix 3', [[['B0', None, None, 0, 0, 60, 5], ['B1', None, None, 0, 0, 60, 4], ['B2', 'S1', 'L1', 145, 30, 60, 2], ['B3', 'S2', 'L2', 100, 40, 20, 1], ['B4', 'S2', 'L1', 131, 30, 40, 2], ['B5', 'S1', 'L1', 145, 30, 40, 7]], ['S1', 'L4', 100, 10]], 'B1'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 8', [[['B0', 'S1', 'L2', 100, 30, 20, 6], ['B1', 'S2', 'L3', 131, 40, 40, 8], ['B2', None, None, 0, 0, 20, 4], ['B3', 'S2', 'L1', 145, 30, 40, 1], ['B4', 'S2', 'L3', 130, 0, 60, 6]], ['S1', 'L1', 130, 20]], 'B2']], [['generated mix 31', [[['B0', 'S1', 'L1', 145, 5, 60, 4], ['B1', None, None, 0, 0, 60, 7], ['B2', 'S1', 'L3', 160, 10, 60, 7], ['B3', 'S1', 'L1', 160, 0, 60, 8]], ['S1', 'L2', 100, 10]], 'B1'], ['generated mix 24', [[['B0', 'S1', 'L1', 131, 10, 40, 3], ['B1', 'S1', 'L2', 131, 5, 40, 9], ['B2', None, None, 0, 0, 60, 9], ['B3', 'S2', 'L1', 120, 40, 40, 8], ['B4', None, None, 0, 0, 60, 8], ['B5', 'S1', 'L3', 160, 0, 40, 6]], ['S2', 'L1', 130, 30]], 'B5'], ['expiry 31 days', [[['A', 'S1', 'L2', 131, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'E'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 9', [[['B0', 'S1', 'L2', 160, 40, 40, 1], ['B1', 'S1', 'L1', 131, 40, 40, 1], ['B2', 'S1', 'L3', 145, 0, 60, 1], ['B3', 'S1', 'L2', 100, 10, 20, 7], ['B4', 'S2', 'L1', 100, 30, 60, 5]], ['S1', 'L4', 150, 20]], 'B2'], ['generated mix 17', [[['B0', 'S2', 'L2', 120, 0, 20, 8], ['B1', 'S1', 'L2', 131, 5, 40, 3], ['B2', 'S2', 'L2', 100, 10, 20, 8], ['B3', 'S2', 'L2', 100, 10, 60, 6], ['B4', 'S1', 'L3', 100, 30, 40, 3], ['B5', 'S1', 'L1', 145, 10, 60, 5]], ['S1', 'L4', 150, 20]], 'B5']], [['generated mix 8', [[['B0', 'S1', 'L2', 100, 30, 20, 6], ['B1', 'S2', 'L3', 131, 40, 40, 8], ['B2', None, None, 0, 0, 20, 4], ['B3', 'S2', 'L1', 145, 30, 40, 1], ['B4', 'S2', 'L3', 130, 0, 60, 6]], ['S1', 'L1', 130, 20]], 'B2'], ['generated mix 71', [[['B0', 'S1', 'L3', 160, 5, 40, 5], ['B1', 'S1', 'L1', 130, 0, 60, 7], ['B2', 'S1', 'L3', 120, 0, 60, 2]], ['S2', 'L1', 100, 10]], 'B2'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 31 days', [[['A', 'S1', 'L2', 131, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'E'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['generated mix 21', [[['B0', 'S1', 'L3', 130, 40, 60, 3], ['B1', 'S1', 'L3', 145, 30, 40, 7], ['B2', 'S1', 'L2', 130, 40, 60, 4], ['B3', 'S2', 'L2', 120, 30, 40, 3], ['B4', 'S1', 'L3', 160, 30, 60, 2], ['B5', 'S1', 'L3', 160, 40, 40, 7]], ['S2', 'L4', 150, 10]], 'B3'], ['generated mix 29', [[['B0', None, None, 0, 0, 60, 5], ['B1', 'S1', 'L3', 100, 0, 60, 7], ['B2', 'S2', 'L2', 160, 40, 60, 7], ['B3', 'S1', 'L3', 131, 30, 60, 3], ['B4', 'S1', 'L1', 145, 10, 20, 4], ['B5', 'S2', 'L1', 130, 5, 40, 3], ['B6', 'S2', 'L2', 160, 40, 60, 5]], ['S1', 'L2', 100, 30]], 'B0']], [['generated mix 34', [[['B0', 'S1', 'L3', 100, 40, 20, 7], ['B1', None, None, 0, 0, 60, 1], ['B2', 'S2', 'L3', 100, 0, 20, 9], ['B3', 'S2', 'L2', 160, 0, 40, 6], ['B4', None, None, 0, 0, 20, 5], ['B5', 'S1', 'L1', 130, 0, 60, 9]], ['S1', 'L2', 130, 10]], 'B1'], ['generated mix 20', [[['B0', None, None, 0, 0, 20, 8], ['B1', 'S1', 'L1', 131, 30, 40, 6], ['B2', None, None, 0, 0, 20, 6], ['B3', 'S1', 'L3', 160, 0, 40, 6], ['B4', 'S1', 'L3', 160, 10, 20, 4], ['B5', 'S1', 'L3', 131, 30, 40, 9], ['B6', None, None, 0, 0, 60, 5]], ['S1', 'L4', 130, 20]], 'B6'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['nothing', [[['X', 'S2', 'L1', 100, 5, 10, 1]], ['S1', 'L1', 100, 20]], 'HOLD'], ['room exact', [[['A', 'S1', 'L1', 100, 20, 40, 9], ['E', None, None, 0, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['generated mix 32', [[['B0', 'S1', 'L3', 145, 40, 40, 7], ['B1', None, None, 0, 0, 40, 3]], ['S2', 'L4', 150, 20]], 'B1'], ['generated mix 41', [[['B0', 'S1', 'L2', 160, 30, 60, 7], ['B1', 'S1', 'L2', 160, 40, 60, 3], ['B2', 'S1', 'L3', 100, 0, 60, 7], ['B3', 'S2', 'L2', 130, 10, 60, 9], ['B4', 'S1', 'L2', 160, 0, 20, 4], ['B5', 'S2', 'L2', 100, 30, 40, 4]], ['S2', 'L1', 130, 30]], 'B3']], [['generated mix 14', [[['B0', 'S2', 'L1', 130, 5, 60, 3], ['B1', 'S2', 'L1', 120, 0, 60, 8], ['B2', None, None, 0, 0, 60, 9]], ['S1', 'L1', 100, 30]], 'B1'], ['generated mix 67', [[['B0', 'S1', 'L2', 145, 0, 20, 8], ['B1', None, None, 0, 0, 40, 5], ['B2', 'S1', 'L2', 160, 40, 20, 4], ['B3', 'S2', 'L3', 160, 5, 60, 4], ['B4', None, None, 0, 0, 20, 8], ['B5', 'S1', 'L3', 145, 30, 40, 1], ['B6', 'S2', 'L2', 131, 5, 20, 6]], ['S1', 'L4', 130, 20]], 'B1'], ['lot code on other sku', [[['X', 'S2', 'L1', 100, 5, 60, 1], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'E'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 0, 40, 1]], ['S1', 'L1', 100, 20]], 'A'], ['expiry 30 days later', [[['A', 'S1', 'L2', 130, 10, 60, 5], ['E', None, None, 0, 0, 60, 1]], ['S1', 'L1', 100, 20]], 'A'], ['residual label empty', [[['R', 'S9', 'L7', 50, 0, 60, 2], ['E', None, None, 0, 0, 60, 4]], ['S1', 'L1', 100, 20]], 'R'], ['generated mix 43', [[['B0', 'S1', 'L1', 120, 30, 40, 4], ['B1', None, None, 0, 0, 20, 9]], ['S1', 'L4', 130, 10]], 'B0'], ['generated mix 51', [[['B0', 'S2', 'L3', 130, 40, 60, 6], ['B1', 'S1', 'L1', 130, 40, 40, 1], ['B2', 'S1', 'L1', 100, 30, 40, 8], ['B3', 'S1', 'L1', 145, 0, 20, 2]], ['S2', 'L1', 100, 30]], 'HOLD']]]
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 fixtureActualExpectedOutcome
generated mix 5B3B3Passed
generated mix 3B1B1Passed
same lot preferredAAPassed
expiry 30 days laterAAPassed
residual label emptyRRPassed
nothingHOLDHOLDPassed
room exactAAPassed
generated mix 8B2B2Passed

SHA-256 / 2993bb1690086c45de12be7dd0bc873767fa8c0b0981a8676a7a67043f506029

Verification & scope

Stipulated day-number expiries and a single receipt; no quarantine or multi-bin splits. 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:10.142399+00:00.

Case digest / 8dfc5128cd8121761271257631d6366dbbe459815f4a6daaa1851716c9ece225