PHP Mod-30 Check Digit Algorithm Producing Incorrect Results

30 Views Asked by At

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:

  • The Mod-30 algorithm uses a character set of "0123456789ACDEFGHJKLMNPRTUVWXY" to calculate check digits.
  • Weights are applied to each character in the identifier string.
  • The correct Mod-30 check digit for "10005" should be "K" and that of "139MT" should be "X".

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

1

There are 1 best solutions below

0
trincot On BEST ANSWER

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;
}