FAILURE MAP
← Case archive

FA-72786 / Check-digit algorithms / Open access

NRIC pairs the wrong series with each letter table · case 01

Either citizen or foreigner numbers get check letters from the other table.

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

ROOT CAUSE

The citizen table is chosen for S and F instead of S and T.

VERIFIED REPAIR

Use the JZIHGFEDCBA table for S/T and XWUTRQPNMLK for F/G.

Unsuccessful approach: Selecting the citizen table only for S sends T-series numbers to the foreigner table.

Case contract

Singapore-style NRIC/FIN check letter for the S, T, F and G series: nine characters, series letter, seven digits, check letter (else "malformed"). Weights 2,7,6,5,4,3,2; add 4 for T and G; r = sum mod 11; the letter is "JZIHGFEDCBA"[r] for S/T and "XWUTRQPNMLK"[r] for F/G. Return [expected letter, match].

Why this case matters

Identity verification forms validate NRIC/FIN numbers before submission.

1 / The failure

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

N = 1
observations = []
def solve(s):
    if len(s) != 9 or not s.isascii() or s[0] not in 'STFG' or not s[1:8].isdigit():
        return 'malformed'
    total = sum(int(ch) * w for ch, w in zip(s[1:8], [2, 7, 6, 5, 4, 3, 2]))
    if s[0] in 'TG':
        total += 4
    r = total % 11
    table = 'JZIHGFEDCBA' if s[0] in 'SF' else 'XWUTRQPNMLK'
    c = table[r]
    return [c, s[8] == c]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["F4595639V"]', ['F4595639V'], ['M', False]], ['regression ["F4116515A"]', ['F4116515A'], ['N', False]], ['partial-repair ["T4006687X"]', ['T4006687X'], ['F', False]], ['partial-repair ["T7996809P"]', ['T7996809P'], ['E', False]], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["G1651468G"]', ['G1651468G'], ['W', False]], ['control ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S3757569X"]', ['S3757569X'], ['J', False]]], [['regression ["F3876611S"]', ['F3876611S'], ['L', False]], ['regression ["T7996809P"]', ['T7996809P'], ['E', False]], ['partial-repair ["T2474569W"]', ['T2474569W'], ['J', False]], ['partial-repair ["T0395099X"]', ['T0395099X'], ['E', False]], ['control ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["G4191951W"]', ['G4191951W'], ['K', False]]], [['regression ["F4160346W"]', ['F4160346W'], ['K', False]], ['regression ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["T7398755K"]', ['T7398755K'], ['A', False]], ['partial-repair ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['control ["S0978420O"]', ['S0978420O'], ['I', False]], ['control ["S1234567D"]', ['S1234567D'], ['D', True]], ['control ["G1234567X"]', ['G1234567X'], ['X', True]], ['control ["S123456D"]', ['S123456D'], 'malformed']], [['regression ["T0395099X"]', ['T0395099X'], ['E', False]], ['regression ["T1007654T"]', ['T1007654T'], ['J', False]], ['partial-repair ["T1234567J"]', ['T1234567J'], ['J', True]], ['partial-repair ["T4006687X"]', ['T4006687X'], ['F', False]], ['control ["SA234567D"]', ['SA234567D'], 'malformed'], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["G1651468G"]', ['G1651468G'], ['W', False]], ['control ["G0138893V"]', ['G0138893V'], ['U', False]]], [['regression ["F5183813A"]', ['F5183813A'], ['X', False]], ['regression ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['partial-repair ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["T2474569W"]', ['T2474569W'], ['J', False]], ['control ["G1812101N"]', ['G1812101N'], ['N', True]], ['control ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]]]]
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 fixtureActualExpectedOutcome
regression ["F4595639V"]['C', False]['M', False]Failed
regression ["F4116515A"]['D', False]['N', False]Failed
partial-repair ["T4006687X"]['Q', False]['F', False]Failed
partial-repair ["T7996809P"]['P', True]['E', False]Failed
control ["S6621805O"]['H', False]['H', False]Passed
control ["G1651468G"]['W', False]['W', False]Passed
control ["G0138893V"]['U', False]['U', False]Passed
control ["S3757569X"]['J', False]['J', False]Passed

