FA-72796 / Check-digit algorithms / Open access
Missing-digit recovery ignores the doubling at the unknown position · case 01
Unknown digits in doubled positions are recovered incorrectly.
ROOT CAUSE
The needed contribution is returned directly even when the unknown digit will be doubled.
VERIFIED REPAIR
Invert the doubling map: an even need n comes from n / 2, an odd need from (n + 9) / 2.
Unsuccessful approach: Halving only (need // 2) fails whenever the needed contribution is odd.
Case contract
Recover the single unknown digit (marked "?") in a Luhn-protected number: input must be at least two characters, ASCII digits and exactly one "?" (else None). Return the digit 0-9 that makes the whole number Luhn-valid; when the unknown sits in a doubled position the needed contribution must be mapped back through the doubling map.
Why this case matters
Customer-service tools reconstruct a smudged or misheard digit of an account number.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(s):
if len(s) < 2 or s.count('?') != 1 or not s.isascii() or not all(c.isdigit() or c == '?' for c in s):
return None
k = len(s) - 1 - s.index('?')
total = 0
for i, ch in enumerate(reversed(s)):
if ch == '?':
continue
d = int(ch)
if i % 2 == 1:
d = d * 2 - 9 if d > 4 else d * 2
total += d
need = (10 - total % 10) % 10
return need
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["7?850"]', ['7?850'], 2], ['regression ["?929942967"]', ['?929942967'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["766?9906"]', ['766?9906'], 2], ['control ["1519774?1518"]', ['1519774?1518'], 5], ['control ["049785409775?16"]', ['049785409775?16'], 5], ['control ["475646793469?297"]', ['475646793469?297'], 0], ['control ["3077312?96877436"]', ['3077312?96877436'], 1]], [['regression ["033?959838019"]', ['033?959838019'], 2], ['regression ["2288?7"]', ['2288?7'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["3102377592002?64"]', ['3102377592002?64'], 6], ['control ["08?8016"]', ['08?8016'], 8], ['control ["1507302?557627"]', ['1507302?557627'], 8], ['control ["7992739871?"]', ['7992739871?'], 3], ['control ["?9927398713"]', ['?9927398713'], 7]], [['regression ["4111111111?11111"]', ['4111111111?11111'], 1], ['regression ["7?850"]', ['7?850'], 2], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["??"]', ['??'], None], ['control ["1"]', ['1'], None], ['control ["12a?"]', ['12a?'], None], ['control ["?"]', ['?'], None], ['control ["?0"]', ['?0'], 0]], [['regression ["6?0334555"]', ['6?0334555'], 3], ['regression ["033?959838019"]', ['033?959838019'], 2], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["0?"]', ['0?'], 0], ['control ["766?9906"]', ['766?9906'], 2], ['control ["1519774?1518"]', ['1519774?1518'], 5], ['control ["049785409775?16"]', ['049785409775?16'], 5], ['control ["475646793469?297"]', ['475646793469?297'], 0]], [['regression ["79927?98713"]', ['79927?98713'], 3], ['regression ["4111111111?11111"]', ['4111111111?11111'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["3077312?96877436"]', ['3077312?96877436'], 1], ['control ["3102377592002?64"]', ['3102377592002?64'], 6], ['control ["08?8016"]', ['08?8016'], 8], ['control ["1507302?557627"]', ['1507302?557627'], 8], ['control ["7992739871?"]', ['7992739871?'], 3]]]
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 |
|---|---|---|---|
| regression ["7?850"] | 4 | 2 | Failed |
| regression ["?929942967"] | 2 | 1 | Failed |
| partial-repair ["405529124?1"] | 9 | 9 | Passed |
| control ["766?9906"] | 2 | 2 | Passed |
| control ["1519774?1518"] | 5 | 5 | Passed |
| control ["049785409775?16"] | 5 | 5 | Passed |
| control ["475646793469?297"] | 0 | 0 | Passed |
| control ["3077312?96877436"] | 1 | 1 | Passed |
SHA-256 / a108de9727c92b992729acf3c6ae4cc473fd478cc4fae72d1b8caeeda3d65370
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(s):
if len(s) < 2 or s.count('?') != 1 or not s.isascii() or not all(c.isdigit() or c == '?' for c in s):
return None
k = len(s) - 1 - s.index('?')
total = 0
for i, ch in enumerate(reversed(s)):
if ch == '?':
continue
d = int(ch)
if i % 2 == 1:
d = d * 2 - 9 if d > 4 else d * 2
total += d
need = (10 - total % 10) % 10
if k % 2 == 1:
need = need // 2
return need
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["7?850"]', ['7?850'], 2], ['regression ["?929942967"]', ['?929942967'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["766?9906"]', ['766?9906'], 2], ['control ["1519774?1518"]', ['1519774?1518'], 5], ['control ["049785409775?16"]', ['049785409775?16'], 5], ['control ["475646793469?297"]', ['475646793469?297'], 0], ['control ["3077312?96877436"]', ['3077312?96877436'], 1]], [['regression ["033?959838019"]', ['033?959838019'], 2], ['regression ["2288?7"]', ['2288?7'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["3102377592002?64"]', ['3102377592002?64'], 6], ['control ["08?8016"]', ['08?8016'], 8], ['control ["1507302?557627"]', ['1507302?557627'], 8], ['control ["7992739871?"]', ['7992739871?'], 3], ['control ["?9927398713"]', ['?9927398713'], 7]], [['regression ["4111111111?11111"]', ['4111111111?11111'], 1], ['regression ["7?850"]', ['7?850'], 2], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["??"]', ['??'], None], ['control ["1"]', ['1'], None], ['control ["12a?"]', ['12a?'], None], ['control ["?"]', ['?'], None], ['control ["?0"]', ['?0'], 0]], [['regression ["6?0334555"]', ['6?0334555'], 3], ['regression ["033?959838019"]', ['033?959838019'], 2], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["0?"]', ['0?'], 0], ['control ["766?9906"]', ['766?9906'], 2], ['control ["1519774?1518"]', ['1519774?1518'], 5], ['control ["049785409775?16"]', ['049785409775?16'], 5], ['control ["475646793469?297"]', ['475646793469?297'], 0]], [['regression ["79927?98713"]', ['79927?98713'], 3], ['regression ["4111111111?11111"]', ['4111111111?11111'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["3077312?96877436"]', ['3077312?96877436'], 1], ['control ["3102377592002?64"]', ['3102377592002?64'], 6], ['control ["08?8016"]', ['08?8016'], 8], ['control ["1507302?557627"]', ['1507302?557627'], 8], ['control ["7992739871?"]', ['7992739871?'], 3]]]
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 |
|---|---|---|---|
| regression ["7?850"] | 2 | 2 | Passed |
| regression ["?929942967"] | 1 | 1 | Passed |
| partial-repair ["405529124?1"] | 4 | 9 | Failed |
| control ["766?9906"] | 2 | 2 | Passed |
| control ["1519774?1518"] | 5 | 5 | Passed |
| control ["049785409775?16"] | 5 | 5 | Passed |
| control ["475646793469?297"] | 0 | 0 | Passed |
| control ["3077312?96877436"] | 1 | 1 | Passed |
SHA-256 / e5a5550e0fc2b8ea9b39f74fe9bff3d5e630a55b158a918a221d33da5ad6c4ad
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(s):
if len(s) < 2 or s.count('?') != 1 or not s.isascii() or not all(c.isdigit() or c == '?' for c in s):
return None
k = len(s) - 1 - s.index('?')
total = 0
for i, ch in enumerate(reversed(s)):
if ch == '?':
continue
d = int(ch)
if i % 2 == 1:
d = d * 2 - 9 if d > 4 else d * 2
total += d
need = (10 - total % 10) % 10
if k % 2 == 1:
need = need // 2 if need % 2 == 0 else (need + 9) // 2
return need
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["7?850"]', ['7?850'], 2], ['regression ["?929942967"]', ['?929942967'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["766?9906"]', ['766?9906'], 2], ['control ["1519774?1518"]', ['1519774?1518'], 5], ['control ["049785409775?16"]', ['049785409775?16'], 5], ['control ["475646793469?297"]', ['475646793469?297'], 0], ['control ["3077312?96877436"]', ['3077312?96877436'], 1]], [['regression ["033?959838019"]', ['033?959838019'], 2], ['regression ["2288?7"]', ['2288?7'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["3102377592002?64"]', ['3102377592002?64'], 6], ['control ["08?8016"]', ['08?8016'], 8], ['control ["1507302?557627"]', ['1507302?557627'], 8], ['control ["7992739871?"]', ['7992739871?'], 3], ['control ["?9927398713"]', ['?9927398713'], 7]], [['regression ["4111111111?11111"]', ['4111111111?11111'], 1], ['regression ["7?850"]', ['7?850'], 2], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["??"]', ['??'], None], ['control ["1"]', ['1'], None], ['control ["12a?"]', ['12a?'], None], ['control ["?"]', ['?'], None], ['control ["?0"]', ['?0'], 0]], [['regression ["6?0334555"]', ['6?0334555'], 3], ['regression ["033?959838019"]', ['033?959838019'], 2], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["0?"]', ['0?'], 0], ['control ["766?9906"]', ['766?9906'], 2], ['control ["1519774?1518"]', ['1519774?1518'], 5], ['control ["049785409775?16"]', ['049785409775?16'], 5], ['control ["475646793469?297"]', ['475646793469?297'], 0]], [['regression ["79927?98713"]', ['79927?98713'], 3], ['regression ["4111111111?11111"]', ['4111111111?11111'], 1], ['partial-repair ["405529124?1"]', ['405529124?1'], 9], ['control ["3077312?96877436"]', ['3077312?96877436'], 1], ['control ["3102377592002?64"]', ['3102377592002?64'], 6], ['control ["08?8016"]', ['08?8016'], 8], ['control ["1507302?557627"]', ['1507302?557627'], 8], ['control ["7992739871?"]', ['7992739871?'], 3]]]
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 |
|---|---|---|---|
| regression ["7?850"] | 2 | 2 | Passed |
| regression ["?929942967"] | 1 | 1 | Passed |
| partial-repair ["405529124?1"] | 9 | 9 | Passed |
| control ["766?9906"] | 2 | 2 | Passed |
| control ["1519774?1518"] | 5 | 5 | Passed |
| control ["049785409775?16"] | 5 | 5 | Passed |
| control ["475646793469?297"] | 0 | 0 | Passed |
| control ["3077312?96877436"] | 1 | 1 | Passed |
SHA-256 / 7c6c7348ecc0a2c58b2396f61bb2012617e53ede07d60cd170626c224a66f5ae
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:41.631091+00:00.
Case digest / 0c80b78f867da4379b8a3ddf12b9b55062e41536e3fcd9129562b99cd5269c98