FAILURE MAP
← Case archive

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

FEFO putaway: exact remaining room rejected · case 01

A receipt that would fill a bin exactly is sent to a fresh bin, fragmenting the lot.

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

ROOT CAUSE

The room test demands strictly more free space than the receipt quantity.

VERIFIED REPAIR

Accept a bin when capacity minus current quantity is at least the receipt quantity.

Unsuccessful approach: Comparing the receipt with total capacity ignores stock already in the bin.

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 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 = [[['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'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 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'], ['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 6', [[['B0', 'S1', 'L1', 160, 40, 20, 6], ['B1', 'S2', 'L2', 131, 40, 20, 7]], ['S1', 'L4', 100, 30]], 'HOLD'], ['generated mix 15', [[['B0', 'S1', 'L2', 145, 40, 20, 2], ['B1', 'S2', 'L1', 145, 40, 60, 3], ['B2', 'S1', 'L1', 100, 30, 40, 3], ['B3', 'S1', 'L2', 145, 5, 60, 3], ['B4', 'S1', 'L1', 131, 0, 20, 6], ['B5', None, None, 0, 0, 40, 9]], ['S2', 'L4', 150, 20]], 'B1']], [['generated mix 43', [[['B0', 'S1', 'L1', 120, 30, 40, 4], ['B1', None, None, 0, 0, 20, 9]], ['S1', 'L4', 130, 10]], 'B0'], ['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 33', [[['B0', 'S1', 'L1', 130, 10, 40, 6], ['B1', 'S1', 'L3', 160, 40, 40, 3], ['B2', None, None, 0, 0, 20, 1], ['B3', 'S2', 'L2', 160, 5, 60, 8], ['B4', None, None, 0, 0, 40, 4], ['B5', 'S2', 'L2', 131, 5, 60, 8]], ['S1', 'L1', 130, 30]], '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'], ['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 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'], ['generated mix 28', [[['B0', 'S1', 'L3', 131, 10, 60, 9], ['B1', 'S2', 'L2', 131, 0, 20, 2], ['B2', 'S2', 'L2', 160, 40, 40, 5]], ['S2', 'L1', 100, 10]], 'B1']], [['generated mix 11', [[['B0', 'S1', 'L3', 131, 5, 20, 6], ['B1', 'S1', 'L1', 160, 10, 40, 4], ['B2', 'S1', 'L3', 130, 10, 20, 8], ['B3', None, None, 0, 0, 20, 1], ['B4', None, None, 0, 0, 20, 6]], ['S1', 'L2', 100, 10]], 'B2'], ['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'], ['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 40', [[['B0', None, None, 0, 0, 40, 3], ['B1', None, None, 0, 0, 20, 4], ['B2', 'S1', 'L3', 100, 5, 20, 7], ['B3', None, None, 0, 0, 60, 7], ['B4', 'S1', 'L1', 160, 40, 60, 2], ['B5', None, None, 0, 0, 40, 2]], ['S1', 'L2', 130, 20]], 'B4']], [['generated mix 48', [[['B0', 'S1', 'L3', 120, 5, 40, 7], ['B1', 'S1', 'L2', 130, 0, 60, 7], ['B2', 'S1', 'L1', 120, 10, 20, 1], ['B3', 'S1', 'L2', 120, 10, 60, 8]], ['S1', 'L1', 100, 10]], 'B2'], ['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'], ['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 52', [[['B0', None, None, 0, 0, 20, 9], ['B1', 'S1', 'L3', 120, 10, 60, 1], ['B2', 'S1', 'L3', 120, 40, 20, 9], ['B3', 'S1', 'L2', 100, 40, 60, 4]], ['S1', 'L4', 100, 10]], 'B3']]]
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
room exactEAFailed
generated mix 8B2B2Passed
same lot preferredAAPassed
expiry 31 daysEEPassed
lot code on other skuEEPassed
expiry 30 days laterAAPassed
generated mix 6HOLDHOLDPassed
generated mix 15B4B1Failed

SHA-256 / 149030f2c91b416f20b08037c6422020e45303f91b0ed0076041b509a440ac5e

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] >= 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 = [[['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'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 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'], ['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 6', [[['B0', 'S1', 'L1', 160, 40, 20, 6], ['B1', 'S2', 'L2', 131, 40, 20, 7]], ['S1', 'L4', 100, 30]], 'HOLD'], ['generated mix 15', [[['B0', 'S1', 'L2', 145, 40, 20, 2], ['B1', 'S2', 'L1', 145, 40, 60, 3], ['B2', 'S1', 'L1', 100, 30, 40, 3], ['B3', 'S1', 'L2', 145, 5, 60, 3], ['B4', 'S1', 'L1', 131, 0, 20, 6], ['B5', None, None, 0, 0, 40, 9]], ['S2', 'L4', 150, 20]], 'B1']], [['generated mix 43', [[['B0', 'S1', 'L1', 120, 30, 40, 4], ['B1', None, None, 0, 0, 20, 9]], ['S1', 'L4', 130, 10]], 'B0'], ['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 33', [[['B0', 'S1', 'L1', 130, 10, 40, 6], ['B1', 'S1', 'L3', 160, 40, 40, 3], ['B2', None, None, 0, 0, 20, 1], ['B3', 'S2', 'L2', 160, 5, 60, 8], ['B4', None, None, 0, 0, 40, 4], ['B5', 'S2', 'L2', 131, 5, 60, 8]], ['S1', 'L1', 130, 30]], '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'], ['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 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'], ['generated mix 28', [[['B0', 'S1', 'L3', 131, 10, 60, 9], ['B1', 'S2', 'L2', 131, 0, 20, 2], ['B2', 'S2', 'L2', 160, 40, 40, 5]], ['S2', 'L1', 100, 10]], 'B1']], [['generated mix 11', [[['B0', 'S1', 'L3', 131, 5, 20, 6], ['B1', 'S1', 'L1', 160, 10, 40, 4], ['B2', 'S1', 'L3', 130, 10, 20, 8], ['B3', None, None, 0, 0, 20, 1], ['B4', None, None, 0, 0, 20, 6]], ['S1', 'L2', 100, 10]], 'B2'], ['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'], ['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 40', [[['B0', None, None, 0, 0, 40, 3], ['B1', None, None, 0, 0, 20, 4], ['B2', 'S1', 'L3', 100, 5, 20, 7], ['B3', None, None, 0, 0, 60, 7], ['B4', 'S1', 'L1', 160, 40, 60, 2], ['B5', None, None, 0, 0, 40, 2]], ['S1', 'L2', 130, 20]], 'B4']], [['generated mix 48', [[['B0', 'S1', 'L3', 120, 5, 40, 7], ['B1', 'S1', 'L2', 130, 0, 60, 7], ['B2', 'S1', 'L1', 120, 10, 20, 1], ['B3', 'S1', 'L2', 120, 10, 60, 8]], ['S1', 'L1', 100, 10]], 'B2'], ['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'], ['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 52', [[['B0', None, None, 0, 0, 20, 9], ['B1', 'S1', 'L3', 120, 10, 60, 1], ['B2', 'S1', 'L3', 120, 40, 20, 9], ['B3', 'S1', 'L2', 100, 40, 60, 4]], ['S1', 'L4', 100, 10]], 'B3']]]
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
room exactAAPassed
generated mix 8B0B2Failed
same lot preferredAAPassed
expiry 31 daysEEPassed
lot code on other skuEEPassed
expiry 30 days laterAAPassed
generated mix 6HOLDHOLDPassed
generated mix 15B1B1Passed

SHA-256 / 75f220afdb78d6b9cc2da9464eaa92200fda69ea04921f1fd1a5068cf23e4264

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 = [[['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'], ['same lot preferred', [[['A', 'S1', 'L1', 100, 10, 40, 9], ['B', 'S1', 'L2', 100, 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'], ['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 6', [[['B0', 'S1', 'L1', 160, 40, 20, 6], ['B1', 'S2', 'L2', 131, 40, 20, 7]], ['S1', 'L4', 100, 30]], 'HOLD'], ['generated mix 15', [[['B0', 'S1', 'L2', 145, 40, 20, 2], ['B1', 'S2', 'L1', 145, 40, 60, 3], ['B2', 'S1', 'L1', 100, 30, 40, 3], ['B3', 'S1', 'L2', 145, 5, 60, 3], ['B4', 'S1', 'L1', 131, 0, 20, 6], ['B5', None, None, 0, 0, 40, 9]], ['S2', 'L4', 150, 20]], 'B1']], [['generated mix 43', [[['B0', 'S1', 'L1', 120, 30, 40, 4], ['B1', None, None, 0, 0, 20, 9]], ['S1', 'L4', 130, 10]], 'B0'], ['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 33', [[['B0', 'S1', 'L1', 130, 10, 40, 6], ['B1', 'S1', 'L3', 160, 40, 40, 3], ['B2', None, None, 0, 0, 20, 1], ['B3', 'S2', 'L2', 160, 5, 60, 8], ['B4', None, None, 0, 0, 40, 4], ['B5', 'S2', 'L2', 131, 5, 60, 8]], ['S1', 'L1', 130, 30]], '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'], ['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 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'], ['generated mix 28', [[['B0', 'S1', 'L3', 131, 10, 60, 9], ['B1', 'S2', 'L2', 131, 0, 20, 2], ['B2', 'S2', 'L2', 160, 40, 40, 5]], ['S2', 'L1', 100, 10]], 'B1']], [['generated mix 11', [[['B0', 'S1', 'L3', 131, 5, 20, 6], ['B1', 'S1', 'L1', 160, 10, 40, 4], ['B2', 'S1', 'L3', 130, 10, 20, 8], ['B3', None, None, 0, 0, 20, 1], ['B4', None, None, 0, 0, 20, 6]], ['S1', 'L2', 100, 10]], 'B2'], ['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'], ['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 40', [[['B0', None, None, 0, 0, 40, 3], ['B1', None, None, 0, 0, 20, 4], ['B2', 'S1', 'L3', 100, 5, 20, 7], ['B3', None, None, 0, 0, 60, 7], ['B4', 'S1', 'L1', 160, 40, 60, 2], ['B5', None, None, 0, 0, 40, 2]], ['S1', 'L2', 130, 20]], 'B4']], [['generated mix 48', [[['B0', 'S1', 'L3', 120, 5, 40, 7], ['B1', 'S1', 'L2', 130, 0, 60, 7], ['B2', 'S1', 'L1', 120, 10, 20, 1], ['B3', 'S1', 'L2', 120, 10, 60, 8]], ['S1', 'L1', 100, 10]], 'B2'], ['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'], ['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 52', [[['B0', None, None, 0, 0, 20, 9], ['B1', 'S1', 'L3', 120, 10, 60, 1], ['B2', 'S1', 'L3', 120, 40, 20, 9], ['B3', 'S1', 'L2', 100, 40, 60, 4]], ['S1', 'L4', 100, 10]], 'B3']]]
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
room exactAAPassed
generated mix 8B2B2Passed
same lot preferredAAPassed
expiry 31 daysEEPassed
lot code on other skuEEPassed
expiry 30 days laterAAPassed
generated mix 6HOLDHOLDPassed
generated mix 15B1B1Passed

SHA-256 / d0c305b1cd459fb8f1f71b37d8f03adfa8443923d0251408fd9218881a659b01

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.056844+00:00.

Case digest / 1380ac9fc7eca29d59717fdce5f3b8a6a4d86e7e35c090e9e3338e96d5d6111a