FA-97636 / Knitting and sewing pattern grading / Open access
Buttonhole row planner: one-based rows · case 01
The first buttonhole is placed one row too low.
ROOT CAUSE
Row numbers are 0-based.
VERIFIED REPAIR
Add 1 to the rounded row offset.
Unsuccessful approach: Ceiling the offset is not the same as adding one row.
Case contract
Buttonholes run from bottom_offset to band_len - top_offset (cm), evenly spaced; a single buttonhole goes at the top position. Convert each position to a 1-based row: half-up of position*rows_per_10cm/10, plus 1. n < 1 -> "error: count"; top position below bottom -> "error: offsets".
Why this case matters
Cardigan button bands place buttonholes evenly between neck and hem offsets.
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(band_len, n, top_offset, bottom_offset, rows_per_10cm):
if n < 1:
return 'error: count'
lo = Fraction(str(bottom_offset))
hi = Fraction(str(band_len)) - Fraction(str(top_offset))
if hi < lo:
return 'error: offsets'
g = Fraction(str(rows_per_10cm)) / 10
if n == 1:
pos = [hi]
else:
gap = (hi - lo) / (n - 1)
pos = [lo + i * gap for i in range(n)]
return [math.floor(p * g + Fraction(1, 2)) for p in pos]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['regression: one-based rows', [60, 2, 2, 4, '32'], [14, 187]],
['repair check: one-based rows', [40, 5, 2, 4, '36.5'], [16, 47, 78, 109, 140]],
['generated control 1', [40, 4, 1.5, 1, '32'], [4, 44, 84, 124]],
['generated control 2', [55.5, 2, 3, 2, '28'], [7, 148]],
['generated control 3', [50, 6, 2, 2.5, '28'], [8, 33, 59, 84, 110, 135]]],
[['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['regression: one-based rows', [40, 2, 2, 2.5, '32'], [9, 123]],
['repair check: one-based rows', [60, 6, 1.5, 2.5, '30'], [9, 42, 76, 109, 143, 177]],
['generated control 1', [40, 0, 3, 2, '28'], 'error: count'],
['generated control 2', [62, 4, 1, 1, '36.5'], [5, 78, 151, 224]],
['generated control 3', [62, 5, 1, 4, '28'], [12, 52, 92, 132, 172]]],
[['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]],
['regression: one-based rows', [40, 5, 1, 2, '36.5'], [8, 42, 76, 110, 143]],
['repair check: one-based rows', [60, 7, 1, 4, '32'], [14, 43, 72, 102, 131, 160, 190]],
['generated control 1', [62, 5, 3, 2.5, '36.5'], [10, 62, 113, 165, 216]],
['generated control 2', [62, 1, 1, 2, '30'], [184]],
['generated control 3', [40, 7, 2, 4, '28'], [12, 28, 44, 60, 76, 92, 107]]],
[['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['regression: one-based rows', [50, 6, 1, 4, '32'], [14, 43, 71, 100, 129, 158]],
['repair check: one-based rows', [62, 5, 3, 2.5, '36.5'], [10, 62, 113, 165, 216]],
['generated control 1', [40, 6, 3, 2, '36.5'], [8, 34, 59, 85, 111, 136]],
['generated control 2', [40, 0, 3, 1, '36.5'], 'error: count'],
['generated control 3', [55.5, 7, 1, 4, '32'], [14, 41, 68, 95, 122, 148, 175]]],
[['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['regression: one-based rows', [55.5, 7, 3, 2.5, '30'], [9, 34, 59, 84, 109, 134, 159]],
['repair check: one-based rows', [60, 6, 1, 2.5, '32'], [9, 45, 81, 117, 154, 190]],
['generated control 1', [62, 1, 1.5, 4, '32'], [195]],
['generated control 2', [60, 7, 3, 4, '36.5'], [16, 48, 80, 112, 145, 177, 209]],
['generated control 3', [40, 5, 1, 1, '28'], [4, 30, 57, 84, 110]]]]
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 |
|---|---|---|---|
| five buttons | [6, 41, 76, 111, 146] | [7, 42, 77, 112, 147] | Failed |
| single button | [106] | [107] | Failed |
| no buttons | error: count | error: count | Passed |
| regression: one-based rows | [13, 186] | [14, 187] | Failed |
| repair check: one-based rows | [15, 46, 77, 108, 139] | [16, 47, 78, 109, 140] | Failed |
| generated control 1 | [3, 43, 83, 123] | [4, 44, 84, 124] | Failed |
| generated control 2 | [6, 147] | [7, 148] | Failed |
| generated control 3 | [7, 32, 58, 83, 109, 134] | [8, 33, 59, 84, 110, 135] | Failed |
SHA-256 / 07e8a9cb2e333144a81ebf2cf8d320a002c1e4f509a72b9ea7c51327f052feea
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(band_len, n, top_offset, bottom_offset, rows_per_10cm):
if n < 1:
return 'error: count'
lo = Fraction(str(bottom_offset))
hi = Fraction(str(band_len)) - Fraction(str(top_offset))
if hi < lo:
return 'error: offsets'
g = Fraction(str(rows_per_10cm)) / 10
if n == 1:
pos = [hi]
else:
gap = (hi - lo) / (n - 1)
pos = [lo + i * gap for i in range(n)]
return [math.ceil(p * g) for p in pos]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['regression: one-based rows', [60, 2, 2, 4, '32'], [14, 187]],
['repair check: one-based rows', [40, 5, 2, 4, '36.5'], [16, 47, 78, 109, 140]],
['generated control 1', [40, 4, 1.5, 1, '32'], [4, 44, 84, 124]],
['generated control 2', [55.5, 2, 3, 2, '28'], [7, 148]],
['generated control 3', [50, 6, 2, 2.5, '28'], [8, 33, 59, 84, 110, 135]]],
[['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['regression: one-based rows', [40, 2, 2, 2.5, '32'], [9, 123]],
['repair check: one-based rows', [60, 6, 1.5, 2.5, '30'], [9, 42, 76, 109, 143, 177]],
['generated control 1', [40, 0, 3, 2, '28'], 'error: count'],
['generated control 2', [62, 4, 1, 1, '36.5'], [5, 78, 151, 224]],
['generated control 3', [62, 5, 1, 4, '28'], [12, 52, 92, 132, 172]]],
[['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]],
['regression: one-based rows', [40, 5, 1, 2, '36.5'], [8, 42, 76, 110, 143]],
['repair check: one-based rows', [60, 7, 1, 4, '32'], [14, 43, 72, 102, 131, 160, 190]],
['generated control 1', [62, 5, 3, 2.5, '36.5'], [10, 62, 113, 165, 216]],
['generated control 2', [62, 1, 1, 2, '30'], [184]],
['generated control 3', [40, 7, 2, 4, '28'], [12, 28, 44, 60, 76, 92, 107]]],
[['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['regression: one-based rows', [50, 6, 1, 4, '32'], [14, 43, 71, 100, 129, 158]],
['repair check: one-based rows', [62, 5, 3, 2.5, '36.5'], [10, 62, 113, 165, 216]],
['generated control 1', [40, 6, 3, 2, '36.5'], [8, 34, 59, 85, 111, 136]],
['generated control 2', [40, 0, 3, 1, '36.5'], 'error: count'],
['generated control 3', [55.5, 7, 1, 4, '32'], [14, 41, 68, 95, 122, 148, 175]]],
[['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['regression: one-based rows', [55.5, 7, 3, 2.5, '30'], [9, 34, 59, 84, 109, 134, 159]],
['repair check: one-based rows', [60, 6, 1, 2.5, '32'], [9, 45, 81, 117, 154, 190]],
['generated control 1', [62, 1, 1.5, 4, '32'], [195]],
['generated control 2', [60, 7, 3, 4, '36.5'], [16, 48, 80, 112, 145, 177, 209]],
['generated control 3', [40, 5, 1, 1, '28'], [4, 30, 57, 84, 110]]]]
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 |
|---|---|---|---|
| five buttons | [6, 41, 76, 111, 146] | [7, 42, 77, 112, 147] | Failed |
| single button | [107] | [107] | Passed |
| no buttons | error: count | error: count | Passed |
| regression: one-based rows | [13, 186] | [14, 187] | Failed |
| repair check: one-based rows | [15, 46, 77, 108, 139] | [16, 47, 78, 109, 140] | Failed |
| generated control 1 | [4, 44, 84, 124] | [4, 44, 84, 124] | Passed |
| generated control 2 | [6, 147] | [7, 148] | Failed |
| generated control 3 | [7, 33, 58, 84, 109, 135] | [8, 33, 59, 84, 110, 135] | Failed |
SHA-256 / 6693349ea8c677c3c7b712624c00bd00f35d86977695a39a8d8f0b64ed24d907
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(band_len, n, top_offset, bottom_offset, rows_per_10cm):
if n < 1:
return 'error: count'
lo = Fraction(str(bottom_offset))
hi = Fraction(str(band_len)) - Fraction(str(top_offset))
if hi < lo:
return 'error: offsets'
g = Fraction(str(rows_per_10cm)) / 10
if n == 1:
pos = [hi]
else:
gap = (hi - lo) / (n - 1)
pos = [lo + i * gap for i in range(n)]
return [math.floor(p * g + Fraction(1, 2)) + 1 for p in pos]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['regression: one-based rows', [60, 2, 2, 4, '32'], [14, 187]],
['repair check: one-based rows', [40, 5, 2, 4, '36.5'], [16, 47, 78, 109, 140]],
['generated control 1', [40, 4, 1.5, 1, '32'], [4, 44, 84, 124]],
['generated control 2', [55.5, 2, 3, 2, '28'], [7, 148]],
['generated control 3', [50, 6, 2, 2.5, '28'], [8, 33, 59, 84, 110, 135]]],
[['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['regression: one-based rows', [40, 2, 2, 2.5, '32'], [9, 123]],
['repair check: one-based rows', [60, 6, 1.5, 2.5, '30'], [9, 42, 76, 109, 143, 177]],
['generated control 1', [40, 0, 3, 2, '28'], 'error: count'],
['generated control 2', [62, 4, 1, 1, '36.5'], [5, 78, 151, 224]],
['generated control 3', [62, 5, 1, 4, '28'], [12, 52, 92, 132, 172]]],
[['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]],
['regression: one-based rows', [40, 5, 1, 2, '36.5'], [8, 42, 76, 110, 143]],
['repair check: one-based rows', [60, 7, 1, 4, '32'], [14, 43, 72, 102, 131, 160, 190]],
['generated control 1', [62, 5, 3, 2.5, '36.5'], [10, 62, 113, 165, 216]],
['generated control 2', [62, 1, 1, 2, '30'], [184]],
['generated control 3', [40, 7, 2, 4, '28'], [12, 28, 44, 60, 76, 92, 107]]],
[['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['regression: one-based rows', [50, 6, 1, 4, '32'], [14, 43, 71, 100, 129, 158]],
['repair check: one-based rows', [62, 5, 3, 2.5, '36.5'], [10, 62, 113, 165, 216]],
['generated control 1', [40, 6, 3, 2, '36.5'], [8, 34, 59, 85, 111, 136]],
['generated control 2', [40, 0, 3, 1, '36.5'], 'error: count'],
['generated control 3', [55.5, 7, 1, 4, '32'], [14, 41, 68, 95, 122, 148, 175]]],
[['single button', [40, 1, 2, 2, '28'], [107]], ['no buttons', [40, 0, 2, 2, '28'], 'error: count'],
['five buttons', [50, 5, 1.5, 2, '30'], [7, 42, 77, 112, 147]],
['regression: one-based rows', [55.5, 7, 3, 2.5, '30'], [9, 34, 59, 84, 109, 134, 159]],
['repair check: one-based rows', [60, 6, 1, 2.5, '32'], [9, 45, 81, 117, 154, 190]],
['generated control 1', [62, 1, 1.5, 4, '32'], [195]],
['generated control 2', [60, 7, 3, 4, '36.5'], [16, 48, 80, 112, 145, 177, 209]],
['generated control 3', [40, 5, 1, 1, '28'], [4, 30, 57, 84, 110]]]]
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 |
|---|---|---|---|
| five buttons | [7, 42, 77, 112, 147] | [7, 42, 77, 112, 147] | Passed |
| single button | [107] | [107] | Passed |
| no buttons | error: count | error: count | Passed |
| regression: one-based rows | [14, 187] | [14, 187] | Passed |
| repair check: one-based rows | [16, 47, 78, 109, 140] | [16, 47, 78, 109, 140] | Passed |
| generated control 1 | [4, 44, 84, 124] | [4, 44, 84, 124] | Passed |
| generated control 2 | [7, 148] | [7, 148] | Passed |
| generated control 3 | [8, 33, 59, 84, 110, 135] | [8, 33, 59, 84, 110, 135] | Passed |
SHA-256 / 5db4e5169bcef7c6afc0a4f194287762cf402d4ed37879010988010b68b394c9
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:34.138761+00:00.
Case digest / 14b35815ffb57a4f80fd1e700be7dbbf8b44b8d6c91d212d2b1234b9114c31b7