Read Washington Medicaid Fee Schedules

The Washington state Health Care Authority website for fee schedules is here.

  • Fee schedules come in Excel format
  • Fee schedules are usually biannual (January and July)
  • Publicly available fee schedules go back to January 2011

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

  • File names are not systematic
  • They do not read directly into R nicely (using either the readxl or xlsx packages)
  • Data lines start at different rows

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 5bde7f3e33e0c83bdace0ed0cf04553a41a8efb1 (5/5/2016). Step 4 is below.


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


Out[3]:
  1. "ambulance_transportation_022016.csv"
  2. "Excel"
  3. "HCA_PREOH_January_1_2013.csv"
  4. "physician_010114.csv"
  5. "physician_010115.csv"
  6. "physician_010116.csv"
  7. "physician_040115.csv"
  8. "physician_040116.csv"
  9. "physician_070114.csv"
  10. "physician_070115.csv"
  11. "physician_100115.csv"
  12. "preoh_010112.csv"
  13. "preoh_01012011.csv"
  14. "preoh_070112.csv"
  15. "preoh_070113.csv"
  16. "preoh_07012011.csv"

Physician-Related/Professional Services


In [2]:
library(data.table)
readFS <- function (f, skip) {
    require(data.table, quietly=TRUE)
    for (i in 11:16) {if (grepl(sprintf("%d\\.csv", i), f)) {year <- as.numeric(sprintf("20%d", i))}}
    for (i in 1:12) {
        monname <- format(as.Date(sprintf("%d-%d-01", year, i)), format="%B")
        if (grepl(sprintf("_%02d", i), f) | grepl(tolower(monname), f, ignore.case=TRUE)) {
            mm <- i
        }
    }
    colClasses <- rep("character", 9)
    D <- data.table(read.csv(f, header=FALSE, colClasses=colClasses, skip=skip, na.strings=c(""), strip.white=TRUE))
    old <- names(D)
    keep <- c("code_status_indicator",
              "code",
              "mod",
              "nfs_maximum_allowable",
              "fs_maximum_allowable",
              "pa_required",
              "global_days",
              "comments")
    if (length(old) > length(keep)) {new <- c(keep, old[(length(keep) + 1):length(old)])}
    else {new <- keep}
    setnames(D, old, new)
    D <- D[, effective_date := as.Date(sprintf("%d-%d-01", year, mm))]
    D[, c(keep, "effective_date"), with=FALSE]
}

In [3]:
fs <- rbindlist(list(readFS(file.path(getwd(), "Data/HCA_PREOH_January_1_2013.csv"), 9),
                     readFS(file.path(getwd(), "Data/physician_010114.csv"), 9),
                     readFS(file.path(getwd(), "Data/physician_010115.csv"), 9),
                     readFS(file.path(getwd(), "Data/physician_010116.csv"), 10),
                     readFS(file.path(getwd(), "Data/physician_040115.csv"), 9),
                     readFS(file.path(getwd(), "Data/physician_040116.csv"), 10),
                     readFS(file.path(getwd(), "Data/physician_070114.csv"), 9),
                     readFS(file.path(getwd(), "Data/physician_070115.csv"), 10),
                     readFS(file.path(getwd(), "Data/physician_100115.csv"), 10),
                     readFS(file.path(getwd(), "Data/preoh_010112.csv"), 6),
                     readFS(file.path(getwd(), "Data/preoh_01012011.csv"), 6),
                     readFS(file.path(getwd(), "Data/preoh_070112.csv"), 9),
                     readFS(file.path(getwd(), "Data/preoh_070113.csv"), 9),
                     readFS(file.path(getwd(), "Data/preoh_07012011.csv"), 6)))
str(fs)


Classes 'data.table' and 'data.frame':	164912 obs. of  9 variables:
 $ code_status_indicator: chr  NA NA NA NA ...
 $ code                 : chr  "00100" "00102" "00103" "00104" ...
 $ mod                  : chr  NA NA NA NA ...
 $ nfs_maximum_allowable: chr  "5 Base" "6 Base" "5 Base" "4 Base" ...
 $ fs_maximum_allowable : chr  "5 Base" "6 Base" "5 Base" "4 Base" ...
 $ pa_required          : chr  NA NA NA NA ...
 $ global_days          : chr  "000" "000" "000" "000" ...
 $ comments             : chr  "N" "N" "N" "N" ...
 $ effective_date       : Date, format: "2013-01-01" "2013-01-01" ...
 - attr(*, ".internal.selfref")=<externalptr> 

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


Out[4]:
effective_dateN
12011-01-0111697
22011-07-0111518
32012-01-0111768
42012-07-0111606
52013-01-0111832
62013-07-0111656
72014-01-0111803
82014-07-0111743
92015-01-0112027
102015-04-0111835
112015-07-0111837
122015-10-0111841
132016-01-0111958
142016-04-0111791

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


