FA-97571 / Knitting and sewing pattern grading / Open access
Cutting layout fabric length: nap rotation · case 01
A one-way print piece is turned sideways, putting the nap the wrong way.
ROOT CAUSE
Directional fabric does not block rotation.
VERIFIED REPAIR
Rotate only non-directional fabric.
Unsuccessful approach: Requiring both conditions still rotates directional pieces that fit sideways.
Case contract
Pieces [w, h, qty] (cm; w across the fabric). A piece wider than the fabric is rotated if the fabric is not directional and its height fits, else "error: piece too wide". Expand qty, sort by (height, width) descending, then shelf-pack: start a new row when the row width plus the piece would exceed the fabric width; each row is as tall as its tallest piece. Return [length_cm, eighths_of_a_yard] with eighths = ceiling(length/91.44*8).
Why this case matters
Pattern envelopes and cutting planners estimate yardage from piece layouts and nap direction.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(pieces, width, directional):
items = []
for w, h, qty in pieces:
for _ in range(qty):
if w > width:
if h > width:
return 'error: piece too wide'
w, h = h, w
items.append((h, w))
items.sort(reverse=True)
length = 0
row_w = 0
row_h = 0
for h, w in items:
if row_w + w > width:
length += row_h
row_w, row_h = 0, 0
row_w += w
row_h = max(row_h, h)
length += row_h
return [length, math.ceil(Fraction(length) / Fraction(9144, 100) * 8)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['regression: nap rotation', [[[150, 80, 1], [70, 30, 3], [110, 45, 2], [35, 30, 3]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[110, 80, 3], [150, 60, 2], [110, 45, 2], [55, 60, 2]], 110, True],
'error: piece too wide'],
['generated control 1', [[[35, 80, 1]], 140, False], [80, 7]],
['generated control 2', [[[35, 100, 1], [150, 80, 1]], 140, False], [150, 14]],
['generated control 3', [[[55, 30, 1]], 150, False], [30, 3]]],
[['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['regression: nap rotation', [[[150, 45, 1]], 140, True], 'error: piece too wide'],
['repair check: nap rotation', [[[70, 45, 1], [35, 60, 1], [70, 80, 1], [150, 30, 1]], 110, True],
'error: piece too wide'],
['generated control 1', [[[20, 100, 3], [35, 100, 1], [40, 80, 1], [20, 80, 3]], 140, True], [180, 16]],
['generated control 2', [[[35, 80, 2]], 150, False], [80, 7]],
['generated control 3', [[[35, 45, 1], [35, 45, 1]], 140, True], [45, 4]]],
[['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['regression: nap rotation', [[[40, 80, 1], [150, 100, 1], [70, 30, 3]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 60, 3], [20, 30, 2]], 140, True], 'error: piece too wide'],
['generated control 1', [[[20, 80, 1], [70, 100, 2]], 110, False], [200, 18]],
['generated control 2', [[[70, 80, 3], [35, 100, 1], [55, 100, 2]], 150, False], [260, 23]],
['generated control 3', [[[70, 100, 2], [20, 80, 1]], 150, False], [180, 16]]],
[['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['regression: nap rotation', [[[150, 100, 2], [35, 80, 1], [55, 60, 1]], 110, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 60, 1], [40, 100, 2], [40, 45, 3]], 110, True],
'error: piece too wide'],
['generated control 1', [[[40, 80, 3], [20, 60, 1], [35, 30, 3]], 140, True], [110, 10]],
['generated control 2', [[[40, 45, 3], [35, 100, 1]], 110, True], [145, 13]],
['generated control 3', [[[40, 45, 1]], 150, True], [45, 4]]],
[['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['regression: nap rotation', [[[150, 30, 1], [40, 80, 1], [70, 100, 2]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 100, 3], [70, 100, 1], [20, 30, 1], [55, 60, 1]], 110, True],
'error: piece too wide'],
['generated control 1', [[[40, 100, 1], [55, 45, 3], [150, 45, 2], [110, 30, 1]], 140, False], [270, 24]],
['generated control 2', [[[55, 100, 3], [70, 60, 1], [35, 45, 2]], 140, False], [245, 22]],
['generated control 3', [[[35, 45, 2]], 140, False], [45, 4]]]]
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 |
|---|---|---|---|
| two pieces one row | [60, 6] | [60, 6] | Passed |
| rotate wide piece | [150, 14] | [150, 14] | Passed |
| directional wide piece | [150, 14] | error: piece too wide | Failed |
| regression: nap rotation | [330, 29] | error: piece too wide | Failed |
| repair check: nap rotation | [690, 61] | error: piece too wide | Failed |
| generated control 1 | [80, 7] | [80, 7] | Passed |
| generated control 2 | [150, 14] | [150, 14] | Passed |
| generated control 3 | [30, 3] | [30, 3] | Passed |
SHA-256 / fb466e15a32bb152eb27ec31163925ae18a71552d29ea640241792778266ac7b
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(pieces, width, directional):
items = []
for w, h, qty in pieces:
for _ in range(qty):
if w > width:
if directional and h > width:
return 'error: piece too wide'
w, h = h, w
items.append((h, w))
items.sort(reverse=True)
length = 0
row_w = 0
row_h = 0
for h, w in items:
if row_w + w > width:
length += row_h
row_w, row_h = 0, 0
row_w += w
row_h = max(row_h, h)
length += row_h
return [length, math.ceil(Fraction(length) / Fraction(9144, 100) * 8)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['regression: nap rotation', [[[150, 80, 1], [70, 30, 3], [110, 45, 2], [35, 30, 3]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[110, 80, 3], [150, 60, 2], [110, 45, 2], [55, 60, 2]], 110, True],
'error: piece too wide'],
['generated control 1', [[[35, 80, 1]], 140, False], [80, 7]],
['generated control 2', [[[35, 100, 1], [150, 80, 1]], 140, False], [150, 14]],
['generated control 3', [[[55, 30, 1]], 150, False], [30, 3]]],
[['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['regression: nap rotation', [[[150, 45, 1]], 140, True], 'error: piece too wide'],
['repair check: nap rotation', [[[70, 45, 1], [35, 60, 1], [70, 80, 1], [150, 30, 1]], 110, True],
'error: piece too wide'],
['generated control 1', [[[20, 100, 3], [35, 100, 1], [40, 80, 1], [20, 80, 3]], 140, True], [180, 16]],
['generated control 2', [[[35, 80, 2]], 150, False], [80, 7]],
['generated control 3', [[[35, 45, 1], [35, 45, 1]], 140, True], [45, 4]]],
[['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['regression: nap rotation', [[[40, 80, 1], [150, 100, 1], [70, 30, 3]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 60, 3], [20, 30, 2]], 140, True], 'error: piece too wide'],
['generated control 1', [[[20, 80, 1], [70, 100, 2]], 110, False], [200, 18]],
['generated control 2', [[[70, 80, 3], [35, 100, 1], [55, 100, 2]], 150, False], [260, 23]],
['generated control 3', [[[70, 100, 2], [20, 80, 1]], 150, False], [180, 16]]],
[['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['regression: nap rotation', [[[150, 100, 2], [35, 80, 1], [55, 60, 1]], 110, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 60, 1], [40, 100, 2], [40, 45, 3]], 110, True],
'error: piece too wide'],
['generated control 1', [[[40, 80, 3], [20, 60, 1], [35, 30, 3]], 140, True], [110, 10]],
['generated control 2', [[[40, 45, 3], [35, 100, 1]], 110, True], [145, 13]],
['generated control 3', [[[40, 45, 1]], 150, True], [45, 4]]],
[['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['regression: nap rotation', [[[150, 30, 1], [40, 80, 1], [70, 100, 2]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 100, 3], [70, 100, 1], [20, 30, 1], [55, 60, 1]], 110, True],
'error: piece too wide'],
['generated control 1', [[[40, 100, 1], [55, 45, 3], [150, 45, 2], [110, 30, 1]], 140, False], [270, 24]],
['generated control 2', [[[55, 100, 3], [70, 60, 1], [35, 45, 2]], 140, False], [245, 22]],
['generated control 3', [[[35, 45, 2]], 140, False], [45, 4]]]]
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 |
|---|---|---|---|
| two pieces one row | [60, 6] | [60, 6] | Passed |
| rotate wide piece | [150, 14] | [150, 14] | Passed |
| directional wide piece | [150, 14] | error: piece too wide | Failed |
| regression: nap rotation | [330, 29] | error: piece too wide | Failed |
| repair check: nap rotation | [690, 61] | error: piece too wide | Failed |
| generated control 1 | [80, 7] | [80, 7] | Passed |
| generated control 2 | [150, 14] | [150, 14] | Passed |
| generated control 3 | [30, 3] | [30, 3] | Passed |
SHA-256 / c281e2fda5acef2644b2d70bd804d0c0676930bcfa4e156926e7f61cca65fe35
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(pieces, width, directional):
items = []
for w, h, qty in pieces:
for _ in range(qty):
if w > width:
if directional or h > width:
return 'error: piece too wide'
w, h = h, w
items.append((h, w))
items.sort(reverse=True)
length = 0
row_w = 0
row_h = 0
for h, w in items:
if row_w + w > width:
length += row_h
row_w, row_h = 0, 0
row_w += w
row_h = max(row_h, h)
length += row_h
return [length, math.ceil(Fraction(length) / Fraction(9144, 100) * 8)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['regression: nap rotation', [[[150, 80, 1], [70, 30, 3], [110, 45, 2], [35, 30, 3]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[110, 80, 3], [150, 60, 2], [110, 45, 2], [55, 60, 2]], 110, True],
'error: piece too wide'],
['generated control 1', [[[35, 80, 1]], 140, False], [80, 7]],
['generated control 2', [[[35, 100, 1], [150, 80, 1]], 140, False], [150, 14]],
['generated control 3', [[[55, 30, 1]], 150, False], [30, 3]]],
[['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['regression: nap rotation', [[[150, 45, 1]], 140, True], 'error: piece too wide'],
['repair check: nap rotation', [[[70, 45, 1], [35, 60, 1], [70, 80, 1], [150, 30, 1]], 110, True],
'error: piece too wide'],
['generated control 1', [[[20, 100, 3], [35, 100, 1], [40, 80, 1], [20, 80, 3]], 140, True], [180, 16]],
['generated control 2', [[[35, 80, 2]], 150, False], [80, 7]],
['generated control 3', [[[35, 45, 1], [35, 45, 1]], 140, True], [45, 4]]],
[['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['regression: nap rotation', [[[40, 80, 1], [150, 100, 1], [70, 30, 3]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 60, 3], [20, 30, 2]], 140, True], 'error: piece too wide'],
['generated control 1', [[[20, 80, 1], [70, 100, 2]], 110, False], [200, 18]],
['generated control 2', [[[70, 80, 3], [35, 100, 1], [55, 100, 2]], 150, False], [260, 23]],
['generated control 3', [[[70, 100, 2], [20, 80, 1]], 150, False], [180, 16]]],
[['exact fit row', [[[55, 80, 2], [40, 45, 1]], 150, False], [80, 7]],
['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['regression: nap rotation', [[[150, 100, 2], [35, 80, 1], [55, 60, 1]], 110, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 60, 1], [40, 100, 2], [40, 45, 3]], 110, True],
'error: piece too wide'],
['generated control 1', [[[40, 80, 3], [20, 60, 1], [35, 30, 3]], 140, True], [110, 10]],
['generated control 2', [[[40, 45, 3], [35, 100, 1]], 110, True], [145, 13]],
['generated control 3', [[[40, 45, 1]], 150, True], [45, 4]]],
[['two pieces one row', [[[70, 60, 2]], 140, False], [60, 6]],
['rotate wide piece', [[[150, 60, 1]], 140, False], [150, 14]],
['directional wide piece', [[[150, 60, 1]], 140, True], 'error: piece too wide'],
['regression: nap rotation', [[[150, 30, 1], [40, 80, 1], [70, 100, 2]], 140, True],
'error: piece too wide'],
['repair check: nap rotation', [[[150, 100, 3], [70, 100, 1], [20, 30, 1], [55, 60, 1]], 110, True],
'error: piece too wide'],
['generated control 1', [[[40, 100, 1], [55, 45, 3], [150, 45, 2], [110, 30, 1]], 140, False], [270, 24]],
['generated control 2', [[[55, 100, 3], [70, 60, 1], [35, 45, 2]], 140, False], [245, 22]],
['generated control 3', [[[35, 45, 2]], 140, False], [45, 4]]]]
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 |
|---|---|---|---|
| two pieces one row | [60, 6] | [60, 6] | Passed |
| rotate wide piece | [150, 14] | [150, 14] | Passed |
| directional wide piece | error: piece too wide | error: piece too wide | Passed |
| regression: nap rotation | error: piece too wide | error: piece too wide | Passed |
| repair check: nap rotation | error: piece too wide | error: piece too wide | Passed |
| generated control 1 | [80, 7] | [80, 7] | Passed |
| generated control 2 | [150, 14] | [150, 14] | Passed |
| generated control 3 | [30, 3] | [30, 3] | Passed |
SHA-256 / c0e8a6b171f148f02ae4f82a5433444d5b8dd1090fc67491463d0783077a3f2e
Verification & scope
A deterministic toy contract stated in full here; it is a bounded teaching model, not an authoritative reference or standards implementation. 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:33.486842+00:00.
Case digest / 89f394526a1bdb7305e033d5aab2d4f37a22b721449248945fc63ad043e94c7f