Skip to content

feat: Add export bestdose function - #384

Open
R-garreau wants to merge 7 commits into
mainfrom
add-export_bestdose-function
Open

R-garreau wants to merge 7 commits into
mainfrom
add-export_bestdose-function

Conversation

@R-garreau

Copy link
Copy Markdown

This PR introduces 3 changes (2 new files and one function update)

1. Create BD_create.R in R/

Has all the set of function to create any model from a Pmetrics run
classic S3 object

2. Add export_bestdose to PM_result

User can now easily export a bestdose model file from any run

run_1 <- PM_load(run  = 1, path)

# export_bestdose has 2 arguments, the file name and the path (optionnal)
run_1$export_bestdose(model_name)

coverage test-BD_create.R file added in tests/testthat

93.6% coverage (only return list() and empty return are not tested)

@R-garreau R-garreau added this to the Pmetrics 3.2 milestone Aug 31, 2026
Copilot AI lite review requested due to automatic review settings August 31, 2026 16:36
@R-garreau R-garreau added the enhancement New feature or request label Aug 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov-commenter

codecov-commenter commented Aug 31, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.81768% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.15%. Comparing base (5613287) to head (1a16756).

Files with missing lines Patch % Lines
R/BD_create.R 93.21% 23 Missing ⚠️
R/PM_result.R 86.95% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #384      +/-   ##
==========================================
+ Coverage   62.73%   63.15%   +0.41%     
==========================================
  Files          51       52       +1     
  Lines       22410    22760     +350     
==========================================
+ Hits        14059    14373     +314     
- Misses       8351     8387      +36     
Files with missing lines Coverage Δ
R/PM_result.R 72.53% <86.95%> (-0.93%) ⬇️
R/BD_create.R 93.21% <93.21%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mhovd mhovd changed the title Add export bestdose function feat: Add export bestdose function Aug 31, 2026
@mhovd

mhovd commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Please run devtools::document() and commit the regenerated Rd/NAMESPACE files so CI will pass

@mhovd mhovd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, Romain.

There are two issues that needs to be fixed:

  • There seems to be a duplicated model parser, can you not use the DSL directly?
  • There is a lot of unnecessary documentation that is BestDose specific, which should be removed.

Other than that I think the function name and scope is OK. A very cheap option would be to use the $save that exists already on the PM_model, and parse that directly.

Comment thread R/BD_create.R Outdated
Comment thread R/BD_create.R Outdated
Comment thread R/BD_create.R Outdated
Comment thread R/BD_create.R
Comment on lines +356 to +391
#' @title Derive the administration routes from the equations
#' @description BestDose needs at least one `{route, compartment}` entry, and that compartment
#' index is what the engine actually doses into. Infusion tokens (`R[N]`, `RATEIV[N]`) map to IV,
#' bolus tokens (`B[N]`) to an extravascular depot. The compartment is inferred, the human label is
#' a guess: confirm it.
#'
#' @param equations The equation list from `extractPMequation`
#' @return A data.frame of routes

