FA-97416 / Knitting and sewing pattern grading / Open access
Yarn yardage estimator: metres per gram · case 01
A 50 g / 200 m fingering yarn is estimated to need fewer metres than grams.
ROOT CAUSE
Grams are converted with grams per metre instead of metres per gram.
VERIFIED REPAIR
Multiply grams by skein metres over skein grams.
Unsuccessful approach: Assuming 100 g skeins breaks 50 g skein yarns.
Case contract
grams = garment_area / swatch_area * swatch_g; meters = grams * skein_m/skein_g; add margin_pct as a multiplier (meters*(1+margin/100)). skeins = ceiling(meters/skein_m); leftover = skeins*skein_m - meters. Return [meters, skeins, leftover] with meters and leftover half-up integers. Non-positive swatch area or skein weight -> "error: swatch".
Why this case matters
Designers and knitters estimate how many skeins to buy from a weighed swatch.
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(swatch_g, swatch_w_cm, swatch_h_cm, garment_area_cm2, skein_g, skein_m, margin_pct):
sw_area = Fraction(str(swatch_w_cm)) * Fraction(str(swatch_h_cm))
if sw_area <= 0 or skein_g <= 0:
return 'error: swatch'
grams = Fraction(str(garment_area_cm2)) / sw_area * Fraction(str(swatch_g))
meters = grams * Fraction(skein_g, skein_m)
meters = meters * (1 + Fraction(margin_pct, 100))
skeins = math.ceil(meters / skein_m)
leftover = skeins * skein_m - meters
return [math.floor(meters + Fraction(1, 2)), skeins, math.floor(leftover + Fraction(1, 2))]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['regression: metres per gram', [15, 10, 15, 4800, 100, 200, 0], [960, 5, 40]],
['repair check: metres per gram', [12, 10, 15, 3000, 50, 200, 0], [960, 5, 40]],
['generated control 1', [20, 10, 10, 2000, 100, 100, 10], [440, 5, 60]],
['generated control 2', [12, 12, 12, 4800, 50, 200, 20], [1920, 10, 80]],
['generated control 3', [12, 15, 10, 9000, 100, 150, 20], [1296, 9, 54]]],
[['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['regression: metres per gram', [12, 12, 15, 4800, 100, 150, 10], [528, 4, 72]],
['repair check: metres per gram', [8, 15, 12, 4800, 50, 100, 10], [469, 5, 31]],
['generated control 1', [20, 12, 10, 2000, 100, 400, 10], [1467, 4, 133]],
['generated control 2', [8, 10, 15, 1500, 100, 150, 15], [138, 1, 12]],
['generated control 3', [15, 12, 15, 2400, 100, 125, 10], [275, 3, 100]]],
[['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['regression: metres per gram', [12, 12, 10, 2400, 100, 125, 15], [345, 3, 30]],
['repair check: metres per gram', [15, 15, 12, 2000, 50, 400, 20], [1600, 4, 0]],
['generated control 1', [15, 10, 15, 1500, 100, 100, 0], [150, 2, 50]],
['generated control 2', [20, 10, 15, 2400, 100, 400, 20], [1536, 4, 64]],
['generated control 3', [10, 10, 12, 4800, 100, 125, 20], [600, 5, 25]]],
[['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['regression: metres per gram', [15, 15, 12, 3000, 50, 100, 15], [575, 6, 25]],
['repair check: metres per gram', [15, 12, 12, 1500, 50, 150, 10], [516, 4, 84]],
['generated control 1', [15, 10, 12, 4800, 50, 125, 15], [1725, 14, 25]],
['generated control 2', [10, 10, 12, 1500, 100, 150, 20], [225, 2, 75]],
['generated control 3', [20, 15, 15, 6000, 50, 150, 20], [1920, 13, 30]]],
[['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['regression: metres per gram', [10, 12, 10, 1500, 50, 200, 10], [550, 3, 50]],
['repair check: metres per gram', [20, 12, 12, 1500, 50, 200, 20], [1000, 5, 0]],
['generated control 1', [10, 12, 12, 1500, 100, 125, 0], [130, 2, 120]],
['generated control 2', [12, 15, 15, 9000, 50, 125, 20], [1440, 12, 60]],
['generated control 3', [20, 10, 10, 6000, 50, 125, 0], [3000, 24, 0]]]]
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 |
|---|---|---|---|
| exact two skeins | [100, 1, 100] | [400, 2, 0] | Failed |
| with margin | [211, 2, 39] | [1320, 11, 55] | Failed |
| zero swatch | error: swatch | error: swatch | Passed |
| regression: metres per gram | [240, 2, 160] | [960, 5, 40] | Failed |
| repair check: metres per gram | [60, 1, 140] | [960, 5, 40] | Failed |
| generated control 1 | [440, 5, 60] | [440, 5, 60] | Passed |
| generated control 2 | [120, 1, 80] | [1920, 10, 80] | Failed |
| generated control 3 | [576, 4, 24] | [1296, 9, 54] | Failed |
SHA-256 / f2035787def649b648ef8206da77de5250cdbe071909e42fd112b0981316733c
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(swatch_g, swatch_w_cm, swatch_h_cm, garment_area_cm2, skein_g, skein_m, margin_pct):
sw_area = Fraction(str(swatch_w_cm)) * Fraction(str(swatch_h_cm))
if sw_area <= 0 or skein_g <= 0:
return 'error: swatch'
grams = Fraction(str(garment_area_cm2)) / sw_area * Fraction(str(swatch_g))
meters = grams * Fraction(skein_m, 100)
meters = meters * (1 + Fraction(margin_pct, 100))
skeins = math.ceil(meters / skein_m)
leftover = skeins * skein_m - meters
return [math.floor(meters + Fraction(1, 2)), skeins, math.floor(leftover + Fraction(1, 2))]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['regression: metres per gram', [15, 10, 15, 4800, 100, 200, 0], [960, 5, 40]],
['repair check: metres per gram', [12, 10, 15, 3000, 50, 200, 0], [960, 5, 40]],
['generated control 1', [20, 10, 10, 2000, 100, 100, 10], [440, 5, 60]],
['generated control 2', [12, 12, 12, 4800, 50, 200, 20], [1920, 10, 80]],
['generated control 3', [12, 15, 10, 9000, 100, 150, 20], [1296, 9, 54]]],
[['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['regression: metres per gram', [12, 12, 15, 4800, 100, 150, 10], [528, 4, 72]],
['repair check: metres per gram', [8, 15, 12, 4800, 50, 100, 10], [469, 5, 31]],
['generated control 1', [20, 12, 10, 2000, 100, 400, 10], [1467, 4, 133]],
['generated control 2', [8, 10, 15, 1500, 100, 150, 15], [138, 1, 12]],
['generated control 3', [15, 12, 15, 2400, 100, 125, 10], [275, 3, 100]]],
[['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['regression: metres per gram', [12, 12, 10, 2400, 100, 125, 15], [345, 3, 30]],
['repair check: metres per gram', [15, 15, 12, 2000, 50, 400, 20], [1600, 4, 0]],
['generated control 1', [15, 10, 15, 1500, 100, 100, 0], [150, 2, 50]],
['generated control 2', [20, 10, 15, 2400, 100, 400, 20], [1536, 4, 64]],
['generated control 3', [10, 10, 12, 4800, 100, 125, 20], [600, 5, 25]]],
[['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['regression: metres per gram', [15, 15, 12, 3000, 50, 100, 15], [575, 6, 25]],
['repair check: metres per gram', [15, 12, 12, 1500, 50, 150, 10], [516, 4, 84]],
['generated control 1', [15, 10, 12, 4800, 50, 125, 15], [1725, 14, 25]],
['generated control 2', [10, 10, 12, 1500, 100, 150, 20], [225, 2, 75]],
['generated control 3', [20, 15, 15, 6000, 50, 150, 20], [1920, 13, 30]]],
[['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['regression: metres per gram', [10, 12, 10, 1500, 50, 200, 10], [550, 3, 50]],
['repair check: metres per gram', [20, 12, 12, 1500, 50, 200, 20], [1000, 5, 0]],
['generated control 1', [10, 12, 12, 1500, 100, 125, 0], [130, 2, 120]],
['generated control 2', [12, 15, 15, 9000, 50, 125, 20], [1440, 12, 60]],
['generated control 3', [20, 10, 10, 6000, 50, 125, 0], [3000, 24, 0]]]]
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 |
|---|---|---|---|
| exact two skeins | [400, 2, 0] | [400, 2, 0] | Passed |
| with margin | [660, 6, 90] | [1320, 11, 55] | Failed |
| zero swatch | error: swatch | error: swatch | Passed |
| regression: metres per gram | [960, 5, 40] | [960, 5, 40] | Passed |
| repair check: metres per gram | [480, 3, 120] | [960, 5, 40] | Failed |
| generated control 1 | [440, 5, 60] | [440, 5, 60] | Passed |
| generated control 2 | [960, 5, 40] | [1920, 10, 80] | Failed |
| generated control 3 | [1296, 9, 54] | [1296, 9, 54] | Passed |
SHA-256 / d925c732f39c39480d61905dc04d56cf6ab69aac0264f6194e118f4e750e0ce7
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(swatch_g, swatch_w_cm, swatch_h_cm, garment_area_cm2, skein_g, skein_m, margin_pct):
sw_area = Fraction(str(swatch_w_cm)) * Fraction(str(swatch_h_cm))
if sw_area <= 0 or skein_g <= 0:
return 'error: swatch'
grams = Fraction(str(garment_area_cm2)) / sw_area * Fraction(str(swatch_g))
meters = grams * Fraction(skein_m, skein_g)
meters = meters * (1 + Fraction(margin_pct, 100))
skeins = math.ceil(meters / skein_m)
leftover = skeins * skein_m - meters
return [math.floor(meters + Fraction(1, 2)), skeins, math.floor(leftover + Fraction(1, 2))]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['regression: metres per gram', [15, 10, 15, 4800, 100, 200, 0], [960, 5, 40]],
['repair check: metres per gram', [12, 10, 15, 3000, 50, 200, 0], [960, 5, 40]],
['generated control 1', [20, 10, 10, 2000, 100, 100, 10], [440, 5, 60]],
['generated control 2', [12, 12, 12, 4800, 50, 200, 20], [1920, 10, 80]],
['generated control 3', [12, 15, 10, 9000, 100, 150, 20], [1296, 9, 54]]],
[['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['regression: metres per gram', [12, 12, 15, 4800, 100, 150, 10], [528, 4, 72]],
['repair check: metres per gram', [8, 15, 12, 4800, 50, 100, 10], [469, 5, 31]],
['generated control 1', [20, 12, 10, 2000, 100, 400, 10], [1467, 4, 133]],
['generated control 2', [8, 10, 15, 1500, 100, 150, 15], [138, 1, 12]],
['generated control 3', [15, 12, 15, 2400, 100, 125, 10], [275, 3, 100]]],
[['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['regression: metres per gram', [12, 12, 10, 2400, 100, 125, 15], [345, 3, 30]],
['repair check: metres per gram', [15, 15, 12, 2000, 50, 400, 20], [1600, 4, 0]],
['generated control 1', [15, 10, 15, 1500, 100, 100, 0], [150, 2, 50]],
['generated control 2', [20, 10, 15, 2400, 100, 400, 20], [1536, 4, 64]],
['generated control 3', [10, 10, 12, 4800, 100, 125, 20], [600, 5, 25]]],
[['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['regression: metres per gram', [15, 15, 12, 3000, 50, 100, 15], [575, 6, 25]],
['repair check: metres per gram', [15, 12, 12, 1500, 50, 150, 10], [516, 4, 84]],
['generated control 1', [15, 10, 12, 4800, 50, 125, 15], [1725, 14, 25]],
['generated control 2', [10, 10, 12, 1500, 100, 150, 20], [225, 2, 75]],
['generated control 3', [20, 15, 15, 6000, 50, 150, 20], [1920, 13, 30]]],
[['with margin', [12, 10, 12, 4800, 50, 125, 10], [1320, 11, 55]],
['zero swatch', [10, 0, 10, 2000, 100, 200, 0], 'error: swatch'],
['exact two skeins', [10, 10, 10, 2000, 100, 200, 0], [400, 2, 0]],
['regression: metres per gram', [10, 12, 10, 1500, 50, 200, 10], [550, 3, 50]],
['repair check: metres per gram', [20, 12, 12, 1500, 50, 200, 20], [1000, 5, 0]],
['generated control 1', [10, 12, 12, 1500, 100, 125, 0], [130, 2, 120]],
['generated control 2', [12, 15, 15, 9000, 50, 125, 20], [1440, 12, 60]],
['generated control 3', [20, 10, 10, 6000, 50, 125, 0], [3000, 24, 0]]]]
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 |
|---|---|---|---|
| exact two skeins | [400, 2, 0] | [400, 2, 0] | Passed |
| with margin | [1320, 11, 55] | [1320, 11, 55] | Passed |
| zero swatch | error: swatch | error: swatch | Passed |
| regression: metres per gram | [960, 5, 40] | [960, 5, 40] | Passed |
| repair check: metres per gram | [960, 5, 40] | [960, 5, 40] | Passed |
| generated control 1 | [440, 5, 60] | [440, 5, 60] | Passed |
| generated control 2 | [1920, 10, 80] | [1920, 10, 80] | Passed |
| generated control 3 | [1296, 9, 54] | [1296, 9, 54] | Passed |
SHA-256 / afc6796370a7d77ed5e2a960d69afb1dba6f7de9744817219d163bb59bf7093d
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:31.958509+00:00.
Case digest / 5e37d4e022200a264363b0145f4b936ecece4eddbf64d3d6c75c2b0b11c2d9f5