FA-96611 / Phone number formatting / Open access
tel URI builder: global numbers lose their plus sign · case 01
"+42 555..." becomes "tel:42-555...", which reads as a local number.
ROOT CAUSE
The plus sign is never written.
VERIFIED REPAIR
Write "+" for global numbers.
Unsuccessful approach: Writing "+" when a context is present marks local numbers as global and global ones as local.
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:' + '' + '-'.join(parts)
if ext:
if not ext.isdigit():
return None
uri += ';ext=' + ext
if not glob:
if not ctx:
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| global number | tel:42-555-010-1211 | tel:+42-555-010-1211 | Failed |
| local number with context | tel:555-0114;phone-context=+42 | tel:555-0114;phone-context=+42 | Passed |
| local number with extension | tel:555-0114;ext=12;phone-context=+42 | tel:555-0114;ext=12;phone-context=+42 | Passed |
| global with extension | tel:42-555-0114;ext=1 | tel:+42-555-0114;ext=1 | Failed |
| local without context | None | None | Passed |
| run of separators | tel:42-555-0114 | tel:+42-555-0114 | Failed |
| slash is not a visual separator | None | None | Passed |
| letters rejected | None | None | Passed |
| non-numeric extension | None | None | Passed |
SHA-256 / 177d9616f320b4c45fa9f611d592685b3ee2e11043604fc4dfab79b8cbab2064
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 ctx else '') + '-'.join(parts)
if ext:
if not ext.isdigit():
return None
uri += ';ext=' + ext
if not glob:
if not ctx:
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| global number | tel:42-555-010-1211 | tel:+42-555-010-1211 | Failed |
| local number with context | tel:+555-0114;phone-context=+42 | tel:555-0114;phone-context=+42 | Failed |
| local number with extension | tel:+555-0114;ext=12;phone-context=+42 | tel:555-0114;ext=12;phone-context=+42 | Failed |
| global with extension | tel:42-555-0114;ext=1 | tel:+42-555-0114;ext=1 | Failed |
| local without context | None | None | Passed |
| run of separators | tel:42-555-0114 | tel:+42-555-0114 | Failed |
| slash is not a visual separator | None | None | Passed |
| letters rejected | None | None | Passed |
| non-numeric extension | None | None | Passed |
SHA-256 / 7e5a0987facbb130197cb57500ddfcdd0094578a5055fa327a3b85a5bcf5a1d6
3 / The verified repair
Exit 0"""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:
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| global number | tel:+42-555-010-1211 | tel:+42-555-010-1211 | Passed |
| local number with context | tel:555-0114;phone-context=+42 | tel:555-0114;phone-context=+42 | Passed |
| local number with extension | tel:555-0114;ext=12;phone-context=+42 | tel:555-0114;ext=12;phone-context=+42 | Passed |
| global with extension | tel:+42-555-0114;ext=1 | tel:+42-555-0114;ext=1 | Passed |
| local without context | None | None | Passed |
| run of separators | tel:+42-555-0114 | tel:+42-555-0114 | Passed |
| slash is not a visual separator | None | None | Passed |
| letters rejected | None | None | Passed |
| non-numeric extension | None | None | Passed |
SHA-256 / 4d179db71c8c01f24d360bd5410491f19a8f7777909b0f89af55d69b0a3d4707
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 / f13940b2f240d75356a4901d4d4bd698e277ef9b43f2fd2359d582baa07a6be1