extractBDroutes <- function(equations) {
text <- tolower(paste(unlist(equations), collapse = " "))
indices <- function(pattern) {
unique(as.integer(stringr::str_match_all(text, pattern)[[1]][, 2]))
}

iv <- indices("(?:^|[^a-z0-9_])(?:r|rateiv)\\s*\\[\\s*([0-9]+)\\s*\\]")
po <- indices("(?:^|[^a-z0-9_])b\\s*\\[\\s*([0-9]+)\\s*\\]")

if (length(iv) + length(po) == 0) {
message(
"No dose input (R[N]/B[N]) found in the equations, defaulting to IV into compartment 1."
)
return(data.frame(route = "IV", compartment = 1L))
}

routes <- data.frame(
route = c(rep("IV", length(iv)), rep("PO", length(po))),
compartment = as.integer(c(iv, po))
)
message(
"Routes inferred from the equations, check the labels: ",
paste(routes$route, "->", routes$compartment, collapse = ", ")
)

return(routes)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this and other helpers, which appear to mirror the parsing of the PM_model to DSL, and is duplicate code.

I assume you would rather want the DSL directly, accessible from a created model using PM_model$dsl

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, i was not familiar with Pmetrics DSL. This is transfered from the previous compile_model() of Pmetrics. This is what is used by bestdose for the moment. We could directly import dsl model in a near future, but this would require substantial changes in BestDose. Would you mind if we let this like that for the moment ? Then when bestdose ModelLibrary will be uploaded to support full DSL, this will be changed in Pmetrics aswell.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth the effort to convert those models to the DSL sooner rather than later. It should not take long at all, and I would be willing to help you with it.

Comment thread R/BD_create.R
Comment on lines +421 to +424

# Starting ranges for the covariates BestDose knows about. These are the clinical bounds shown in
# the UI, not statistical ones: a Pmetrics fit carries none of this, so anything missing here is
# filled with a placeholder that needs a review.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not Pmetrics documentation, but BestDose specific

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should i document of just make it @internal ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just remove it

Comment thread R/PM_result.R
Comment on lines +317 to +331
export_bestdose = function(drug_name = "Drug", dir_path = NULL) {
# create model using functions in BD_create.R
model_file <- createBDmodel(self, drug_name = drug_name)
path = ifelse(is.null(dir_path), paste0(drug_name, ".json"), paste0(dir_path, "/", drug_name, ".json"))

# #export to JSON
jsonlite::write_json(
model_file,
path,
auto_unbox = TRUE,
dataframe = "rows",
null = "null",
pretty = TRUE
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be enough to have a JSON of the DSL and the support points? Or what else is needed for BestDose?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, Its not for the moment. This is a planed change for 1.1.0 in bestdose probably. But we are far from it at the moment, hence this adaptation of the code to get everything from model$arg_list instead of model$dsl

Copilot AI review requested due to automatic review settings September 1, 2026 14:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Tests and user-facing API/export plumbing need fixes (unexported helper usage in tests, export_bestdose() argument/path portability, and roxygen/NAMESPACE export sync for createBDmodel()).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

tests/testthat/test-BD_create.R:8

  • This test file calls many helpers from R/BD_create.R (e.g., bdKey, extractPMBlock) that are not exported. With only library(Pmetrics) attached, these symbols won’t be found and the tests will fail unless you qualify them (e.g., Pmetrics:::bdKey) or bind them from the namespace.
library(Pmetrics)
library(testthat)
PM_result <- NPex

  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread R/BD_create.R
Comment on lines +534 to +547
#' @title Create a BestDose model list from a PM model object
#' @description
#' `r lifecycle::badge("experimental")`
#'
#' @param PM_result A PM model result object
#' @param drug_name The drug the model attaches to
#' @param model_name The model file name, without the .json extension
#' @param description Free text description of the model
#' @param reference Citation for the model
#' @param reference_url URL for the citation
#' @return A list containing the description, model and support points formatted for BestDose
#'
#' @export

Comment thread R/PM_result.R
Comment on lines +317 to +320
export_bestdose = function(drug_name = "Drug", dir_path = NULL) {
# create model using functions in BD_create.R
model_file <- createBDmodel(self, drug_name = drug_name)
path = ifelse(is.null(dir_path), paste0(drug_name, ".json"), paste0(dir_path, "/", drug_name, ".json"))
Comment on lines +1 to +2
# Checks for BD_create.R — run with testthat after sourcing the exporter:
# source("docs/BD_create.R"); testthat::test_file("docs/test-BD_create.R")
Copilot AI review requested due to automatic review settings September 4, 2026 08:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

As-is, the new public API/export flow is inconsistent with the PR’s documented usage and the tests/docs depend on export/namespace details that are currently missing (e.g., roxygen/NAMESPACE updates and direct calls to non-exported helpers).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (3)

tests/testthat/test-BD_create.R:2

  • The header comment references docs/BD_create.R and docs/test-BD_create.R, but this test lives under tests/testthat/ and the implementation is in R/BD_create.R, so the suggested manual commands are incorrect.
# Checks for BD_create.R — run with testthat after sourcing the exporter:
#   source("docs/BD_create.R"); testthat::test_file("docs/test-BD_create.R")

R/BD_create.R:546

  • createBDmodel() is tagged with @export, but the generated NAMESPACE in this branch does not currently export it, so it won't be available to users (and tests referring to it as a public API will fail). The roxygen outputs (at least NAMESPACE, and likely a man/createBDmodel.Rd) need to be regenerated and committed.
#' @param reference Citation for the model
#' @param reference_url URL for the citation
#' @return A list containing the description, model and support points formatted for BestDose
#'
#' @export

R/PM_result.R:318

  • The PR description/example shows export_bestdose(model_name) (file name + optional path), but the implementation currently takes drug_name and uses it for both the JSON drug field and the output filename. This makes it impossible to set a different model filename from the drug name and conflicts with the documented usage; consider exposing model_name explicitly and using file.path() for portable path construction.
    #' @param drug_name Name of the drug to use in the exported JSON file. Default is "Drug".
    #' @param dir_path Optional path to save the JSON file. If not provided, the file will be saved in the current working directory with the name "{drug_name}.json".
    
    export_bestdose = function(drug_name = "Drug", dir_path = NULL) {
      # create model using functions in BD_create.R
  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment on lines +4 to +13
# generate data
library(Pmetrics)
library(testthat)
PM_result <- NPex

PMmodel <- NPex$model$arg_list

# =================================
# Test Helper
# ================================
Comment thread R/BD_create.R
stringr::str_replace(x, "<-", "=")
})

# input route replacte "rateiv[n]" or "R[n]" by "r[n]" and "Bolus[n]" or "b[n]" by "B[n]"
Comment thread R/PM_result.R
Comment on lines +304 to +313
#' @description
#' `r lifecycle::badge("experimental")`
#' export the model in a JSON file format that can be used in the BestDose.
#' This is an experimental feature and may change in future versions.
#'
#' @details
#' This feature is useful if the user want to use this specific model to perform MIPD.
#' The file will contains most of the features and covariates, but the user will have to specify
#' some additional information after importing this file.
#'

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants