简体   繁体   中英

How do I validate an Australian Medicare number?

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:

  • Eight digits;
  • A check digit (one digit); and
  • An issue number (one digit).

Note: the first digit of the Medicare card number should be in the range 2 to 6.

Medicare card number check digit calculation

  1. 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))

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.

  1. Divide the calculated sum by 10.
  2. The check digit is the remainder.

Example: For Medicare card number 2123 4567.

  1. (2) + (1 * 3) + (2 * 7) + (3 * 9) + (4) + (5 * 3) + (6 * 7) + (7 * 9) = 170
  2. Divide 170 by 10. The remainder is 0.
  3. The check digit for this Medicare number is 0.

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:

  1. The first 9 Numbers in the medicare card would correspond to the actual medicare number (used in the check).
  2. The 9th digit is a check digit calculated in the GetMedicareChecksum method.
  3. The 10th digit identifies the number of the card, so if you've been issued 3 cards (because you've lost it or whatever), the number would be 3
  4. The 11th digit would identify the family member inside the group.

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:

  • Me: 5101 20591 8-1
  • My wife: 5101 20591 8-2
  • My first child: 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

Microsoft SQL Server 2017

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

Example

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM