FAILURE MAP
← Case archive

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.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

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 fixtureActualExpectedOutcome
exact two skeins[100, 1, 100][400, 2, 0]Failed
with margin[211, 2, 39][1320, 11, 55]Failed
zero swatcherror: swatcherror: swatchPassed
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 fixtureActualExpectedOutcome
exact two skeins[400, 2, 0][400, 2, 0]Passed
with margin[660, 6, 90][1320, 11, 55]Failed
zero swatcherror: swatcherror: swatchPassed
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 fixtureActualExpectedOutcome
exact two skeins[400, 2, 0][400, 2, 0]Passed
with margin[1320, 11, 55][1320, 11, 55]Passed
zero swatcherror: swatcherror: swatchPassed
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