FAILURE MAP
← Case archive

FA-96426 / Phone number formatting / Open access

Vanity keypad translator: lowercase letters are dropped · case 01

"(555) quizzap" is rejected as too short.

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

ROOT CAUSE

Input is not uppercased before table lookup, so lowercase letters match no key.

VERIFIED REPAIR

Uppercase the input first.

Unsuccessful approach: Capitalizing only the first character leaves later lowercase letters unmatched.

Case contract

Uppercase. Digits are kept, letters map to keypad digits (ABC2 DEF3 GHI4 JKL5 MNO6 PQRS7 TUV8 WXYZ9), spaces, hyphens, dots and parentheses are ignored and anything else returns None. At least ten symbols are needed; symbols after the tenth may only be letters (vanity overflow) and are dropped. Return the first ten digits as "DDD-DDD-DDDD".

Why this case matters

Vanity numbers printed in advertising are translated before dialing or storing; a wrong keypad table dials a stranger.

1 / The failure

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

N = 1
observations = []
def solve(x):
    KEYS = {'ABC': '2', 'DEF': '3', 'GHI': '4', 'JKL': '5', 'MNO': '6', 'PQRS': '7', 'TUV': '8', 'WXYZ': '9'}
    out = []
    for c in x:
        if c.isdigit():
            out.append(c)
        elif c.isalpha():
            for letters, digit in KEYS.items():
                if c in letters:
                    out.append(digit + '*')
                    break
        elif c not in ' -.()':
            return None
    if len(out) < 10:
        return None
    if any(not t.endswith('*') for t in out[10:]):
        return None
    d = ''.join(t[0] for t in out[:10])
    return d[:3] + '-' + d[3:6] + '-' + d[6:]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['letters with Q and Z', '555-QUIZZAP', '555-784-9927'], ['lowercase letters', '(555) quizzap', '555-784-9927'], ['dotted vanity number', '555.QUIZZAP', '555-784-9927'], ['extra vanity letter is ignored', '555-QUIZZAPS', '555-784-9927'], ['extra digit is rejected', '555QUIZZAP9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZAP', None]], [['letters with Q and Z', '555-QUIZZIP', '555-784-9947'], ['lowercase letters', '(555) quizzip', '555-784-9947'], ['dotted vanity number', '555.QUIZZIP', '555-784-9947'], ['extra vanity letter is ignored', '555-QUIZZIPS', '555-784-9947'], ['extra digit is rejected', '555QUIZZIP9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZIP', None]], [['letters with Q and Z', '555-QUIZZOO', '555-784-9966'], ['lowercase letters', '(555) quizzoo', '555-784-9966'], ['dotted vanity number', '555.QUIZZOO', '555-784-9966'], ['extra vanity letter is ignored', '555-QUIZZOOS', '555-784-9966'], ['extra digit is rejected', '555QUIZZOO9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZOO', None]], [['letters with Q and Z', '555-QUIZZEN', '555-784-9936'], ['lowercase letters', '(555) quizzen', '555-784-9936'], ['dotted vanity number', '555.QUIZZEN', '555-784-9936'], ['extra vanity letter is ignored', '555-QUIZZENS', '555-784-9936'], ['extra digit is rejected', '555QUIZZEN9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZEN', None]], [['letters with Q and Z', '555-QUIZZED', '555-784-9933'], ['lowercase letters', '(555) quizzed', '555-784-9933'], ['dotted vanity number', '555.QUIZZED', '555-784-9933'], ['extra vanity letter is ignored', '555-QUIZZEDS', '555-784-9933'], ['extra digit is rejected', '555QUIZZED9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZED', None]]]
for label, arg, expected in fixtures[N - 1]:
    check(label, solve(arg), 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
letters with Q and Z555-784-9927555-784-9927Passed
lowercase lettersNone555-784-9927Failed
dotted vanity number555-784-9927555-784-9927Passed
extra vanity letter is ignored555-784-9927555-784-9927Passed
extra digit is rejectedNoneNonePassed
too shortNoneNonePassed
slash rejectedNoneNonePassed

SHA-256 / b43712a64405dce365767ebc4fec9c02588291bdfe63bfb8065caa8668699abb

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    KEYS = {'ABC': '2', 'DEF': '3', 'GHI': '4', 'JKL': '5', 'MNO': '6', 'PQRS': '7', 'TUV': '8', 'WXYZ': '9'}
    out = []
    for c in x.capitalize():
        if c.isdigit():
            out.append(c)
        elif c.isalpha():
            for letters, digit in KEYS.items():
                if c in letters:
                    out.append(digit + '*')
                    break
        elif c not in ' -.()':
            return None
    if len(out) < 10:
        return None
    if any(not t.endswith('*') for t in out[10:]):
        return None
    d = ''.join(t[0] for t in out[:10])
    return d[:3] + '-' + d[3:6] + '-' + d[6:]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['letters with Q and Z', '555-QUIZZAP', '555-784-9927'], ['lowercase letters', '(555) quizzap', '555-784-9927'], ['dotted vanity number', '555.QUIZZAP', '555-784-9927'], ['extra vanity letter is ignored', '555-QUIZZAPS', '555-784-9927'], ['extra digit is rejected', '555QUIZZAP9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZAP', None]], [['letters with Q and Z', '555-QUIZZIP', '555-784-9947'], ['lowercase letters', '(555) quizzip', '555-784-9947'], ['dotted vanity number', '555.QUIZZIP', '555-784-9947'], ['extra vanity letter is ignored', '555-QUIZZIPS', '555-784-9947'], ['extra digit is rejected', '555QUIZZIP9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZIP', None]], [['letters with Q and Z', '555-QUIZZOO', '555-784-9966'], ['lowercase letters', '(555) quizzoo', '555-784-9966'], ['dotted vanity number', '555.QUIZZOO', '555-784-9966'], ['extra vanity letter is ignored', '555-QUIZZOOS', '555-784-9966'], ['extra digit is rejected', '555QUIZZOO9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZOO', None]], [['letters with Q and Z', '555-QUIZZEN', '555-784-9936'], ['lowercase letters', '(555) quizzen', '555-784-9936'], ['dotted vanity number', '555.QUIZZEN', '555-784-9936'], ['extra vanity letter is ignored', '555-QUIZZENS', '555-784-9936'], ['extra digit is rejected', '555QUIZZEN9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZEN', None]], [['letters with Q and Z', '555-QUIZZED', '555-784-9933'], ['lowercase letters', '(555) quizzed', '555-784-9933'], ['dotted vanity number', '555.QUIZZED', '555-784-9933'], ['extra vanity letter is ignored', '555-QUIZZEDS', '555-784-9933'], ['extra digit is rejected', '555QUIZZED9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZED', None]]]
for label, arg, expected in fixtures[N - 1]:
    check(label, solve(arg), 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
letters with Q and ZNone555-784-9927Failed
lowercase lettersNone555-784-9927Failed
dotted vanity numberNone555-784-9927Failed
extra vanity letter is ignoredNone555-784-9927Failed
extra digit is rejectedNoneNonePassed
too shortNoneNonePassed
slash rejectedNoneNonePassed

SHA-256 / 41af6bf564bec2937725e20f8dd607b169413f6ab0633bd5c49e351e2dd8956e

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    KEYS = {'ABC': '2', 'DEF': '3', 'GHI': '4', 'JKL': '5', 'MNO': '6', 'PQRS': '7', 'TUV': '8', 'WXYZ': '9'}
    out = []
    for c in x.upper():
        if c.isdigit():
            out.append(c)
        elif c.isalpha():
            for letters, digit in KEYS.items():
                if c in letters:
                    out.append(digit + '*')
                    break
        elif c not in ' -.()':
            return None
    if len(out) < 10:
        return None
    if any(not t.endswith('*') for t in out[10:]):
        return None
    d = ''.join(t[0] for t in out[:10])
    return d[:3] + '-' + d[3:6] + '-' + d[6:]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['letters with Q and Z', '555-QUIZZAP', '555-784-9927'], ['lowercase letters', '(555) quizzap', '555-784-9927'], ['dotted vanity number', '555.QUIZZAP', '555-784-9927'], ['extra vanity letter is ignored', '555-QUIZZAPS', '555-784-9927'], ['extra digit is rejected', '555QUIZZAP9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZAP', None]], [['letters with Q and Z', '555-QUIZZIP', '555-784-9947'], ['lowercase letters', '(555) quizzip', '555-784-9947'], ['dotted vanity number', '555.QUIZZIP', '555-784-9947'], ['extra vanity letter is ignored', '555-QUIZZIPS', '555-784-9947'], ['extra digit is rejected', '555QUIZZIP9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZIP', None]], [['letters with Q and Z', '555-QUIZZOO', '555-784-9966'], ['lowercase letters', '(555) quizzoo', '555-784-9966'], ['dotted vanity number', '555.QUIZZOO', '555-784-9966'], ['extra vanity letter is ignored', '555-QUIZZOOS', '555-784-9966'], ['extra digit is rejected', '555QUIZZOO9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZOO', None]], [['letters with Q and Z', '555-QUIZZEN', '555-784-9936'], ['lowercase letters', '(555) quizzen', '555-784-9936'], ['dotted vanity number', '555.QUIZZEN', '555-784-9936'], ['extra vanity letter is ignored', '555-QUIZZENS', '555-784-9936'], ['extra digit is rejected', '555QUIZZEN9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZEN', None]], [['letters with Q and Z', '555-QUIZZED', '555-784-9933'], ['lowercase letters', '(555) quizzed', '555-784-9933'], ['dotted vanity number', '555.QUIZZED', '555-784-9933'], ['extra vanity letter is ignored', '555-QUIZZEDS', '555-784-9933'], ['extra digit is rejected', '555QUIZZED9', None], ['too short', '555-FAKE', None], ['slash rejected', '555/QUIZZED', None]]]
for label, arg, expected in fixtures[N - 1]:
    check(label, solve(arg), 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
letters with Q and Z555-784-9927555-784-9927Passed
lowercase letters555-784-9927555-784-9927Passed
dotted vanity number555-784-9927555-784-9927Passed
extra vanity letter is ignored555-784-9927555-784-9927Passed
extra digit is rejectedNoneNonePassed
too shortNoneNonePassed
slash rejectedNoneNonePassed

SHA-256 / daae50b438ddd3cced1c118c7ed824812a7812e6d11ff2c1580281c9ff60722b

Verification & scope

A deterministic toy contract over synthetic, obviously fictitious telephone numbers and toy country tables. The rules are stipulated for this model only and make no claim of conformance with any numbering plan or telecommunications standard. 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:22.610321+00:00.

Case digest / 281963e60e5421680dd70a596fa05d57530641559f889d1960a2eb43b2ee10ba