FAILURE MAP
← Case archive

FA-72781 / Check-digit algorithms / Open access

NRIC omits the offset for T and G series · case 01

Identifiers issued from 2000 onwards (T, G) get the wrong check letter.

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

ROOT CAUSE

The +4 adjustment for the T and G series is missing.

VERIFIED REPAIR

Add 4 to the weighted sum for T and G prefixes.

Unsuccessful approach: Adding the offset only for T still breaks G-series FINs.

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]))
    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 ["G1651468G"]', ['G1651468G'], ['W', False]], ['regression ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["F4595639V"]', ['F4595639V'], ['M', False]], ['control ["S3757569X"]', ['S3757569X'], ['J', False]], ['control ["F4116515A"]', ['F4116515A'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]]], [['regression ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['regression ["T4006687X"]', ['T4006687X'], ['F', False]], ['partial-repair ["G4191951W"]', ['G4191951W'], ['K', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["F3876611S"]', ['F3876611S'], ['L', False]], ['control ["F9230492U"]', ['F9230492U'], ['L', False]], ['control ["F4160346W"]', ['F4160346W'], ['K', False]], ['control ["S1375256W"]', ['S1375256W'], ['G', False]]], [['regression ["G4191951W"]', ['G4191951W'], ['K', False]], ['regression ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["G1651468G"]', ['G1651468G'], ['W', False]], ['partial-repair ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S0978420O"]', ['S0978420O'], ['I', False]], ['control ["F5183813A"]', ['F5183813A'], ['X', False]], ['control ["S1234567D"]', ['S1234567D'], ['D', True]], ['control ["F1234567N"]', ['F1234567N'], ['N', True]]], [['regression ["T0395099X"]', ['T0395099X'], ['E', False]], ['regression ["T1007654T"]', ['T1007654T'], ['J', False]], ['partial-repair ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['partial-repair ["G4191951W"]', ['G4191951W'], ['K', False]], ['control ["M1234567K"]', ['M1234567K'], 'malformed'], ['control ["SA234567D"]', ['SA234567D'], 'malformed'], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["F4595639V"]', ['F4595639V'], ['M', False]]], [['regression ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['regression ["T5563306D"]', ['T5563306D'], ['H', False]], ['partial-repair ["G1651468G"]', ['G1651468G'], ['W', False]], ['partial-repair ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["F4116515A"]', ['F4116515A'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["F3876611S"]', ['F3876611S'], ['L', False]]]]
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 ["G1651468G"]['M', False]['W', False]Failed
regression ["G0138893V"]['L', False]['U', False]Failed
control ["S6621805O"]['H', False]['H', False]Passed
control ["F4595639V"]['M', False]['M', False]Passed
control ["S3757569X"]['J', False]['J', False]Passed
control ["F4116515A"]['N', False]['N', False]Passed
control ["S5101399J"]['I', False]['I', False]Passed
control ["S0965290F"]['F', True]['F', True]Passed

SHA-256 / c86639d10c8601cf022c263711bb381fefef610943985647f63fdaa717c296d0

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] == 'T':
        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 ["G1651468G"]', ['G1651468G'], ['W', False]], ['regression ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["F4595639V"]', ['F4595639V'], ['M', False]], ['control ["S3757569X"]', ['S3757569X'], ['J', False]], ['control ["F4116515A"]', ['F4116515A'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]]], [['regression ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['regression ["T4006687X"]', ['T4006687X'], ['F', False]], ['partial-repair ["G4191951W"]', ['G4191951W'], ['K', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["F3876611S"]', ['F3876611S'], ['L', False]], ['control ["F9230492U"]', ['F9230492U'], ['L', False]], ['control ["F4160346W"]', ['F4160346W'], ['K', False]], ['control ["S1375256W"]', ['S1375256W'], ['G', False]]], [['regression ["G4191951W"]', ['G4191951W'], ['K', False]], ['regression ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["G1651468G"]', ['G1651468G'], ['W', False]], ['partial-repair ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S0978420O"]', ['S0978420O'], ['I', False]], ['control ["F5183813A"]', ['F5183813A'], ['X', False]], ['control ["S1234567D"]', ['S1234567D'], ['D', True]], ['control ["F1234567N"]', ['F1234567N'], ['N', True]]], [['regression ["T0395099X"]', ['T0395099X'], ['E', False]], ['regression ["T1007654T"]', ['T1007654T'], ['J', False]], ['partial-repair ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['partial-repair ["G4191951W"]', ['G4191951W'], ['K', False]], ['control ["M1234567K"]', ['M1234567K'], 'malformed'], ['control ["SA234567D"]', ['SA234567D'], 'malformed'], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["F4595639V"]', ['F4595639V'], ['M', False]]], [['regression ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['regression ["T5563306D"]', ['T5563306D'], ['H', False]], ['partial-repair ["G1651468G"]', ['G1651468G'], ['W', False]], ['partial-repair ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["F4116515A"]', ['F4116515A'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["F3876611S"]', ['F3876611S'], ['L', False]]]]
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 ["G1651468G"]['M', False]['W', False]Failed
regression ["G0138893V"]['L', False]['U', False]Failed
control ["S6621805O"]['H', False]['H', False]Passed
control ["F4595639V"]['M', False]['M', False]Passed
control ["S3757569X"]['J', False]['J', False]Passed
control ["F4116515A"]['N', False]['N', False]Passed
control ["S5101399J"]['I', False]['I', False]Passed
control ["S0965290F"]['F', True]['F', True]Passed

SHA-256 / 1ad057bb7918dfe34978936533941edbb02bf4fcfbed34326ac037680da1c550

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 ["G1651468G"]', ['G1651468G'], ['W', False]], ['regression ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["F4595639V"]', ['F4595639V'], ['M', False]], ['control ["S3757569X"]', ['S3757569X'], ['J', False]], ['control ["F4116515A"]', ['F4116515A'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]]], [['regression ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['regression ["T4006687X"]', ['T4006687X'], ['F', False]], ['partial-repair ["G4191951W"]', ['G4191951W'], ['K', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["F3876611S"]', ['F3876611S'], ['L', False]], ['control ["F9230492U"]', ['F9230492U'], ['L', False]], ['control ["F4160346W"]', ['F4160346W'], ['K', False]], ['control ["S1375256W"]', ['S1375256W'], ['G', False]]], [['regression ["G4191951W"]', ['G4191951W'], ['K', False]], ['regression ["T5805633Y"]', ['T5805633Y'], ['I', False]], ['partial-repair ["G1651468G"]', ['G1651468G'], ['W', False]], ['partial-repair ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["S0978420O"]', ['S0978420O'], ['I', False]], ['control ["F5183813A"]', ['F5183813A'], ['X', False]], ['control ["S1234567D"]', ['S1234567D'], ['D', True]], ['control ["F1234567N"]', ['F1234567N'], ['N', True]]], [['regression ["T0395099X"]', ['T0395099X'], ['E', False]], ['regression ["T1007654T"]', ['T1007654T'], ['J', False]], ['partial-repair ["G9259714Y"]', ['G9259714Y'], ['N', False]], ['partial-repair ["G4191951W"]', ['G4191951W'], ['K', False]], ['control ["M1234567K"]', ['M1234567K'], 'malformed'], ['control ["SA234567D"]', ['SA234567D'], 'malformed'], ['control ["S6621805O"]', ['S6621805O'], ['H', False]], ['control ["F4595639V"]', ['F4595639V'], ['M', False]]], [['regression ["T6283906Q"]', ['T6283906Q'], ['B', False]], ['regression ["T5563306D"]', ['T5563306D'], ['H', False]], ['partial-repair ["G1651468G"]', ['G1651468G'], ['W', False]], ['partial-repair ["G0138893V"]', ['G0138893V'], ['U', False]], ['control ["F4116515A"]', ['F4116515A'], ['N', False]], ['control ["S5101399J"]', ['S5101399J'], ['I', False]], ['control ["S0965290F"]', ['S0965290F'], ['F', True]], ['control ["F3876611S"]', ['F3876611S'], ['L', False]]]]
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 ["G1651468G"]['W', False]['W', False]Passed
regression ["G0138893V"]['U', False]['U', False]Passed
control ["S6621805O"]['H', False]['H', False]Passed
control ["F4595639V"]['M', False]['M', False]Passed
control ["S3757569X"]['J', False]['J', False]Passed
control ["F4116515A"]['N', False]['N', False]Passed
control ["S5101399J"]['I', False]['I', False]Passed
control ["S0965290F"]['F', True]['F', True]Passed

SHA-256 / 3d057a8c301e8d04e0a2f68beb8d1676a8a6a05f540939aff8b0a1b673dffc6b

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

Case digest / 0118c07a237a754c3a9bf8f6dcee5edb9c0271660eb9b7f1591d5a20d28660ff