SHA-256 / fe45df2d1a1d9f776618159d6a80e3c534e06b79c9b7c7df2e30c3154d57b7ce

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) != 9 or not s.isascii() or s[0] not in 'STFG' or not s[1:8].isdigit():
        return 'malformed'
    total = sum(int(ch) * w for ch, w in zip(s[1:8], [2, 7, 6, 5, 4, 3, 2]))
    if s[0] in 'TG':
        total += 4
    r = total % 11
    table = 'JZIHGFEDCBA' if s[0] == 'S' else 'XWUTRQPNMLK'
    c = table[r]
    return [c, s[8] == c]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["F4595639V"]', ['F4595639V'], ['M', False]], ['regression ["F4116515A"]', ['F4116515A'], ['N', False]], ['partial-repair ["T4006687X"]', ['T4006687X'], ['F', False]], ['partial-repair ["T7996809P"]', ['T7996809P'], ['E', False]], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["G1651468G"]', ['G1651468G'], ['W', False]], ['control ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S3757569X"]', ['S3757569X'], ['J', False]]], [['regression ["F3876611S"]', ['F3876611S'], ['L', False]], ['regression ["T7996809P"]', ['T7996809P'], ['E', False]], ['partial-repair ["T2474569W"]', ['T2474569W'], ['J', False]], ['partial-repair ["T0395099X"]', ['T0395099X'], ['E', False]], ['control ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["G4191951W"]', ['G4191951W'], ['K', False]]], [['regression ["F4160346W"]', ['F4160346W'], ['K', False]], ['regression ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["T7398755K"]', ['T7398755K'], ['A', False]], ['partial-repair ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['control ["S0978420O"]', ['S0978420O'], ['I', False]], ['control ["S1234567D"]', ['S1234567D'], ['D', True]], ['control ["G1234567X"]', ['G1234567X'], ['X', True]], ['control ["S123456D"]', ['S123456D'], 'malformed']], [['regression ["T0395099X"]', ['T0395099X'], ['E', False]], ['regression ["T1007654T"]', ['T1007654T'], ['J', False]], ['partial-repair ["T1234567J"]', ['T1234567J'], ['J', True]], ['partial-repair ["T4006687X"]', ['T4006687X'], ['F', False]], ['control ["SA234567D"]', ['SA234567D'], 'malformed'], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["G1651468G"]', ['G1651468G'], ['W', False]], ['control ["G0138893V"]', ['G0138893V'], ['U', False]]], [['regression ["F5183813A"]', ['F5183813A'], ['X', False]], ['regression ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['partial-repair ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["T2474569W"]', ['T2474569W'], ['J', False]], ['control ["G1812101N"]', ['G1812101N'], ['N', True]], ['control ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]]]]
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 fixtureActualExpectedOutcome
regression ["F4595639V"]['M', False]['M', False]Passed
regression ["F4116515A"]['N', False]['N', False]Passed
partial-repair ["T4006687X"]['Q', False]['F', False]Failed
partial-repair ["T7996809P"]['P', True]['E', False]Failed
control ["S6621805O"]['H', False]['H', False]Passed
control ["G1651468G"]['W', False]['W', False]Passed
control ["G0138893V"]['U', False]['U', False]Passed
control ["S3757569X"]['J', False]['J', False]Passed

SHA-256 / b4635ee55b49b57cbd154c073efcf2d729c3509648745da08ef32ca9743d914f

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) != 9 or not s.isascii() or s[0] not in 'STFG' or not s[1:8].isdigit():
        return 'malformed'
    total = sum(int(ch) * w for ch, w in zip(s[1:8], [2, 7, 6, 5, 4, 3, 2]))
    if s[0] in 'TG':
        total += 4
    r = total % 11
    table = 'JZIHGFEDCBA' if s[0] in 'ST' else 'XWUTRQPNMLK'
    c = table[r]
    return [c, s[8] == c]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["F4595639V"]', ['F4595639V'], ['M', False]], ['regression ["F4116515A"]', ['F4116515A'], ['N', False]], ['partial-repair ["T4006687X"]', ['T4006687X'], ['F', False]], ['partial-repair ["T7996809P"]', ['T7996809P'], ['E', False]], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["G1651468G"]', ['G1651468G'], ['W', False]], ['control ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S3757569X"]', ['S3757569X'], ['J', False]]], [['regression ["F3876611S"]', ['F3876611S'], ['L', False]], ['regression ["T7996809P"]', ['T7996809P'], ['E', False]], ['partial-repair ["T2474569W"]', ['T2474569W'], ['J', False]], ['partial-repair ["T0395099X"]', ['T0395099X'], ['E', False]], ['control ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["G4191951W"]', ['G4191951W'], ['K', False]]], [['regression ["F4160346W"]', ['F4160346W'], ['K', False]], ['regression ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["T7398755K"]', ['T7398755K'], ['A', False]], ['partial-repair ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['control ["S0978420O"]', ['S0978420O'], ['I', False]], ['control ["S1234567D"]', ['S1234567D'], ['D', True]], ['control ["G1234567X"]', ['G1234567X'], ['X', True]], ['control ["S123456D"]', ['S123456D'], 'malformed']], [['regression ["T0395099X"]', ['T0395099X'], ['E', False]], ['regression ["T1007654T"]', ['T1007654T'], ['J', False]], ['partial-repair ["T1234567J"]', ['T1234567J'], ['J', True]], ['partial-repair ["T4006687X"]', ['T4006687X'], ['F', False]], ['control ["SA234567D"]', ['SA234567D'], 'malformed'], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["G1651468G"]', ['G1651468G'], ['W', False]], ['control ["G0138893V"]', ['G0138893V'], ['U', False]]], [['regression ["F5183813A"]', ['F5183813A'], ['X', False]], ['regression ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['partial-repair ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["T2474569W"]', ['T2474569W'], ['J', False]], ['control ["G1812101N"]', ['G1812101N'], ['N', True]], ['control ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]]]]
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 fixtureActualExpectedOutcome
regression ["F4595639V"]['M', False]['M', False]Passed
regression ["F4116515A"]['N', False]['N', False]Passed
partial-repair ["T4006687X"]['F', False]['F', False]Passed
partial-repair ["T7996809P"]['E', False]['E', False]Passed
control ["S6621805O"]['H', False]['H', False]Passed
control ["G1651468G"]['W', False]['W', False]Passed
control ["G0138893V"]['U', False]['U', False]Passed
control ["S3757569X"]['J', False]['J', False]Passed

SHA-256 / 9bf0a054476aa5baf9e5a5a444b7d815bd10291bea09723e8f282f172289c1c9

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

Case digest / c81e0c58780f879f9931735be83a730d12cc6e44f6bb261566b6140fe901b59d