FAILURE MAP
← Case archive

FA-72711 / Check-digit algorithms / Open access

Luhn mod N generation starts with factor one · case 01

Generated check characters fail validation.

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

ROOT CAUSE

Generation starts the factor at 1, the validation phase, although the check character is not yet present.

VERIFIED REPAIR

Start at factor 2 for the rightmost payload character.

Unsuccessful approach: Choosing the start factor from the payload length parity only works for half of the lengths.

Case contract

Luhn mod N check-character generation over the base-36 alphabet 0-9a-z (input is lower-cased; empty input or any other character returns None). From the rightmost payload character the factor alternates 2,1,2,...; each addend factor * codepoint is folded as addend // 36 + addend % 36; the check is the alphabet character at (36 - sum % 36) % 36.

Why this case matters

Voucher and licence-key systems protect alphanumeric codes with Luhn mod N check characters.

1 / The failure

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

N = 1
observations = []
def solve(s):
    A = '0123456789abcdefghijklmnopqrstuvwxyz'
    n = len(A)
    t = s.lower()
    if not t or any(ch not in A for ch in t):
        return None
    factor = 1
    total = 0
    for ch in reversed(t):
        addend = factor * A.index(ch)
        factor = 1 if factor == 2 else 2
        total += addend // n + addend % n
    return A[(n - total % n) % n]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["lyx7g3y"]', ['lyx7g3y'], 'x'], ['regression ["1"]', ['1'], 'y'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None]], [['regression ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['regression ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1']], [['regression ["iu2qty"]', ['iu2qty'], 'k'], ['regression ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["um"]', ['um'], 'x'], ['partial-repair ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6']], [['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['regression ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None]], [['regression ["um"]', ['um'], 'x'], ['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2']]]
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 ["lyx7g3y"]nxFailed
regression ["1"]zyFailed
partial-repair ["s8zc"]w5Failed
partial-repair ["i3wknpx64b"]zaFailed
control ["0rr"]qqPassed
control ["z"]11Passed
control ["zz"]22Passed
control ["abc-1"]NoneNonePassed

SHA-256 / 5e59c88255dc93942ac5d936cd9afb377a4e763bb6c9741112affef65e5fcd51

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(s):
    A = '0123456789abcdefghijklmnopqrstuvwxyz'
    n = len(A)
    t = s.lower()
    if not t or any(ch not in A for ch in t):
        return None
    factor = 2 if len(t) % 2 else 1
    total = 0
    for ch in reversed(t):
        addend = factor * A.index(ch)
        factor = 1 if factor == 2 else 2
        total += addend // n + addend % n
    return A[(n - total % n) % n]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["lyx7g3y"]', ['lyx7g3y'], 'x'], ['regression ["1"]', ['1'], 'y'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None]], [['regression ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['regression ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1']], [['regression ["iu2qty"]', ['iu2qty'], 'k'], ['regression ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["um"]', ['um'], 'x'], ['partial-repair ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6']], [['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['regression ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None]], [['regression ["um"]', ['um'], 'x'], ['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2']]]
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 ["lyx7g3y"]xxPassed
regression ["1"]yyPassed
partial-repair ["s8zc"]w5Failed
partial-repair ["i3wknpx64b"]zaFailed
control ["0rr"]qqPassed
control ["z"]11Passed
control ["zz"]22Passed
control ["abc-1"]NoneNonePassed

SHA-256 / 13443148cd54d76154d093045ec334b6c751c897d1cc4051b2a5da38afd82662

3 / The verified repair

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

N = 1
observations = []
def solve(s):
    A = '0123456789abcdefghijklmnopqrstuvwxyz'
    n = len(A)
    t = s.lower()
    if not t or any(ch not in A for ch in t):
        return None
    factor = 2
    total = 0
    for ch in reversed(t):
        addend = factor * A.index(ch)
        factor = 1 if factor == 2 else 2
        total += addend // n + addend % n
    return A[(n - total % n) % n]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["lyx7g3y"]', ['lyx7g3y'], 'x'], ['regression ["1"]', ['1'], 'y'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None]], [['regression ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['regression ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1']], [['regression ["iu2qty"]', ['iu2qty'], 'k'], ['regression ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["um"]', ['um'], 'x'], ['partial-repair ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6']], [['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['regression ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None]], [['regression ["um"]', ['um'], 'x'], ['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2']]]
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 ["lyx7g3y"]xxPassed
regression ["1"]yyPassed
partial-repair ["s8zc"]55Passed
partial-repair ["i3wknpx64b"]aaPassed
control ["0rr"]qqPassed
control ["z"]11Passed
control ["zz"]22Passed
control ["abc-1"]NoneNonePassed

SHA-256 / 108df848c30b6b43819a8ba4a6810f8780fdd5b9f824bc49cbd4900c43456098

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:40.886715+00:00.

Case digest / ba96a34b910545dc055708bfd36e426e759d3d87b687bf24753f57a2d344b531