5  Query

Quick Query

x <- pro_endpoint("asc_facility")

`%|||%` <- function(x, y) {
  if (!is.null(x)) y else NULL
}

cli_query <- function(x) {
  x <- unlist(x)
  glue_col("{silver {names(x)}=}{red {bold {unname(x)}}}") |> glue_collapse(sep = "&")
}

quick_query_ <- new_generic("quick_query_", "x")

method(quick_query_, providertwo:::class_endpoint) <- function(x, query = NULL) {
  n <- providertwo:::identifier_(x) |>
    request() |>
    req_url_query(
      count   = "true",
      format  = "json",
      keys    = "true",
      results = "false",
      rowIds  = "false",
      schema  = "false",
      offset  = 0L,
      limit   = 1L,
      !!!query
    ) |>
    providertwo:::perform_simple() |>
    _$count
  
  if (n == 0) {
    cli::cli_alert_danger("Query {.var {cli_query(query)}} returned {.emph 0} results.", wrap = TRUE)
    return(invisible(NULL))
  }
  
  if (n <= providertwo:::limit_(x)) {
    cli::cli_alert_success("Returning {.emph {n}} results...", wrap = TRUE)
    return(
      providertwo:::identifier_(x) |>
        map(
          function(i)
            request(i) |>
            req_url_query(
              count   = "false",
              format  = "json",
              keys    = "true",
              results = "true",
              rowIds  = "false",
              schema  = "false",
              offset  = 0L,
              limit   = providertwo:::limit_(x),
              !!!query
            )
        ) |>
        req_perform_parallel(on_error = "continue") |>
        map(
          function(x)
            providertwo:::parse_string(x, query = "results") |>
            as_tbl() |>
            providertwo:::map_na_if()
        ) |>
        pluck(1) |>
        providertwo:::name_fields_(x)
    )
  }
  
  if (n > providertwo:::limit_(x)) {
    cli::cli_alert_danger(
      "UNIMPLEMENTED: Query returned {.emph {n}} results, exceeding the limit of {providertwo:::limit_(x)}.",
      wrap = TRUE
    )
    return(invisible(NULL))
  }
}

q1 <- list(
  "conditions[0][property]" = "state",
  "conditions[0][operator]" = "=",
  "conditions[0][value]" = "NY"
)

q2 <- list(
  "conditions[0][property]" = "state",
  "conditions[0][operator]" = "IN",
  "conditions[0][value][1]" = "CA",
  "conditions[0][value][2]" = "GA",
  "conditions[0][value][3]" = "NY"
)

q3 <- list(
  "conditions[0][property]" = "asc2_rate",
  "conditions[0][operator]" = "<",
  "conditions[0][value]" = "0.02"
)

r1 <- quick_query_(pro_endpoint("asc_facility"), query = q1)
 Returning 326 results...
r1 |> str()
tibble [326 × 50] (S3: tbl_df/tbl/data.frame)
 $ facility_name             : chr [1:326] "RENAL FOCUS ASC LLC DBA RENAL FOC"..
 $ facility_id               : chr [1:326] "33C0001208" "33C0001208" "33C0001"..
 $ npi                       : chr [1:326] "1003575218" "1003575218" "1023551"..
 $ citytown                  : chr [1:326] "PLAINVIEW" "PLAINVIEW" "BRONX" "B"..
 $ state                     : chr [1:326] "NY" "NY" "NY" "NY" ...
 $ zip_code                  : chr [1:326] "11803" "11803" "10453" "10453" ...
 $ year                      : chr [1:326] "2023" "2024_Q2" "2023" "2024_Q2" ...
 $ asc1_rate                 : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc1_footnote             : chr [1:326] "5" "5" "5" "5" ...
 $ asc2_rate                 : chr [1:326] "0.03" "N/A" "0.03" "N/A" ...
 $ asc2_footnote             : chr [1:326] NA "5" NA "5" ...
 $ asc3_rate                 : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc3_footnote             : chr [1:326] "5" "5" "5" "5" ...
 $ asc4_rate                 : chr [1:326] "0.17" "N/A" "0.06" "N/A" ...
 $ asc4_footnote             : chr [1:326] NA "5" NA "5" ...
 $ asc9_rate                 : chr [1:326] "N/A" "N/A" "82.41" "N/A" ...
 $ asc9_footnote             : chr [1:326] "5" "5" NA "5" ...
 $ asc11_rate                : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc11_footnote            : chr [1:326] "5" "5" "5" "5" ...
 $ asc12_total_cases         : chr [1:326] "N/A" "N/A" "111" "N/A" ...
 $ asc12_performance_category: chr [1:326] "N/A" "N/A" "No Different Than the"..
 $ asc12_rshv_rate           : chr [1:326] "N/A" "N/A" "10.3" "N/A" ...
 $ asc12_interval_lower_limit: chr [1:326] "N/A" "N/A" "7.1" "N/A" ...
 $ asc12_interval_upper_limit: chr [1:326] "N/A" "N/A" "14.8" "N/A" ...
 $ asc12_footnote            : chr [1:326] "5" "5" NA "5" ...
 $ asc13_rate                : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc13_footnote            : chr [1:326] "5" "5" "5" "5" ...
 $ asc14_rate                : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc14_footnote            : chr [1:326] "5" "5" "5" "5" ...
 $ asc17_total_cases         : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc17_performance_category: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc17_rshv_rate           : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc17_interval_lower_limit: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc17_interval_upper_limit: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc17_footnote            : chr [1:326] "5" "5" "5" "5" ...
 $ asc18_total_cases         : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc18_performance_category: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc18_rshv_rate           : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc18_interval_lower_limit: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc18_interval_upper_limit: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc18_footnote            : chr [1:326] "5" "5" "5" "5" ...
 $ asc19_total_cases         : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc19_performance_category: chr [1:326] "Number of Cases Too Small" "N/A" "..
 $ asc19_rshv_rate           : chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc19_interval_lower_limit: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc19_interval_upper_limit: chr [1:326] "N/A" "N/A" "N/A" "N/A" ...
 $ asc19_footnote            : chr [1:326] "1" "5" "7" "5" ...
 $ asc20_sample              : chr [1:326] NA "33" NA "52" ...
 $ asc20_rate                : chr [1:326] "N/A" "0.00" "N/A" "3.80" ...
 $ asc20_footnote            : chr [1:326] "5" NA "5" NA ...

