Read Colorado Medicaid Fee Schedules

The Colorado Department of Health Care Policy and Financing (HCPF) website for fee schedules is here.

  • Fee schedules come in Excel format
  • Fee schedules are biannual (January and July)
  • Publicly available fee schedules go back to January 2012
  • Fee schedule instructions are also available; instructions for January 2015 are linked here

However, Colorado's Medicaid fee schedules are a pain in the ass. They are publicly available as Microsoft Excel files but...

  • File names are not systematic
  • File formats are not uniform (.xls and .xlsx)
  • They do not read directly into R nicely (using either the readxl or xlsx packages)

All these issues makes codifying difficult. As a workaround, the following steps were taken.

  1. Excel files are saved locally
  2. Excel files are converted to CSV
  3. CSV files are version controlled in this repository (since they are not large)
  4. CSV files are read into R

The first 3 steps were done manually. The SHA for the commit of the CSV files is bfbbd07a2d538ec57e61cddf3616993aa74b78b1 (5/4/2016). Step 4 is below.


In [1]:
files <- paste("Data", list.files(file.path(getwd(), "Data")), sep="/")
files


Out[1]:
  1. "Data/Fee_Schedule_Jan2012_File.csv"
  2. "Data/Fee_Schedule_Jan2014_File .csv"
  3. "Data/Fee_Schedule_July2013.csv"
  4. "Data/jan2015feeschedule_1.csv"
  5. "Data/Jan2016feeschedule 0316.csv"
  6. "Data/January 2013 Medicaid Fee Schedule.csv"
  7. "Data/Jul2015feeschedule_Web_2.csv"
  8. "Data/July12014feeschedule_0116.csv"

In [2]:
library(data.table)
readFS <- function (f) {
    require(data.table, quietly=TRUE)
    if (grepl("jan", f, ignore.case=TRUE)) {month <- 1}
    if (grepl("jul", f, ignore.case=TRUE)) {month <- 7}
    for (i in 2012:2016) {if (grepl(sprintf("%d", i), f)) {year <- i}}
    colClasses <- c("character", "character", "numeric", "character", "character", rep("numeric", 3), "character")
    D <- data.table(read.csv(f, header=FALSE, colClasses=colClasses, skip=5, na.strings=c(""), strip.white=TRUE))
    old <- names(D)
    keep <- c("procedure_code",
              "modifier",
              "base_value",
              "conversion_factor",
              "total_allowable",
              "min_age",
              "max_age",
              "postop_days",
              "prior_auth_needed")
    if (length(old) > length(keep)) {new <- c(keep, old[(length(keep) + 1):length(old)])}
    else {new <- keep}
    setnames(D, old, new)
    D <- D[!is.na(procedure_code)]
    D <- D[,
           `:=` (conversion_factor = as.numeric(conversion_factor),
                 total_allowable = toupper(total_allowable),
                 prior_auth_needed = toupper(prior_auth_needed),
                 effective_date = as.Date(sprintf("%d-%d-01", year, month)))]
    D <- D[, total_allowable := gsub("MANNUAL", "MANUAL", total_allowable)]
    D <- D[, total_allowable := gsub("\\bMANUAL\\b", "MANUALLY", total_allowable)]
    D <- D[, total_allowable := gsub("IMMUNZATION", "IMMUNIZATION", total_allowable)]
    D[, c(keep, "effective_date"), with=FALSE]
}

Cycle through all the CSV files.


In [3]:
D <- list()
for (i in 1:length(files)) {
    D[[i]] <- readFS(file.path(getwd(), files[i]))
}
fs <- rbindlist(D)

In [4]:
fs[, .N, effective_date][order(effective_date)]


Out[4]:
effective_dateN
12012-01-0117215
22013-01-0117390
32013-07-0117299
42014-01-0117581
52014-07-0117328
62015-01-0117449
72015-07-0117521
82016-01-0117802

In [5]:
summary(fs[, base_value])
summary(fs[, conversion_factor])
fs[grep("[a-z]", total_allowable, ignore.case=TRUE), .N, total_allowable]
summary(fs[, min_age])
summary(fs[, max_age])
fs[, .N, prior_auth_needed]


Out[5]:
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
    0.00     5.00    14.00   153.00    36.36 36160.00    27133 
Out[5]:
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   0.00    1.00    7.55   15.41   33.11   33.94   27133 
Out[5]:
total_allowableN
1NOT A BENEFIT20202
2VARIES BASED ON TOTAL ANESTHESIA TIME2185
3CODE IS MANUALLY PRICED6329
4AVAILABLE THROUGH CO IMMUNIZATION PROGRAM6
5AVAILABLE THROUGH VFC287
6RATE VARIES6
Out[5]:
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  0.000   0.000   0.000   1.196   0.000 998.000   20606 
Out[5]:
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
    0.0   999.0   999.0   959.3   999.0  9999.0   20606 
Out[5]:
prior_auth_neededN
1NA20248
2NO104556
3YES13542
4SOMETIMES1239

In [6]:
head(fs)
tail(fs)


Out[6]:
procedure_codemodifierbase_valueconversion_factortotal_allowablemin_agemax_agepostop_daysprior_auth_neededeffective_date
10001FNANANANOT A BENEFITNANANANA2012-01-01
20005FNANANANOT A BENEFITNANANANA2012-01-01
300100NA520.17VARIES BASED ON TOTAL ANESTHESIA TIME09990NO2012-01-01
400102NA620.17VARIES BASED ON TOTAL ANESTHESIA TIME09990NO2012-01-01
500103NA520.17VARIES BASED ON TOTAL ANESTHESIA TIME09990NO2012-01-01
600104NA420.17VARIES BASED ON TOTAL ANESTHESIA TIME09990NO2012-01-01
Out[6]:
procedure_codemodifierbase_valueconversion_factortotal_allowablemin_agemax_agepostop_daysprior_auth_neededeffective_date
1V5275NA39.061$39.060200NO2014-07-01
2V5299NA26.571$26.570200NO2014-07-01
3V5336NANANANOT A BENEFITNANANANA2014-07-01
4V5362NANANANOT A BENEFITNANANANA2014-07-01
5V5363NANANANOT A BENEFITNANANANA2014-07-01
6V5364NANANANOT A BENEFITNANANANA2014-07-01