FAILURE MAP
← Case archive

FA-96126 / Postal address normalization / Open access

Ordinal street name normalizer: hyphenated compounds are left spelled out · case 01

"TWENTY-FIRST" passes through unchanged.

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

ROOT CAUSE

Hyphens are not turned into token separators.

THE FAILURE

Hyphens are not turned into token separators.

Unsuccessful approach: Deleting hyphens produces "TWENTYFIRST", which matches no table.

Case contract

Uppercase, turn hyphens into spaces and split. A tens word (TWENTY, THIRTY, FORTY) immediately followed by a unit ordinal (FIRST..NINTH) forms one number; unit ordinals and TENTH..THIRTEENTH stand alone; digit tokens ending in ST/ND/RD/TH are re-suffixed. Each number is written with the suffix TH for values ending 11-13 (mod 100), else ST/ND/RD for final digits 1/2/3, else TH. Other tokens pass through.

Why this case matters

"Twenty-First Ave" and "21st Ave" must produce the same key or two addresses for one building never match.

1 / The failure

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

N = 1
observations = []
def solve(x):
    UNITS = {'FIRST': 1, 'SECOND': 2, 'THIRD': 3, 'FOURTH': 4, 'FIFTH': 5, 'SIXTH': 6, 'SEVENTH': 7,
             'EIGHTH': 8, 'NINTH': 9}
    TEENS = {'TENTH': 10, 'ELEVENTH': 11, 'TWELFTH': 12, 'THIRTEENTH': 13}
    TENS = {'TWENTY': 20, 'THIRTY': 30, 'FORTY': 40}
    def suffix(n):
        if n % 100 in (11, 12, 13):
            return 'TH'
        return {1: 'ST', 2: 'ND', 3: 'RD'}.get(n % 10, 'TH')
    t = x.upper().split()
    out, i = [], 0
    while i < len(t):
        w = t[i]
        if w in TENS and i + 1 < len(t) and t[i + 1] in UNITS:
            n = TENS[w] + UNITS[t[i + 1]]
            i += 2
        elif w in UNITS or w in TEENS:
            n = UNITS.get(w) or TEENS[w]
            i += 1
        elif w[:-2].isdigit() and w[-2:] in ('ST', 'ND', 'RD', 'TH'):
            n = int(w[:-2])
            i += 1
        else:
            out.append(w)
            i += 1
            continue
        out.append(str(n) + suffix(n))
    return ' '.join(out)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['hyphenated compound ordinal', 'Twenty-First AVE', '21ST AVE'], ['spaced compound ordinal', 'THIRTY SECOND AVE', '32ND AVE'], ['spaced compound followed by suffix', 'TWENTY THIRD AVE NORTH', '23RD AVE NORTH'], ['eleventh', 'ELEVENTH AVE', '11TH AVE'], ['thirteenth', 'THIRTEENTH AVE', '13TH AVE'], ['three-digit teen ordinal', '111TH AVE', '111TH AVE'], ['numeric ordinal with wrong suffix', '2ST AVE', '2ND AVE'], ['numeric ordinal with another wrong suffix', '43ND AVE', '43RD AVE'], ['simple ordinal', 'THIRD AVE', '3RD AVE'], ['tens word not followed by a unit is a name', 'FORTY OAKS AVE', 'FORTY OAKS AVE']], [['hyphenated compound ordinal', 'Twenty-First ST', '21ST ST'], ['spaced compound ordinal', 'THIRTY SECOND ST', '32ND ST'], ['spaced compound followed by suffix', 'TWENTY THIRD ST NORTH', '23RD ST NORTH'], ['eleventh', 'ELEVENTH ST', '11TH ST'], ['thirteenth', 'THIRTEENTH ST', '13TH ST'], ['three-digit teen ordinal', '211TH ST', '211TH ST'], ['numeric ordinal with wrong suffix', '2ST ST', '2ND ST'], ['numeric ordinal with another wrong suffix', '43ND ST', '43RD ST'], ['simple ordinal', 'THIRD ST', '3RD ST'], ['tens word not followed by a unit is a name', 'FORTY OAKS ST', 'FORTY OAKS ST']], [['hyphenated compound ordinal', 'Twenty-First PL', '21ST PL'], ['spaced compound ordinal', 'THIRTY SECOND PL', '32ND PL'], ['spaced compound followed by suffix', 'TWENTY THIRD PL NORTH', '23RD PL NORTH'], ['eleventh', 'ELEVENTH PL', '11TH PL'], ['thirteenth', 'THIRTEENTH PL', '13TH PL'], ['three-digit teen ordinal', '311TH PL', '311TH PL'], ['numeric ordinal with wrong suffix', '2ST PL', '2ND PL'], ['numeric ordinal with another wrong suffix', '43ND PL', '43RD PL'], ['simple ordinal', 'THIRD PL', '3RD PL'], ['tens word not followed by a unit is a name', 'FORTY OAKS PL', 'FORTY OAKS PL']], [['hyphenated compound ordinal', 'Twenty-First CT', '21ST CT'], ['spaced compound ordinal', 'THIRTY SECOND CT', '32ND CT'], ['spaced compound followed by suffix', 'TWENTY THIRD CT NORTH', '23RD CT NORTH'], ['eleventh', 'ELEVENTH CT', '11TH CT'], ['thirteenth', 'THIRTEENTH CT', '13TH CT'], ['three-digit teen ordinal', '411TH CT', '411TH CT'], ['numeric ordinal with wrong suffix', '2ST CT', '2ND CT'], ['numeric ordinal with another wrong suffix', '43ND CT', '43RD CT'], ['simple ordinal', 'THIRD CT', '3RD CT'], ['tens word not followed by a unit is a name', 'FORTY OAKS CT', 'FORTY OAKS CT']], [['hyphenated compound ordinal', 'Twenty-First LN', '21ST LN'], ['spaced compound ordinal', 'THIRTY SECOND LN', '32ND LN'], ['spaced compound followed by suffix', 'TWENTY THIRD LN NORTH', '23RD LN NORTH'], ['eleventh', 'ELEVENTH LN', '11TH LN'], ['thirteenth', 'THIRTEENTH LN', '13TH LN'], ['three-digit teen ordinal', '511TH LN', '511TH LN'], ['numeric ordinal with wrong suffix', '2ST LN', '2ND LN'], ['numeric ordinal with another wrong suffix', '43ND LN', '43RD LN'], ['simple ordinal', 'THIRD LN', '3RD LN'], ['tens word not followed by a unit is a name', 'FORTY OAKS LN', 'FORTY OAKS LN']]]
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
hyphenated compound ordinalTWENTY-FIRST AVE21ST AVEFailed
spaced compound ordinal32ND AVE32ND AVEPassed
spaced compound followed by suffix23RD AVE NORTH23RD AVE NORTHPassed
eleventh11TH AVE11TH AVEPassed
thirteenth13TH AVE13TH AVEPassed
three-digit teen ordinal111TH AVE111TH AVEPassed
numeric ordinal with wrong suffix2ND AVE2ND AVEPassed
numeric ordinal with another wrong suffix43RD AVE43RD AVEPassed
simple ordinal3RD AVE3RD AVEPassed
tens word not followed by a unit is a nameFORTY OAKS AVEFORTY OAKS AVEPassed