quick_query_(pro_endpoint("asc_facility"), query = q2)
 UNIMPLEMENTED: Query returned 2334 results, exceeding the limit of 2000.

r3 <- quick_query_(pro_endpoint("asc_facility"), query = q3)
 Returning 1879 results...
r3 |> str()
tibble [1,879 × 50] (S3: tbl_df/tbl/data.frame)
 $ facility_name             : chr [1:1879] "NORTHERN NJ ENDOSCOPY CENTER LLC"..
 $ facility_id               : chr [1:1879] "31C0001270" "05C0001672" "03C000"..
 $ npi                       : chr [1:1879] "1003116823" "1003138736" "100333"..
 $ citytown                  : chr [1:1879] "NEWTON" "COSTA MESA" "SCOTTSDALE"..
 $ state                     : chr [1:1879] "NJ" "CA" "AZ" "CA" ...
 $ zip_code                  : chr [1:1879] "07860" "92627" "85258" "90621" ...
 $ year                      : chr [1:1879] "2023" "2023" "2023" "2023" ...
 $ asc1_rate                 : chr [1:1879] "0.00" "0.00" "0.00" "0.00" ...
 $ asc1_footnote             : chr [1:1879] NA NA NA NA ...
 $ asc2_rate                 : chr [1:1879] "0.00" "0.00" "0.00" "0.00" ...
 $ asc2_footnote             : chr [1:1879] NA NA NA NA ...
 $ asc3_rate                 : chr [1:1879] "0.00" "0.00" "0.00" "0.00" ...
 $ asc3_footnote             : chr [1:1879] NA NA NA NA ...
 $ asc4_rate                 : chr [1:1879] "0.00" "0.05" "0.02" "0.00" ...
 $ asc4_footnote             : chr [1:1879] NA NA NA NA ...
 $ asc9_rate                 : chr [1:1879] "86.59" "N/A" "N/A" "N/A" ...
 $ asc9_footnote             : chr [1:1879] NA "5" "5" "5" ...
 $ asc11_rate                : chr [1:1879] "0.00" "N/A" "N/A" "N/A" ...
 $ asc11_footnote            : chr [1:1879] NA "5" "5" "5" ...
 $ asc12_total_cases         : chr [1:1879] "1056" "N/A" "N/A" "N/A" ...
 $ asc12_performance_category: chr [1:1879] "No Different Than the National R"..
 $ asc12_rshv_rate           : chr [1:1879] "9.3" "N/A" "N/A" "N/A" ...
 $ asc12_interval_lower_limit: chr [1:1879] "6.8" "N/A" "N/A" "N/A" ...
 $ asc12_interval_upper_limit: chr [1:1879] "13.0" "N/A" "N/A" "N/A" ...
 $ asc12_footnote            : chr [1:1879] NA "5" "5" "5" ...
 $ asc13_rate                : chr [1:1879] "0.00" "100.00" "100.00" "N/A" ...
 $ asc13_footnote            : chr [1:1879] NA NA NA "5" ...
 $ asc14_rate                : chr [1:1879] "0.00" "N/A" "N/A" "N/A" ...
 $ asc14_footnote            : chr [1:1879] NA "5" "5" "5" ...
 $ asc17_total_cases         : chr [1:1879] "N/A" "39" "N/A" "N/A" ...
 $ asc17_performance_category: chr [1:1879] "N/A" "No Different Than the Nati"..
 $ asc17_rshv_rate           : chr [1:1879] "N/A" "2.2" "N/A" "N/A" ...
 $ asc17_interval_lower_limit: chr [1:1879] "N/A" "1.4" "N/A" "N/A" ...
 $ asc17_interval_upper_limit: chr [1:1879] "N/A" "3.3" "N/A" "N/A" ...
 $ asc17_footnote            : chr [1:1879] "5" NA "1" "1" ...
 $ asc18_total_cases         : chr [1:1879] "N/A" "121" "N/A" "N/A" ...
 $ asc18_performance_category: chr [1:1879] "N/A" "No Different Than the Nati"..
 $ asc18_rshv_rate           : chr [1:1879] "N/A" "4.6" "N/A" "N/A" ...
 $ asc18_interval_lower_limit: chr [1:1879] "N/A" "3.0" "N/A" "N/A" ...
 $ asc18_interval_upper_limit: chr [1:1879] "N/A" "7.1" "N/A" "N/A" ...
 $ asc18_footnote            : chr [1:1879] "5" NA "5" "5" ...
 $ asc19_total_cases         : chr [1:1879] "N/A" "169" "N/A" "N/A" ...
 $ asc19_performance_category: chr [1:1879] "N/A" "No Different than expected"..
 $ asc19_rshv_rate           : chr [1:1879] "N/A" "0.9" "N/A" "N/A" ...
 $ asc19_interval_lower_limit: chr [1:1879] "N/A" "0.6" "N/A" "N/A" ...
 $ asc19_interval_upper_limit: chr [1:1879] "N/A" "1.5" "N/A" "N/A" ...
 $ asc19_footnote            : chr [1:1879] "5" NA "1" "5" ...
 $ asc20_sample              : chr [1:1879] NA NA NA NA ...
 $ asc20_rate                : chr [1:1879] "N/A" "N/A" "N/A" "N/A" ...
 $ asc20_footnote            : chr [1:1879] "5" "5" "5" "5" ...

