use("shiny") use("bslib") use("mrgsolve") use("dplyr") use("ggplot2") use("tidyr") # Pharmacokinetics and Pharmacodynamics of Glucarpidase Rescue Treatment # after High-dose Methotrexate Therapy Based on Modeling and Simulation # Kimura et al. Anticancer Research 43:1919-1924 (2023) model_code <- ' $PARAM @annotated CL_MTX : 0.615 : Methotrexate clearance in delayed elimination setting (L/h) VC_MTX : 26.683 : Methotrexate central volume (L) VP_MTX : 2.253 : Methotrexate peripheral volume (L) Q_MTX : 0.078 : Methotrexate intercompartmental clearance (L/h) CL_CPG2 : 0.310 : Glucarpidase clearance for 60 kg patient (L/h) V_CPG2 : 4.114 : Glucarpidase volume for 60 kg patient (L) KM_MTX : 86 : Michaelis-Menten constant for MTX hydrolysis by CPG2 (umol/L) ALPHA : 1.20 : Catalytic capacity per glucarpidase activity concentration (umol/L/h per U/L) WT : 60 : Body weight (kg) REF_WT : 60 : Reference body weight (kg) $CMT @annotated MTX_CENT : Methotrexate central amount (umol) MTX_PERI : Methotrexate peripheral amount (umol) CPG2_CENT : Glucarpidase amount (U) $MAIN double CL_MTX_i = CL_MTX; double VC_MTX_i = VC_MTX * (WT / REF_WT); double VP_MTX_i = VP_MTX * (WT / REF_WT); double Q_MTX_i = Q_MTX; double CL_CPG2_i = CL_CPG2 * (WT / REF_WT); double V_CPG2_i = V_CPG2 * (WT / REF_WT); $ODE double C_MTX = MTX_CENT / VC_MTX_i; double C_CPG2_U_L = CPG2_CENT / V_CPG2_i; double hydrolysis_umol_per_h = VC_MTX_i * ALPHA * C_CPG2_U_L * C_MTX / (KM_MTX + C_MTX); if (hydrolysis_umol_per_h > MTX_CENT / 0.001) hydrolysis_umol_per_h = MTX_CENT / 0.001; dxdt_MTX_CENT = -(CL_MTX_i / VC_MTX_i) * MTX_CENT - (Q_MTX_i / VC_MTX_i) * MTX_CENT + (Q_MTX_i / VP_MTX_i) * MTX_PERI - hydrolysis_umol_per_h; dxdt_MTX_PERI = (Q_MTX_i / VC_MTX_i) * MTX_CENT - (Q_MTX_i / VP_MTX_i) * MTX_PERI; dxdt_CPG2_CENT = -(CL_CPG2_i / V_CPG2_i) * CPG2_CENT; $TABLE double MTX_PLASMA = MTX_CENT / VC_MTX_i; double MTX_PERIPHERAL = MTX_PERI / VP_MTX_i; double CPG2_PLASMA = CPG2_CENT / V_CPG2_i; double TARGET_01 = MTX_PLASMA <= 0.1; double TARGET_1 = MTX_PLASMA <= 1.0; $CAPTURE @annotated MTX_PLASMA : Plasma methotrexate concentration (umol/L) MTX_PERIPHERAL : Peripheral methotrexate concentration (umol/L) CPG2_PLASMA : Plasma glucarpidase activity concentration (U/L) TARGET_01 : Plasma MTX below 0.1 umol/L TARGET_1 : Plasma MTX below 1.0 umol/L ' mod <- mrgsolve::mcode("glucarpidase_mtx_mm", model_code) mtx_mw_mg_per_umol <- 0.45444 simulate_glucarpidase <- function(mtx_g_m2 = 1, bsa = 1.73, infusion_h = 4, cpg2_dose_u_kg = 50, cpg2_time_h = 48, wt = 60, cl_fraction = 0.10, alpha = 1.20, end_h = 144, delta_h = 0.25) { mtx_mg <- mtx_g_m2 * 1000 * bsa mtx_umol <- mtx_mg / mtx_mw_mg_per_umol cpg2_units <- cpg2_dose_u_kg * wt dose_events <- data.frame( time = c(0, cpg2_time_h), amt = c(mtx_umol, cpg2_units), cmt = c(1, 3), evid = c(1, 1), rate = c(mtx_umol / infusion_h, 0) ) mod |> mrgsolve::param( WT = wt, CL_MTX = 6.15 * cl_fraction, ALPHA = alpha ) |> mrgsolve::ev(dose_events) %>% mrgsolve::mrgsim(end = end_h, delta = delta_h) |> as.data.frame() |> dplyr::mutate( time_h = time, MTX_PLASMA = pmax(MTX_PLASMA, 0), MTX_PERIPHERAL = pmax(MTX_PERIPHERAL, 0), CPG2_PLASMA = pmax(CPG2_PLASMA, 0) ) } metric_card <- function(output_id, label, class = "metric-primary") { div(class = paste("metric-card", class), div(class = "metric-value", textOutput(output_id)), div(class = "metric-label", label) ) } 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; } .small-note { color: #6c757d; font-size: 12px; } ") ui <- page_sidebar( title = "Glucarpidase Rescue Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, accordion( open = c("Methotrexate", "Glucarpidase"), accordion_panel( "Methotrexate", numericInput("mtx_g_m2", "MTX dose (g/m²)", value = 1, min = 0.1, max = 12, step = 0.1), numericInput("bsa", "Body surface area (m²)", value = 1.73, min = 0.5, max = 2.5, step = 0.01), sliderInput("infusion_h", "MTX infusion duration (h)", min = 1, max = 24, value = 4, step = 1), sliderInput("cl_fraction", "MTX clearance fraction of normal", min = 0.02, max = 1, value = 0.10, step = 0.01) ), accordion_panel( "Glucarpidase", sliderInput("cpg2_dose", "Glucarpidase dose (U/kg)", min = 10, max = 80, value = 50, step = 10), sliderInput("cpg2_time", "Glucarpidase time after MTX start (h)", min = 4, max = 72, value = 48, step = 1), sliderInput("alpha", "Catalytic capacity scale", min = 0.05, max = 2.5, value = 1.20, step = 0.05) ), accordion_panel( "Patient and Display", sliderInput("wt", "Body weight (kg)", min = 10, max = 120, value = 60, step = 1), sliderInput("end_h", "Simulation duration (h)", min = 72, max = 240, value = 144, step = 12), checkboxInput("log_scale", "Log scale (Y-axis)", value = TRUE), checkboxInput("show_peripheral", "Show peripheral MTX", value = TRUE), checkboxInput("show_cpg2", "Show glucarpidase", value = TRUE) ) ) ), layout_column_wrap( width = 1/4, fill = FALSE, metric_card("mtx70", "MTX at 70 h (µmol/L)", "metric-success"), metric_card("mtx120", "MTX at 120 h (µmol/L)", "metric-warning"), metric_card("time_below_01", "First <0.1 µmol/L (h)", "metric-primary"), metric_card("cpg2_half", "Glucarpidase t½ (h)", "metric-info") ), card( card_header("Methotrexate Concentration-Time Profile"), full_screen = TRUE, plotOutput("pkPlot", height = "500px") ), navset_card_underline( title = "Model Information", nav_panel("Model Information", markdown("\ ## Glucarpidase rescue after high-dose methotrexate **Model structure:** Two-compartment methotrexate PK linked to one-compartment glucarpidase PK through a modified Michaelis-Menten hydrolysis term. **Methotrexate parameters used for the default delayed-elimination scenario:** CL 0.615 L/h, Vc 26.683 L, Vp 2.253 L, Q 0.078 L/h. The default clearance fraction is 10% of normal, matching the simulation setting in the paper. **Glucarpidase parameters:** CL 0.310 L/h and Vdss 4.114 L for a 60-kg patient, scaled linearly by weight. Default dose is 50 U/kg. **Hydrolysis:** Km = 86 µmol/L. The catalytic scale is exposed because the paper reports the conversion constant indirectly from Vmax at 1 mol/L CPG2; the default is calibrated to the reported MTX <0.1 µmol/L at 70 h and rebound above 0.1 µmol/L at 120 h. **Targets:** MTX <1.0 µmol/L is the clinically important reduction endpoint; MTX <0.1 µmol/L is the nontoxic threshold emphasized in the publication. ")), nav_panel("References", div(class = "ref-box", tags$h5("Key Reference"), tags$ol( tags$li("Kimura S, et al. Pharmacokinetics and Pharmacodynamics of Glucarpidase Rescue Treatment After High-dose Methotrexate Therapy Based on Modeling and Simulation. Anticancer Research. 2023;43:1919-1924."), tags$li("VORAXAZE (glucarpidase) prescribing information: recommended dose 50 U/kg IV for toxic plasma methotrexate concentrations with delayed elimination.") ), tags$h5("Therapeutic Context"), tags$ul( tags$li(tags$strong("Class:"), " Recombinant bacterial carboxypeptidase enzyme"), tags$li(tags$strong("Route:"), " Intravenous"), tags$li(tags$strong("Use:"), " Rescue treatment for delayed methotrexate elimination"), tags$li(tags$strong("Monitoring:"), " MTX rebound can occur; long-term monitoring beyond 144 h may be needed.") ) )) ), 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) { sim_data <- reactive({ shiny::req(input$mtx_g_m2, input$bsa, input$cpg2_dose, input$wt) simulate_glucarpidase( mtx_g_m2 = input$mtx_g_m2, bsa = input$bsa, infusion_h = input$infusion_h, cpg2_dose_u_kg = input$cpg2_dose, cpg2_time_h = input$cpg2_time, wt = input$wt, cl_fraction = input$cl_fraction, alpha = input$alpha, end_h = input$end_h ) }) value_at_time <- function(data, target_h) { data$MTX_PLASMA[which.min(abs(data$time_h - target_h))] } output$mtx70 <- renderText({ d <- sim_data() sprintf("%.3g", value_at_time(d, 70)) }) output$mtx120 <- renderText({ d <- sim_data() sprintf("%.3g", value_at_time(d, 120)) }) output$time_below_01 <- renderText({ d <- sim_data() |> dplyr::filter(time_h >= input$cpg2_time, MTX_PLASMA < 0.1) if (nrow(d) == 0) return("> end") sprintf("%.1f", min(d$time_h)) }) output$cpg2_half <- renderText({ half_h <- log(2) / ((0.310 * input$wt / 60) / (4.114 * input$wt / 60)) sprintf("%.1f", half_h) }) output$pkPlot <- renderPlot({ d <- sim_data() plot_data <- d |> dplyr::select(time_h, MTX_PLASMA, MTX_PERIPHERAL, CPG2_PLASMA) |> tidyr::pivot_longer( cols = c("MTX_PLASMA", "MTX_PERIPHERAL", "CPG2_PLASMA"), names_to = "Analyte", values_to = "Concentration" ) |> dplyr::mutate( Analyte = dplyr::recode( Analyte, MTX_PLASMA = "Plasma MTX (µmol/L)", MTX_PERIPHERAL = "Peripheral MTX (µmol/L)", CPG2_PLASMA = "Glucarpidase (U/L)" ) ) if (!input$show_peripheral) { plot_data <- plot_data |> dplyr::filter(Analyte != "Peripheral MTX (µmol/L)") } if (!input$show_cpg2) { plot_data <- plot_data |> dplyr::filter(Analyte != "Glucarpidase (U/L)") } if (input$log_scale) { plot_data <- plot_data |> dplyr::filter(Concentration > 0.001) } p <- ggplot(plot_data, aes(x = time_h, y = Concentration, color = Analyte)) + annotate("rect", xmin = -Inf, xmax = Inf, ymin = 0.1, ymax = 1.0, fill = "#10b981", alpha = 0.10) + geom_hline(yintercept = c(0.1, 1.0), linetype = "dashed", color = "#10b981", alpha = 0.7) + geom_vline(xintercept = input$cpg2_time, linetype = "dotted", color = "#8b5cf6", alpha = 0.8) + geom_line(linewidth = 0.9) + scale_color_manual(values = c( "Plasma MTX (µmol/L)" = "#111827", "Peripheral MTX (µmol/L)" = "#ef4444", "Glucarpidase (U/L)" = "#10b981" )) + labs( x = "Time after methotrexate start (h)", y = "Concentration", title = paste0("MTX ", input$mtx_g_m2, " g/m² over ", input$infusion_h, " h + glucarpidase ", input$cpg2_dose, " U/kg at ", input$cpg2_time, " h"), subtitle = "Green band marks the 0.1–1.0 µmol/L monitoring range; dotted line marks glucarpidase administration", color = NULL ) + theme_minimal(base_size = 14) + theme(legend.position = "bottom") if (input$log_scale) { p <- p + scale_y_log10() } p }) } shinyApp(ui = ui, server = server)