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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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