Params with length > 1

foo <- function(x) {
  
  x_name <- deparse(substitute(x))
  
  glue::glue("{x_name}[{seq_along(x)}]={x}") |> 
    glue::glue_collapse(sep = "&")
}

ID <- 5:7

foo(ID)
ID[1]=5&ID[2]=6&ID[3]=7

foo(x = list(ID = 1:2))
list(ID = 1:2)[1]=1:2

Multi-value Params

multi_value <- function(x) {
  glue::glue("[{seq_along(x)}]={x}") |> 
    glue::glue_collapse(sep = "&")
}

multi_value(state.abb[1:5])
[1]=AL&[2]=AK&[3]=AZ&[4]=AR&[5]=CA

Query Generation

  1. Select Dataset/Method
  2. Build JSON Query
  3. Build Request
  4. Retrieve Number of Results
  5. Build Offset Sequence

Allowed Operators

=           : equals(x)
<>          : equals(x, negate = TRUE)
>           : greater_than(x)
>=          : greater_than(x, equals = TRUE)
<           : less_than(x)
<=          : less_than(x, equals = TRUE)
STARTS_WITH : starts_with(x)
CONTAINS    : contains(x)
ENDS_WITH   : ends_with(x)
IN          : is_in(x)
NOT IN      : is_in(x, negate = TRUE)
BETWEEN     : between(x)
NOT BETWEEN : between(x, negate = TRUE)
IS NULL     : blank(x)
IS NOT NULL : blank(x, negate = TRUE)
new_query <- function(args, operator) {
  
  len    <- cheapr::lengths_(args, names = TRUE)
  single <- args[cheapr::which_(len == 1)]
  multi  <- args[cheapr::which_(len > 1)]
  
  x <- list(s = if (vctrs::vec_is_empty(single)) NULL else single, 
            m = if (vctrs::vec_is_empty(multi)) NULL else multi)
  
  idx <- rlang::set_names(seq_along(args), names(args))
  opr <- rlang::set_names(operator, names(args))

  g_single <- glue::glue(
    "filter[{idx[names(x$s)]}][condition][path]={names(x$s)}\n",
    "filter[{idx[names(x$s)]}][condition][operator]={opr[names(x$s)]}\n",
    "filter[{idx[names(x$s)]}][condition][value]={providertwo:::delist(x$s)}")
  
  g_mult <- glue::as_glue(
    c(
      glue::glue(
        "filter[{idx[names(x$m)]}][condition][path]={names(x$m)}\n",
        "filter[{idx[names(x$m)]}][condition][operator]={opr[names(x$m)]}"), 
      glue::glue(
        "filter[{idx[names(x$m)]}][condition][value]",
        "[{seq_along(providertwo:::delist(x$m))}]={providertwo:::delist(x$m)}"))
    )
  
  glue::glue_collapse(c(g_single, g_mult), sep = "\n")
}

