FA-97741 / Knitting and sewing pattern grading / Open access
Knitting needle size converter: tie-break toward larger · case 01
5.75 mm converts to US 9 instead of US 10.
ROOT CAUSE
Equidistant sizes keep the first (smaller) match.
VERIFIED REPAIR
Break ties toward the larger millimetre size.
Unsuccessful approach: Comparing US labels as strings ranks "9" above "10".
Case contract
Table US->mm: 0 2.0, 1 2.25, 2 2.75, 3 3.25, 4 3.5, 5 3.75, 6 4.0, 7 4.5, 8 5.0, 9 5.5, 10 6.0, 10.5 6.5, 11 8.0, 13 9.0, 15 10.0. "us->mm": trim, "10 1/2" means "10.5", exact match or "error: size". "mm->us": nearest size within 0.5 mm inclusive, ties to the larger mm, else "error: size". Other directions -> "error: direction".
Why this case matters
Patterns list needle sizes in US or metric and knitters convert between them.
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(value, direction):
T = [('0', 2.0), ('1', 2.25), ('2', 2.75), ('3', 3.25), ('4', 3.5), ('5', 3.75), ('6', 4.0), ('7', 4.5),
('8', 5.0), ('9', 5.5), ('10', 6.0), ('10.5', 6.5), ('11', 8.0), ('13', 9.0), ('15', 10.0)]
if direction == 'us->mm':
v = str(value).strip().replace(' 1/2', '.5')
for us, mm in T:
if us == v:
return mm
return 'error: size'
if direction == 'mm->us':
x = Fraction(str(value))
best = None
for us, mm in T:
d = abs(Fraction(str(mm)) - x)
if d > Fraction(1, 2):
continue
if best is None or d < best[0] or False:
best = (d, us, mm)
return best[1] if best else 'error: size'
return 'error: direction'
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['US 8', ['8', 'us->mm'], 5.0], ['half size', ['10 1/2', 'us->mm'], 6.5],
['tie between 9 and 10', [5.75, 'mm->us'], '10'],
['regression: tie-break toward larger', [6.25, 'mm->us'], '10.5'],
['generated control 1', [11, 'us->mm'], 8.0], ['generated control 2', [3.3, 'uk'], 'error: direction'],
['generated control 3', ['10.5', 'us->mm'], 6.5],
['generated control 4', [1.5, 'uk'], 'error: direction']],
[['half size', ['10 1/2', 'us->mm'], 6.5], ['tie between 9 and 10', [5.75, 'mm->us'], '10'],
['edge of tolerance', [1.5, 'mm->us'], '0'],
['regression: tie-break toward larger', [3.625, 'mm->us'], '5'],
['generated control 1', ['0', 'us->mm'], 2.0], ['generated control 2', [3.0, 'mm->us'], '3'],
['generated control 3', [' 7 ', 'us->mm'], 4.5], ['generated control 4', ['8', 'us->mm'], 5.0]],
[['tie between 9 and 10', [5.75, 'mm->us'], '10'], ['edge of tolerance', [1.5, 'mm->us'], '0'],
['no match', [11.0, 'mm->us'], 'error: size'],
['regression: tie-break toward larger', [6.25, 'mm->us'], '10.5'],
['generated control 1', [7.0, 'mm->us'], '10.5'], ['generated control 2', ['10.5', 'us->mm'], 6.5],
['generated control 3', [7.25, 'mm->us'], 'error: size'],
['generated control 4', ['12', 'us->mm'], 'error: size']],
[['edge of tolerance', [1.5, 'mm->us'], '0'], ['no match', [11.0, 'mm->us'], 'error: size'],
['US 8', ['8', 'us->mm'], 5.0], ['regression: tie-break toward larger', [4.25, 'mm->us'], '7'],
['repair check: tie-break toward larger', [5.75, 'mm->us'], '10'],
['generated control 1', [7.0, 'mm->us'], '10.5'], ['generated control 2', ['10.5', 'us->mm'], 6.5],
['generated control 3', [1.5, 'uk'], 'error: direction']],
[['no match', [11.0, 'mm->us'], 'error: size'], ['US 8', ['8', 'us->mm'], 5.0],
['half size', ['10 1/2', 'us->mm'], 6.5], ['regression: tie-break toward larger', [3.625, 'mm->us'], '5'],
['repair check: tie-break toward larger', [5.75, 'mm->us'], '10'],
['generated control 1', ['10.5', 'us->mm'], 6.5], ['generated control 2', ['12', 'us->mm'], 'error: size'],
['generated control 3', [3.3, 'mm->us'], '3']]]
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 |
|---|---|---|---|
| US 8 | 5.0 | 5.0 | Passed |
| half size | 6.5 | 6.5 | Passed |
| tie between 9 and 10 | 9 | 10 | Failed |
| regression: tie-break toward larger | 10 | 10.5 | Failed |
| generated control 1 | 8.0 | 8.0 | Passed |
| generated control 2 | error: direction | error: direction | Passed |
| generated control 3 | 6.5 | 6.5 | Passed |
| generated control 4 | error: direction | error: direction | Passed |
SHA-256 / a6fccb6bcc5088459d8ed06ffbedc0a320d37e3e2ecd8f0d3cc274b91e057885
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(value, direction):
T = [('0', 2.0), ('1', 2.25), ('2', 2.75), ('3', 3.25), ('4', 3.5), ('5', 3.75), ('6', 4.0), ('7', 4.5),
('8', 5.0), ('9', 5.5), ('10', 6.0), ('10.5', 6.5), ('11', 8.0), ('13', 9.0), ('15', 10.0)]
if direction == 'us->mm':
v = str(value).strip().replace(' 1/2', '.5')
for us, mm in T:
if us == v:
return mm
return 'error: size'
if direction == 'mm->us':
x = Fraction(str(value))
best = None
for us, mm in T:
d = abs(Fraction(str(mm)) - x)
if d > Fraction(1, 2):
continue
if best is None or d < best[0] or (d == best[0] and us > best[1]):
best = (d, us, mm)
return best[1] if best else 'error: size'
return 'error: direction'
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['US 8', ['8', 'us->mm'], 5.0], ['half size', ['10 1/2', 'us->mm'], 6.5],
['tie between 9 and 10', [5.75, 'mm->us'], '10'],
['regression: tie-break toward larger', [6.25, 'mm->us'], '10.5'],
['generated control 1', [11, 'us->mm'], 8.0], ['generated control 2', [3.3, 'uk'], 'error: direction'],
['generated control 3', ['10.5', 'us->mm'], 6.5],
['generated control 4', [1.5, 'uk'], 'error: direction']],
[['half size', ['10 1/2', 'us->mm'], 6.5], ['tie between 9 and 10', [5.75, 'mm->us'], '10'],
['edge of tolerance', [1.5, 'mm->us'], '0'],
['regression: tie-break toward larger', [3.625, 'mm->us'], '5'],
['generated control 1', ['0', 'us->mm'], 2.0], ['generated control 2', [3.0, 'mm->us'], '3'],
['generated control 3', [' 7 ', 'us->mm'], 4.5], ['generated control 4', ['8', 'us->mm'], 5.0]],
[['tie between 9 and 10', [5.75, 'mm->us'], '10'], ['edge of tolerance', [1.5, 'mm->us'], '0'],
['no match', [11.0, 'mm->us'], 'error: size'],
['regression: tie-break toward larger', [6.25, 'mm->us'], '10.5'],
['generated control 1', [7.0, 'mm->us'], '10.5'], ['generated control 2', ['10.5', 'us->mm'], 6.5],
['generated control 3', [7.25, 'mm->us'], 'error: size'],
['generated control 4', ['12', 'us->mm'], 'error: size']],
[['edge of tolerance', [1.5, 'mm->us'], '0'], ['no match', [11.0, 'mm->us'], 'error: size'],
['US 8', ['8', 'us->mm'], 5.0], ['regression: tie-break toward larger', [4.25, 'mm->us'], '7'],
['repair check: tie-break toward larger', [5.75, 'mm->us'], '10'],
['generated control 1', [7.0, 'mm->us'], '10.5'], ['generated control 2', ['10.5', 'us->mm'], 6.5],
['generated control 3', [1.5, 'uk'], 'error: direction']],
[['no match', [11.0, 'mm->us'], 'error: size'], ['US 8', ['8', 'us->mm'], 5.0],
['half size', ['10 1/2', 'us->mm'], 6.5], ['regression: tie-break toward larger', [3.625, 'mm->us'], '5'],
['repair check: tie-break toward larger', [5.75, 'mm->us'], '10'],
['generated control 1', ['10.5', 'us->mm'], 6.5], ['generated control 2', ['12', 'us->mm'], 'error: size'],
['generated control 3', [3.3, 'mm->us'], '3']]]
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 |
|---|---|---|---|
| US 8 | 5.0 | 5.0 | Passed |
| half size | 6.5 | 6.5 | Passed |
| tie between 9 and 10 | 9 | 10 | Failed |
| regression: tie-break toward larger | 10.5 | 10.5 | Passed |
| generated control 1 | 8.0 | 8.0 | Passed |
| generated control 2 | error: direction | error: direction | Passed |
| generated control 3 | 6.5 | 6.5 | Passed |
| generated control 4 | error: direction | error: direction | Passed |
SHA-256 / 100c799c37e5104ff29ed31ead52709ef1999c0c0b147d9575b1086e7bec3321
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(value, direction):
T = [('0', 2.0), ('1', 2.25), ('2', 2.75), ('3', 3.25), ('4', 3.5), ('5', 3.75), ('6', 4.0), ('7', 4.5),
('8', 5.0), ('9', 5.5), ('10', 6.0), ('10.5', 6.5), ('11', 8.0), ('13', 9.0), ('15', 10.0)]
if direction == 'us->mm':
v = str(value).strip().replace(' 1/2', '.5')
for us, mm in T:
if us == v:
return mm
return 'error: size'
if direction == 'mm->us':
x = Fraction(str(value))
best = None
for us, mm in T:
d = abs(Fraction(str(mm)) - x)
if d > Fraction(1, 2):
continue
if best is None or d < best[0] or (d == best[0] and mm > best[2]):
best = (d, us, mm)
return best[1] if best else 'error: size'
return 'error: direction'
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['US 8', ['8', 'us->mm'], 5.0], ['half size', ['10 1/2', 'us->mm'], 6.5],
['tie between 9 and 10', [5.75, 'mm->us'], '10'],
['regression: tie-break toward larger', [6.25, 'mm->us'], '10.5'],
['generated control 1', [11, 'us->mm'], 8.0], ['generated control 2', [3.3, 'uk'], 'error: direction'],
['generated control 3', ['10.5', 'us->mm'], 6.5],
['generated control 4', [1.5, 'uk'], 'error: direction']],
[['half size', ['10 1/2', 'us->mm'], 6.5], ['tie between 9 and 10', [5.75, 'mm->us'], '10'],
['edge of tolerance', [1.5, 'mm->us'], '0'],
['regression: tie-break toward larger', [3.625, 'mm->us'], '5'],
['generated control 1', ['0', 'us->mm'], 2.0], ['generated control 2', [3.0, 'mm->us'], '3'],
['generated control 3', [' 7 ', 'us->mm'], 4.5], ['generated control 4', ['8', 'us->mm'], 5.0]],
[['tie between 9 and 10', [5.75, 'mm->us'], '10'], ['edge of tolerance', [1.5, 'mm->us'], '0'],
['no match', [11.0, 'mm->us'], 'error: size'],
['regression: tie-break toward larger', [6.25, 'mm->us'], '10.5'],
['generated control 1', [7.0, 'mm->us'], '10.5'], ['generated control 2', ['10.5', 'us->mm'], 6.5],
['generated control 3', [7.25, 'mm->us'], 'error: size'],
['generated control 4', ['12', 'us->mm'], 'error: size']],
[['edge of tolerance', [1.5, 'mm->us'], '0'], ['no match', [11.0, 'mm->us'], 'error: size'],
['US 8', ['8', 'us->mm'], 5.0], ['regression: tie-break toward larger', [4.25, 'mm->us'], '7'],
['repair check: tie-break toward larger', [5.75, 'mm->us'], '10'],
['generated control 1', [7.0, 'mm->us'], '10.5'], ['generated control 2', ['10.5', 'us->mm'], 6.5],
['generated control 3', [1.5, 'uk'], 'error: direction']],
[['no match', [11.0, 'mm->us'], 'error: size'], ['US 8', ['8', 'us->mm'], 5.0],
['half size', ['10 1/2', 'us->mm'], 6.5], ['regression: tie-break toward larger', [3.625, 'mm->us'], '5'],
['repair check: tie-break toward larger', [5.75, 'mm->us'], '10'],
['generated control 1', ['10.5', 'us->mm'], 6.5], ['generated control 2', ['12', 'us->mm'], 'error: size'],
['generated control 3', [3.3, 'mm->us'], '3']]]
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 |
|---|---|---|---|
| US 8 | 5.0 | 5.0 | Passed |
| half size | 6.5 | 6.5 | Passed |
| tie between 9 and 10 | 10 | 10 | Passed |
| regression: tie-break toward larger | 10.5 | 10.5 | Passed |
| generated control 1 | 8.0 | 8.0 | Passed |
| generated control 2 | error: direction | error: direction | Passed |
| generated control 3 | 6.5 | 6.5 | Passed |
| generated control 4 | error: direction | error: direction | Passed |
SHA-256 / d170f5963493a27c3eda8a5b15371bcd804b9182abe07c8905758dc00d047ca3
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:35.134293+00:00.
Case digest / 04b9ca2afeb904df9b690e1b35d1cfd88e3c15932cb936b5cf505516532893ba