I'm developing an online form in which user-entered Medicare numbers will need to be validated.
(My specific problem concerns Australian Medicare numbers, but I'm happy for answers regarding American ones too. This question is about Medicare numbers in general.)
So how should I do it?
(It would be good to have the answer in Javascript or a regex.)
The regex supplied by Jeffrey Kemp (March 11) would help to validate the allowed characters, but the check algorithm below should be enough to validate that the number conforms to Medicare's rules.
The Medicare card number comprises:
Note: the first digit of the Medicare card number should be in the range 2 to 6.
Medicare card number check digit calculation
where digit 1 is the highest place value digit of the Medicare card number and digit 8 is the lowest place value digit of the Medicare card number.
Example: for Medicare card number '2123 45670 1', digit 1 is 2 and digit 8 is 7.
Example: For Medicare card number 2123 4567.
Source: "Use of Healthcare Identifiers in Health Software Systems - Software Conformance Requirements, Version 1.4", NEHTA, 3/05/2011
If you are looking for a C# version, give this a try:
using System.Linq;
//...
public bool IsMedicareFormatValid(string medicareNumber)
{
if (!(medicareNumber?.Length >= 10 && medicareNumber.Length <12) || !medicareNumber.All(char.IsDigit))
return false;
var digits = medicareNumber.Select(c => (int) char.GetNumericValue(c)).ToArray();
return digits[8] == GetMedicareChecksum(digits.Take(8).ToArray());
}
private int GetMedicareChecksum(int[] digits)
{
return digits.Zip(new[] { 1, 3, 7, 9, 1, 3, 7, 9 }, (m, d) => m*d).Sum() % 10;
}
Note: This will return false for null values, you might want to throw an exception.
To clarify:
GetMedicareChecksum
method.Hope someone finds this useful.
Here's a Typescript or modern Javascript solution:
validateMedicare(medicare) {
let isValid = false;
if (medicare && medicare.length === 10) {
const matches = medicare.match(/^(\d{8})(\d)/);
if (!matches) {
return { invalid: true };
}
const base = matches[1];
const checkDigit = matches[2];
const weights = [1, 3, 7, 9, 1, 3, 7, 9];
let sum = 0;
for (let i = 0; i < weights.length; i++) {
sum += parseInt(base[i], 10) * weights[i];
}
isValid = sum % 10 === parseInt(checkDigit, 10);
}
return isValid;
}
Please refer to http://clearwater.com.au/code/medicare for an explanation.
To test, generate medicare number here: https://precedencehealthcare.com/rmig/
Added Swift version
class func isMedicareValid(input : String, validateWithIrn : Bool) -> Bool {
let multipliers = [1, 3, 7, 9, 1, 3, 7, 9]
let pattern = "^(\\d{8})(\\d)"
let medicareNumber = input.removeWhitespace()
let length = validateWithIrn ? 11 : 10
if medicareNumber.characters.count != length {return false}
let expression = try! NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.CaseInsensitive)
let matches = expression.matchesInString(medicareNumber, options: NSMatchingOptions.ReportProgress, range: NSMakeRange(0, length))
if (matches.count > 0 && matches[0].numberOfRanges > 2) {
let base = medicareNumber.substringWithRange(medicareNumber.startIndex...medicareNumber.startIndex.advancedBy(matches[0].rangeAtIndex(1).length))
let checkDigitStartIndex = medicareNumber.startIndex.advancedBy(matches[0].rangeAtIndex(2).location )
let checkDigitEndIndex = checkDigitStartIndex.advancedBy(matches[0].rangeAtIndex(2).length)
let checkDigit = medicareNumber.substringWithRange(checkDigitStartIndex..<checkDigitEndIndex)
var total = 0
for i in 0..<multipliers.count {
total += Int(base.charAtIndex(i))! * multipliers[i]
}
return (total % 10) == Int(checkDigit)
}
return false
}
I use some String extensions as well to simplify some operations.
extension String {
func charAtIndex (index: Int) -> String{
var character = ""
if (index < self.characters.count){
let locationStart = self.startIndex.advancedBy(index)
let locationEnd = self.startIndex.advancedBy(index + 1 )
character = self.substringWithRange(locationStart..<locationEnd)
}
return character
}
func replace(string:String, replacement:String) -> String {
return self.stringByReplacingOccurrencesOfString(string, withString: replacement, options: NSStringCompareOptions.LiteralSearch, range: nil)
}
func removeWhitespace() -> String {
return self.replace(" ", replacement: "")
}
}
Added Java Version
public static boolean isMedicareValid(String input, boolean validateWithIRN){
int[] multipliers = new int[]{1, 3, 7, 9, 1, 3, 7, 9};
String pattern = "^(\\d{8})(\\d)";
String medicareNumber = input.replace(" " , "");
int length = validateWithIRN ? 11 : 10;
if (medicareNumber.length() != length) {return false;}
Pattern medicatePattern = Pattern.compile(pattern);
Matcher matcher = medicatePattern.matcher(medicareNumber);
if (matcher.find()){
String base = matcher.group(1);
String checkDigit = matcher.group(2);
int total = 0;
for (int i = 0; i < multipliers.length; i++){
total += base.charAt(i) * multipliers[i];
}
return ((total % 10) == Integer.parseInt(checkDigit));
}
return false;
}
My Australian Medicare number is 11 numeric digits and includes no letters or other characters.
It is formatted in groups, and the last digit varies according to the member of my family, eg:
5101 20591 8-1
5101 20591 8-2
5101 20591 8-3
I've seen medicare numbers formatted without the spaces and the dash, but the meaning is the same, so I'd expect to accept 51012059181
as a valid Medicare number as well.
I've also seen context where the last digit is not required or not supposed to be entered; eg 5101205918
, I guess where they're only interested in the family as a whole.
Therefore, I think this may be appropriate:
^\d{4}[ ]?\d{5}[ ]?\d{1}[- ]?\d?$
EDIT
Based on the logic in user2247167's answer, I've used the following PL/SQL function in my Apex application to give a user-friendly warning to the user:
FUNCTION validate_medicare_no (i_medicare_no IN VARCHAR2)
RETURN VARCHAR2 IS
v_digit1 CHAR(1);
v_digit2 CHAR(1);
v_digit3 CHAR(1);
v_digit4 CHAR(1);
v_digit5 CHAR(1);
v_digit6 CHAR(1);
v_digit7 CHAR(1);
v_digit8 CHAR(1);
v_check CHAR(1);
v_result NUMBER;
BEGIN
IF NOT REGEXP_LIKE(i_medicare_no, '^\d{10}\d?{2}$') THEN
RETURN 'Must be 10-12 digits, no spaces or other characters';
ELSE
v_digit1 := SUBSTR(i_medicare_no, 1, 1);
IF v_digit1 NOT IN ('2','3','4','5','6') THEN
RETURN 'Not a valid Medicare number - please check and re-enter';
ELSE
v_digit2 := SUBSTR(i_medicare_no, 2, 1);
v_digit3 := SUBSTR(i_medicare_no, 3, 1);
v_digit4 := SUBSTR(i_medicare_no, 4, 1);
v_digit5 := SUBSTR(i_medicare_no, 5, 1);
v_digit6 := SUBSTR(i_medicare_no, 6, 1);
v_digit7 := SUBSTR(i_medicare_no, 7, 1);
v_digit8 := SUBSTR(i_medicare_no, 8, 1);
v_check := SUBSTR(i_medicare_no, 9, 1);
v_result := mod( to_number(v_digit1)
+ (to_number(v_digit2) * 3)
+ (to_number(v_digit3) * 7)
+ (to_number(v_digit4) * 9)
+ to_number(v_digit5)
+ (to_number(v_digit6) * 3)
+ (to_number(v_digit7) * 7)
+ (to_number(v_digit8) * 9)
,10);
IF TO_NUMBER(v_check) != v_result THEN
RETURN 'Not a valid Medicare number - please check and re-enter';
END IF;
END IF;
END IF;
-- no error
RETURN NULL;
END validate_medicare_no;
The accepted answer, adapted to JavaScript:
var validator = function (input, validateWithIrn) {
if (!input) {
return false;
}
var medicareNumber;
var pattern;
var length;
var matches;
var base;
var checkDigit;
var total;
var multipliers;
var isValid;
pattern = /^(\d{8})(\d)/;
medicareNumber = input.toString().replace(/ /g, '');
length = validateWithIrn ? 11 : 10;
if (medicareNumber.length === length) {
matches = pattern.exec(medicareNumber);
if (matches) {
base = matches[1];
checkDigit = matches[2];
total = 0;
multipliers = [1, 3, 7, 9, 1, 3, 7, 9];
for (var i = 0; i < multipliers.length; i++) {
total += base[i] * multipliers[i];
}
isValid = (total % 10) === Number(checkDigit);
} else {
isValid = false;
}
} else {
isValid = false;
}
return isValid;
};
I found a forum discussion on the topic:
http://regexadvice.com/forums/thread/57337.aspx
I'm going to try the one 'Aussie Susan' came up with:
^\d{9}B[ADGHJKLNPQRTWY1-9,]?$
You can create Validation Attribute to validate Medicare number
You can use it by
[AustralianMedicareNumberOnly]
public string MedicareNo { get; set; }
Code
public class AustralianMedicareNumberOnlyAttribute : ValidationAttribute
{
private string exampleNumber = "Example: 2234 56789 1-2";
public AustralianMedicareNumberOnlyAttribute()
{
ErrorMessage = string.Concat("{0} is not in correct format, ", exampleNumber);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
string objectValueString;
int[] checksumDigits = new int[] { 1, 3, 7, 9, 1, 3, 7, 9 };
int checksumDigit;
int checksumtotal = 0;
int checksumDigitCalculated;
//convert incomming object value to string
objectValueString = Convert.ToString(value).Trim();
// check medicare number format should be 1234 56789 1-2
if (!Regex.IsMatch(objectValueString, @"^[2-6]\d{3}\s\d{5}\s\d{1}-\d{1}$"))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
else
{
//Check checksum value
//--------------------
// replace two spaces and one dash
objectValueString = objectValueString.Replace(" ", "").Replace("-", "");
// Calculate the sum of: ((digit 1) + (digit 2 * 3) + (digit 3 * 7) + (digit 4 * 9) + (digit 5) + (digit 6 * 3) + (digit 7 * 7) + (digit 8 * 9))
for (int i = 0; i < checksumDigits.Length; i++)
{
int digit = Convert.ToInt32(objectValueString.Substring(i, 1));
checksumtotal += digit * checksumDigits[i];
}
//find out checksum digit
checksumDigit = Convert.ToInt32(objectValueString.Substring(8, 1));
checksumDigitCalculated = checksumtotal % 10;
// check calculated checksum with medicare checksum digit
if (checksumDigit!= checksumDigitCalculated)
{
return new ValidationResult("The Medicare Number is not Valid.");
}
}
}
return ValidationResult.Success;
}
}
Added Dart version:
bool isMedicareValid(String input, {bool validateWithIrn = true}) {
final medicareNumber = input.replaceAll(" ", "");
final length = validateWithIrn ? 11 : 10;
if (medicareNumber.length != length) {
return false;
}
final multipliers = [1, 3, 7, 9, 1, 3, 7, 9];
final regexp = RegExp("^(\\d{8})(\\d)", caseSensitive: false);
final matches = regexp.allMatches(medicareNumber).toList();
if (matches.length > 0 && matches[0].groupCount >= 2) {
final base = matches[0].group(1);
final checkDigit = matches[0].group(2);
var total = Iterable.generate(multipliers.length)
.fold(0, (current, index) => current += (int.tryParse(base[index]) * multipliers[index]));
return (total % 10) == int.parse(checkDigit);
}
return false;
}
Another Swift implementation with comments:
func validateMedicareNumber(input: String) -> Bool {
let weights = [1, 3, 7, 9, 1, 3, 7, 9]
// Remove all whitespace
var trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: .whitespaces)
.joined()
// The medicare card number has 8 digits identifier + 1 digit checksum + 1 digit issue number.
// Either 9 or 10 numbers will be valid
guard trimmed.count == 9 || trimmed.count == 10 else {
return false
}
// Drop the issue number if it was added
if trimmed.count == 10 {
trimmed = String(trimmed.dropLast())
}
// The last digit is a checksum
guard let lastElement = trimmed.last,
let checkSum = Int(String(lastElement)) else {
return false
}
// Remove the checksum from our input
trimmed = String(trimmed.dropLast())
// Multiply the numbers by weights
let weightedNumbers: [Int] = trimmed.enumerated().map { index, element in
guard let number = Int(String(element)) else {
// -1 will throw the calculations out of the window which
// will guarantee invalid checksum
return -1
}
return number * weights[index]
}
// Validate the weighted numbers against the checksum
return weightedNumbers.reduce(0, +) % 10 == checkSum
}
If you need a test card number for development, use this one. It's for John Doe
TEST CARD NUMBER > 2428 77813 2/1
Expanding on Daniel Ormeño answer, for asp.net you can put the check into an attribute and decorate the property in the model
public class MedicareValidation : ValidationAttribute
{
public override bool IsValid(object value)
{
string medicareNumber = value.ToString();
if (!(medicareNumber?.Length >= 10 && medicareNumber.Length < 12) || !medicareNumber.All(char.IsDigit))
return false;
var digits = medicareNumber.Select(c => (int)char.GetNumericValue(c)).ToArray();
return digits[8] == GetMedicareChecksum(digits.Take(8).ToArray());
}
private int GetMedicareChecksum(int[] digits)
{
return digits.Zip(new[] { 1, 3, 7, 9, 1, 3, 7, 9 }, (m, d) => m * d).Sum() % 10;
}
}
Then in the model
[DisplayName("Medicare Number")]
[MedicareValidation]
public string MedicareNumber {get; set;}
Here's a Python version! Note that it expects a full 11 digit Medicare number. If you're validating 10 digit Medicare numbers without the individual reference number, you'll need to tweak the regex in the re.match
line.
def validate_medicare_number(medicare_number: str) -> bool:
"""Given a string containing a medicare number, return True if valid,
False if invalid.
>>> validate_medicare_number("2428 77813 2/1")
True
>>> validate_medicare_number("7428 77818 2/1")
False
>>> validate_medicare_number("2428 77815 2/1")
False
"""
# See here for checksum algorithm:
# https://stackoverflow.com/a/15823818
# https://clearwater.com.au/code/medicare
# Remove whitespace
medicare_number = re.sub(r"[^\d]+", "", medicare_number)
if re.match(r"^[2-6]\d{10}$", medicare_number):
medicare_digits = list(map(int, medicare_number))
checksum_weights = (1, 3, 7, 9) * 2
digit_weight_pairs = zip(medicare_digits, checksum_weights)
checksum = sum([digit * weight for digit, weight in digit_weight_pairs]) % 10
return checksum == medicare_digits[8]
else:
return False
Assumes the inputted medicare number is clean. Validates 10-digit medicare numbers. No reference numbers included.
if object_id('dbo.validate_medicare') is not null
drop function dbo.validate_medicare;
go
create function dbo.validate_medicare(@medicareNumber varchar(max))
returns int
as
begin
if len(@medicareNumber) <> 10
return 0;
if isnumeric(@medicareNumber) = 0
return 0;
declare @count as int = 0;
declare @firstDigit as int = cast(substring(@medicareNumber, 1, 1) as int);
if @firstDigit not between 2 and 6
return 0;
declare @actualCheckDigit as int = cast(substring(@medicareNumber, 9, 1) as int);
declare @computedCheckDigit as int;
declare @weight as int = 1;
declare @tempStuff table (
digit int,
weight int
);
declare @isValid as int;
while @count < len(substring(@medicareNumber, 1, 8))
begin
declare @selected_digit as int = substring(@medicareNumber, @count+1, 1);
if @weight > 9
set @weight = 1;
insert into @tempStuff(digit, weight) values (@selected_digit, @weight);
set @weight = @weight + 2;
if @weight = 5
set @weight = @weight + 2;
set @count = @count + 1;
end;
-- Set the computed Check digit
select @computedCheckDigit = (sum(digit * weight)) % 10 from @tempStuff;
-- Check if computed check digit equals the actual check digit
set @isValid = case
when @computedCheckDigit = @actualCheckDigit
then 1
else 0
end;
return @isValid;
end;
go
declare @medicareNumber as varchar(11) = '2123456701';
select dbo.validate_medicare(@medicareNumber) as IsValid;
You can use simple regex validation: .replace(/\\W/gi, "") .replace(/(.{4})(.{5})/g, "$1 $2 ");
check my example here: codesandbox.io
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.