new_query(args = list(state = c("GA", "MD"), 
                      last_name = "SMITH", 
                      npi = 1234567890), 
          operator = c("IN", "STARTS_WITH", "="))
filter[2][condition][path]=last_name
filter[2][condition][operator]=STARTS_WITH
filter[2][condition][value]=SMITH
filter[3][condition][path]=npi
filter[3][condition][operator]==
filter[3][condition][value]=1234567890
filter[1][condition][path]=state
filter[1][condition][operator]=IN
filter[1][condition][value][1]=GA
filter[1][condition][value][2]=MD

class_query

class_query <- new_class(
  "class_query",
  properties = list(
    path     = class_character,
    operator = class_character,
    value    = class_vector,
    limit    = class_numeric,
    sort     = class_character, 
    column   = class_vector,
    group    = new_property(
      class_list,
      default = list(
        conjunction = "AND",
        memberOf    = ""))
  )
)

query Formatters

ex <- list(
  state  = c("GA", "MD"), 
  name   = "Jerry", 
  number = 300.12)

list_lengths(ex)
[1] 2 1 1

args <- list_combine(ex, i = set_names(seq_along(ex), names(ex)))

list_assign(x = ex, values = set_names(as.list(seq_along(ex)), names(ex)))
$state
[1] 1

$name
[1] 2

$number
[1] 3

query_comparison

query_comparison <- function(args, 
                             operator = c("=", ">", ">=", "<", "<=", "<>")) {
  
  o <- match.arg(operator)
  x <- args[cheapr::which_(cheapr::lengths_(args, names = TRUE) == 1)]
  i <- args$i[names(x)]
  
  glue::glue(
  "
  filter[{i}][condition][path]={names(x)}
  filter[{i}][condition][operator]={o}
  filter[{i}][condition][value]={providertwo:::delist(x)}
  "
  )
}

query_comparison(args)
filter[2][condition][path]=name
filter[2][condition][operator]==
filter[2][condition][value]=Jerry
filter[3][condition][path]=number
filter[3][condition][operator]==
filter[3][condition][value]=300.12
query_comparison(args, ">=")
filter[2][condition][path]=name
filter[2][condition][operator]=>=
filter[2][condition][value]=Jerry
filter[3][condition][path]=number
filter[3][condition][operator]=>=
filter[3][condition][value]=300.12

query_equals_simple

query_equals_simple <- function(args) {
  
  args <- args[cheapr::which_(cheapr::lengths_(args, names = TRUE) == 1)]
  
  glue::glue("filter[{names(args)}]={providertwo:::delist(args)}")
}

query_equals_simple(ex)
filter[name]=Jerry
filter[number]=300.12

query_in_notin

query_in_notin <- function(args, operator = "IN") {
  
  o <- match.arg(operator, c("IN", "NOT IN"))
  x <- args[cheapr::which_(cheapr::lengths_(args, names = TRUE) > 1)]
  i <- args$i[names(x)]
  i <- names(i)[!cheapr::is_na(names(i))]
  x$i <- NULL
  
  glue::as_glue(
    c(
      glue::glue(
        "filter[{i}][condition][path]={names(x)} ",
        "filter[{i}][condition][operator]={o}"
        ), 
      glue::glue(
        "filter[{i}]",
        "[condition][value]",
        "[{seq_along(providertwo:::delist(x))}]={providertwo:::delist(x)}"
        )
      )
    )
}

query_in_notin(args, "IN")
filter[state][condition][path]=state filter[state][condition][operator]=IN
filter[state][condition][value][1]=GA
filter[state][condition][value][2]=MD
query_in_notin(ex, "NOT IN")

Finalizing query Components

glue::as_glue(c(query_in_notin(ex), query_comparison(ex)))

New Function

