use("shiny") use("bslib") use("mrgsolve") use("dplyr") use("ggplot2") use("tidyr") mw_cyclophosphamide_g_mol <- 261.086 young_child_target_4oh_auc <- 107.2 young_infant_mean_4oh_auc <- 138.4 cl4oh_calibration <- 0.9 paper_scenario <- function(key) { switch( key, young_infant = list( label = "Young infant (<6 months)", age_months = 3, dose_g_m2 = 1.2, interval_days = 28, n_cycles = 4, note = "Dose reduced 20% from 1.5 to 1.2 g/m2 to match the young-child 4OH-CTX exposure reference." ), infant = list( label = "Infant (6-12 months)", age_months = 9, dose_g_m2 = 1.3, interval_days = 28, n_cycles = 4, note = "Dose reduced 13% from 1.5 to 1.3 g/m2 in the paper's simulation exercise." ), toddler = list( label = "Toddler (1-2 years)", age_months = 18, dose_g_m2 = 1.4, interval_days = 28, n_cycles = 4, note = "Dose reduced 6.7% from 1.5 to 1.4 g/m2 in the paper's simulation exercise." ), young_child = list( label = "Young child (2-5 years)", age_months = 36, dose_g_m2 = 1.5, interval_days = 28, n_cycles = 4, note = "Reference pediatric regimen from the study population used to define the target 4OH-CTX exposure." ) ) } age_band_label <- function(age_months) { if (age_months < 6) { "Young infant (<6 months)" } else if (age_months < 12) { "Infant (6-12 months)" } else if (age_months < 24) { "Toddler (1-2 years)" } else { "Young child (2-5 years)" } } dose_to_umol_m2 <- function(dose_g_m2) { dose_g_m2 / mw_cyclophosphamide_g_mol * 1e6 } trap_auc <- function(time, conc) { if (length(time) < 2 || length(conc) < 2) { return(NA_real_) } dt <- diff(time) avg <- (head(conc, -1) + tail(conc, -1)) / 2 sum(dt * avg) } estimate_terminal_half_life <- function(time, conc, start_h = 6, end_h = 24) { keep <- conc > 0 & is.finite(conc) & time >= start_h & time <= end_h if (sum(keep) < 3) { return(NA_real_) } fit <- stats::lm(log(conc[keep]) ~ time[keep]) slope <- unname(stats::coef(fit)[2]) if (!is.finite(slope) || slope >= 0) { return(NA_real_) } log(2) / abs(slope) } build_cycle_events <- function(dose_g_m2, interval_days, n_cycles, infusion_h = 1) { dose_umol_m2 <- dose_to_umol_m2(dose_g_m2) dose_times_h <- seq(0, by = interval_days * 24, length.out = n_cycles) tibble::tibble( ID = 1, time = dose_times_h, amt = dose_umol_m2, cmt = 1, evid = 1, rate = dose_umol_m2 / infusion_h ) } model_code <- " $PARAM @annotated AGE : 36 : Patient age (months) PHENO : 0 : Phenobarbital cotreatment indicator (0/1) CYP : 0 : CYP2B6 rs4802101 variant indicator (0/1) TAU : 672 : Dosing interval for cycle-specific time reset (h) CLCTX : 2.1 : Parent base clearance parameter (L/h/m2) VCTX : 11.6 : Parent central volume parameter (L/m2) QCTX : 1.1 : Parent intercompartmental clearance (L/h/m2) VPCTX : 2.8 : Parent peripheral volume (L/m2) BETA : 0.029 : Parent time-dependent formation coefficient (1/h) CL4OH : 44.8 : 4OH-CTX apparent linear clearance (L/h/m2) CLM4 : 0.48 : 4OH-CTX apparent metabolic clearance to CEPM (L/h/m2) GAMMA : 0.034 : 4OH-CTX time-dependent formation coefficient to CEPM (1/h) CLCEP : 0.49 : CEPM clearance (L/h/m2) V4OH : 0.57 : 4OH-CTX fixed volume (L/m2) VCEPM : 0.57 : CEPM fixed volume (L/m2) $CMT @annotated CTX_CENT : Cyclophosphamide central amount (umol/m2) CTX_PERIPH : Cyclophosphamide peripheral amount (umol/m2) OH4_CENT : 4OH-CTX amount (umol/m2) CEPM_CENT : CEPM amount (umol/m2) $MAIN double age_ratio = AGE / 21.3; double clctx_i = CLCTX * exp(0.87 * PHENO) * exp(0.087 * CYP); double vctx_i = VCTX * pow(age_ratio, 0.11); double qctx_i = QCTX; double vpctx_i = VPCTX; double beta_i = BETA * exp(0.23 * CYP); double cl4oh_i = CL4OH * 0.9 * exp(-0.17 * CYP) * pow(age_ratio, 0.13); double clcepm_i = CLCEP * pow(age_ratio, 0.18); $ODE double t_cycle = fmod(SOLVERTIME, TAU); double cl_other = 0.25 * clctx_i; double cl_form4 = 0.75 * clctx_i * exp(beta_i * t_cycle); double cl_form_cepm = CLM4 * exp(GAMMA * t_cycle); double c_ctx = CTX_CENT / vctx_i; double c_periph = CTX_PERIPH / vpctx_i; double c_4oh = OH4_CENT / V4OH; double c_cepm = CEPM_CENT / VCEPM; dxdt_CTX_CENT = -(cl_other + cl_form4) * c_ctx - qctx_i * (c_ctx - c_periph); dxdt_CTX_PERIPH = qctx_i * (c_ctx - c_periph); dxdt_OH4_CENT = cl_form4 * c_ctx - cl4oh_i * c_4oh - cl_form_cepm * c_4oh; dxdt_CEPM_CENT = cl_form_cepm * c_4oh - clcepm_i * c_cepm; $TABLE double t_cycle_tab = fmod(TIME, TAU); double CTX = CTX_CENT / vctx_i; double OH4 = OH4_CENT / V4OH; double CEPM = CEPM_CENT / VCEPM; double CLFORM_CTX = 0.75 * clctx_i * exp(BETA * exp(0.23 * CYP) * t_cycle_tab); double CLFORM_4OH = CLM4 * exp(GAMMA * t_cycle_tab); $CAPTURE @annotated CTX : Cyclophosphamide plasma concentration (uM) OH4 : 4OH-CTX plasma concentration (uM) CEPM : CEPM plasma concentration (uM) CLFORM_CTX : Parent time-dependent formation clearance to 4OH-CTX (L/h/m2) CLFORM_4OH : 4OH-CTX time-dependent formation clearance to CEPM (L/h/m2) " mod <- mcode("cyclophosphamide_peds_351", model_code, quiet = TRUE) run_simulation <- function( dose_g_m2, interval_days, n_cycles, age_months, phenobarbital, cyp_variant, infusion_h = 1 ) { events <- build_cycle_events(dose_g_m2, interval_days, n_cycles, infusion_h = infusion_h) last_dose_time <- max(events$time) sim_end <- last_dose_time + 48 out <- mod %>% param( AGE = age_months, PHENO = as.numeric(isTRUE(phenobarbital)), CYP = as.numeric(isTRUE(cyp_variant)), TAU = interval_days * 24 ) %>% data_set(events) %>% mrgsim(end = sim_end, delta = 0.05) %>% as.data.frame() |> mutate( time_since_last_dose = time - last_dose_time, cycle_day = time / 24, ctx_uM = CTX, oh4_uM = OH4, cepm_uM = CEPM ) long <- out |> dplyr::filter(time_since_last_dose >= 0, time_since_last_dose <= 48) |> select(time, time_since_last_dose, ctx_uM, oh4_uM, cepm_uM) |> tidyr::pivot_longer( cols = c(ctx_uM, oh4_uM, cepm_uM), names_to = "analyte", values_to = "conc_uM" ) |> mutate( analyte = dplyr::recode( analyte, ctx_uM = "Cyclophosphamide", oh4_uM = "4OH-CTX", cepm_uM = "CEPM" ) ) list( sim = out, long = long, events = events, last_dose_time = last_dose_time ) } compute_metrics <- function(sim_bundle, analyte_name) { analyte_col <- switch( analyte_name, "Cyclophosphamide" = "CTX", "4OH-CTX" = "OH4", "CEPM" = "CEPM" ) sim <- sim_bundle$sim |> dplyr::filter(time_since_last_dose >= 0, time_since_last_dose <= 24) conc <- sim[[analyte_col]] time_h <- sim$time_since_last_dose keep_unique <- !duplicated(time_h) list( cmax = max(conc, na.rm = TRUE), c24 = stats::approx(time_h[keep_unique], conc[keep_unique], xout = 24, rule = 2)$y, auc24 = trap_auc(time_h, conc), thalf = estimate_terminal_half_life(time_h, conc) ) } build_exposure_summary <- function(sim_bundle) { analytes <- c("Cyclophosphamide", "4OH-CTX", "CEPM") rows <- lapply(analytes, function(analyte) { met <- compute_metrics(sim_bundle, analyte) data.frame( Analyte = analyte, Cmax_uM = round(met$cmax, 1), C24h_uM = round(met$c24, 2), AUC0_24h_uMh = round(met$auc24, 1), Half_life_h = round(met$thalf, 2), check.names = FALSE ) }) do.call(rbind, rows) } app_theme <- bs_theme( version = 5, bootswatch = "flatly", primary = "#8b5cf6" ) |> bs_add_rules(" .metric-card { background: #f8f9fa; border-radius: 8px; padding: 15px; margin: 5px; text-align: center; border: 1px solid #dee2e6; } .metric-value { font-size: 24px; font-weight: bold; color: #2c3e50; } .metric-label { font-size: 12px; color: #7f8c8d; } .metric-success .metric-value { color: #10b981; } .metric-warning .metric-value { color: #f59e0b; } .metric-primary .metric-value { color: #8b5cf6; } .metric-info .metric-value { color: #0dcaf0; } .ref-box { background: #f0f4ff; border-left: 4px solid #8b5cf6; padding: 12px 16px; border-radius: 4px; margin-top: 10px; font-size: 13px; } .ref-box a { color: #8b5cf6; } ") ui <- page_sidebar( title = "Cyclophosphamide Pediatric PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, accordion( open = c("Paper presets", "Dosing", "Patient", "Display"), accordion_panel( "Paper presets", layout_column_wrap( width = 1 / 2, fill = FALSE, actionButton("load_young_infant", "Young infant", class = "btn btn-outline-primary btn-sm"), actionButton("load_infant", "Infant", class = "btn btn-outline-primary btn-sm"), actionButton("load_toddler", "Toddler", class = "btn btn-outline-primary btn-sm"), actionButton("load_young_child", "Young child", class = "btn btn-outline-primary btn-sm") ), div( class = "ref-box", strong("Published exposure anchor"), br(), "The paper used the young-child 4OH-CTX exposure range as the dose-adjustment target. The abstract reports mean 4OH-CTX AUC0-24h values of 138.4 uM*h in young infants and 107.2 uM*h in young children at the original 1.5 g/m2 regimen." ) ), accordion_panel( "Dosing", sliderInput("dose_g_m2", "Cyclophosphamide dose (g/m2)", min = 1.0, max = 1.5, value = 1.5, step = 0.1), sliderInput("interval_days", "Cycle interval (days)", min = 21, max = 35, value = 28, step = 1), numericInput("n_cycles", "Number of cycles", value = 4, min = 1, max = 6), sliderInput("infusion_h", "Infusion duration (h)", min = 0.5, max = 2, value = 1, step = 0.25) ), accordion_panel( "Patient", sliderInput("age_months", "Age (months)", min = 1, max = 60, value = 36, step = 1), checkboxInput("phenobarbital", "Phenobarbital cotreatment", value = FALSE), checkboxInput("cyp_variant", "CYP2B6 rs4802101 variant", value = FALSE) ), accordion_panel( "Display", selectInput("primary_analyte", "Metric cards focus", choices = c("Cyclophosphamide", "4OH-CTX", "CEPM"), selected = "4OH-CTX"), checkboxInput("show_all_analytes", "Overlay all analytes on plot", value = TRUE), checkboxInput("log_scale", "Log scale (Y-axis)", value = TRUE) ) ), div( class = "ref-box", textOutput("scenario_note"), br(), textOutput("reference_note"), br(), textOutput("covariate_note") ) ), layout_column_wrap( width = 1 / 4, fill = FALSE, div( class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax")), div(class = "metric-label", "Cmax (uM)") ), div( class = "metric-card metric-warning", div(class = "metric-value", textOutput("c24")), div(class = "metric-label", "C24h (uM)") ), div( class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc24")), div(class = "metric-label", "AUC0-24h (uM*h)") ), div( class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "t1/2 (h)") ) ), card( full_screen = TRUE, plotOutput("pkPlot", height = "560px"), card_footer(textOutput("plot_caption")) ), card( full_screen = TRUE, card_header("Exposure Summary Over the Last Cycle"), tableOutput("summary_table") ), navset_card_underline( title = "Drug Information", nav_panel( "Model Information", div( class = "ref-box", markdown(" ## Cyclophosphamide Parent-Metabolite PopPK Model **Primary paper:** Campagne O, Zhong B, Nair S, et al. *Clinical Cancer Research* (2020) 26:1563-1573. **Drug:** Cyclophosphamide **Class:** Alkylating agent / oxazaphosphorine prodrug **Population:** Infants and young children with primary brain tumors ### Final model structure - **Cyclophosphamide:** 2-compartment IV infusion model - **4OH-CTX:** 1-compartment metabolite model - **CEPM:** 1-compartment downstream metabolite model - **Parent to 4OH-CTX formation:** fixed **75%** of time-dependent parent metabolic clearance - **Other parent elimination:** fixed **25%** - **4OH-CTX and CEPM fixed volumes:** **0.57 L/m2** ### Covariates retained in the final model - **Age** increased cyclophosphamide central volume and both metabolite clearances - **Phenobarbital** increased cyclophosphamide clearance - **CYP2B6 rs4802101 variant** changed cyclophosphamide clearance, the parent time-dependent formation coefficient, and 4OH-CTX clearance ### Key parameter estimates from Supplementary Table S3 | Parameter | Estimate | Units | |-----------|----------|-------| | CLCTX | 2.1 | L/h/m2 | | VCTX | 11.6 | L/m2 | | QCTX | 1.1 | L/h/m2 | | VPCTX | 2.8 | L/m2 | | beta | 0.029 | 1/h | | CL4OH | 44.8 | L/h/m2 | | CLm4OH | 0.48 | L/h/m2 | | gamma | 0.034 | 1/h | | CLCEPM | 0.49 | L/h/m2 | ### Paper dosing conclusion - Standard regimen in the study: **1.5 g/m2 over 1 hour every 28 days** - The model suggested lower doses for younger patients to match the 4OH-CTX exposure seen in young children: - **1.2 g/m2** for young infants - **1.3 g/m2** for infants - **1.4 g/m2** for toddlers ") ) ), nav_panel( "References", div( class = "ref-box", tags$h5("Primary References"), tags$ol( tags$li( "Campagne O, Zhong B, Nair S, Lin T, Huang J, Onar-Thomas A, Robinson G, Gajjar A, Stewart CF. (2020). ", tags$em("Exposure-Toxicity Association of Cyclophosphamide and Its Metabolites in Infants and Young Children with Primary Brain Tumors: Implications for Dosing."), " Clinical Cancer Research 26(7):1563-1573. ", tags$a(href = "https://doi.org/10.1158/1078-0432.CCR-19-2685", target = "_blank", "doi:10.1158/1078-0432.CCR-19-2685") ), tags$li( "McCune JS, Salinger DH, Vicini P, et al. (2009). ", tags$em("Population pharmacokinetics of cyclophosphamide and metabolites in children with neuroblastoma."), " Journal of Clinical Pharmacology." ), tags$li( "de Jonge ME, Huitema ADR, van Dam SM, et al. (2005). ", tags$em("Population pharmacokinetics of cyclophosphamide and its metabolites 4-hydroxycyclophosphamide and CEPM."), " Therapeutic Drug Monitoring." ) ), tags$h5("Clinical Context"), tags$ul( tags$li(tags$strong("Mechanism:"), " Prodrug activation to cytotoxic phosphoramide mustard via 4OH-CTX."), tags$li(tags$strong("Route in this study:"), " 1-hour intravenous infusion."), tags$li(tags$strong("Use case here:"), " Exposure exploration, pediatric dose adjustment, and covariate sensitivity."), tags$li(tags$strong("Important caveat:"), " The paper linked 4OH-CTX exposure to hematologic toxicity, but did not define a formal therapeutic concentration window.") ) ) ) ), div( style = "text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #e9ecef; color: #6c757d; font-size: 12px;", "Powered by ", tags$a(href = "https://www.pkpdbuilder.com", target = "_blank", style = "color: #8b5cf6; font-weight: 500;", "PKPDBuilder.com"), " • Built by Sunny ☀️ (Husain Attarwala's AI Assistant)", br(), tags$span(style = "font-size: 10px;", "For research and educational purposes only. Not for clinical decision-making.") ) ) server <- function(input, output, session) { load_scenario <- function(key) { scn <- paper_scenario(key) updateSliderInput(session, "dose_g_m2", value = scn$dose_g_m2) updateSliderInput(session, "interval_days", value = scn$interval_days) updateNumericInput(session, "n_cycles", value = scn$n_cycles) updateSliderInput(session, "age_months", value = scn$age_months) } observeEvent(input$load_young_infant, { load_scenario("young_infant") }) observeEvent(input$load_infant, { load_scenario("infant") }) observeEvent(input$load_toddler, { load_scenario("toddler") }) observeEvent(input$load_young_child, { load_scenario("young_child") }) sim_bundle <- reactive({ shiny::req( input$dose_g_m2, input$interval_days, input$n_cycles, input$age_months, input$infusion_h, input$primary_analyte ) run_simulation( dose_g_m2 = input$dose_g_m2, interval_days = input$interval_days, n_cycles = input$n_cycles, age_months = input$age_months, phenobarbital = input$phenobarbital, cyp_variant = input$cyp_variant, infusion_h = input$infusion_h ) }) metrics <- reactive({ compute_metrics(sim_bundle(), input$primary_analyte) }) output$scenario_note <- renderText({ band <- age_band_label(input$age_months) paste0( "Current age band: ", band, " • dose ", sprintf("%.1f", input$dose_g_m2), " g/m2 over ", sprintf("%.2f", input$infusion_h), " h every ", input$interval_days, " days for ", input$n_cycles, " cycle(s)." ) }) output$reference_note <- renderText({ met_4oh <- compute_metrics(sim_bundle(), "4OH-CTX") paste0( "Current 4OH-CTX AUC0-24h = ", sprintf("%.1f", met_4oh$auc24), " uM*h. Young-child paper target = ", sprintf("%.1f", young_child_target_4oh_auc), " uM*h. Delta = ", sprintf("%+.1f", met_4oh$auc24 - young_child_target_4oh_auc), " uM*h." ) }) output$covariate_note <- renderText({ paste0( "Phenobarbital = ", if (isTRUE(input$phenobarbital)) "on" else "off", " • CYP2B6 variant = ", if (isTRUE(input$cyp_variant)) "present" else "wild-type", ". In the paper, phenobarbital lowered parent exposure and younger age increased metabolite exposure." ) }) output$cmax <- renderText({ sprintf("%.1f", metrics()$cmax) }) output$c24 <- renderText({ sprintf("%.2f", metrics()$c24) }) output$auc24 <- renderText({ sprintf("%.1f", metrics()$auc24) }) output$thalf <- renderText({ if (is.na(metrics()$thalf)) { "NA" } else { sprintf("%.2f", metrics()$thalf) } }) output$plot_caption <- renderText({ met <- compute_metrics(sim_bundle(), "4OH-CTX") paste0( "Paper anchor: mean 4OH-CTX AUC0-24h was 138.4 uM*h in young infants and 107.2 uM*h in young children at the original 1.5 g/m2 regimen. Current simulation gives ", sprintf("%.1f", met$auc24), " uM*h for 4OH-CTX over the first 24 hours after the last cycle." ) }) output$summary_table <- renderTable({ build_exposure_summary(sim_bundle()) }, striped = TRUE, bordered = TRUE, spacing = "s") output$pkPlot <- renderPlot({ bundle <- sim_bundle() plot_data <- bundle$long if (!isTRUE(input$show_all_analytes)) { plot_data <- plot_data |> dplyr::filter(analyte == input$primary_analyte) } plot_data <- plot_data |> mutate( alpha_group = ifelse(analyte == input$primary_analyte, "primary", "secondary") ) p <- ggplot(plot_data, aes(x = time_since_last_dose, y = conc_uM, color = analyte, alpha = alpha_group)) + geom_line(linewidth = 0.9) + scale_alpha_manual(values = c(primary = 1, secondary = 0.35), guide = "none") + scale_color_manual( values = c( "Cyclophosphamide" = "#2563eb", "4OH-CTX" = "#8b5cf6", "CEPM" = "#10b981" ) ) + labs( x = "Hours since start of last cycle", y = "Concentration (uM)", title = paste0( "Cyclophosphamide parent-metabolite profile • age ", input$age_months, " months • ", sprintf("%.1f", input$dose_g_m2), " g/m2" ), subtitle = "Metrics focus on the selected analyte; all traces are simulated from the final parent-metabolite population PK model." ) + theme_minimal(base_size = 14) + theme(legend.title = element_blank()) if (isTRUE(input$log_scale)) { plot_data_pos <- plot_data |> dplyr::filter(conc_uM > 0) p <- ggplot(plot_data_pos, aes(x = time_since_last_dose, y = conc_uM, color = analyte, alpha = alpha_group)) + geom_line(linewidth = 0.9) + scale_alpha_manual(values = c(primary = 1, secondary = 0.35), guide = "none") + scale_color_manual( values = c( "Cyclophosphamide" = "#2563eb", "4OH-CTX" = "#8b5cf6", "CEPM" = "#10b981" ) ) + scale_y_log10() + labs( x = "Hours since start of last cycle", y = "Concentration (uM, log scale)", title = paste0( "Cyclophosphamide parent-metabolite profile • age ", input$age_months, " months • ", sprintf("%.1f", input$dose_g_m2), " g/m2" ), subtitle = "Metrics focus on the selected analyte; all traces are simulated from the final parent-metabolite population PK model." ) + theme_minimal(base_size = 14) + theme(legend.title = element_blank()) } p }) } shinyApp(ui = ui, server = server)