andrewallenbruce / provider

Public Healthcare Provider APIs :stethoscope:
https://andrewallenbruce.github.io/provider/
Other
18 stars 2 forks source link

Valid NPI Creation: "Double-Add-Double" Luhn #17

Closed andrewallenbruce closed 8 months ago

andrewallenbruce commented 9 months ago

Luhn Formula for Modulus 10 “double-add-double” Check Digit

Example of Check Digit Calculation for NPI used without Prefix:

# Assume the NPI is 1234567893:
npi_test <- "1234567893"
#> [1] "1234567893"

# Remove the 10th digit to create the 9-position identifier part of the NPI:
id <- unlist(strsplit(npi_test, ""))[1:9]
#> [1] "1" "2" "3" "4" "5" "6" "7" "8" "9"

# Reverse order of digits
x <- rev(id)
#> [1] "9" "8" "7" "6" "5" "4" "3" "2" "1"

# Select every other digit beginning with the leftmost digit:
idx <- seq(1, length(x), 2)
#> [1] 1 3 5 7 9

# Double the value of the alternate digits
x[idx] <- as.numeric(x[idx]) * 2
#> [1] "18" "8"  "14" "6"  "10" "4"  "6"  "2"  "2"

# Reverse order of digits again
x <- rev(x)
#> [1] "2"  "2"  "6"  "4"  "10" "6"  "14" "8"  "18"

# Split and unlist to separate digits
x <- unlist(strsplit(x, ""))
#>  [1] "2" "2" "6" "4" "1" "0" "6" "1" "4" "8" "1" "8"

# Add constant 24 to the sum of the digits
x <- sum(as.numeric(x)) + 24
#> [1] 67

# Find the next higher number ending in zero
y <- ceiling(x / 10) * 10
#> [1] 70

# Find the check digit by subtracting x from y
z <- y - x
#> [1] 3

# Append the check digit to the end of the 9-digit identifier
id[10] <- z
#>  [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "3"

# Collapse the vector into a single string
npi_valid <- paste0(id, collapse = "")
#> [1] "1234567893"

# The syntactically valid NPI is identical to the original NPI input
identical(npi_valid, npi_test)
#> [1] TRUE

Created on 2023-10-12 with reprex v2.0.2

andrewallenbruce commented 8 months ago

Could create an NPI constructor, ala:

Function code

``` r library(provider) library(dplyr) library(purrr) library(rlang) create_npi <- function(npi, arg = rlang::caller_arg(npi), call = rlang::caller_env()) { # Must be numeric if (grepl("^[[:digit:]]+$", npi) == FALSE) { cli::cli_abort(c( "An {.strong NPI} must be {.emph numeric}.", "x" = "{.val {npi}} contains {.emph non-numeric} characters."), call = call) } # Must be 10 char length if (nchar(npi) != 10L) { cli::cli_abort(c( "An {.strong NPI} must be {.emph 100 digits long}.", "x" = "{.val {npi}} contains {.val {nchar(npi)}} digit{?s}."), call = call) } # Must pass Luhn algorithm npi_test <- as.character(npi) # Remove the 10th digit to create the 9-position identifier part of the NPI id <- unlist(strsplit(npi_test, ""), use.names = FALSE)[1:9] # Reverse order of digits x <- rev(id) # Select index of every other digit idx <- seq(1, length(x), 2) # Double the value of the alternate digits x[idx] <- as.numeric(x[idx]) * 2 # Reverse order of digits again x <- rev(x) # Split and unlist to separate digits x <- unlist(strsplit(x, ""), use.names = FALSE) # Add constant 24 to the sum of the digits x <- sum(as.numeric(x)) + 24 # Find the next higher number ending in zero y <- ceiling(x / 10) * 10 # Find the check digit by subtracting x from y z <- y - x # Append the check digit to the end of the 9-digit identifier id[10] <- z # Collapse the vector into a single string npi_valid <- paste0(id, collapse = "") results <- list( input = npi_test, valid = npi_valid, equal = identical(npi_valid, npi_test)) return(results) } ```

# invalid
create_npi(1234567891)
#> $input
#> [1] "1234567891"
#> 
#> $valid
#> [1] "1234567893"
#> 
#> $equal
#> [1] FALSE

create_npi(1528060439)
#> $input
#> [1] "1528060439"
#> 
#> $valid
#> [1] "1528060431"
#> 
#> $equal
#> [1] FALSE

# valid
create_npi(1528060837)
#> $input
#> [1] "1528060837"
#> 
#> $valid
#> [1] "1528060837"
#> 
#> $equal
#> [1] TRUE

Created on 2023-10-23 with reprex v2.0.2

andrewallenbruce commented 8 months ago

Replace check_npi() calls with validate_npi()