fields <- c(
  "year",
  "covered_recipient_npi",                                         
  "covered_recipient_type",                                        
  "covered_recipient_first_name",                                  
  "covered_recipient_last_name",                                   
  "recipient_city",                                                
  "recipient_state",                                               
  "recipient_zip_code",                                            
  "teaching_hospital_name",                                        
  "form_of_payment_or_transfer_of_value",                          
  "nature_of_payment_or_transfer_of_value",                        
  "applicable_manufacturer_or_applicable_gpo_making_payment_name",
  "applicable_manufacturer_or_applicable_gpo_making_payment_id")

process_params <- \(arg_names, field_names) {
  
  nms <- set_names(arg_names, field_names)
  
  parse_expr(
    paste0(
      "list2(", 
      glue_collapse(
        glue('{names(nms)} = {unname(nms)}'), 
        sep = ", "), ")"))
}

has_operator <- \(args) {
  map_lgl(args, function(x) inherits(x, "query_operator"))
}

name_path <- \(args) {
  map(args, \(x) x[[names(x)]][["path"]] <- names(x))
}
open_pay <- function(year,
                     npi               = NULL,
                     covered_type      = NULL,
                     first             = NULL,
                     last              = NULL,
                     city              = NULL,
                     state             = NULL,
                     zip               = NULL,
                     teaching_hospital = NULL,
                     payment_form      = NULL,
                     payment_nature    = NULL,
                     payer             = NULL,
                     payer_id          = NULL) {
  
  fn_args <- process_params(fn_fmls_names(), fields) |> 
    eval_bare() |> 
    compact()
  
  # if (any(has_operator(fn_args))) 
  # fn_args[has_operator(fn_args)] <- name_path(fn_args[has_operator(fn_args)])
  
  fn_args
}
open_pay(year = 2021, npi = "1043218118")
$year
[1] 2021

$covered_recipient_npi
[1] "1043218118"
open_pay(year = 2021, payment_nature = "Royalty or License")
$year
[1] 2021

$nature_of_payment_or_transfer_of_value
[1] "Royalty or License"
open_pay(year = 2021, payment_form = "Stock option")
$year
[1] 2021

$form_of_payment_or_transfer_of_value
[1] "Stock option"
open_pay(year = 2021, payer = "Adaptive Biotechnologies Corporation")
$year
[1] 2021

$applicable_manufacturer_or_applicable_gpo_making_payment_name
[1] "Adaptive Biotechnologies Corporation"
open_pay(year = 2021, teaching_hospital = "Nyu Langone Hospitals")
$year
[1] 2021

$teaching_hospital_name
[1] "Nyu Langone Hospitals"
open_pay(year = 2017:2023, npi = "1043477615")
$year
[1] 2017 2018 2019 2020 2021 2022 2023

$covered_recipient_npi
[1] "1043477615"
starts_with <- \(x) {
  structure(
    c(path      = NA_character_,
      operator  = "STARTS_WITH",
      value     = x),
    class = "query_operator")
}

starts_with("Royalty or License")
                path             operator                value 
                  NA        "STARTS_WITH" "Royalty or License" 
attr(,"class")
[1] "query_operator"

x <- open_pay(
  year = 2021, 
  payment_nature = starts_with("Royalty or License"), 
  teaching_hospital = starts_with("NYU"))

x
$year
[1] 2021

$teaching_hospital_name
         path      operator         value 
           NA "STARTS_WITH"         "NYU" 
attr(,"class")
[1] "query_operator"

$nature_of_payment_or_transfer_of_value
                path             operator                value 
                  NA        "STARTS_WITH" "Royalty or License" 
attr(,"class")
[1] "query_operator"

names(x[has_operator(x)])
[1] "teaching_hospital_name"                
[2] "nature_of_payment_or_transfer_of_value"

x[has_operator(x)][[1]][["path"]] <- names(x[has_operator(x)])[[1]]

x
$year
[1] 2021

$teaching_hospital_name
                    path                 operator                    value 
"teaching_hospital_name"            "STARTS_WITH"                    "NYU" 
attr(,"class")
[1] "query_operator"

$nature_of_payment_or_transfer_of_value
                path             operator                value 
                  NA        "STARTS_WITH" "Royalty or License" 
attr(,"class")
[1] "query_operator"

# map(args, \(x) x[[names(x)]][["path"]] <- names(x))
# 
# x[has_operator(x)] <- providertwo:::map2(
#   x[has_operator(x)], 
#   names(x[has_operator(x)]), 
#   function(x, y) x[["path"]] <- y)
# 
# x

Query Format

