FAILURE MAP
← Case archive

FA-72446 / Check-digit algorithms / Open access

ISBN-13 conversion accepts an invalid ISBN-10 · case 01

Mistyped ISBN-10 values are converted into valid-looking ISBN-13 numbers.

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

ROOT CAUSE

The mod-11 check of the source ISBN-10 is skipped, so the error is laundered by the new check digit.

VERIFIED REPAIR

Reject the input unless the ISBN-10 weighted sum is divisible by 11.

Unsuccessful approach: Checking the source with mod 10 rejects valid X-terminated numbers and admits many bad ones.

Case contract

Convert a hyphenated or plain ISBN-10 to ISBN-13. The input must be a valid ISBN-10 (mod 11, X allowed as the final character) or None is returned. The result is "978" + the nine ISBN-10 payload digits + a freshly computed EAN-13 check digit (weights 1,3 alternating from the left).

Why this case matters

Library and retail systems migrate legacy ISBN-10 records to 13-digit EAN identifiers.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(s):
    t = s.replace('-', '')
    if len(t) != 10 or not t.isascii() or not t[:9].isdigit() or not (t[9].isdigit() or t[9] == 'X'):
        return None
    if sum((10 - i) * (10 if ch == 'X' else int(ch)) for i, ch in enumerate(t)) % 11 and False:
        return None
    body = '978' + t[:9]
    total = sum(int(ch) * (1 if i % 2 == 0 else 3) for i, ch in enumerate(body))
    return body + str((10 - total % 10) % 10)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["5892872930"]', ['5892872930'], None], ['regression ["2399553951"]', ['2399553951'], None], ['partial-repair ["5892872939"]', ['5892872939'], '9785892872935'], ['partial-repair ["2399553950"]', ['2399553950'], '9782399553954'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289']], [['regression ["8292844660"]', ['8292844660'], None], ['regression ["9379774827"]', ['9379774827'], None], ['partial-repair ["829284466X"]', ['829284466X'], '9788292844663'], ['partial-repair ["9379774826"]', ['9379774826'], '9789379774828'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002']], [['regression ["0306406153"]', ['0306406153'], None], ['regression ["5892872930"]', ['5892872930'], None], ['partial-repair ["2892681790"]', ['2892681790'], '9782892681796'], ['partial-repair ["5900307905"]', ['5900307905'], '9785900307909'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897']], [['regression ["9465545440"]', ['9465545440'], None], ['regression ["8292844660"]', ['8292844660'], None], ['partial-repair ["7113753523"]', ['7113753523'], '9787113753528'], ['partial-repair ["1475433131"]', ['1475433131'], '9781475433135'], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None]], [['regression ["2266685687"]', ['2266685687'], None], ['regression ["0306406153"]', ['0306406153'], None], ['partial-repair ["8449627923"]', ['8449627923'], '9788449627927'], ['partial-repair ["0436178273"]', ['0436178273'], '9780436178276'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289']]]
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
regression ["5892872930"]9785892872935NoneFailed
regression ["2399553951"]9782399553954NoneFailed
partial-repair ["5892872939"]97858928729359785892872935Passed
partial-repair ["2399553950"]97823995539549782399553954Passed
control ["0000000000"]97800000000029780000000002Passed
control ["123456789X"]97812345678979781234567897Passed
control ["12345"]NoneNonePassed
control ["1-84356-028-3"]97818435602899781843560289Passed

SHA-256 / 9d87391557de5f180bccedf89f78395016a0923ca4b3f3362a27fefbe8c646f4

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(s):
    t = s.replace('-', '')
    if len(t) != 10 or not t.isascii() or not t[:9].isdigit() or not (t[9].isdigit() or t[9] == 'X'):
        return None
    if sum((10 - i) * (10 if ch == 'X' else int(ch)) for i, ch in enumerate(t)) % 10:
        return None
    body = '978' + t[:9]
    total = sum(int(ch) * (1 if i % 2 == 0 else 3) for i, ch in enumerate(body))
    return body + str((10 - total % 10) % 10)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["5892872930"]', ['5892872930'], None], ['regression ["2399553951"]', ['2399553951'], None], ['partial-repair ["5892872939"]', ['5892872939'], '9785892872935'], ['partial-repair ["2399553950"]', ['2399553950'], '9782399553954'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289']], [['regression ["8292844660"]', ['8292844660'], None], ['regression ["9379774827"]', ['9379774827'], None], ['partial-repair ["829284466X"]', ['829284466X'], '9788292844663'], ['partial-repair ["9379774826"]', ['9379774826'], '9789379774828'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002']], [['regression ["0306406153"]', ['0306406153'], None], ['regression ["5892872930"]', ['5892872930'], None], ['partial-repair ["2892681790"]', ['2892681790'], '9782892681796'], ['partial-repair ["5900307905"]', ['5900307905'], '9785900307909'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897']], [['regression ["9465545440"]', ['9465545440'], None], ['regression ["8292844660"]', ['8292844660'], None], ['partial-repair ["7113753523"]', ['7113753523'], '9787113753528'], ['partial-repair ["1475433131"]', ['1475433131'], '9781475433135'], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None]], [['regression ["2266685687"]', ['2266685687'], None], ['regression ["0306406153"]', ['0306406153'], None], ['partial-repair ["8449627923"]', ['8449627923'], '9788449627927'], ['partial-repair ["0436178273"]', ['0436178273'], '9780436178276'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289']]]
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
regression ["5892872930"]NoneNonePassed
regression ["2399553951"]NoneNonePassed
partial-repair ["5892872939"]None9785892872935Failed
partial-repair ["2399553950"]None9782399553954Failed
control ["0000000000"]97800000000029780000000002Passed
control ["123456789X"]97812345678979781234567897Passed
control ["12345"]NoneNonePassed
control ["1-84356-028-3"]97818435602899781843560289Passed

SHA-256 / df6416e25365cadd673845614b0e06156edb1a08cde8c591b2c14edd380deb13

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(s):
    t = s.replace('-', '')
    if len(t) != 10 or not t.isascii() or not t[:9].isdigit() or not (t[9].isdigit() or t[9] == 'X'):
        return None
    if sum((10 - i) * (10 if ch == 'X' else int(ch)) for i, ch in enumerate(t)) % 11:
        return None
    body = '978' + t[:9]
    total = sum(int(ch) * (1 if i % 2 == 0 else 3) for i, ch in enumerate(body))
    return body + str((10 - total % 10) % 10)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["5892872930"]', ['5892872930'], None], ['regression ["2399553951"]', ['2399553951'], None], ['partial-repair ["5892872939"]', ['5892872939'], '9785892872935'], ['partial-repair ["2399553950"]', ['2399553950'], '9782399553954'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289']], [['regression ["8292844660"]', ['8292844660'], None], ['regression ["9379774827"]', ['9379774827'], None], ['partial-repair ["829284466X"]', ['829284466X'], '9788292844663'], ['partial-repair ["9379774826"]', ['9379774826'], '9789379774828'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002']], [['regression ["0306406153"]', ['0306406153'], None], ['regression ["5892872930"]', ['5892872930'], None], ['partial-repair ["2892681790"]', ['2892681790'], '9782892681796'], ['partial-repair ["5900307905"]', ['5900307905'], '9785900307909'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897']], [['regression ["9465545440"]', ['9465545440'], None], ['regression ["8292844660"]', ['8292844660'], None], ['partial-repair ["7113753523"]', ['7113753523'], '9787113753528'], ['partial-repair ["1475433131"]', ['1475433131'], '9781475433135'], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None]], [['regression ["2266685687"]', ['2266685687'], None], ['regression ["0306406153"]', ['0306406153'], None], ['partial-repair ["8449627923"]', ['8449627923'], '9788449627927'], ['partial-repair ["0436178273"]', ['0436178273'], '9780436178276'], ['control ["0000000000"]', ['0000000000'], '9780000000002'], ['control ["123456789X"]', ['123456789X'], '9781234567897'], ['control ["12345"]', ['12345'], None], ['control ["1-84356-028-3"]', ['1-84356-028-3'], '9781843560289']]]
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
regression ["5892872930"]NoneNonePassed
regression ["2399553951"]NoneNonePassed
partial-repair ["5892872939"]97858928729359785892872935Passed
partial-repair ["2399553950"]97823995539549782399553954Passed
control ["0000000000"]97800000000029780000000002Passed
control ["123456789X"]97812345678979781234567897Passed
control ["12345"]NoneNonePassed
control ["1-84356-028-3"]97818435602899781843560289Passed

SHA-256 / 21624018add72ffb36b09a6bf380aedd358b7c41225e90e386d2119c9e878102

Verification & scope

A deterministic, bounded teaching model of the named scheme under the stated contract; not a certified validator. 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:48:38.555836+00:00.

Case digest / 49e953aebb2c45f3c5131ebe14393f9fa900ff9a5890516ff26c49178fcbf906