phpalgorithmluhncheck-digitopenmrs

PHP Mod-30 Check Digit Algorithm Producing Incorrect Results


I'm working on implementing a Mod-30 check digit algorithm in PHP for validating identifiers, similar to what's used in systems like OpenMRS. However, I'm encountering issues with the algorithm producing incorrect results.

The Mod-30 algorithm is supposed to calculate a check digit based on a given identifier string, using a character set of "0123456789ACDEFGHJKLMNPRTUVWXY" and a set of weights applied to each character. The correct implementation should return the expected check digit for identifiers.

Here's the PHP code I've written:

function generateMod30CheckDigit($identifier) {
  // Define the character set for Mod-30 algorithm
  $charset = "0123456789ACDEFGHJKLMNPRTUVWXY";

  // Initialize checksum
  $checksum = 0;

  // Reverse the identifier for easier processing
  $identifier = strrev($identifier);

  // Define weights for Mod-30 algorithm (starting from rightmost digit)
  $weights = [2, 3, 4, 5, 6, 7]; // Adjusted weights

  // Iterate over the identifier
  for ($i = 0; $i < strlen($identifier); $i++) {
    $char = $identifier[$i];

    // Convert character to its position in the charset
    $position = strpos($charset, strtoupper($char));

    // Multiply the value by its weight
    $position *= $weights[$i % count($weights)];

    // Add the weighted value to the checksum
    $checksum += $position;
  }

  // Calculate the check digit position (30 - remainder of checksum / 30)
  $checkDigitPosition = (30 - ($checksum % 30)) % 30;

  // Determine the check digit from the character set
  $checkDigit = $charset[$checkDigitPosition];

  return $checkDigit;
}

// Example usage:
$identifier1 = "10005";
$checkDigit1 = generateMod30CheckDigit($identifier1);
echo "Check digit for identifier $identifier1 is: $checkDigit1 <br>";

$identifier2 = "139MT";
$checkDigit2 = generateMod30CheckDigit($identifier2);
echo "Check digit for identifier $identifier2 is: $checkDigit2";

I have also tried to follow what is available here Invalid check digit for identifier : While create a new patient?

For example, for the identifier "10005", the expected check digit is "K", but the algorithm is returning "F".

I've reviewed the algorithm and character set multiple times, but I can't seem to identify where the issue lies. Can someone please help me identify the problem with my implementation and suggest a fix?

Additional Context:

Any insights or assistance would be greatly appreciated. Thank you!


Solution

  • I didn't find where you got the weights from (2, 3, 4, 5, 6, 7). I looked at the code available for openmrs-module-idgen, and there only two weights are used: 2 and 1. This is also in line with what wiki.openmrs.org explains about the weights:

    Work right-to-left, [...] and doubling every other digit.

    Secondly, instead of just adding $position to the checksum, it adds the equivalent of intdiv($position, 30) + $position % 30 to it. This is to cover for the case that $position has two "digits" in 30-base notation, in which case those digits should be added separately.

    When I apply those two changes to your script, it produces the desired outputs for the two examples you've given:

    function generateMod30CheckDigit($identifier) {
      $charset = "0123456789ACDEFGHJKLMNPRTUVWXY";
      $checksum = 0;
      $identifier = strrev($identifier);
      $weights = [2, 1]; // Weights changed
      
      for ($i = 0; $i < strlen($identifier); $i++) {
        $char = $identifier[$i];
        $position = strpos($charset, strtoupper($char));
        $position *= $weights[$i % count($weights)];
        $checksum += intdiv($position, 30) + $position % 30; // Modified formula
      }
      $checkDigitPosition = (30 - ($checksum % 30)) % 30;
      $checkDigit = $charset[$checkDigitPosition];
      return $checkDigit;
    }