FAILURE MAP
← Case archive

FA-96586 / Phone number formatting / Open access

tel URI builder: local numbers are emitted without a context · case 01

"5550115" becomes "tel:5550115" with no phone-context.

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

ROOT CAUSE

The missing-context rejection is absent.

THE FAILURE

The missing-context rejection is absent.

Unsuccessful approach: Requiring a context only for multi-group local numbers still emits unqualified single-group numbers.

Case contract

Input {"number", optional "ext", optional "context"}. A number starting with "+" is global. Digits are grouped at runs of visual separators (- . space parentheses); any other character returns None. Build "tel:" + ("+" if global) + groups joined by "-", then ";ext=" + digits when an extension is given, then for local numbers ";phone-context=" + context (required).

Why this case matters

tel URIs are exchanged between softphones, CRMs and SIP gateways; a missing context or misordered parameter makes local numbers undialable.

1 / The failure

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

N = 1
observations = []
def solve(x):
    num, ext, ctx = x['number'].strip(), x.get('ext', ''), x.get('context', '')
    glob = num.startswith('+')
    body = num[1:] if glob else num
    parts, cur = [], ''
    for c in body:
        if c.isdigit():
            cur += c
        elif c in '-. ()':
            if cur:
                parts.append(cur)
                cur = ''
        else:
            return None
    if cur:
        parts.append(cur)
    if not parts:
        return None
    uri = 'tel:' + ('+' if glob else '') + '-'.join(parts)
    if ext:
        if not ext.isdigit():
            return None
        uri += ';ext=' + ext
    if not glob:
        uri += ';phone-context=' + ctx
    return uri
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['global number', {'number': '+42 555-010-1211'}, 'tel:+42-555-010-1211'], ['local number with context', {'number': '555-0114', 'context': '+42'}, 'tel:555-0114;phone-context=+42'], ['local number with extension', {'number': '555-0114', 'ext': '12', 'context': '+42'}, 'tel:555-0114;ext=12;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0114', 'ext': '1'}, 'tel:+42-555-0114;ext=1'], ['local without context', {'number': '5550114'}, None], ['run of separators', {'number': '+42 555 - 0114'}, 'tel:+42-555-0114'], ['slash is not a visual separator', {'number': '+42 555/0114'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0114', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1222'}, 'tel:+42-555-010-1222'], ['local number with context', {'number': '555-0125', 'context': '+42'}, 'tel:555-0125;phone-context=+42'], ['local number with extension', {'number': '555-0125', 'ext': '22', 'context': '+42'}, 'tel:555-0125;ext=22;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0125', 'ext': '2'}, 'tel:+42-555-0125;ext=2'], ['local without context', {'number': '5550125'}, None], ['run of separators', {'number': '+42 555 - 0125'}, 'tel:+42-555-0125'], ['slash is not a visual separator', {'number': '+42 555/0125'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0125', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1233'}, 'tel:+42-555-010-1233'], ['local number with context', {'number': '555-0136', 'context': '+42'}, 'tel:555-0136;phone-context=+42'], ['local number with extension', {'number': '555-0136', 'ext': '32', 'context': '+42'}, 'tel:555-0136;ext=32;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0136', 'ext': '3'}, 'tel:+42-555-0136;ext=3'], ['local without context', {'number': '5550136'}, None], ['run of separators', {'number': '+42 555 - 0136'}, 'tel:+42-555-0136'], ['slash is not a visual separator', {'number': '+42 555/0136'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0136', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1244'}, 'tel:+42-555-010-1244'], ['local number with context', {'number': '555-0147', 'context': '+42'}, 'tel:555-0147;phone-context=+42'], ['local number with extension', {'number': '555-0147', 'ext': '42', 'context': '+42'}, 'tel:555-0147;ext=42;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0147', 'ext': '4'}, 'tel:+42-555-0147;ext=4'], ['local without context', {'number': '5550147'}, None], ['run of separators', {'number': '+42 555 - 0147'}, 'tel:+42-555-0147'], ['slash is not a visual separator', {'number': '+42 555/0147'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0147', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1255'}, 'tel:+42-555-010-1255'], ['local number with context', {'number': '555-0158', 'context': '+42'}, 'tel:555-0158;phone-context=+42'], ['local number with extension', {'number': '555-0158', 'ext': '52', 'context': '+42'}, 'tel:555-0158;ext=52;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0158', 'ext': '5'}, 'tel:+42-555-0158;ext=5'], ['local without context', {'number': '5550158'}, None], ['run of separators', {'number': '+42 555 - 0158'}, 'tel:+42-555-0158'], ['slash is not a visual separator', {'number': '+42 555/0158'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0158', 'ext': '1a'}, 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
global numbertel:+42-555-010-1211tel:+42-555-010-1211Passed
local number with contexttel:555-0114;phone-context=+42tel:555-0114;phone-context=+42Passed
local number with extensiontel:555-0114;ext=12;phone-context=+42tel:555-0114;ext=12;phone-context=+42Passed
global with extensiontel:+42-555-0114;ext=1tel:+42-555-0114;ext=1Passed
local without contexttel:5550114;phone-context=NoneFailed
run of separatorstel:+42-555-0114tel:+42-555-0114Passed
slash is not a visual separatorNoneNonePassed
letters rejectedNoneNonePassed
non-numeric extensionNoneNonePassed

SHA-256 / 00b6f8cf86211284e6e5f4df7f4a3aca91ef495eee87e1942a768f63f3ab6ce4

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    num, ext, ctx = x['number'].strip(), x.get('ext', ''), x.get('context', '')
    glob = num.startswith('+')
    body = num[1:] if glob else num
    parts, cur = [], ''
    for c in body:
        if c.isdigit():
            cur += c
        elif c in '-. ()':
            if cur:
                parts.append(cur)
                cur = ''
        else:
            return None
    if cur:
        parts.append(cur)
    if not parts:
        return None
    uri = 'tel:' + ('+' if glob else '') + '-'.join(parts)
    if ext:
        if not ext.isdigit():
            return None
        uri += ';ext=' + ext
    if not glob:
        if not ctx and len(parts) > 1:
            return None
        uri += ';phone-context=' + ctx
    return uri
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['global number', {'number': '+42 555-010-1211'}, 'tel:+42-555-010-1211'], ['local number with context', {'number': '555-0114', 'context': '+42'}, 'tel:555-0114;phone-context=+42'], ['local number with extension', {'number': '555-0114', 'ext': '12', 'context': '+42'}, 'tel:555-0114;ext=12;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0114', 'ext': '1'}, 'tel:+42-555-0114;ext=1'], ['local without context', {'number': '5550114'}, None], ['run of separators', {'number': '+42 555 - 0114'}, 'tel:+42-555-0114'], ['slash is not a visual separator', {'number': '+42 555/0114'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0114', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1222'}, 'tel:+42-555-010-1222'], ['local number with context', {'number': '555-0125', 'context': '+42'}, 'tel:555-0125;phone-context=+42'], ['local number with extension', {'number': '555-0125', 'ext': '22', 'context': '+42'}, 'tel:555-0125;ext=22;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0125', 'ext': '2'}, 'tel:+42-555-0125;ext=2'], ['local without context', {'number': '5550125'}, None], ['run of separators', {'number': '+42 555 - 0125'}, 'tel:+42-555-0125'], ['slash is not a visual separator', {'number': '+42 555/0125'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0125', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1233'}, 'tel:+42-555-010-1233'], ['local number with context', {'number': '555-0136', 'context': '+42'}, 'tel:555-0136;phone-context=+42'], ['local number with extension', {'number': '555-0136', 'ext': '32', 'context': '+42'}, 'tel:555-0136;ext=32;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0136', 'ext': '3'}, 'tel:+42-555-0136;ext=3'], ['local without context', {'number': '5550136'}, None], ['run of separators', {'number': '+42 555 - 0136'}, 'tel:+42-555-0136'], ['slash is not a visual separator', {'number': '+42 555/0136'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0136', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1244'}, 'tel:+42-555-010-1244'], ['local number with context', {'number': '555-0147', 'context': '+42'}, 'tel:555-0147;phone-context=+42'], ['local number with extension', {'number': '555-0147', 'ext': '42', 'context': '+42'}, 'tel:555-0147;ext=42;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0147', 'ext': '4'}, 'tel:+42-555-0147;ext=4'], ['local without context', {'number': '5550147'}, None], ['run of separators', {'number': '+42 555 - 0147'}, 'tel:+42-555-0147'], ['slash is not a visual separator', {'number': '+42 555/0147'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0147', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1255'}, 'tel:+42-555-010-1255'], ['local number with context', {'number': '555-0158', 'context': '+42'}, 'tel:555-0158;phone-context=+42'], ['local number with extension', {'number': '555-0158', 'ext': '52', 'context': '+42'}, 'tel:555-0158;ext=52;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0158', 'ext': '5'}, 'tel:+42-555-0158;ext=5'], ['local without context', {'number': '5550158'}, None], ['run of separators', {'number': '+42 555 - 0158'}, 'tel:+42-555-0158'], ['slash is not a visual separator', {'number': '+42 555/0158'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0158', 'ext': '1a'}, 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
global numbertel:+42-555-010-1211tel:+42-555-010-1211Passed
local number with contexttel:555-0114;phone-context=+42tel:555-0114;phone-context=+42Passed
local number with extensiontel:555-0114;ext=12;phone-context=+42tel:555-0114;ext=12;phone-context=+42Passed
global with extensiontel:+42-555-0114;ext=1tel:+42-555-0114;ext=1Passed
local without contexttel:5550114;phone-context=NoneFailed
run of separatorstel:+42-555-0114tel:+42-555-0114Passed
slash is not a visual separatorNoneNonePassed
letters rejectedNoneNonePassed
non-numeric extensionNoneNonePassed

SHA-256 / 273bc3c26d61865ffaba2a13cf9a1df58c5564986eabbe1cdbba91557404727d

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 9 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

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

Case digest / 8c5fa95730ba6898787ba17ce81d29bd512813e5f45818cf1dbdf8c98a06db89