FA-94911 / Warehouse slotting and bin packing / Open access
Replenishment: empty small face never refilled · case 01
An empty pick face whose max is smaller than one case pack is never replenished.
ROOT CAUSE
The emergency one-case rule for stocked-out faces is missing.
VERIFIED REPAIR
When the face is empty and no full case fits under max, send one case if reserve holds a full case.
Unsuccessful approach: Sending the emergency case without checking reserve creates tasks for stock that does not exist.
Case contract
faces [[loc, on_hand, min, max, case_pack, reserve_units]], min >= 1. Replenish when on_hand <= min. Quantity is the largest number of full cases that keeps on_hand <= max, limited to full cases available in reserve. A stocked-out face whose max leaves no room for a full case still gets one case if reserve has one. Zero-quantity tasks are dropped. Tasks are ordered by on_hand/min ascending, then loc.
Why this case matters
Min/max replenishment keeps forward pick faces stocked from reserve.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
N = 1
observations = []
def solve(faces):
tasks = []
for loc, oh, mn, mx, cp, reserve in faces:
if oh > mn:
continue
avail = reserve // cp * cp
qty = (mx - oh) // cp * cp
qty = min(qty, avail)
if qty == 0:
continue
tasks.append((Fraction(oh, mn), loc, qty))
tasks.sort()
return [[loc, qty] for _, loc, qty in tasks]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 6', [[['F0', 1, 12, 18, 6, 12], ['F1', 10, 10, 10, 1, 100], ['F2', 0, 6, 9, 12, 3]]], [['F0', 12]]], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['generated mix 9', [[['F0', 6, 6, 9, 1, 100], ['F1', 1, 12, 18, 6, 0]]], [['F0', 3]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 26', [[['F0', 7, 6, 6, 12, 48], ['F1', 0, 4, 28, 6, 12], ['F2', 0, 6, 30, 6, 0], ['F3', 1, 6, 6, 1, 3]]], [['F1', 12], ['F3', 3]]], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['generated mix 11', [[['F0', 5, 6, 9, 4, 100]]], [['F0', 4]]], ['generated mix 19', [[['F0', 7, 6, 6, 1, 12], ['F1', 0, 4, 16, 6, 0], ['F2', 4, 4, 28, 12, 48]]], [['F2', 24]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 45', [[['F0', 1, 4, 7, 6, 100], ['F1', 13, 12, 12, 6, 3], ['F2', 5, 6, 9, 4, 3], ['F3', 0, 6, 36, 6, 0]]], [['F0', 6]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['generated mix 22', [[['F0', 5, 6, 12, 4, 0], ['F1', 9, 10, 13, 1, 24], ['F2', 0, 10, 16, 1, 48]]], [['F2', 16], ['F1', 4]]], ['generated mix 30', [[['F0', 0, 12, 18, 6, 48], ['F1', 0, 6, 30, 12, 24]]], [['F0', 18], ['F1', 24]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 62', [[['F0', 0, 10, 34, 12, 3], ['F1', 5, 4, 28, 4, 100]]], []], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['generated mix 33', [[['F0', 13, 12, 36, 12, 48], ['F1', 5, 4, 10, 6, 100], ['F2', 4, 4, 10, 12, 24]]], []], ['generated mix 41', [[['F0', 0, 4, 16, 1, 12], ['F1', 7, 6, 30, 1, 100], ['F2', 5, 6, 18, 12, 24]]], [['F0', 12], ['F2', 12]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 76', [[['F0', 0, 10, 40, 1, 0]]], []], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['generated mix 44', [[['F0', 4, 4, 4, 6, 3], ['F1', 11, 10, 10, 1, 48], ['F2', 11, 12, 24, 4, 48], ['F3', 3, 6, 9, 4, 3]]], [['F2', 12]]], ['generated mix 52', [[['F0', 11, 10, 16, 6, 3], ['F1', 10, 10, 13, 4, 24], ['F2', 0, 4, 28, 1, 100], ['F3', 12, 12, 42, 4, 0]]], [['F2', 28]]]]]
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 |
|---|---|---|---|
| stockout small max | [] | [['B', 6]] | Failed |
| generated mix 6 | [['F0', 12]] | [['F0', 12]] | Passed |
| at min triggers | [['A', 24]] | [['A', 24]] | Passed |
| reserve partial case | [['A', 12]] | [['A', 12]] | Passed |
| priority by ratio | [['Z', 40], ['Y', 35], ['X', 35]] | [['Z', 40], ['Y', 35], ['X', 35]] | Passed |
| above min idle | [] | [] | Passed |
| stockout no reserve | [] | [] | Passed |
| generated mix 9 | [['F0', 3]] | [['F0', 3]] | Passed |
SHA-256 / 126e01fccc692abbd6047808eba87e5856460845f46322fe03b37da4329d2efa
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
N = 1
observations = []
def solve(faces):
tasks = []
for loc, oh, mn, mx, cp, reserve in faces:
if oh > mn:
continue
avail = reserve // cp * cp
qty = (mx - oh) // cp * cp
qty = min(qty, avail)
if qty == 0 and oh == 0:
qty = cp
if qty == 0:
continue
tasks.append((Fraction(oh, mn), loc, qty))
tasks.sort()
return [[loc, qty] for _, loc, qty in tasks]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 6', [[['F0', 1, 12, 18, 6, 12], ['F1', 10, 10, 10, 1, 100], ['F2', 0, 6, 9, 12, 3]]], [['F0', 12]]], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['generated mix 9', [[['F0', 6, 6, 9, 1, 100], ['F1', 1, 12, 18, 6, 0]]], [['F0', 3]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 26', [[['F0', 7, 6, 6, 12, 48], ['F1', 0, 4, 28, 6, 12], ['F2', 0, 6, 30, 6, 0], ['F3', 1, 6, 6, 1, 3]]], [['F1', 12], ['F3', 3]]], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['generated mix 11', [[['F0', 5, 6, 9, 4, 100]]], [['F0', 4]]], ['generated mix 19', [[['F0', 7, 6, 6, 1, 12], ['F1', 0, 4, 16, 6, 0], ['F2', 4, 4, 28, 12, 48]]], [['F2', 24]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 45', [[['F0', 1, 4, 7, 6, 100], ['F1', 13, 12, 12, 6, 3], ['F2', 5, 6, 9, 4, 3], ['F3', 0, 6, 36, 6, 0]]], [['F0', 6]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['generated mix 22', [[['F0', 5, 6, 12, 4, 0], ['F1', 9, 10, 13, 1, 24], ['F2', 0, 10, 16, 1, 48]]], [['F2', 16], ['F1', 4]]], ['generated mix 30', [[['F0', 0, 12, 18, 6, 48], ['F1', 0, 6, 30, 12, 24]]], [['F0', 18], ['F1', 24]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 62', [[['F0', 0, 10, 34, 12, 3], ['F1', 5, 4, 28, 4, 100]]], []], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['generated mix 33', [[['F0', 13, 12, 36, 12, 48], ['F1', 5, 4, 10, 6, 100], ['F2', 4, 4, 10, 12, 24]]], []], ['generated mix 41', [[['F0', 0, 4, 16, 1, 12], ['F1', 7, 6, 30, 1, 100], ['F2', 5, 6, 18, 12, 24]]], [['F0', 12], ['F2', 12]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 76', [[['F0', 0, 10, 40, 1, 0]]], []], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['generated mix 44', [[['F0', 4, 4, 4, 6, 3], ['F1', 11, 10, 10, 1, 48], ['F2', 11, 12, 24, 4, 48], ['F3', 3, 6, 9, 4, 3]]], [['F2', 12]]], ['generated mix 52', [[['F0', 11, 10, 16, 6, 3], ['F1', 10, 10, 13, 4, 24], ['F2', 0, 4, 28, 1, 100], ['F3', 12, 12, 42, 4, 0]]], [['F2', 28]]]]]
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 |
|---|---|---|---|
| stockout small max | [['B', 6]] | [['B', 6]] | Passed |
| generated mix 6 | [['F2', 12], ['F0', 12]] | [['F0', 12]] | Failed |
| at min triggers | [['A', 24]] | [['A', 24]] | Passed |
| reserve partial case | [['A', 12]] | [['A', 12]] | Passed |
| priority by ratio | [['Z', 40], ['Y', 35], ['X', 35]] | [['Z', 40], ['Y', 35], ['X', 35]] | Passed |
| above min idle | [] | [] | Passed |
| stockout no reserve | [['B', 6]] | [] | Failed |
| generated mix 9 | [['F0', 3]] | [['F0', 3]] | Passed |
SHA-256 / e94f09979eab57e2820037cf55d0196351c48e327653a37d4afd9161144e58f0
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
N = 1
observations = []
def solve(faces):
tasks = []
for loc, oh, mn, mx, cp, reserve in faces:
if oh > mn:
continue
avail = reserve // cp * cp
qty = (mx - oh) // cp * cp
qty = min(qty, avail)
if qty == 0 and oh == 0 and avail >= cp:
qty = cp
if qty == 0:
continue
tasks.append((Fraction(oh, mn), loc, qty))
tasks.sort()
return [[loc, qty] for _, loc, qty in tasks]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 6', [[['F0', 1, 12, 18, 6, 12], ['F1', 10, 10, 10, 1, 100], ['F2', 0, 6, 9, 12, 3]]], [['F0', 12]]], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['generated mix 9', [[['F0', 6, 6, 9, 1, 100], ['F1', 1, 12, 18, 6, 0]]], [['F0', 3]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 26', [[['F0', 7, 6, 6, 12, 48], ['F1', 0, 4, 28, 6, 12], ['F2', 0, 6, 30, 6, 0], ['F3', 1, 6, 6, 1, 3]]], [['F1', 12], ['F3', 3]]], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['generated mix 11', [[['F0', 5, 6, 9, 4, 100]]], [['F0', 4]]], ['generated mix 19', [[['F0', 7, 6, 6, 1, 12], ['F1', 0, 4, 16, 6, 0], ['F2', 4, 4, 28, 12, 48]]], [['F2', 24]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 45', [[['F0', 1, 4, 7, 6, 100], ['F1', 13, 12, 12, 6, 3], ['F2', 5, 6, 9, 4, 3], ['F3', 0, 6, 36, 6, 0]]], [['F0', 6]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['generated mix 22', [[['F0', 5, 6, 12, 4, 0], ['F1', 9, 10, 13, 1, 24], ['F2', 0, 10, 16, 1, 48]]], [['F2', 16], ['F1', 4]]], ['generated mix 30', [[['F0', 0, 12, 18, 6, 48], ['F1', 0, 6, 30, 12, 24]]], [['F0', 18], ['F1', 24]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 62', [[['F0', 0, 10, 34, 12, 3], ['F1', 5, 4, 28, 4, 100]]], []], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['at min triggers', [[['A', 10, 10, 34, 12, 100]]], [['A', 24]]], ['generated mix 33', [[['F0', 13, 12, 36, 12, 48], ['F1', 5, 4, 10, 6, 100], ['F2', 4, 4, 10, 12, 24]]], []], ['generated mix 41', [[['F0', 0, 4, 16, 1, 12], ['F1', 7, 6, 30, 1, 100], ['F2', 5, 6, 18, 12, 24]]], [['F0', 12], ['F2', 12]]]], [['stockout small max', [[['B', 0, 2, 5, 6, 12]]], [['B', 6]]], ['generated mix 76', [[['F0', 0, 10, 40, 1, 0]]], []], ['reserve partial case', [[['A', 0, 5, 30, 12, 20]]], [['A', 12]]], ['priority by ratio', [[['X', 5, 10, 40, 5, 100], ['Y', 2, 8, 40, 5, 100], ['Z', 0, 6, 40, 5, 100]]], [['Z', 40], ['Y', 35], ['X', 35]]], ['above min idle', [[['A', 11, 10, 34, 12, 100]]], []], ['stockout no reserve', [[['B', 0, 2, 20, 6, 5]]], []], ['generated mix 44', [[['F0', 4, 4, 4, 6, 3], ['F1', 11, 10, 10, 1, 48], ['F2', 11, 12, 24, 4, 48], ['F3', 3, 6, 9, 4, 3]]], [['F2', 12]]], ['generated mix 52', [[['F0', 11, 10, 16, 6, 3], ['F1', 10, 10, 13, 4, 24], ['F2', 0, 4, 28, 1, 100], ['F3', 12, 12, 42, 4, 0]]], [['F2', 28]]]]]
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 |
|---|---|---|---|
| stockout small max | [['B', 6]] | [['B', 6]] | Passed |
| generated mix 6 | [['F0', 12]] | [['F0', 12]] | Passed |
| at min triggers | [['A', 24]] | [['A', 24]] | Passed |
| reserve partial case | [['A', 12]] | [['A', 12]] | Passed |
| priority by ratio | [['Z', 40], ['Y', 35], ['X', 35]] | [['Z', 40], ['Y', 35], ['X', 35]] | Passed |
| above min idle | [] | [] | Passed |
| stockout no reserve | [] | [] | Passed |
| generated mix 9 | [['F0', 3]] | [['F0', 3]] | Passed |
SHA-256 / 8710226297fa104a1f32191860ca36d28cb74a5492ee9a8044225609e256a2e5
Verification & scope
Stipulated min/max policy with whole-case moves only. 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:08.819913+00:00.
Case digest / 0dd9fb693a5022dd5cc05156171d9eeb2d3ff141ee9b36744d14a623f78ac766