url_parse(
  base_url = "https://openpaymentsdata.cms.gov/api/1/datastore/", 
  url      = "query/fdc3c773-018a-412c-8a81-d7b8a13a037b/0") |> 
  url_modify_query(
    `conditions[0][property]` = "covered_recipient_last_name",
    `conditions[0][value]`    = "SMITH",
    `conditions[0][operator]` = "=",
    schema                    = "false",
    keys                      = "false",
    results                   = "false",
    offset                    = 0,
    limit                     = 500) |>
  url_build() |> 
  request() |> 
  providertwo:::perform_simple() |>
  _[["count"]]
[1] 77
url_parse(
  base_url = "https://openpaymentsdata.cms.gov/api/1/datastore/", 
  url      = "query/fdc3c773-018a-412c-8a81-d7b8a13a037b/0") |> 
  url_modify_query(
    `conditions[0][property]` = "covered_recipient_first_name",
    `conditions[0][value]`    = "PATRICK",
    `conditions[0][operator]` = "=",
    `conditions[1][property]` = "covered_recipient_last_name",
    `conditions[1][value]`    = "WARD",
    `conditions[1][operator]` = "=",
    schema                    = "false",
    keys                      = "false",
    results                   = "false",
    offset                    = 0) |>
  url_build() |> 
  request() |> 
  providertwo:::perform_simple() |>
  _[["count"]]
[1] 48

Other Examples

exquery <- glue::glue(
  '
  "filter[{i}][path]" = "{PATH}",
  "filter[{i}][operator]" = "{OPERATOR}",
  "filter[{i}][value]" = "{VALUE}"
  ', 
  i = 1:2, 
  PATH = c("STATE_CD", "LAST_NAME"), 
  OPERATOR = "=",
  VALUE = c("GA", "SMITH")
  ) |> 
  glue::glue_collapse(sep = ",\n")

exquery
"filter[1][path]" = "STATE_CD",
"filter[1][operator]" = "=",
"filter[1][value]" = "GA",
"filter[2][path]" = "LAST_NAME",
"filter[2][operator]" = "=",
"filter[2][value]" = "SMITH"

glue::glue('c({exquery})') |> 
  rlang::parse_expr() |> 
  rlang::eval_bare()
    filter[1][path] filter[1][operator]    filter[1][value]     filter[2][path] 
         "STATE_CD"                 "="                "GA"         "LAST_NAME" 
filter[2][operator]    filter[2][value] 
                "="             "SMITH" 
glue::glue(
  '
  filter[{PATH}][operator]={OPERATOR}
  filter[{PATH}][value]={VALUE}
  ', 
  PATH = c("STATE_CD", "LAST_NAME"), 
  OPERATOR = "IN",
  VALUE = c("GA", "SMITH")
  )
filter[STATE_CD][operator]=IN
filter[STATE_CD][value]=GA
filter[LAST_NAME][operator]=IN
filter[LAST_NAME][value]=SMITH
format_syntax <- \(name, value) {
  setNames(
    value, 
    paste0(
      name, 
      "[id-", 
      seq_along(value), 
      "][condition][value]"))
}

format_syntax("filter", c("GA", "NY"))
filter[id-1][condition][value] filter[id-2][condition][value] 
                          "GA"                           "NY" 


# query_syntax <- c(
#   "filter[STATE_CD]" = "GA",
#   "filter[LAST_NAME]" = "SMITH"
#   )

Pagination

base <- request("https://data.cms.gov/data-api/v1/dataset") |> 
  req_url_path_append("9887a515-7552-4693-bf58-735c77af46d7") |> 
  req_url_path_append("data")

base

GET
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data
Body: empty

stats <- base |> 
  req_url_path_append("stats")

stats

GET
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data/stats
Body: empty

stats <- stats |> 
  req_perform() |> 
  resp_body_json(simplifyVector = TRUE)

stats |> purse()
$found_rows i1  51174$total_rows i1  51174

# Offset shouldn't go to 50000
providertwo:::offset_seq(stats$found_rows, 5000)
 [1]     0  5000 10000 15000 20000 25000 30000 35000 40000 45000 50000

urls <- glue::glue(
  "{base$url}",
  "?size=5000&",
  "offset=",
  "{providertwo:::offset_seq(stats$found_rows, 5000)}"
)

urls
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=0
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=5000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=10000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=15000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=20000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=25000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=30000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=35000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=40000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=45000
https://data.cms.gov/data-api/v1/dataset/9887a515-7552-4693-bf58-735c77af46d7/data?size=5000&offset=50000

resp <- urls[1:3] |> 
  map(
    function(x) {
      request(x) |> 
        req_perform() |> 
        resp_body_json(simplifyVector = TRUE) |> 
        tibble()
    }) |> 
  list_rbind(names_to = "reqID") |> 
  janitor::clean_names()

