Last active
August 31, 2022 03:29
-
-
Save mcshaz/b41dc6bd4aa3104d54da677e2b4f6b45 to your computer and use it in GitHub Desktop.
NZ NHI validator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using NHIValidation; | |
using System.Collections.Generic; | |
using Xunit; | |
using Xunit.Abstractions; | |
namespace TestNhi | |
{ | |
public class NHIValidationUnitTests | |
{ | |
private readonly ITestOutputHelper _output; | |
public NHIValidationUnitTests(ITestOutputHelper output) | |
{ | |
_output = output; | |
} | |
[Theory] | |
[MemberData(nameof(NHIs))] | |
public void TestNHIsPass(string nhi) | |
{ | |
Assert.True(NHIValidator.IsValid(nhi)); | |
} | |
[Theory] | |
[MemberData(nameof(NHIs))] | |
public void TestOtherNHIChecksumsFail(string nhi) | |
{ | |
char checksum = nhi[6]; | |
string baseNHI = nhi[0..6]; | |
char[] possibleChecksums = char.IsDigit(checksum) | |
? new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' } | |
: new[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; | |
foreach (var c in possibleChecksums) | |
{ | |
if (c != checksum) | |
{ | |
var n = baseNHI + c; | |
Assert.False(NHIValidator.IsValid(n), $"NHI: {n} incorrectly found valid"); | |
_output.WriteLine($"NHI: {n} correctly found invalid"); | |
} | |
} | |
} | |
[Theory] | |
[InlineData("ZZZ0044")] // no digit can be added to "ZZZ004\d" | |
[InlineData("ZZZZ000")] // 3 letters followed by either 4 numbers OR 2 numbers + 2 letters | |
[InlineData("ZZZ?000")] | |
public void TestNHIsFail(string nhi) | |
{ | |
Assert.False(NHIValidator.IsValid(nhi)); | |
} | |
public static IEnumerable<object[]> NHIs => | |
new List<object[]> | |
{ | |
new object[] { "ZZZ0016" }, | |
new object[] { "ZZZ0024" }, | |
new object[] { "ZZZ00AX" }, | |
new object[] { "ALU18KZ" } | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text.RegularExpressions; | |
namespace NHIValidation | |
{ | |
public static class NHIValidator | |
{ | |
// The first 3 characters of an NHI number must be alphabetic, but not ‘I’ or ‘O’, to avoid confusion with one and zero. The 4th to 6th characters must be numeric. The 7th character is also numeric, and is a check digit based on modulus 11. | |
// Each alphabet character is assigned a number based on the following table, plus 1 | |
private static readonly char[] LetterToNumberMap = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; | |
public static bool IsValid(string NHIString) | |
{ | |
if (!Regex.IsMatch(NHIString, @"^([A-HJ-NP-Z]{3}\d{4}|[A-HJ-NP-Z]{3}\d{2}[A-HJ-NP-Z]{2})$")) | |
{ | |
return false; | |
} | |
int cumulative = 0; | |
for (int i = 0; i < 6; ++i) | |
{ | |
cumulative += GetCharNumberValue(NHIString[i]) * (7 - i); | |
} | |
char checksumRaw = NHIString[6]; | |
if (Char.IsDigit(checksumRaw)) // old NHI | |
{ | |
int modulus = cumulative % 11; | |
if (modulus == 0) return false; | |
// Subtract checksum from 11 to create check digit. | |
// If the check digit equals ‘10’, convert to ‘0’ | |
return modulus == 1 | |
? checksumRaw == '0' | |
: 11 - modulus == (int)Char.GetNumericValue(checksumRaw); | |
} | |
else // new NHI format | |
{ | |
int modulus = cumulative % 24; | |
// note in the old NHI format, a modulus of 0 fails, but NOT in the new format | |
return LetterToNumberMap[23 - modulus] == checksumRaw; | |
} | |
} | |
private static int GetCharNumberValue(char character) | |
{ | |
return Char.IsDigit(character) | |
? (int)Char.GetNumericValue(character) | |
: Array.IndexOf(LetterToNumberMap, character) + 1; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function isValidNHI(nhiString) { | |
if (!/([A-HJ-NP-Z]{3}\d{4}|[A-HJ-NP-Z]{3}\d{2}[A-HJ-NP-Z]{2})/i.test(nhiString)) { | |
return false; | |
} | |
const alphaLookup = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; | |
const checksumRaw = nhiString.slice(-1); | |
nhiString = nhiString.slice(0, -1).toUpperCase(); | |
let cum = 0; | |
let multiplier = 7; | |
for (const c of nhiString) { | |
let val = parseInt(c, 10); | |
if (isNaN(val)) { | |
val = alphaLookup.indexOf(c) + 1; | |
if (val === 0) { return false; } | |
} | |
cum += val * multiplier--; | |
} | |
const checksumVal = parseInt(checksumRaw, 10); | |
if (isNaN(checksumVal)) { // newer NHI format | |
const modulus = cum % 24; | |
return alphaLookup[23 - modulus] === checksumRaw.toUpperCase(); | |
} else { // old NHI format | |
const modulus = cum % 11; | |
if (modulus === 0) { return false; } | |
return modulus === 1 | |
? checksumVal === 0 | |
: (checksumVal === 11 - modulus); | |
} | |
} |
@Nice-Mark Thank you very much for noticing and providing a concrete example - you are absolutely correct that the modulus of 0 fails the old version of NHIs, but provides a valid checksum in the new NHI format (equating to Z). The NHI validation code and the unit test is now updated.
@mcshaz Wow that was fast, thank you! I finally signed up to GitHub after all these years to ask this question. 😁
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Brent. Thanks for this code. Could you check "ALU18KZ" please? The Ministry of Health lists it as a sample NHI but it fails your validator (because modulus = 0). Thanks.