diff --git a/.gitignore b/.gitignore index e00a5273..35240a68 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ inputs_selection_app/ .cache/ tmp/ + +app_data/ \ No newline at end of file diff --git a/R/ZZZ.R b/R/ZZZ.R index 5803c243..5cf6b37e 100644 --- a/R/ZZZ.R +++ b/R/ZZZ.R @@ -9,11 +9,6 @@ utils::globalVariables(c( ".env" )) -rtt_specialties <- function() { - app_sys("app", "data", "rtt_specialties.csv") |> - readr::read_csv(col_types = "cc") -} - sanitize_input_name <- function(.x) { .x |> stringr::str_to_lower() |> @@ -21,12 +16,6 @@ sanitize_input_name <- function(.x) { stringr::str_remove_all("[^a-z0-9-]+") } -# suppress vs code / languageserver "no visible binding" warnings -if (FALSE) { - .data <- NULL - .env <- NULL -} - md_file_to_html <- function(...) { file <- app_sys(...) @@ -77,38 +66,30 @@ is_local <- function() { Sys.getenv("SHINY_PORT") == "" || !getOption("golem.app.prod", TRUE) } -encrypt_filename <- function( - filename, - key_b64 = Sys.getenv("NHP_ENCRYPT_KEY") -) { - key <- openssl::base64_decode(key_b64) - - f <- charToRaw(filename) - - ct <- openssl::aes_cbc_encrypt(f, key, NULL) - hm <- as.raw(openssl::sha256(ct, key)) - - openssl::base64_encode(c(hm, ct)) -} - -get_params_schema_text <- function( +download_params_schema <- function( + data_path = "app_data", app_version = Sys.getenv("INPUTS_DATA_VERSION", "dev") ) { - tf <- tempfile() + file_path <- file.path(data_path, "params-schema.json") - utils::download.file( - glue::glue( - "https://the-strategy-unit.github.io/nhp_model/{app_version}/params-schema.json" - ), - tf - ) - # append a newline to the end of the params-schema file, otherwise you get a warning - # "incomplete final line found" - cat("\n", file = tf, append = TRUE) + params_schema <- httr2::request( + "https://the-strategy-unit.github.io" + ) |> + httr2::req_url_path("nhp_model", app_version, "params-schema.json") |> + httr2::req_perform() |> + httr2::resp_body_string() + + readr::write_lines(params_schema, file_path) - paste(readLines(tf), collapse = "\n") + invisible(file_path) } -create_params_schema <- function(schema_text) { - jsonvalidate::json_schema$new(schema_text) +.schema_cache <- new.env() +get_params_schema <- function() { + if (!exists("schema", envir = .schema_cache)) { + .schema_cache[["schema"]] <- file.path("app_data", "params-schema.json") |> + readr::read_file() |> + jsonvalidate::json_schema$new() + } + .schema_cache[["schema"]] } diff --git a/R/app_server.R b/R/app_server.R index d8b4d10f..29f118c3 100644 --- a/R/app_server.R +++ b/R/app_server.R @@ -5,140 +5,40 @@ #' @import shiny #' @noRd app_server <- function(input, output, session) { - # in fct_create_data_cache, we utilise this env var to invalidate the cache - # we can use it's value to allow us to cache all of the reactive data without - # having to bind to some other input which might change - cache_version <- shiny::reactive({ - Sys.getenv("CACHE_VERSION", 0) - }) - - diagnoses_lkup <- shiny::reactive({ - readr::read_csv(app_sys("app", "data", "diagnoses.csv"), col_types = "ccc") - }) - - procedures_lkup <- shiny::reactive({ - readr::read_csv(app_sys("app", "data", "procedures.csv"), col_types = "ccc") - }) - - mitigator_codes_lkup <- shiny::reactive({ - lkup <- app_sys("app", "data", "mitigator-codes.csv") |> - readr::read_csv(col_types = "c") - - purrr::set_names( - paste0(lkup[["strategy_name"]], " (", lkup[["mitigator_code"]], ")"), - lkup[["strategy"]] - ) - }) - - providers <- shiny::reactive({ - app_sys("app", "data", "providers.csv") |> - readr::read_csv(col_types = "cc") |> - tibble::deframe() # convert tibble to named vector - }) - - peers <- shiny::reactive({ - readr::read_csv(app_sys("app", "data", "peers.csv"), col_types = "cc") - }) - params <- mod_home_server( "home", - providers(), shiny::reactive(input$params_file) ) - # we could probably drop the need for start now, kept for historical reasons - start <- shiny::reactive({ - shiny::req(length(params) > 0) - shiny::req(params$dataset) - shiny::req(params$scenario) - 1 - }) - # load all data rates_data <- shiny::reactive({ - rates <- load_provider_data("rates") |> - dplyr::select(-"crude_rate") |> - dplyr::rename(rate = "std_rate") - - national_rate <- rates |> - dplyr::filter( - .data$provider == "national" - ) |> - dplyr::summarise( - .by = c("fyear", "strategy"), - national_rate = dplyr::first(.data$rate) - ) - - rates |> - dplyr::filter(.data$provider != "national") |> - dplyr::inner_join(national_rate, by = c("fyear", "strategy")) - }) |> - shiny::bindCache(cache_version()) + get_rates_data() + }) age_sex_data <- shiny::reactive({ - age_sex <- load_provider_data("age_sex") - # nolint start: object_usage_linter - age_fct <- age_sex |> _[["age_group"]] |> unique() |> sort() - # nolint end - age_sex |> - dplyr::mutate( - dplyr::across("sex", as.character), - age_group = factor( - .data[["age_group"]], - levels = .env[["age_fct"]] - ) - ) - }) |> - shiny::bindCache(cache_version()) - - diagnoses_data <- shiny::reactive({ - load_provider_data("diagnoses") - }) |> - shiny::bindCache(cache_version()) + provider <- params$dataset + fyear <- params$start_year - procedures_data <- shiny::reactive({ - load_provider_data("procedures") - }) |> - shiny::bindCache(cache_version()) - - baseline_data <- shiny::reactive({ - load_provider_data("baseline") - }) |> - shiny::bindCache(cache_version()) - - wli_data <- shiny::reactive({ - load_provider_data("wli") - }) |> - shiny::bindCache(cache_version()) - - inequalities_data <- shiny::reactive({ - load_provider_data("inequalities") - }) |> - shiny::bindCache(cache_version()) + get_age_sex_data(provider, fyear) + }) - expat_data <- shiny::reactive({ - load_provider_data("expat") - }) |> - shiny::bindCache(cache_version()) + diagnoses_data <- shiny::reactive({ + provider <- params$dataset + fyear <- params$start_year - repat_local_data <- shiny::reactive({ - load_provider_data("repat_local") - }) |> - shiny::bindCache(cache_version()) + get_diagnoses_data(provider, fyear) + }) - repat_nonlocal_data <- shiny::reactive({ - load_provider_data("repat_nonlocal") - }) |> - shiny::bindCache(cache_version()) + procedures_data <- shiny::reactive({ + provider <- params$dataset + fyear <- params$start_year - params_schema_text <- shiny::reactive({ - get_params_schema_text() - }) |> - shiny::bindCache(cache_version()) + get_procedures_data(provider, fyear) + }) # load all other modules once the home module has finished loading init <- shiny::observe({ - shiny::req(start() > 0) + shiny::req(params$dataset) available_strategies <- shiny::reactive({ # nolint start: object_usage_linter @@ -159,17 +59,13 @@ app_server <- function(input, output, session) { unique() }) - mod_baseline_adjustment_server( - "baseline_adjustment", - baseline_data(), - params - ) + mod_baseline_adjustment_server("baseline_adjustment", params) mod_population_growth_server("population_growth", params) mod_health_status_adjustment_server("health_status_adjustment", params) - observe({ + shiny::observe({ can_set_inequalities <- any( c("nhp_devs", "nhp_power_users", "nhp_test_inequalities") %in% session$groups @@ -181,26 +77,13 @@ app_server <- function(input, output, session) { ) }) - mod_inequalities_server("inequalities", inequalities_data(), params) - - mod_waiting_list_imbalances_server( - "waiting_list_imbalances", - wli_data(), - params - ) + mod_inequalities_server("inequalities", params) - mod_expat_repat_server( - "expat_repat", - expat_data(), - repat_local_data(), - repat_nonlocal_data(), - params, - providers() - ) + mod_expat_repat_server("expat_repat", params) mod_non_demographic_adjustment_server("non_demographic_adjustment", params) - mod_mitigators_summary_server("mitigators_summary", age_sex_data(), params) + mod_mitigators_summary_server("mitigators_summary", age_sex_data, params) purrr::walk( c( @@ -221,15 +104,11 @@ app_server <- function(input, output, session) { ), mod_mitigators_server, params, - rates_data(), - age_sex_data(), - diagnoses_data(), - procedures_data(), - available_strategies, - diagnoses_lkup(), - procedures_lkup(), - mitigator_codes_lkup(), - peers() + rates_data, + age_sex_data, + diagnoses_data, + procedures_data, + available_strategies ) # enable the run_model page for certain users/running locally @@ -239,15 +118,13 @@ app_server <- function(input, output, session) { ) if (is_local() || can_run_model) { shinyjs::show("run-model-container") - mod_run_model_server("run_model", params, params_schema_text()) + mod_run_model_server("run_model", params) } init$destroy() - }) |> - shiny::bindEvent(start()) + }) shiny::observe({ - shiny::req(start() > 0) shiny::req(params$dataset) shiny::req(params$scenario) @@ -260,28 +137,10 @@ app_server <- function(input, output, session) { params |> shiny::reactiveValuesToList() |> - mod_run_model_fix_params(params_schema_text()) |> + mod_run_model_fix_params() |> jsonlite::write_json(file, pretty = TRUE, auto_unbox = TRUE) }) - if (as.logical(Sys.getenv("ENABLE_AUTO_RECONNECT", FALSE))) { - cat("auto reconnect enabled\n") - session$allowReconnect("force") - } - - shiny::observe({ - shiny::req("nhp_devs" %in% session$groups) - - u <- shiny::parseQueryString(session$clientData$url_search) - - shiny::req(!is.null(u$reset_cache)) - cat("reset cache\n") - - dc <- shiny::shinyOptions()$cache - - dc$reset() - }) - # return NULL } diff --git a/R/app_ui.R b/R/app_ui.R index c68d1860..9e759792 100644 --- a/R/app_ui.R +++ b/R/app_ui.R @@ -201,10 +201,6 @@ app_ui <- function(request) { tabName = "tab_nda", mod_non_demographic_adjustment_ui("non_demographic_adjustment") ), - bs4Dash::tabItem( - tabName = "tab_wli", - mod_waiting_list_imbalances_ui("waiting_list_imbalances") - ), bs4Dash::tabItem( tabName = "tab_er", mod_expat_repat_ui("expat_repat") diff --git a/R/fct_azure_storage.R b/R/fct_azure_storage.R index 59fa200b..d4c28d25 100644 --- a/R/fct_azure_storage.R +++ b/R/fct_azure_storage.R @@ -1,22 +1,27 @@ -#' Get Provider Data +#' Download Provider Data #' -#' Read the parquet file containing a selected type of provider data. +#' Download a selected type of provider data from ADLS and save it as a parquet +#' file in the app's data directory. #' -#' @param file The name of the file to read. +#' @param file The name of the file to download. +#' @param data_path The path to the app's data directory. #' @param inputs_data_version The version of the inputs data to use. -#' @return A tibble. -load_provider_data <- function( +#' @param ... Additional arguments to pass to `AzureStor::download_adls_file()`. +#' +#' @return The path to the downloaded file. +download_provider_data <- function( file, - inputs_data_version = Sys.getenv("NHP_INPUTS_DATA_VERSION", "dev") + data_path = file.path("app_data"), + inputs_data_version = Sys.getenv("NHP_INPUTS_DATA_VERSION", "dev"), + ... ) { fs <- get_adls_fs() - fs |> - AzureStor::download_adls_file( - glue::glue("{inputs_data_version}/provider/{file}.parquet"), - dest = NULL - ) |> - arrow::read_parquet() |> - tibble::as_tibble() + src <- glue::glue("{inputs_data_version}/provider/{file}.parquet") + dest <- file.path(data_path, glue::glue("{file}.parquet")) + + AzureStor::download_adls_file(fs, src = src, dest = dest, ...) + + invisible(dest) } #' Get ADLS Filesystem @@ -32,3 +37,46 @@ get_adls_fs <- function() { AzureStor::adls_endpoint(token = token) |> AzureStor::adls_filesystem(Sys.getenv("AZ_STORAGE_CONTAINER")) } + +#' Get All Data Files +#' +#' Download all provider data files from ADLS and save them as parquet files in +#' the app's data directory. +#' +#' @param inputs_data_version The version of the inputs data to use. +#' @return NULL +get_all_data_files <- function( + inputs_data_version = Sys.getenv("NHP_INPUTS_DATA_VERSION", "dev") +) { + data_path <- file.path("app_data") + if (!dir.exists(data_path)) { + dir.create(data_path, recursive = TRUE) + } + + files <- c( + "rates", + "age_sex", + "diagnoses", + "procedures", + "baseline", + "inequalities", + "expat", + "repat_local", + "repat_nonlocal" + ) + + purrr::walk( + purrr::set_names(files), + download_provider_data, + inputs_data_version = inputs_data_version, + data_path = data_path, + overwrite = TRUE + ) + + download_params_schema( + data_path = data_path, + app_version = inputs_data_version + ) + + invisible(NULL) +} diff --git a/R/fct_create_data_cache.R b/R/fct_create_data_cache.R deleted file mode 100644 index f9b9c82a..00000000 --- a/R/fct_create_data_cache.R +++ /dev/null @@ -1,33 +0,0 @@ -#' create_data_cache -#' -#' @description A fct function -#' -#' @return The return value, if any, from executing the function. -#' -#' @noRd - -create_data_cache <- function() { - if (!dir.exists(".cache")) { - dir.create(".cache") - } - - dc <- cachem::cache_disk(".cache/data_cache", 200 * 1024^2) # 200MB - - # in case we need to invalidate the cache on rsconnect quickly, we can increment the "CACHE_VERSION" env var - cache_version <- ifelse( - file.exists(".cache/cache_version.txt"), - as.numeric(readLines(".cache/cache_version.txt")), - -1 - ) - - if (Sys.getenv("CACHE_VERSION", 0) > cache_version) { - cat("Invalidating cache\n") - dc$reset() - cache_version <- Sys.getenv("CACHE_VERSION", 0) - writeLines(as.character(cache_version), ".cache/cache_version.txt") - } - - shiny::shinyOptions(cache = dc) - - invisible(NULL) -} diff --git a/R/fct_load_provider_data.R b/R/fct_load_provider_data.R new file mode 100644 index 00000000..03eac7b9 --- /dev/null +++ b/R/fct_load_provider_data.R @@ -0,0 +1,102 @@ +.data_cache <- new.env() + +#' Get Provider Data +#' +#' Read the parquet file containing a selected type of provider data. +#' +#' @param file The name of the file to read. +#' @param data_path The path to the directory containing the parquet files. +#' @return A tibble. +load_provider_data <- function(file, data_path = file.path("app_data")) { + if (!exists(file, envir = .data_cache)) { + .data_cache[[file]] <- file.path(data_path, glue::glue("{file}.parquet")) |> + arrow::read_parquet() |> + tibble::as_tibble() + } + .data_cache[[file]] +} + +get_rates_data <- function() { + rates <- load_provider_data("rates") |> + dplyr::select(-"crude_rate") |> + dplyr::rename(rate = "std_rate") + + national_rate <- rates |> + dplyr::filter( + .data$provider == "national" + ) |> + dplyr::summarise( + .by = c("fyear", "strategy"), + national_rate = dplyr::first(.data$rate) + ) + + rates |> + dplyr::filter(.data$provider != "national") |> + dplyr::inner_join(national_rate, by = c("fyear", "strategy")) +} + +get_age_sex_data <- function(provider, fyear) { + age_sex <- load_provider_data("age_sex") + + age_fct <- sort(unique(age_sex[["age_group"]])) + + age_sex |> + dplyr::filter( + .data$provider == .env$provider, + .data$fyear == .env$fyear + ) |> + dplyr::mutate( + dplyr::across("sex", as.character), + age_group = factor( + .data[["age_group"]], + levels = .env[["age_fct"]] + ) + ) +} + +get_diagnoses_data <- function(provider, fyear) { + load_provider_data("diagnoses") |> + dplyr::filter( + .data$provider == .env$provider, + .data$fyear == .env$fyear + ) +} + +get_procedures_data <- function(provider, fyear) { + load_provider_data("procedures") |> + dplyr::filter( + .data$provider == .env$provider, + .data$fyear == .env$fyear + ) +} + +get_baseline_data <- function(provider, fyear) { + load_provider_data("baseline") |> + dplyr::filter( + .data$provider == .env$provider, + .data$fyear == .env$fyear + ) +} + +get_inequalities_data <- function(provider, fyear) { + load_provider_data("inequalities") |> + dplyr::filter( + .data$provider == .env$provider, + .data$fyear == .env$fyear + ) +} + +get_expat_data <- function(provider) { + load_provider_data("expat") |> + dplyr::filter( + .data$provider == .env$provider + ) +} + +get_repat_local_data <- function() { + load_provider_data("repat_local") +} + +get_repat_nonlocal_data <- function() { + load_provider_data("repat_nonlocal") +} diff --git a/R/fct_reduce_values.R b/R/fct_reduce_values.R deleted file mode 100644 index e3c98460..00000000 --- a/R/fct_reduce_values.R +++ /dev/null @@ -1,36 +0,0 @@ -reduce_values <- function(values, target) { - # ensure values are valid - stopifnot( - "values must be between 0 and 1" = all(values >= 0, values <= 1) - ) - - # start of including all of the non-target items which are greater than 0 - include <- values[values > 0] |> - names() |> - stringr::str_subset(stringr::fixed(target), TRUE) - - # create a recursive function to reduce the values until sum(values) <= 1 - fn <- function(values, include) { - # get the sum of the values - s <- sum(values) - # if the sum is less than or equal to 1 we are ok - if (s <= 1) { - return(values) - } - # work out the amount over - over <- s - 1 - # how many values can we reduce - n <- length(include) - # figure out how much to reduce the values by - # - use either the smallest value in the list, - # - or equally reduce all of the values - r <- pmin(min(values[include]), over / n) - # update the values - values[include] <- values[include] - r - # recurse: remove items that are now 0 - fn(values, include[values[include] > 0]) - } - - # run the function - fn(values, include) -} diff --git a/R/fct_utils_lookups.R b/R/fct_utils_lookups.R new file mode 100644 index 00000000..9efcf96e --- /dev/null +++ b/R/fct_utils_lookups.R @@ -0,0 +1,109 @@ +lookup_file_path <- function(file) { + app_sys("app", "reference", file) +} + +get_diagnoses_lookup <- function() { + readr::read_csv( + lookup_file_path("diagnoses.csv"), + col_types = "ccc", + progress = FALSE + ) +} + +get_procedures_lookup <- function() { + readr::read_csv( + lookup_file_path("procedures.csv"), + col_types = "ccc", + progress = FALSE + ) +} + +get_mitigators_lookup <- function() { + lookup_file_path("mitigator-codes.csv") |> + readr::read_csv(col_types = "c", progress = FALSE) |> + dplyr::mutate( + strategy_name_full = glue::glue("{strategy_name} ({mitigator_code})") + ) +} + +get_peers_lookup <- function() { + readr::read_csv( + lookup_file_path("peers.csv"), + col_types = "cc", + progress = FALSE + ) +} + +get_providers_lookup <- function() { + lookup_file_path("providers.csv") |> + readr::read_csv(col_types = "cc", progress = FALSE) |> + tibble::deframe() +} + +get_ndg_variants_lookup <- function() { + lookup_file_path("ndg_variants.json") |> + jsonlite::read_json(simplifyVector = TRUE) |> + purrr::keep_at(c("variant_2", "variant_3")) +} + +get_nee_lookup <- function() { + readr::read_csv( + lookup_file_path("nee_table.csv"), + col_types = "cddd", + progress = FALSE + ) +} + +get_rtt_specialties_lookup <- function() { + lookup_file_path("rtt_specialties.csv") |> + readr::read_csv(col_types = "cc", progress = FALSE) |> + dplyr::mutate(sanitized_code = sanitize_input_name(.data[["code"]])) +} + +get_waiting_list_multipliers <- function() { + lookup_file_path("waiting_list_params.csv") |> + readr::read_csv(col_types = "cddddd", progress = FALSE) |> + dplyr::transmute( + .data[["tretspef"]], + ip = .data[["mixed_split"]] * + .data[["avg_ip_activity_per_pathway_mixed"]], + op = .data[["op_only_split"]] * + .data[["avg_op_first_activity_per_pathway_op_only"]] + + .data[["mixed_split"]] * + .data[["avg_op_first_activity_per_pathway_mixed"]] + ) |> + tidyr::pivot_longer( + c("ip", "op"), + names_to = "activity_type", + values_to = "multiplier" + ) +} + +get_icb_boundaries <- function() { + sf::read_sf(lookup_file_path("icb_boundaries.geojson")) +} + +# use a singleton pattern to cache lookups in memory, but prevent the files from +# being read immediately when the package is attached +.lookups_cache <- new.env() + +get_lookups <- function() { + if (length(ls(envir = .lookups_cache)) == 0) { + list2env( + list( + "diagnoses" = get_diagnoses_lookup(), + "procedures" = get_procedures_lookup(), + "mitigators" = get_mitigators_lookup(), + "peers" = get_peers_lookup(), + "providers" = get_providers_lookup(), + "ndg_variants" = get_ndg_variants_lookup(), + "nee_table" = get_nee_lookup(), + "rtt_specialties" = get_rtt_specialties_lookup(), + "waiting_list_multipliers" = get_waiting_list_multipliers(), + "icb_boundaries" = get_icb_boundaries() + ), + envir = .lookups_cache + ) + } + .lookups_cache +} diff --git a/R/mod_baseline_adjustment_server.R b/R/mod_baseline_adjustment_server.R index 7497ac01..150a3bf9 100644 --- a/R/mod_baseline_adjustment_server.R +++ b/R/mod_baseline_adjustment_server.R @@ -1,16 +1,21 @@ #' baseline_adjustment Server Functions #' #' @noRd -mod_baseline_adjustment_server <- function(id, baseline_data, params) { +mod_baseline_adjustment_server <- function(id, params) { mod_reasons_server(shiny::NS(id, "reasons"), params, "baseline_adjustment") + rtt_specialties <- get_lookups()[["rtt_specialties"]] + shiny::moduleServer(id, function(input, output, session) { + baseline_data <- shiny::reactive({ + get_baseline_data(params$dataset, params$start_year) + }) + # static data ---- # creates a table containing all of the options shown in the baseline adjustment page, including the input id # for each slider - specs <- rtt_specialties() |> - dplyr::mutate(sanitized_code = sanitize_input_name(.data[["code"]])) |> + specs <- rtt_specialties |> dplyr::cross_join( dplyr::bind_rows( ip = tibble::tibble(g = c("elective", "non-elective", "maternity")), @@ -47,7 +52,7 @@ mod_baseline_adjustment_server <- function(id, baseline_data, params) { year <- as.character(shiny::req(params$start_year)) # nolint end - baseline_data |> + baseline_data() |> dplyr::filter( .data[["fyear"]] == .env[["year"]], .data[["provider"]] == .env[["dataset"]] diff --git a/R/mod_baseline_adjustment_ui.R b/R/mod_baseline_adjustment_ui.R index 67d20432..66da856e 100644 --- a/R/mod_baseline_adjustment_ui.R +++ b/R/mod_baseline_adjustment_ui.R @@ -10,8 +10,7 @@ mod_baseline_adjustment_ui <- function(id) { ns <- shiny::NS(id) - specs <- rtt_specialties() |> - dplyr::mutate(sanitized_code = sanitize_input_name(.data[["code"]])) + specs <- get_lookups()[["rtt_specialties"]] create_table <- function(at, g, df = specs) { df |> diff --git a/R/mod_expat_repat_server.R b/R/mod_expat_repat_server.R index 31b89385..16498eec 100644 --- a/R/mod_expat_repat_server.R +++ b/R/mod_expat_repat_server.R @@ -1,25 +1,29 @@ #' expat_repat Server Functions #' #' @noRd -mod_expat_repat_server <- function( - id, - expat_data, - repat_local_data, - repat_nonlocal_data, - params, - providers -) { +mod_expat_repat_server <- function(id, params) { + providers <- get_lookups()[["providers"]] + + rtt_specialties <- get_lookups()[["rtt_specialties"]] |> + dplyr::select("specialty", "code") |> + tibble::deframe() + + icb_boundaries <- get_lookups()[["icb_boundaries"]] + mod_reasons_server(shiny::NS(id, "reasons"), params, "expat_repat") shiny::moduleServer(id, function(input, output, session) { - # static data ---- - rtt_specialties <- rtt_specialties() |> - tibble::deframe() - icb_boundaries <- sf::read_sf(app_sys( - "app", - "data", - "icb_boundaries.geojson" - )) + expat_data <- shiny::reactive({ + get_expat_data(params$dataset) + }) + + repat_local_data <- shiny::reactive({ + get_repat_local_data() + }) + + repat_nonlocal_data <- shiny::reactive({ + get_repat_nonlocal_data() + }) # helpers ---- @@ -51,24 +55,21 @@ mod_expat_repat_server <- function( # extract the expat data for the current selection expat <- shiny::reactive({ - ds <- shiny::req(params$dataset) - - expat_data |> - dplyr::filter(.data$provider == ds) |> + expat_data() |> extract_expat_repat_data() |> dplyr::select("fyear", "count") }) # extract the repat local data for the current selection repat_local <- shiny::reactive({ - repat_local_data |> + repat_local_data() |> extract_expat_repat_data() |> dplyr::select("fyear", "icb", "provider", "count", "pcnt") }) # extract the repat nonlocal data for the current selection repat_nonlocal <- shiny::reactive({ - repat_nonlocal_data |> + repat_nonlocal_data() |> extract_expat_repat_data() |> dplyr::select( "fyear", diff --git a/R/mod_expat_repat_ui.R b/R/mod_expat_repat_ui.R index b2526347..ff854b52 100644 --- a/R/mod_expat_repat_ui.R +++ b/R/mod_expat_repat_ui.R @@ -84,13 +84,17 @@ mod_expat_repat_ui <- function(id) { generate_param_controls("repat_local", 100, 500, c(100, 105)), shiny::fluidRow( col_6( - shiny::plotOutput( - ns("repat_local_plot"), + shinycssloaders::withSpinner( + shiny::plotOutput( + ns("repat_local_plot"), + ) ) ), col_6( - shiny::plotOutput( - ns("repat_local_split_plot") + shinycssloaders::withSpinner( + shiny::plotOutput( + ns("repat_local_split_plot") + ) ) ) ) @@ -101,18 +105,24 @@ mod_expat_repat_ui <- function(id) { generate_param_controls("repat_nonlocal", 100, 500, c(100, 105)), shiny::fluidRow( col_4( - shiny::plotOutput( - ns("repat_nonlocal_pcnt_plot") + shinycssloaders::withSpinner( + shiny::plotOutput( + ns("repat_nonlocal_pcnt_plot") + ) ) ), col_4( - shiny::plotOutput( - ns("repat_nonlocal_n") + shinycssloaders::withSpinner( + shiny::plotOutput( + ns("repat_nonlocal_n") + ) ) ), col_4( - leaflet::leafletOutput( - ns("repat_nonlocal_icb_map") + shinycssloaders::withSpinner( + leaflet::leafletOutput( + ns("repat_nonlocal_icb_map") + ) ) ) ) diff --git a/R/mod_home_server.R b/R/mod_home_server.R index 411b6091..dc5317bf 100644 --- a/R/mod_home_server.R +++ b/R/mod_home_server.R @@ -1,7 +1,7 @@ #' home Server Functions #' #' @noRd -mod_home_server <- function(id, providers, filename) { +mod_home_server <- function(id, filename) { shiny::moduleServer(id, function(input, output, session) { # reactives ---- diff --git a/R/mod_inequalities_server.R b/R/mod_inequalities_server.R index 484fe871..03f25a90 100644 --- a/R/mod_inequalities_server.R +++ b/R/mod_inequalities_server.R @@ -1,10 +1,17 @@ #' inequalities Server Functions #' #' @noRd -mod_inequalities_server <- function(id, inequalities_data, params) { +mod_inequalities_server <- function(id, params) { shiny::moduleServer(id, function(input, output, session) { ns <- session$ns + inequalities_data <- shiny::reactive({ + provider <- params$dataset + fyear <- params$start_year + + get_inequalities_data(provider, fyear) + }) + mod_reasons_server(shiny::NS(id, "reasons"), params, "inequalities") # This is the data for each HRG split by IMD for the selector provider @@ -12,7 +19,7 @@ mod_inequalities_server <- function(id, inequalities_data, params) { provider_inequalities <- shiny::reactive({ dataset <- shiny::req(params$dataset) # nolint: object_usage_linter - inequalities_data |> + inequalities_data() |> dplyr::filter( .data[["provider"]] == .env[["dataset"]] ) diff --git a/R/mod_mitigators_server.R b/R/mod_mitigators_server.R index ab8077c4..977297f5 100644 --- a/R/mod_mitigators_server.R +++ b/R/mod_mitigators_server.R @@ -8,12 +8,12 @@ mod_mitigators_server <- function( age_sex_data, diagnoses_data, procedures_data, - available_strategies, - diagnoses_lkup, - procedures_lkup, - mitigator_codes_lkup, - peers + available_strategies ) { + lookups <- get_lookups() + + default_interval <- c(0.95, 1.0) + config <- get_golem_config("mitigators_config")[[id]] activity_type <- config$activity_type @@ -29,19 +29,25 @@ mod_mitigators_server <- function( ) shiny::moduleServer(id, function(input, output, session) { + # -------------------------------------------------------------------------- + # module initialization + # -------------------------------------------------------------------------- + + # this will contain the slider values for each strategy. this is separate + # from the params reactiveValues, because we want to keep track of the + # values a user has set whether they have clicked "include" or not. + # if a value is in the params, it will be equal to the value in + # slider_values slider_values <- shiny::reactiveValues() + # get the available strategies for this module + # + # uses the config values to filter all of the strategies to just those that + # are relevant to this module, and are available for the selected provider strategies <- shiny::reactive({ # make sure a provider is selected shiny::req(params$dataset) - shiny::observe( - input$strategy |> - shiny::req() |> - reasons_key() - ) |> - shiny::bindEvent(input$strategy) - # need to invert this list (flip names -> values) strats_subset <- config$strategy_subset available_subset <- intersect( @@ -49,17 +55,16 @@ mod_mitigators_server <- function( available_strategies() ) - purrr::set_names( - available_subset, - mitigator_codes_lkup[available_subset] # e.g. 'IP-EF-017: Enhanced Recovery (Hip)' - ) + lookups[["mitigators"]] |> + dplyr::select("strategy_name_full", "strategy") |> + dplyr::filter(.data$strategy %in% available_subset) |> + tibble::deframe() }) - get_default <- function(rate) { - c(0.95, 1) - } - - init <- shiny::observe( + # initialize the module with the values from the loaded parameters file + # + # runs when strategies() has loaded, and only runs once + shiny::observe( { strategies <- shiny::req(strategies()) @@ -75,26 +80,14 @@ mod_mitigators_server <- function( purrr::map("interval") strategies |> - # remove the friendly name for the strategy, replace with itself - purrr::set_names() |> purrr::walk(\(i) { - # get the rates data for this strategy (for the provider in the baseline year) - r <- rates_data |> - dplyr::filter( - .data$strategy == strategies[i], - .data$provider == params$dataset, - .data$fyear == params$start_year - ) - slider_values[[mitigators_type]][[i]] <- c( # add the additional param items if they exist. - config$params_items |> - # if the additional item is a list, chose the value for the current strategy - purrr::map_if(is.list, ~ .x[[i]]) |> - # if the additional item is a function, evaluate it with the rates data - purrr::map_if(is.function, rlang::exec, r), + + # if the additional item is a list, chose the value for the current strategy + purrr::map_if(config$params_items, is.list, ~ .x[[i]]), list( - interval = loaded_values[[i]] %||% get_default(r$rate) + interval = loaded_values[[i]] %||% default_interval ) ) @@ -104,11 +97,22 @@ mod_mitigators_server <- function( slider_values[[mitigators_type]][[i]] } }) - - init$destroy() }, priority = 100 - ) + ) |> + shiny::bindEvent(strategies(), once = TRUE) + + # -------------------------------------------------------------------------- + # input$strategy observers + # -------------------------------------------------------------------------- + + # update the reasons module with the selected strategy + shiny::observe( + input$strategy |> + shiny::req() |> + reasons_key() + ) |> + shiny::bindEvent(input$strategy) # set the strategy text by loading the contents of the file for that strategy output$strategy_text <- shiny::renderUI({ @@ -122,22 +126,59 @@ mod_mitigators_server <- function( "strategy_text", paste0(files[stringr::str_detect(strategy, files)], ".md") ) - }) + }) |> + shiny::bindEvent(input$strategy) + + # update the state of the "include?" checkbox when the strategy is changed + # + # runs when the strategy dropdown is changed and sets the checkbox to + # TRUE/FALSE depending on whether the value is currently in params or not + # + # ensures that the module is initialized by checking that slider_values is + # not null for the selected strategy + shiny::observe({ + # ensure include checkbox is on or off given param value + strategy <- shiny::req(input$strategy) + # Wait for slider to be initialized before updating checkbox + shiny::req(slider_values[[mitigators_type]][[strategy]]) + include <- !is.null(params[[mitigators_type]][[activity_type]][[ + strategy + ]]) + shiny::updateCheckboxInput(session, "include", value = include) + }) |> + shiny::bindEvent(input$strategy) - # rates data baseline year ---- + # update the slider values when the strategy is changed + # + # runs when the strategy dropdown is changed and loads the currently + # saved values for the selected strategy + shiny::observe({ + # update slider + strategy <- shiny::req(input$strategy) + scale <- 100 + values <- slider_values[[mitigators_type]][[strategy]]$interval * scale + shiny::updateSliderInput( + session, + "slider", + value = values + ) + }) |> + shiny::bindEvent(input$strategy) + + # rates data for the baseline year rates_baseline_data <- shiny::reactive({ strategy <- shiny::req(input$strategy) # nolint start: object_usage_linter - scheme_peers <- peers |> + scheme_peers <- lookups[["peers"]] |> dplyr::filter( .data$procode == params$dataset & .data$peer != params$dataset ) |> dplyr::pull(.data$peer) # nolint end - rates_data |> + rates_data() |> dplyr::filter( .data$strategy == .env$strategy, .data$fyear == params$start_year @@ -150,42 +191,18 @@ mod_mitigators_server <- function( ) ) |> dplyr::arrange(dplyr::desc(.data$is_peer)) # to plot focal scheme last - }) - - # params controls ---- - - provider_max_value <- shiny::reactive({ - r <- dplyr::filter(rates_baseline_data(), !.data$is_peer)$rate - m <- config$slider_scale / config$slider_step - floor(r * m) / m - }) - - shiny::observe({ - # ensure include checkbox is on or off given param value - strategy <- shiny::req(input$strategy) - # Wait for slider to be initialized before updating checkbox - shiny::req(slider_values[[mitigators_type]][[strategy]]) - include <- !is.null(params[[mitigators_type]][[activity_type]][[ - strategy - ]]) - shiny::updateCheckboxInput(session, "include", value = include) }) |> shiny::bindEvent(input$strategy) - shiny::observe({ - # update slider - strategy <- shiny::req(input$strategy) - scale <- 100 - - values <- slider_values[[mitigators_type]][[strategy]]$interval * scale - shiny::updateSliderInput( - session, - "slider", - value = values - ) - }) |> - shiny::bindEvent(input$strategy) + # -------------------------------------------------------------------------- + # params controls + # -------------------------------------------------------------------------- + # update the params when the slider or include checkbox is changed + # + # runs when the slider or include checkbox is changed and saves the values + # for the selected strategy to the params reactiveValues (or, removes it + # from params if include is FALSE) shiny::observe({ values <- input$slider strategy <- shiny::req(input$strategy) @@ -203,13 +220,23 @@ mod_mitigators_server <- function( }) |> shiny::bindEvent(input$slider, input$include) + # enable/disable the slider depending on whether the "include?" checkbox is + # checked + # + # runs when the include checkbox is changed shiny::observe({ shinyjs::toggleState("slider", condition = input$include) }) |> shiny::bindEvent(input$include) - # plot ribbon to show selected params ---- + # -------------------------------------------------------------------------- + # output reactives + # -------------------------------------------------------------------------- + # plot ribbon to show selected params + # + # draws a yellow ribbon on the trend, funnel, and boxplot charts to show the + # interval selected by the slider. plot_ribbon <- shiny::reactive({ baseline_value <- dplyr::filter( rates_baseline_data(), @@ -236,37 +263,31 @@ mod_mitigators_server <- function( } }) - # trend plot ---- + # trend plot + # # use the rates data, filtered to the provider that has been selected - trend_data <- shiny::reactive({ strategy <- shiny::req(input$strategy) - rates_data |> + rates_data() |> dplyr::filter( .data$strategy == .env$strategy, .data$provider == params$dataset ) }) - output$trend_plot <- shiny::renderPlot({ - rates_trend_plot( - trend_data(), - params$start_year, - plot_range(), - config$y_axis_title, - config$x_axis_title, - config$number_type, - plot_ribbon() - ) - }) - - # funnel plot ---- + # funnel plot + # + # use the rates data for the baseline year to generate the funnel plot data, + # showing all providers, highlighting the selected provider and its peers funnel_data <- shiny::reactive({ rates_baseline_data() |> generate_rates_funnel_data() }) # calculate the range across our plots + # + # used to ensure that the y-axis is the same across the trend, funnel, and + # boxplot plot_range <- shiny::reactive({ td_rate <- shiny::req(trend_data())$rate @@ -283,6 +304,35 @@ mod_mitigators_server <- function( c(0, max(c(td_rate, fd_rate))) }) + # get the value in the baseline year for the selected provider + # + # runs when the rates_baseline_data changes (which happens when the strategy + # dropdown is changed) + provider_baseline_value <- shiny::reactive({ + rates_baseline_data() |> + dplyr::filter(.data$provider == params$dataset) |> + purrr::pluck("rate") + }) |> + shiny::bindEvent(rates_baseline_data()) + + # -------------------------------------------------------------------------- + # output renderers + # -------------------------------------------------------------------------- + + # render the trend plot + output$trend_plot <- shiny::renderPlot({ + rates_trend_plot( + trend_data(), + params$start_year, + plot_range(), + config$y_axis_title, + config$x_axis_title, + config$number_type, + plot_ribbon() + ) + }) + + # render the funnel plot output$funnel_plot <- shiny::renderPlot({ plot( funnel_data(), @@ -292,33 +342,29 @@ mod_mitigators_server <- function( ) }) - # boxplot ---- - + # render the boxplot output$boxplot <- shiny::renderPlot({ rates_baseline_data() |> rates_boxplot(plot_range(), plot_ribbon()) }) - # diagnoses ---- - + # render the diagnoses table output$diagnoses_table <- gt::render_gt({ + data <- diagnoses_data() + shiny::validate( shiny::need( - diagnoses_data, + data, message = "Insufficient or suppressed data." ) ) strategy <- shiny::req(input$strategy) - data <- diagnoses_data |> - dplyr::filter( - .data$provider == params$dataset, - .data$strategy == .env$strategy, - .data$fyear == params$start_year - ) |> + data <- data |> + dplyr::filter(.data$strategy == .env$strategy) |> dplyr::inner_join( - diagnoses_lkup, + lookups[["diagnoses"]], by = c("diagnosis" = "diagnosis_code") ) |> dplyr::select("diagnosis_description", "n", "pcnt") @@ -389,34 +435,32 @@ mod_mitigators_server <- function( ) }) - # procedures ---- - + # render the procedures table output$procedures_table <- gt::render_gt({ + data <- procedures_data() + shiny::validate( shiny::need( - procedures_data, + data, message = "Insufficient or suppressed data." ) ) - pd <- procedures_data - shiny::validate( shiny::need( - !is.null(pd) && nrow(pd) > 0, + !is.null(data) && nrow(data) > 0, "No procedures to display" ) ) strategy <- shiny::req(input$strategy) - data <- pd |> - dplyr::filter( - .data$provider == params$dataset, - .data$strategy == .env$strategy, - .data$fyear == params$start_year + data <- data |> + dplyr::filter(.data$strategy == .env$strategy) |> + dplyr::left_join( + lookups[["procedures"]], + by = c("procedure_code" = "code") ) |> - dplyr::left_join(procedures_lkup, by = c("procedure_code" = "code")) |> tidyr::replace_na(list( description = "Unknown/Invalid Procedure Code" )) |> @@ -488,27 +532,20 @@ mod_mitigators_server <- function( ) }) - # age group ---- - + # render the age group pyramid plot output$age_grp_plot <- shiny::renderPlot({ strategy <- shiny::req(input$strategy) - age_data <- age_sex_data |> - dplyr::filter( - .data$provider == params$dataset, - .data$strategy == .env$strategy, - .data$fyear == params$start_year - ) + age_data <- age_sex_data() |> + dplyr::filter(.data$strategy == .env$strategy) shiny::req(nrow(age_data) > 0) age_pyramid(age_data) }) - # NEE result ---- - + # render the NEE result output$nee_result <- shiny::renderPlot( { - nee_params <- app_sys("app", "data", "nee_table.csv") |> - readr::read_csv(col_types = "cddd") |> + nee_params <- lookups[["nee_table"]] |> dplyr::filter(.data[["param_name"]] == input$strategy) shiny::validate( @@ -553,11 +590,10 @@ mod_mitigators_server <- function( height = 60 ) - # rate values ---- - + # render the slider absolute values text output$slider_absolute <- shiny::renderUI({ strategy <- shiny::req(input$strategy) - baseline_value <- provider_max_value() + baseline_value <- provider_baseline_value() number_format <- config$interval_text_number_type %||% config$number_type @@ -581,6 +617,7 @@ mod_mitigators_server <- function( shiny::tags$p(shiny::HTML(text), style = style) }) + # render the slider interval explanation text output$slider_interval_text <- shiny::renderUI({ text <- glue::glue( "Click a slider handle and use your arrow keys for finer control.", diff --git a/R/mod_mitigators_summary_server.R b/R/mod_mitigators_summary_server.R index 79fe8167..6d43a2eb 100644 --- a/R/mod_mitigators_summary_server.R +++ b/R/mod_mitigators_summary_server.R @@ -4,33 +4,13 @@ mod_mitigators_summary_server <- function(id, age_sex_data, params) { shiny::moduleServer(id, function(input, output, session) { mitigators_summary <- shiny::reactive({ - year <- as.character(shiny::req(params$start_year)) - - strategy_codes <- readr::read_csv( - app_sys("app", "data", "mitigator-codes.csv"), - col_select = c("strategy", "strategy_name", "mitigator_code"), - col_types = "c" + strategy_codes <- dplyr::select( + get_lookups()[["mitigators"]], + "strategy_name" = "strategy_name_full", + "strategy" ) - strategy_names <- get_golem_config("mitigators_config") |> - purrr::map("strategy_subset") |> - purrr::flatten() |> - tibble::enframe("strategy", "strategy_name") |> - tidyr::unnest("strategy_name") |> - dplyr::left_join( - strategy_codes, - by = dplyr::join_by("strategy", "strategy_name") - ) |> - dplyr::mutate( - strategy_name = glue::glue("{strategy_name} ({mitigator_code})") - ) |> - dplyr::select(-"mitigator_code") - - age_sex_data |> - dplyr::filter( - .data[["fyear"]] == year, - .data[["provider"]] == params$dataset - ) |> + age_sex_data() |> dplyr::count( .data[["strategy"]], wt = .data[["n"]], @@ -38,7 +18,7 @@ mod_mitigators_summary_server <- function(id, age_sex_data, params) { sort = TRUE ) |> dplyr::slice(1:20) |> - dplyr::inner_join(strategy_names, by = dplyr::join_by("strategy")) |> + dplyr::inner_join(strategy_codes, by = dplyr::join_by("strategy")) |> dplyr::select(-"strategy") }) diff --git a/R/mod_non_demographic_adjustment_server.R b/R/mod_non_demographic_adjustment_server.R index 2e273ad8..33db9de1 100644 --- a/R/mod_non_demographic_adjustment_server.R +++ b/R/mod_non_demographic_adjustment_server.R @@ -8,11 +8,9 @@ mod_non_demographic_adjustment_server <- function(id, params) { "non-demographic_adjustment" ) - shiny::moduleServer(id, function(input, output, session) { - ndg_variants <- app_sys("app", "data", "ndg_variants.json") |> - jsonlite::read_json(simplifyVector = TRUE) |> - purrr::keep_at(c("variant_2", "variant_3")) + ndg_variants <- get_lookups()[["ndg_variants"]] + shiny::moduleServer(id, function(input, output, session) { # reactives ---- non_demographic_adjustment <- shiny::reactive({ diff --git a/R/mod_run_model_fix_params.R b/R/mod_run_model_fix_params.R index 4de82a2e..0d0e7c57 100644 --- a/R/mod_run_model_fix_params.R +++ b/R/mod_run_model_fix_params.R @@ -1,5 +1,5 @@ -mod_run_model_fix_params <- function(p, schema_text) { - p <- mod_run_model_remove_invalid_mitigators(p, schema_text) +mod_run_model_fix_params <- function(p) { + p <- mod_run_model_remove_invalid_mitigators(p) # nolint start: commented_code_linter # some of the items in our params will be lists of length 0. diff --git a/R/mod_run_model_remove_invalid_mitigators.R b/R/mod_run_model_remove_invalid_mitigators.R index 6e6d1716..e44deed1 100644 --- a/R/mod_run_model_remove_invalid_mitigators.R +++ b/R/mod_run_model_remove_invalid_mitigators.R @@ -1,9 +1,15 @@ -mod_run_model_remove_invalid_mitigators <- function(p, schema_text) { - schema <- create_params_schema(schema_text) +mod_run_model_remove_invalid_mitigators <- function(p) { + schema <- get_params_schema() json_p <- jsonlite::toJSON(p, auto_unbox = TRUE) - paths <- schema$validate(json_p, verbose = TRUE) |> + validate <- schema$validate(json_p, verbose = TRUE) + + if (validate) { + return(p) + } + + paths <- validate |> attr("errors") |> dplyr::filter( .data[["keyword"]] == "additionalProperties", diff --git a/R/mod_run_model_server.R b/R/mod_run_model_server.R index 05067f15..816d3e78 100644 --- a/R/mod_run_model_server.R +++ b/R/mod_run_model_server.R @@ -1,7 +1,7 @@ #' run_model Server Functions #' #' @noRd -mod_run_model_server <- function(id, params, schema_text) { +mod_run_model_server <- function(id, params) { mod_reasons_server(shiny::NS(id, "reasons"), params, "model_run") shiny::moduleServer(id, function(input, output, session) { @@ -23,7 +23,7 @@ mod_run_model_server <- function(id, params, schema_text) { params |> shiny::reactiveValuesToList() |> - mod_run_model_fix_params(schema_text) + mod_run_model_fix_params() }) # output the status of the model run after submit is pressed @@ -78,7 +78,7 @@ mod_run_model_server <- function(id, params, schema_text) { }) params_json_validation <- shiny::reactive({ - schema <- create_params_schema(schema_text) + schema <- get_params_schema() v <- schema$validate(params_json(), verbose = TRUE) diff --git a/R/mod_waiting_list_imbalances_server.R b/R/mod_waiting_list_imbalances_server.R index d29b465a..010627a3 100644 --- a/R/mod_waiting_list_imbalances_server.R +++ b/R/mod_waiting_list_imbalances_server.R @@ -2,6 +2,8 @@ #' #' @noRd mod_waiting_list_imbalances_server <- function(id, wli_data, params) { + multipliers <- get_lookups()[["waiting_list_multipliers"]] + mod_reasons_server( shiny::NS(id, "reasons"), params, @@ -9,26 +11,6 @@ mod_waiting_list_imbalances_server <- function(id, wli_data, params) { ) shiny::moduleServer(id, function(input, output, session) { - # static values ---- - multipliers <- readr::read_csv( - "inst/app/data/waiting_list_params.csv", - col_types = "cddddd" - ) |> - dplyr::transmute( - .data[["tretspef"]], - ip = .data[["mixed_split"]] * - .data[["avg_ip_activity_per_pathway_mixed"]], - op = .data[["op_only_split"]] * - .data[["avg_op_first_activity_per_pathway_op_only"]] + - .data[["mixed_split"]] * - .data[["avg_op_first_activity_per_pathway_mixed"]] - ) |> - tidyr::pivot_longer( - c("ip", "op"), - names_to = "activity_type", - values_to = "multiplier" - ) - # reactives ---- # load the waiting list data from azure diff --git a/R/mod_waiting_list_imbalances_utils.R b/R/mod_waiting_list_imbalances_utils.R index 4b36218d..bdff17f5 100644 --- a/R/mod_waiting_list_imbalances_utils.R +++ b/R/mod_waiting_list_imbalances_utils.R @@ -1,7 +1,7 @@ mod_waiting_list_imbalances_table <- function(df) { - rtt_specialties() |> + get_lookups()[["rtt_specialties"]] |> dplyr::inner_join(df, c(code = "tretspef")) |> - dplyr::select(-"code") |> + dplyr::select(-"code", -"sanitized_code") |> tidyr::pivot_wider( names_from = "activity_type", values_from = c("count", "param") diff --git a/R/run_app.R b/R/run_app.R index c52c5d0c..582e681c 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -17,15 +17,18 @@ run_app <- function( uiPattern = "/", # nolint ... ) { - if (getOption("golem.app.prod", FALSE)) { - create_data_cache() - } - # required for async promise calls if (!is_local()) { future::plan(future::multicore, workers = 2) } + # check files exist before starting app + # TODO: consider more rigorous checks, e.g. specific files exist, or number of files + if (!dir.exists(file.path("app_data"))) { + cat("Initialising data directory...\n") + get_all_data_files() + } + golem::with_golem_options( app = shiny::shinyApp( ui = app_ui, diff --git a/inst/app/data/diagnoses.csv b/inst/app/reference/diagnoses.csv similarity index 100% rename from inst/app/data/diagnoses.csv rename to inst/app/reference/diagnoses.csv diff --git a/inst/app/data/icb_boundaries.geojson b/inst/app/reference/icb_boundaries.geojson similarity index 100% rename from inst/app/data/icb_boundaries.geojson rename to inst/app/reference/icb_boundaries.geojson diff --git a/inst/app/data/mitigator-codes.csv b/inst/app/reference/mitigator-codes.csv similarity index 100% rename from inst/app/data/mitigator-codes.csv rename to inst/app/reference/mitigator-codes.csv diff --git a/inst/app/data/ndg_variants.json b/inst/app/reference/ndg_variants.json similarity index 100% rename from inst/app/data/ndg_variants.json rename to inst/app/reference/ndg_variants.json diff --git a/inst/app/data/nee_table.csv b/inst/app/reference/nee_table.csv similarity index 100% rename from inst/app/data/nee_table.csv rename to inst/app/reference/nee_table.csv diff --git a/inst/app/data/peers.csv b/inst/app/reference/peers.csv similarity index 100% rename from inst/app/data/peers.csv rename to inst/app/reference/peers.csv diff --git a/inst/app/data/procedures.csv b/inst/app/reference/procedures.csv similarity index 100% rename from inst/app/data/procedures.csv rename to inst/app/reference/procedures.csv diff --git a/inst/app/data/providers.csv b/inst/app/reference/providers.csv similarity index 100% rename from inst/app/data/providers.csv rename to inst/app/reference/providers.csv diff --git a/inst/app/data/rtt_specialties.csv b/inst/app/reference/rtt_specialties.csv similarity index 100% rename from inst/app/data/rtt_specialties.csv rename to inst/app/reference/rtt_specialties.csv diff --git a/inst/app/data/waiting_list_params.csv b/inst/app/reference/waiting_list_params.csv similarity index 100% rename from inst/app/data/waiting_list_params.csv rename to inst/app/reference/waiting_list_params.csv diff --git a/inst/golem-config.yml b/inst/golem-config.yml index a87720e4..d4408841 100644 --- a/inst/golem-config.yml +++ b/inst/golem-config.yml @@ -32,8 +32,6 @@ default: number_type: !expr scales::comma_format(accuracy = 0.001, scale = 1000) funnel_x_title: "Catchment Population of Trust" funnel_number_type: !expr scales::comma_format() - slider_scale: 1000 - slider_step: 0.1 strategy_subset: alcohol_partially_attributable_acute: "Alcohol Related Admissions (Acute Conditions - Partially Attributable)" alcohol_partially_attributable_chronic: "Alcohol Related Admissions (Chronic Conditions - Partially Attributable)" @@ -75,8 +73,6 @@ default: x_axis_title: "Financial Year" number_type: !expr scales::comma_format(accuracy = 0.1) funnel_x_title: "Number of Admissions" - slider_scale: 1 - slider_step: 0.1 strategy_subset: emergency_elderly: "Emergency Admission of Older People" enhanced_recovery_bladder: "Enhanced Recovery (Bladder)" @@ -105,8 +101,6 @@ default: number_type: !expr scales::comma_format(accuracy = 0.001, scale = 1000) funnel_x_title: "Catchment Population of Trust" funnel_number_type: !expr scales::comma_format() - slider_scale: 1000 - slider_step: 0.1 strategy_subset: same_day_emergency_care_low: "Same Day Emergency Care (Low Potential)" same_day_emergency_care_moderate: "Same Day Emergency Care (Moderate Potential)" @@ -121,8 +115,6 @@ default: x_axis_title: "Financial Year" number_type: !expr scales::comma_format(accuracy = 0.1, scale = 1000) funnel_x_title: "Number of Procedures Performed" - slider_scale: 1000 - slider_step: 0.1 strategy_subset: pre-op_los_1-day: "Pre-op Length of Stay of 1 day" pre-op_los_2-day: "Pre-op Length of Stay of 2 days" @@ -139,8 +131,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Procedures Performed" - slider_scale: 100 - slider_step: 0.1 strategy_subset: day_procedures_occasionally_dc: "Day Procedures: Occasionally performed as a Daycase" day_procedures_usually_dc: "Day Procedures: Usually performed as a Daycase" @@ -154,8 +144,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Procedures Performed" - slider_scale: 100 - slider_step: 0.1 strategy_subset: day_procedures_occasionally_op: "Day Procedures: Occasionally performed in Outpatients" day_procedures_usually_op: "Day Procedures: Usually performed in Outpatients" @@ -169,8 +157,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Appointments" - slider_scale: 100 - slider_step: 0.1 strategy_subset: consultant_to_consultant_reduction_adult_non-surgical: "Outpatient Consultant to Consultant Referrals (Adult, Non-Surgical)" consultant_to_consultant_reduction_adult_surgical: "Outpatient Consultant to Consultant Referrals (Adult, Surgical)" @@ -184,8 +170,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Appointments" - slider_scale: 100 - slider_step: 0.1 strategy_subset: convert_to_tele_adult_non-surgical: "Outpatient Convert to Tele-Attendance (Adult, Non-Surgical)" convert_to_tele_adult_surgical: "Outpatient Convert to Tele-Attendance (Adult, Surgical)" @@ -198,8 +182,6 @@ default: x_axis_title: "Financial Year" number_type: !expr scales::comma_format() funnel_x_title: "Number of Appointments" - slider_scale: 1 - slider_step: 0.1 strategy_subset: followup_reduction_adult_non-surgical: "Outpatient Followup Appointment Reduction (Adult, Non-Surgical)" followup_reduction_adult_surgical: "Outpatient Followup Appointment Reduction (Adult, Surgical)" @@ -213,8 +195,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Appointments" - slider_scale: 100 - slider_step: 0.1 strategy_subset: gp_referred_first_attendance_reduction_adult_non-surgical: "Outpatient GP Referred First Attendance Reduction (Adult, Non-Surgical)" gp_referred_first_attendance_reduction_adult_surgical: "Outpatient GP Referred First Attendance Reduction (Adult, Surgical)" @@ -228,8 +208,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Attendances" - slider_scale: 100 - slider_step: 0.1 strategy_subset: frequent_attenders_adult_ambulance: "A&E Frequent Attenders (Adult, Ambulance Conveyed)" frequent_attenders_adult_walk-in: "A&E Frequent Attenders (Adult, Walk-in)" @@ -243,8 +221,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Attendances" - slider_scale: 100 - slider_step: 0.1 strategy_subset: left_before_seen_adult_ambulance: "A&E Patients Left Before Being Treated (Adult, Ambulance Conveyed)" left_before_seen_adult_walk-in: "A&E Patients Left Before Being Treated (Adult, Walk-in)" @@ -258,8 +234,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Attendances" - slider_scale: 100 - slider_step: 0.1 strategy_subset: discharged_no_treatment_adult_ambulance: "A&E Discharged No Investigation or Treatment (Adult, Ambulance Conveyed)" discharged_no_treatment_adult_walk-in: "A&E Discharged No Investigation or Treatment (Adult, Walk-in)" @@ -273,8 +247,6 @@ default: number_type: !expr scales::percent_format() interval_text_number_type: !expr scales::percent_format(accuracy = 0.01) funnel_x_title: "Number of Attendances" - slider_scale: 100 - slider_step: 0.1 strategy_subset: low_cost_discharged_adult_ambulance: "A&E Low Cost Discharged Attendances (Adult, Ambulance Conveyed)" low_cost_discharged_adult_walk-in: "A&E Low Cost Discharged Attendances (Adult, Walk-in)" diff --git a/man/download_provider_data.Rd b/man/download_provider_data.Rd new file mode 100644 index 00000000..c168d0c3 --- /dev/null +++ b/man/download_provider_data.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fct_azure_storage.R +\name{download_provider_data} +\alias{download_provider_data} +\title{Download Provider Data} +\usage{ +download_provider_data( + file, + data_path = file.path("app_data"), + inputs_data_version = Sys.getenv("NHP_INPUTS_DATA_VERSION", "dev"), + ... +) +} +\arguments{ +\item{file}{The name of the file to download.} + +\item{data_path}{The path to the app's data directory.} + +\item{inputs_data_version}{The version of the inputs data to use.} + +\item{...}{Additional arguments to pass to `AzureStor::download_adls_file()`.} +} +\value{ +The path to the downloaded file. +} +\description{ +Download a selected type of provider data from ADLS and save it as a parquet +file in the app's data directory. +} diff --git a/man/get_all_data_files.Rd b/man/get_all_data_files.Rd new file mode 100644 index 00000000..60f73238 --- /dev/null +++ b/man/get_all_data_files.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fct_azure_storage.R +\name{get_all_data_files} +\alias{get_all_data_files} +\title{Get All Data Files} +\usage{ +get_all_data_files( + inputs_data_version = Sys.getenv("NHP_INPUTS_DATA_VERSION", "dev") +) +} +\arguments{ +\item{inputs_data_version}{The version of the inputs data to use.} +} +\description{ +Download all provider data files from ADLS and save them as parquet files in +the app's data directory. +} diff --git a/man/load_provider_data.Rd b/man/load_provider_data.Rd index 6f071993..b0cd5360 100644 --- a/man/load_provider_data.Rd +++ b/man/load_provider_data.Rd @@ -1,18 +1,15 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fct_azure_storage.R +% Please edit documentation in R/fct_load_provider_data.R \name{load_provider_data} \alias{load_provider_data} \title{Get Provider Data} \usage{ -load_provider_data( - file, - inputs_data_version = Sys.getenv("NHP_INPUTS_DATA_VERSION", "dev") -) +load_provider_data(file, data_path = file.path("app_data")) } \arguments{ \item{file}{The name of the file to read.} -\item{inputs_data_version}{The version of the inputs data to use.} +\item{data_path}{The path to the directory containing the parquet files.} } \value{ A tibble.