Out[5]:
code_status_indicatorcodemodnfs_maximum_allowablefs_maximum_allowablepa_requiredglobal_dayscommentseffective_date
1NA00100NA5 Base5 BaseNA000N2013-01-01
2NA00102NA6 Base6 BaseNA000N2013-01-01
3NA00103NA5 Base5 BaseNA000N2013-01-01
4NA00104NA4 Base4 BaseNA000N2013-01-01
5NA00120NA5 Base5 BaseNA000N2013-01-01
6NA00124NA4 Base4 BaseNA000N2013-01-01
Out[5]:
code_status_indicatorcodemodnfs_maximum_allowablefs_maximum_allowablepa_requiredglobal_dayscommentseffective_date
1NAV2631NA342.42342.42NA000N2011-07-01
2NAV2632NA342.42342.42NA000N2011-07-01
3NAV2785NA##NA000N2011-07-01
4NAV2787NA##NA000N2011-07-01
5NAV2788NA##NA000N2011-07-01
6NAV2799NA##NA000N2011-07-01

Rename object


In [ ]:
fsPhysician <- fs

Ambulance Transportation


In [18]:
library(data.table)
f <- file.path(getwd(), "Data/ambulance_transportation_022016.csv")
D <- data.table(read.csv(f, header=TRUE, na.strings=c(""), strip.white=TRUE, stringsAsFactors=FALSE))
old <- names(D)
new <- c("code_status_indicator",
         "code",
         "description",
         "fs_maximum_allowable",
         "limits")
setnames(D, old, new)
D <- D[, fs_maximum_allowable := as.numeric(gsub("[^0-9\\.]", "", fs_maximum_allowable))]
D <- D[, effective_date := as.Date("2006-07-01")]
str(D)
D


Classes 'data.table' and 'data.frame':	14 obs. of  6 variables:
 $ code_status_indicator: logi  NA NA NA NA NA NA ...
 $ code                 : chr  "A0430" "A0431" "A0435" "A0436" ...
 $ description          : chr  "Ambulance service, conventional air services, transport, one way (fixed wing)" "Ambulance service, conventional air services, transport, one way (rotary wing)" "Fixed wing air mileage, per statute mile" "Rotary wing air mileage, per statute mile" ...
 $ fs_maximum_allowable : num  910.8 804.5 5.5 13.3 115.3 ...
 $ limits               : chr  "Per client transported." "Per client transported." "One way, per flight, equally divided by the number of clients transported." "One way, per flight, equally divided by the number of clients transported." ...
 $ effective_date       : Date, format: "2006-07-01" "2006-07-01" ...
 - attr(*, ".internal.selfref")=<externalptr> 
Out[18]:
code_status_indicatorcodedescriptionfs_maximum_allowablelimitseffective_date
1NAA0430Ambulance service, conventional air services, transport, one way (fixed wing)910.81Per client transported.2006-07-01
2NAA0431Ambulance service, conventional air services, transport, one way (rotary wing)804.45Per client transported.2006-07-01
3NAA0435Fixed wing air mileage, per statute mile5.5One way, per flight, equally divided by the number of clients transported.2006-07-01
4NAA0436Rotary wing air mileage, per statute mile13.31One way, per flight, equally divided by the number of clients transported.2006-07-01
5NAA0428Ambulance service, basic life support, non-emergency transport (BLS)115.34Origin and destination modifiers required. For each additional client, use modifier GM in addition to the origin and destination modifiers.2006-07-01
6NAA0429Ambulance service, basic life support, emergency transport (BLS-emergency) 115.34Origin and destination modifiers required. For each additional client, use modifier GM in addition to the origin and destination modifiers.2006-07-01
7NAA0426Ambulance service, advanced life support non-emergency transport, level 1 (ALS 1)168.43Origin and destination modifiers required. For each additional client, use modifier GM in addition to the origin and destination modifiers.2006-07-01
8NAA0427Ambulance service, advanced life support, emergency transport, level 1 (ALS 1 emergency)168.43Origin and destination modifiers required. For each additional client, use modifier GM in addition to the origin and destination modifiers.2006-07-01
9NAA0433Advanced life support, level 2 (ALS 2)168.43Origin and destination modifiers required. For each additional client, use modifier GM in addition to the origin and destination modifiers.2006-07-01
10NAA0434Specialty care transport (SCT)168.43Origin and destination modifiers required. For each additional client, use modifier GM in addition to the origin and destination modifiers.2006-07-01
11NAA0425Ground mileage, per statute mile5.08Origin and destination modifiers required.2006-07-01
12NAA0170Transportation ancillary: parking fees, tolls, other Invoice required.NAOrigin and destination modifiers required.2006-07-01
13NAA0424Extra ambulance attendant, ground (ALS or BLS) or air (fixed or rotary winged); (requires medical review)23.18Pertinent documentation to evaluate medical appropriateness should be included when this code is reported. Origin and destination modifiers required. Justification required: * The client weighs 300 pounds or more; or * Client is violent or difficult to move safely; or * More than one client is being transported, and each requires medical attention and/or close monitoring. Note: DSHS pays for an extra attendant in ground ambulance transports only. No payment is made for an extra attendant in air ambulance transports.2006-07-01
14NAA0998Ambulance response and treatment, no transport NACode not payable. Used for data collection purposes only.2006-07-01