resp |> 
  janitor::get_dupes()
No variable names specified - using all columns.
# A tibble: 6 × 15
  req_id first_name last_name npi        specialty  optout_effective_date
                                           
1      2 Joseph     Kunnel    1740295518 Dentist    09/25/2015           
2      2 Joseph     Kunnel    1740295518 Dentist    09/25/2015           
3      3 David      Fernitz   1225232226 Dentist    03/15/2016           
4      3 David      Fernitz   1225232226 Dentist    03/15/2016           
5      3 Martin     Drooker   1326103029 Psychiatry 04/01/2016           
6      3 Martin     Drooker   1326103029 Psychiatry 04/01/2016           
# ℹ 9 more variables: optout_end_date , first_line_street_address ,
#   second_line_street_address , city_name , state_code ,
#   zip_code , eligible_to_order_and_refer , last_updated ,
#   dupe_count 

dupes <- resp |> 
  count(npi, sort = TRUE) |> 
  filter(n > 1) |> 
  pull(npi)

resp |> 
  filter(npi %in% dupes) |> 
  arrange(npi)
# A tibble: 161 × 14
   req_id first_name last_name npi        specialty        optout_effective_date
                                                  
 1      2 Barbara    Lino      1013130632 Clinical Psycho… 04/16/2021           
 2      2 Barbara    Lino      1013130632 Clinical Psycho… 04/19/2021           
 3      2 Victoria   Lasser    1043597586 Clinical Psycho… 10/21/2015           
 4      3 Victoria   Lasser    1043597586 Clinical Psycho… 01/13/2016           
 5      2 Peter      Chang     1053320606 Dentist          09/25/2015           
 6      3 Peter      Chang     1053320606 Dentist          05/31/2016           
 7      1 Donna      Lewinter  1053493767 Psychiatry       10/10/2002           
 8      1 Donna      Lewinter  1053493767 Psychiatry       10/10/2012           
 9      1 Jeffrey    Dean      1073687711 Oral Surgery     06/25/2023           
10      1 Jeffrey    Dean      1073687711 Maxillofacial S… 06/25/2013           
# ℹ 151 more rows
# ℹ 8 more variables: optout_end_date , first_line_street_address ,
#   second_line_street_address , city_name , state_code ,
#   zip_code , eligible_to_order_and_refer , last_updated 

resp |> 
  filter(npi %in% c("1225232226", "1740295518", "1326103029")) |> 
  relocate(last_updated) |> 
  arrange(npi)
# A tibble: 6 × 14
  last_updated req_id first_name last_name npi   specialty optout_effective_date
                                             
1 10/15/2024        3 David      Fernitz   1225… Dentist   03/15/2016           
2 10/15/2024        3 David      Fernitz   1225… Dentist   03/15/2016           
3 05/15/2024        3 Martin     Drooker   1326… Psychiat… 04/01/2016           
4 05/15/2024        3 Martin     Drooker   1326… Psychiat… 04/01/2016           
5 10/15/2024        2 Joseph     Kunnel    1740… Dentist   09/25/2015           
6 10/15/2024        2 Joseph     Kunnel    1740… Dentist   09/25/2015           
# ℹ 7 more variables: optout_end_date , first_line_street_address ,
#   second_line_street_address , city_name , state_code ,
#   zip_code , eligible_to_order_and_refer 

Example 2

Select Dataset

distribution <- read_json_arrow(
  file = "https://data.cms.gov/data.json",
  col_select = c("dataset"),
  as_data_frame = TRUE) |> 
  to_duckdb() |> 
  pull(dataset) |> 
  pluck(1) |> 
  select(distribution) |> 
  as_tibble() |> 
  unnest(distribution) |> 
  # rename_with(remove_at_symbol) |> 
  filter(format      == "API", 
         description == "latest")

distribution |> 
  filter(grepl("Enrollment", title)) |> 
  pull(title)

accessURL <- distribution |> 
  filter(grepl("Opt Out Affidavits : ", title)) |> 
  pull(accessURL)

accessURL

Format Query

query <- glue::glue(
  'c(
    "filter[{i}][path]"  = "{PATH}",
    "filter[{i}][value]" = "{VALUE}"
  )', 
  i = 1, 
  PATH = "State Code", 
  VALUE = "CA")

query

query <- query |> 
  parse_expr() |> 
  eval_bare()

query

Retrieve Number of Results

queryurl <- accessURL |> 
  request() |> 
  req_url_query(size = 5000, !!!query)

nresults <- queryurl |> 
  req_url_path_append("stats") |> 
  req_perform() |> 
  resp_body_json() |> 
  fuimus::gelm("found")