SHA-256 / 0af9aaceb42b7f42ade41859028c73c1dd28861b1ccd14344b66a3575739f94b

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    UNITS = {'FIRST': 1, 'SECOND': 2, 'THIRD': 3, 'FOURTH': 4, 'FIFTH': 5, 'SIXTH': 6, 'SEVENTH': 7,
             'EIGHTH': 8, 'NINTH': 9}
    TEENS = {'TENTH': 10, 'ELEVENTH': 11, 'TWELFTH': 12, 'THIRTEENTH': 13}
    TENS = {'TWENTY': 20, 'THIRTY': 30, 'FORTY': 40}
    def suffix(n):
        if n % 100 in (11, 12, 13):
            return 'TH'
        return {1: 'ST', 2: 'ND', 3: 'RD'}.get(n % 10, 'TH')
    t = x.upper().replace('-', '').split()
    out, i = [], 0
    while i < len(t):
        w = t[i]
        if w in TENS and i + 1 < len(t) and t[i + 1] in UNITS:
            n = TENS[w] + UNITS[t[i + 1]]
            i += 2
        elif w in UNITS or w in TEENS:
            n = UNITS.get(w) or TEENS[w]
            i += 1
        elif w[:-2].isdigit() and w[-2:] in ('ST', 'ND', 'RD', 'TH'):
            n = int(w[:-2])
            i += 1
        else:
            out.append(w)
            i += 1
            continue
        out.append(str(n) + suffix(n))
    return ' '.join(out)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['hyphenated compound ordinal', 'Twenty-First AVE', '21ST AVE'], ['spaced compound ordinal', 'THIRTY SECOND AVE', '32ND AVE'], ['spaced compound followed by suffix', 'TWENTY THIRD AVE NORTH', '23RD AVE NORTH'], ['eleventh', 'ELEVENTH AVE', '11TH AVE'], ['thirteenth', 'THIRTEENTH AVE', '13TH AVE'], ['three-digit teen ordinal', '111TH AVE', '111TH AVE'], ['numeric ordinal with wrong suffix', '2ST AVE', '2ND AVE'], ['numeric ordinal with another wrong suffix', '43ND AVE', '43RD AVE'], ['simple ordinal', 'THIRD AVE', '3RD AVE'], ['tens word not followed by a unit is a name', 'FORTY OAKS AVE', 'FORTY OAKS AVE']], [['hyphenated compound ordinal', 'Twenty-First ST', '21ST ST'], ['spaced compound ordinal', 'THIRTY SECOND ST', '32ND ST'], ['spaced compound followed by suffix', 'TWENTY THIRD ST NORTH', '23RD ST NORTH'], ['eleventh', 'ELEVENTH ST', '11TH ST'], ['thirteenth', 'THIRTEENTH ST', '13TH ST'], ['three-digit teen ordinal', '211TH ST', '211TH ST'], ['numeric ordinal with wrong suffix', '2ST ST', '2ND ST'], ['numeric ordinal with another wrong suffix', '43ND ST', '43RD ST'], ['simple ordinal', 'THIRD ST', '3RD ST'], ['tens word not followed by a unit is a name', 'FORTY OAKS ST', 'FORTY OAKS ST']], [['hyphenated compound ordinal', 'Twenty-First PL', '21ST PL'], ['spaced compound ordinal', 'THIRTY SECOND PL', '32ND PL'], ['spaced compound followed by suffix', 'TWENTY THIRD PL NORTH', '23RD PL NORTH'], ['eleventh', 'ELEVENTH PL', '11TH PL'], ['thirteenth', 'THIRTEENTH PL', '13TH PL'], ['three-digit teen ordinal', '311TH PL', '311TH PL'], ['numeric ordinal with wrong suffix', '2ST PL', '2ND PL'], ['numeric ordinal with another wrong suffix', '43ND PL', '43RD PL'], ['simple ordinal', 'THIRD PL', '3RD PL'], ['tens word not followed by a unit is a name', 'FORTY OAKS PL', 'FORTY OAKS PL']], [['hyphenated compound ordinal', 'Twenty-First CT', '21ST CT'], ['spaced compound ordinal', 'THIRTY SECOND CT', '32ND CT'], ['spaced compound followed by suffix', 'TWENTY THIRD CT NORTH', '23RD CT NORTH'], ['eleventh', 'ELEVENTH CT', '11TH CT'], ['thirteenth', 'THIRTEENTH CT', '13TH CT'], ['three-digit teen ordinal', '411TH CT', '411TH CT'], ['numeric ordinal with wrong suffix', '2ST CT', '2ND CT'], ['numeric ordinal with another wrong suffix', '43ND CT', '43RD CT'], ['simple ordinal', 'THIRD CT', '3RD CT'], ['tens word not followed by a unit is a name', 'FORTY OAKS CT', 'FORTY OAKS CT']], [['hyphenated compound ordinal', 'Twenty-First LN', '21ST LN'], ['spaced compound ordinal', 'THIRTY SECOND LN', '32ND LN'], ['spaced compound followed by suffix', 'TWENTY THIRD LN NORTH', '23RD LN NORTH'], ['eleventh', 'ELEVENTH LN', '11TH LN'], ['thirteenth', 'THIRTEENTH LN', '13TH LN'], ['three-digit teen ordinal', '511TH LN', '511TH LN'], ['numeric ordinal with wrong suffix', '2ST LN', '2ND LN'], ['numeric ordinal with another wrong suffix', '43ND LN', '43RD LN'], ['simple ordinal', 'THIRD LN', '3RD LN'], ['tens word not followed by a unit is a name', 'FORTY OAKS LN', 'FORTY OAKS LN']]]
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
hyphenated compound ordinalTWENTYFIRST AVE21ST AVEFailed
spaced compound ordinal32ND AVE32ND AVEPassed
spaced compound followed by suffix23RD AVE NORTH23RD AVE NORTHPassed
eleventh11TH AVE11TH AVEPassed
thirteenth13TH AVE13TH AVEPassed
three-digit teen ordinal111TH AVE111TH AVEPassed
numeric ordinal with wrong suffix2ND AVE2ND AVEPassed
numeric ordinal with another wrong suffix43RD AVE43RD AVEPassed
simple ordinal3RD AVE3RD AVEPassed
tens word not followed by a unit is a nameFORTY OAKS AVEFORTY OAKS AVEPassed

SHA-256 / 803d62791ffb2a41c972b59f1c315997c0b7359868b30597bac42d127605b70c

HELD IN THE MEMBER ARCHIVE

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

This mechanism has 10 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 addresses. The rules are stipulated for this model only and make no claim of conformance with any postal authority 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:20.023425+00:00.

Case digest / 3fcbecb88ce10ec6d6b8f5e95c3c72cd7e349fdf34a71a68bf0c8f795c7aa8ab