diff --git a/NEWS.md b/NEWS.md index d8cb1e7fd..7c6310fe5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -64,6 +64,7 @@ ## Bugs fixed +* R script and settings export now include volume unit simplifications. Unit change detection uses value comparison (`PPSTRESU != PPORRESU`) instead of an edit-tracking flag, so automatic simplifications (e.g. `mg*L/mL` → `mg`) are captured alongside user edits (#1190) * Fixed ratio calculations with `Aggregate Subject = yes` or `if-needed` not aggregating reference values, and ratio parameter columns (FABS, FREL, etc.) not appearing in NCA Results (#1273) * Last dose interval end time now extends to the last observed sample instead of being cut off at TRTRINT (tau), ensuring all collected data points are included in NCA calculations (#1235) * Fixed NA `PPSTRESU` handling across NCA results: descriptive statistics no longer crash when a parameter group has all-NA units, and manual interval parameters (e.g., RCAMINT) no longer get `NA` appended to their column names (#1216) diff --git a/inst/shiny/functions/zip-utils.R b/inst/shiny/functions/zip-utils.R index 45a54cb12..8a55e0fcb 100644 --- a/inst/shiny/functions/zip-utils.R +++ b/inst/shiny/functions/zip-utils.R @@ -523,10 +523,12 @@ prepare_export_files <- function(target_dir, .export_settings <- function(target_dir, session) { settings_list <- session$userData$settings() - if (!is.null(settings_list$units)) { - settings_list$units <- settings_list$units %>% - dplyr::filter(!default) %>% - dplyr::select(-default) + # Units are stored separately from settings() to avoid triggering + # the settings debounce cascade. Read directly for export. + units_snapshot <- session$userData$units_table() + if (!is.null(units_snapshot)) { + settings_list$units <- units_snapshot %>% + dplyr::filter(!is.na(PPSTRESU), !is.na(PPORRESU), PPSTRESU != PPORRESU) } settings_list$ratio_table <- session$userData$ratio_table() diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index aad2ad910..d13e23a17 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -203,9 +203,7 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars, settings_override, # Update units table processed_pknca_data <- processed_pknca_data() if (!is.null(session$userData$units_table())) { - custom_units <- select( - session$userData$units_table(), -any_of("default") - ) + custom_units <- session$userData$units_table() by_cols <- intersect(names(processed_pknca_data$units), names(custom_units)) by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor")) processed_pknca_data$units <- rows_update( @@ -214,6 +212,11 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars, settings_override, by = by_cols, unmatched = "ignore" ) + } else { + # First NCA run: populate units_table from the data so settings + # export includes volume-simplified units even if the user never + # opens the Units modal. + session$userData$units_table(processed_pknca_data$units) } #' Calculate results diff --git a/inst/shiny/modules/tab_nca/nca_setup.R b/inst/shiny/modules/tab_nca/nca_setup.R index 5ca278b0e..337d19d1f 100644 --- a/inst/shiny/modules/tab_nca/nca_setup.R +++ b/inst/shiny/modules/tab_nca/nca_setup.R @@ -169,7 +169,23 @@ nca_setup_server <- function(id, data, adnca_data, extra_group_vars, settings_ov has_full_units <- all(c("PPORRESU", "conversion_factor") %in% names(imported_units)) if (has_full_units) { - session$userData$units_table(imported_units) + # Imported settings contain only changed rows (PPSTRESU != PPORRESU). + # Merge them into the full data-derived units table so downstream code + # that expects the complete table (e.g. nca_results.R) works correctly. + observe({ + req(processed_pknca_data()) + data_units <- processed_pknca_data()$units + by_cols <- intersect(names(data_units), names(imported_units)) + by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor")) + merged <- rows_update( + data_units, + imported_units, + by = by_cols, + unmatched = "ignore" + ) + session$userData$units_table(merged) + }) %>% + bindEvent(processed_pknca_data(), once = TRUE) } else { # Defaults-only format: wait for data-derived units, then resolve. observe({ @@ -231,10 +247,12 @@ nca_setup_server <- function(id, data, adnca_data, extra_group_vars, settings_ov }, content = function(con) { export_settings <- final_settings() - if (!is.null(export_settings$units)) { - export_settings$units <- export_settings$units %>% - filter(!default) %>% - select(-default) + # Units are stored separately from settings() to avoid triggering + # the settings debounce cascade. Read directly for export. + units_snapshot <- session$userData$units_table() + if (!is.null(units_snapshot)) { + export_settings$units <- units_snapshot %>% + filter(!is.na(PPSTRESU), !is.na(PPORRESU), PPSTRESU != PPORRESU) } export_settings$ratio_table <- ratio_table() payload <- list( diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index 93bab1922..a8512496c 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -502,8 +502,7 @@ settings_server <- function(id, data, adnca_data, settings_override) { is.checked = input$LAMZSPN_rule, threshold = input$LAMZSPN_threshold ) - ), - units = session$userData$units_table() + ) ) }) diff --git a/inst/shiny/modules/tab_nca/units_table.R b/inst/shiny/modules/tab_nca/units_table.R index f0ada8f0c..f4e51d2ac 100644 --- a/inst/shiny/modules/tab_nca/units_table.R +++ b/inst/shiny/modules/tab_nca/units_table.R @@ -26,13 +26,12 @@ units_table_server <- function(id, mydata) { modal_units_table <- reactiveVal(NULL) observeEvent(input$open_units_table, { - default_units <- mydata()$units %>% - dplyr::mutate(default = TRUE) + default_units <- mydata()$units if (!is.null(session$userData$units_table())) { - custom_units <- dplyr::mutate(session$userData$units_table(), default = FALSE) + custom_units <- session$userData$units_table() by_cols <- intersect(names(default_units), names(custom_units)) - by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor", "default")) + by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor")) dplyr::rows_update( default_units, custom_units, @@ -102,8 +101,7 @@ units_table_server <- function(id, mydata) { PPORRESU = colDef(name = "Default Unit"), PPSTRESU = colDef(name = "Custom Unit"), conversion_factor = colDef(name = "Conversion Factor"), - is_hidden = colDef(show = FALSE), - default = colDef(show = FALSE) + is_hidden = colDef(show = FALSE) ), pagination = FALSE, filterable = TRUE, @@ -156,7 +154,6 @@ units_table_server <- function(id, mydata) { ) } - modal_units_table[info$row, "default"] <- FALSE modal_units_table[info$row, "conversion_factor"] <- conversion_factor_value } @@ -192,9 +189,7 @@ units_table_server <- function(id, mydata) { } log_trace("Applying custom units specification.") - modal_units_table() %>% - dplyr::filter(!default) %>% - session$userData$units_table() + session$userData$units_table(modal_units_table()) # Close the modal message window for the user removeModal() @@ -202,12 +197,11 @@ units_table_server <- function(id, mydata) { #' Update local `modal_units_table()` if the global value changes. observeEvent(session$userData$units_table(), { - default_units <- mydata()$units %>% - dplyr::mutate(default = TRUE) + default_units <- mydata()$units - custom_units <- dplyr::mutate(session$userData$units_table(), default = FALSE) + custom_units <- session$userData$units_table() by_cols <- intersect(names(default_units), names(custom_units)) - by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor", "default")) + by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor")) dplyr::rows_update( default_units, custom_units,