nresults
url_parse(queryurl$url)

Generate and Send Requests

reqs <- map(
  glue(
    "{url}", 
    "&offset={off}", 
    url = queryurl$url, 
    off = providertwo:::offset_seq(nresults, 5000)), 
  httr2::request)

res <- req_perform_parallel(reqs) |> 
  resps_successes() |> 
  map(\(x) tibble(resp_body_json(x, simplifyVector = TRUE))) |> 
  bind_rows()

providertwo:::set_clean(res, names(res)) |> 
  providertwo:::map_na_if() |> 
  mtt(
    optout_effective_date = providertwo:::as_date(optout_effective_date, fmt = "%m/%d/%Y"),
    optout_end_date = providertwo:::as_date(optout_end_date, fmt = "%m/%d/%Y"),
    last_updated = providertwo:::as_date(last_updated, fmt = "%m/%d/%Y")
    )

Testing in a Function

protofunc <- function(id     = NULL, 
                      state  = NULL, 
                      name   = NULL, 
                      number = NULL) {
  arg <- class_args(
    id     = id,
    state  = state,
    name   = name,
    number = number)
  
  query_comparison(compact(props(arg)))
  
}

protofunc(state  = c("GA"), 
          name   = "Jerry", 
          number = 300.12)

Try Again

arg_list <- new_class(
  "arg_list",
  properties = list(params = class_list),
  constructor = function(...) new_object(S7_object(), params = rlang::list2(...)),
  validator = function(self) if (!rlang::is_named(self@params)) "all @params must all be named")

less_than <- function(x, equals = FALSE) {
  `attr<-`(x, "operator", if (equals) "<=" else "<")
}

is_in <- function(x, negate = FALSE) {
  `attr<-`(x, "operator", ifelse(negate, "NOT IN", "IN"))
}

a <- arg_list(
  npi   = less_than("1234567890"),
  state = is_in(state.abb),
  name  = "Jerry")

# get_operator <- purrr::attr_getter("operator")

# attr(prop(a, "params")$npi, "operator")

has_operator <- \(x) names(attributes(x)) %in% "operator"

has_operator(a@params$npi)
[1] TRUE

has_operator(a@params$name)
logical(0)

map(a@params, has_operator) |> 
  compact() |> 
  names()
[1] "npi"   "state"

a@params[names(a@params) %in% "npi"]
$npi
[1] "1234567890"
attr(,"operator")
[1] "<"
startswith <- \(x) {
  list(
    OPERATOR = "STARTS_WITH", 
    VALUE    = as.character(x)
  )
}

startswith("Royalty or License")

between <- \(x, negate = FALSE) {
  list(
    OPERATOR = ifelse(negate, "NOT BETWEEN", "BETWEEN"), 
    VALUE    = as.character(x)
  )
}

between(c(1, 10))

between(c(1, 10), negate = TRUE)

endswith <- \(x) {
  list(
    OPERATOR = "ENDS_WITH", 
    VALUE    = as.character(x)
  )
}

endswith("NYU")

is_in <- \(x, negate = FALSE) {
  list(
    OPERATOR = ifelse(negate, "NOT IN", "IN"), 
    VALUE    = as.character(x)
  )
}

is_in(c("GA", "NY"))

is_in(c("GA", "NY"), negate = TRUE)
x <- list(npi = ~ providertwo:::starts_with("gdklfgj"))
x
$npi
~providertwo:::starts_with("gdklfgj")

x$npi[1]
`~`()

rlang::is_formula(x$npi, lhs = TRUE)
[1] FALSE


foo <- function(npi            = NULL,
                entity         = NULL,
                first          = NULL,
                last           = NULL,
                organization   = NULL,
                name_type      = NULL,
                taxonomy_desc  = NULL,
                city           = NULL,
                state          = NULL,
                zip            = NULL,
                country        = NULL) {
  rlang::list2(
    number               = npi,
    enumeration_type     = entity,
    first_name           = first,
    last_name            = last,
    name_purpose         = name_type,
    organization_name    = organization,
    taxonomy_description = taxonomy_desc,
    city                 = city,
    state                = state,
    postal_code          = zip,
    country_code         = country,
    skip                 = 0L
  )
}

x <- foo(npi = ~ providertwo:::starts_with(1234567890))


rlang::f_lhs(x$number) <- rlang::expr(NPI)

x$number
NPI ~ providertwo:::starts_with(1234567890)

rlang::f_lhs(x$number)
NPI

rlang::f_rhs(x$number) |> 
  rlang::eval_tidy()
$field
character(0)

$operator
[1] "STARTS_WITH"

$input
[1] "1234567890"