library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) library(tidyr) # ───────────────────────────────────────────── # mrgsolve model: Capecitabine → 5'-DFCR → 5'-DFUR → 5-FU # Urien et al. (2005) J Pharmacokinet Pharmacodyn 32:817-833 # ───────────────────────────────────────────── model_code <- " $PARAM @annotated Ka : 2.07 : Absorption rate constant (1/h) TLAG : 0.28 : Lag time (h) V1 : 338 : Capecitabine apparent volume of distribution (L) CL10 : 218 : Capecitabine total apparent clearance (L/h) CL12 : 12.9 : Capecitabine conversion CL to 5'-DFCR (L/h) K23 : 10.7 : 5'-DFCR to 5'-DFUR rate constant (1/h) K34 : 5.30 : 5'-DFUR to 5-FU rate constant (1/h) K40 : 66.0 : 5-FU elimination rate constant (1/h) BILT : 8.8 : Total bilirubin (umol/L) [median 8.8, range 3-22] $CMT @annotated DEPOT : Oral depot (umol) CAP : Capecitabine central (umol) DFCR : 5-prime-DFCR (umol) DFUR : 5-prime-DFUR (umol) FU : 5-FU (umol) $MAIN // Covariate adjustments (Urien 2005, Table II) double CL10i = CL10 * pow(BILT / 8.8, 0.32); double K34i = K34 * pow(BILT / 8.8, -0.36); double k10i = CL10i / V1; // elimination double k12i = CL12 / V1; // conversion to 5'-DFCR ALAG_DEPOT = TLAG; $ODE dxdt_DEPOT = -Ka * DEPOT; dxdt_CAP = Ka * DEPOT - (k10i + k12i) * CAP; dxdt_DFCR = k12i * CAP - K23 * DFCR; dxdt_DFUR = K23 * DFCR - K34i * DFUR; dxdt_FU = K34i * DFUR - K40 * FU; $TABLE double C_CAP = CAP / V1; // umol/L (proper concentration) double C_DFCR = DFCR; // umol/L (V2=1 L fixed) double C_DFUR = DFUR; // umol/L (V3=1 L fixed) double C_FU = FU; // umol/L (V4=1 L fixed) $CAPTURE C_CAP C_DFCR C_DFUR C_FU " # Capecitabine molecular weight: 359.35 g/mol CAP_MW <- 359.35 mod <- mcode("capecitabine_popPK", model_code, quiet = TRUE) # ───────────────────────────────────────────── # Theme # ───────────────────────────────────────────── 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: 22px; font-weight: bold; color: #2c3e50; } .metric-label { font-size: 11px; color: #7f8c8d; margin-top: 2px; } .metric-cap .metric-value { color: #8b5cf6; } .metric-fu .metric-value { color: #e74c3c; } .metric-auc .metric-value { color: #10b981; } .metric-half .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 # ───────────────────────────────────────────── ui <- page_sidebar( title = "Capecitabine / 5-FU Population PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), sliderInput("dose_bsa", "Dose (mg/m\u00b2/dose, BID)", min = 500, max = 1500, value = 1000, step = 50), sliderInput("bsa", "Body Surface Area (m\u00b2)", min = 1.2, max = 2.2, value = 1.73, step = 0.05), numericInput("n_days", "Duration (days)", value = 2, min = 1, max = 5), hr(), h6("Patient Characteristics"), sliderInput("bilt", "Total Bilirubin (\u00b5mol/L)", min = 3, max = 25, value = 8.8, step = 0.5), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = TRUE), checkboxInput("show_metabolites", "Show all metabolites", value = TRUE) ), navset_card_underline( title = "Capecitabine \u2192 5-FU Simulator", full_screen = TRUE, nav_panel("Simulation", layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-cap", div(class = "metric-value", textOutput("cap_cmax")), div(class = "metric-label", "Capecitabine Cmax (\u00b5mol/L)")), div(class = "metric-card metric-fu", div(class = "metric-value", textOutput("fu_cmax")), div(class = "metric-label", "5-FU Cmax (\u00b5mol/L)")), div(class = "metric-card metric-auc", div(class = "metric-value", textOutput("cap_auc")), div(class = "metric-label", "Capecitabine AUC\u03c4 (\u00b5mol\u00b7h/L)")), div(class = "metric-card metric-half", div(class = "metric-value", textOutput("cap_thalf")), div(class = "metric-label", "Capecitabine t\u00bd (h)")) ), card( full_screen = TRUE, min_height = "500px", plotOutput("pkPlot", height = "500px") ) ), nav_panel("Model Information", card( markdown(" ## Capecitabine Population PK Model **Drug:** Capecitabine (Xeloda\u00ae) — oral fluoropyrimidine prodrug **Model structure:** Sequential 4-compartment metabolite model **Reference:** Urien et al. (2005) *J Pharmacokinet Pharmacodyn* 32:817\u2013833 **Software:** NONMEM V (FO method), n = 40 patients, 75 PK courses --- ### Metabolic Pathway ``` Oral Capecitabine \u2193 Ka = 2.07 h\u207b\u00b9, Tlag = 0.28 h Capecitabine (CMT 1) \u2193 k12 = CL12/V1 (via liver: carboxylesterase) 5\u2019-DFCR (CMT 2) \u2193 K23 = 10.7 h\u207b\u00b9 (cytidine deaminase, liver/tumor) 5\u2019-DFUR (CMT 3) \u2193 K34 = 5.30 h\u207b\u00b9 (thymidine phosphorylase, tumor-preferential) 5-FU (CMT 4) \u2192 eliminated (K40 = 66 h\u207b\u00b9) ``` --- ### Population PK Parameters (Typical Values) | Parameter | Estimate | SE | Description | |-----------|---------|------|-------------| | Ka | 2.07 h\u207b\u00b9 | 0.27 | First-order absorption | | Tlag | 0.28 h | 0.11 | Absorption lag time | | V1 | 338 L | 31 | Capecitabine apparent volume | | CL10 | 218 L/h | 18 | Total capecitabine clearance | | CL12 | 12.9 L/h | 5.1 | Conversion to 5\u2019-DFCR | | K23 | 10.7 h\u207b\u00b9 | 3.1 | 5\u2019-DFCR \u2192 5\u2019-DFUR | | K34 | 5.30 h\u207b\u00b9 | 2.0 | 5\u2019-DFUR \u2192 5-FU | | K40 | 66 h\u207b\u00b9 | 24 | 5-FU elimination | ### Covariate Effects (Total Bilirubin, BILT) | Effect | Equation | |--------|----------| | CL10 | 218 \u00d7 (BILT/8.8)\u207a\u00b0\u00b7\u00b3\u00b2 | | K34 | 5.30 \u00d7 (BILT/8.8)\u207b\u00b0\u00b7\u00b3\u00b6 | Elevated bilirubin \u2192 \u2191 non-conversion CL10 (less 5-FU produced) + \u2193 K34 (slower 5\u2019-DFUR \u2192 5-FU) Net effect: capecitabine, 5\u2019-DFCR, 5-FU AUC changes \u00b112% at \u00b130% BILT variation (clinically negligible). ### Notes - Metabolite volumes (V2, V3, V4) fixed to 1 L (not identifiable from data) - Metabolite concentrations are shown in relative units (normalized to V=1 L) - 5-FU half-life is flip-flop: apparent t\u00bd driven by K34 (rate-limiting), not K40 - IOV on Ka: 167%; ISV on TLAG: 110% (highly variable oral absorption) ") ) ), nav_panel("References", div(class = "ref-box", tags$h5("\U0001f4da Key References"), tags$ol( tags$li("Urien S, Reza\u00ef K, Lokiec F. (2005) Pharmacokinetic Modelling of 5-FU Production from Capecitabine\u2014A Population Study in 40 Adult Patients with Metastatic Cancer. ", tags$em("J Pharmacokinet Pharmacodyn"), " 32:817\u2013833. ", tags$a(href="https://doi.org/10.1007/s10928-005-0018-2", target="_blank", "DOI")), tags$li("Gieschke R et al. (2002) Population pharmacokinetic analysis of the major metabolites of capecitabine. ", tags$em("J Pharmacokinet Pharmacodyn"), " 29:25\u201347."), tags$li("Reigner B, Blesch K, Weidekamm E. (2001) Clinical pharmacokinetics of capecitabine. ", tags$em("Clin Pharmacokinet"), " 40:85\u2013104."), tags$li("Gieschke R et al. (2003) Population pharmacokinetics and concentration-effect relationships of capecitabine metabolites in colorectal cancer patients. ", tags$em("Br J Clin Pharmacol"), " 55:252\u2013263.") ), tags$h5("\U0001f48a Therapeutic Context"), tags$ul( tags$li(tags$strong("Drug class:"), " Oral fluoropyrimidine prodrug (antimetabolite)"), tags$li(tags$strong("Indications:"), " Colorectal cancer, breast cancer, gastric cancer"), tags$li(tags$strong("Standard dose:"), " 1000\u20131250 mg/m\u00b2 BID \u00d7 14 days, 7 days off (21-day cycle)"), tags$li(tags$strong("Active metabolite:"), " 5-FU (cytotoxic; inhibits thymidylate synthase)"), tags$li(tags$strong("Key enzymes:"), " CES1/CES2 (capecitabine \u2192 5\u2019-DFCR), CDA (5\u2019-DFCR \u2192 5\u2019-DFUR), TP (5\u2019-DFUR \u2192 5-FU)"), tags$li(tags$strong("DPD deficiency:"), " Severe toxicity risk from reduced 5-FU catabolism; genotype testing recommended") ) ) ) ), div( style = paste0( "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"), " \u2022 Built by Sunny \u2600\ufe0f (Husain Attarwala's AI Assistant)", tags$br(), tags$span( style = "font-size:10px;", "For research and educational purposes only. Not for clinical decision-making." ) ) ) # ───────────────────────────────────────────── # Server # ───────────────────────────────────────────── server <- function(input, output, session) { sim_data <- reactive({ shiny::req(input$dose_bsa, input$bsa, input$bilt, input$n_days) # Dose conversion: mg/m²/dose × BSA → mg → µmol dose_mg <- input$dose_bsa * input$bsa dose_umol <- dose_mg * 1000 / CAP_MW # mg × (1 mmol/359.35 mg) × 1000 µmol/mmol n_doses <- as.integer(input$n_days * 2) # BID end_h <- input$n_days * 24 ev1 <- ev( amt = dose_umol, cmt = 1, ii = 12, addl = n_doses - 1 ) mod |> param(BILT = input$bilt) |> ev(ev1) |> mrgsim(end = end_h, delta = 0.1) |> as.data.frame() }) # ── Metric: Capecitabine Cmax (last dosing interval) output$cap_cmax <- renderText({ d <- sim_data() last_start <- (input$n_days - 1) * 24 last_dose <- dplyr::filter(d, time >= last_start) sprintf("%.1f", max(last_dose$C_CAP, na.rm = TRUE)) }) # ── Metric: 5-FU Cmax (last dosing interval) output$fu_cmax <- renderText({ d <- sim_data() last_start <- (input$n_days - 1) * 24 last_dose <- dplyr::filter(d, time >= last_start) sprintf("%.2f", max(last_dose$C_FU, na.rm = TRUE)) }) # ── Metric: Capecitabine AUCtau (last interval trapezoid) output$cap_auc <- renderText({ d <- sim_data() last_start <- (input$n_days - 1) * 24 seg <- dplyr::filter(d, time >= last_start, time <= last_start + 12) if (nrow(seg) < 2) return("--") auc <- sum(diff(seg$time) * (utils::head(seg$C_CAP, -1) + utils::tail(seg$C_CAP, -1)) / 2) sprintf("%.1f", auc) }) # ── Metric: Capecitabine t½ (computed from k_el = (CL10+CL12)/V1) output$cap_thalf <- renderText({ cl10i <- 218 * (input$bilt / 8.8)^0.32 k_el <- (cl10i + 12.9) / 338 sprintf("%.2f", log(2) / k_el) }) # ── Main PK plot output$pkPlot <- renderPlot({ d <- sim_data() # Pivot to long format for ggplot d_long <- tidyr::pivot_longer( d[, c("time", "C_CAP", "C_DFCR", "C_DFUR", "C_FU")], cols = c("C_CAP", "C_DFCR", "C_DFUR", "C_FU"), names_to = "compound", values_to = "conc" ) d_long$compound <- factor( d_long$compound, levels = c("C_CAP", "C_DFCR", "C_DFUR", "C_FU"), labels = c("Capecitabine", "5\u2019-DFCR", "5\u2019-DFUR", "5-FU") ) if (!input$show_metabolites) { d_long <- dplyr::filter(d_long, compound %in% c("Capecitabine", "5-FU")) } # Remove zero/negative for log scale if (input$log_scale) { d_long <- dplyr::filter(d_long, conc > 1e-6) } colours <- c( "Capecitabine" = "#8b5cf6", "5\u2019-DFCR" = "#3b82f6", "5\u2019-DFUR" = "#f59e0b", "5-FU" = "#e74c3c" ) dose_mg <- input$dose_bsa * input$bsa dose_umol <- round(dose_mg * 1000 / CAP_MW) p <- ggplot2::ggplot(d_long, ggplot2::aes(x = time, y = conc, colour = compound)) + ggplot2::geom_line(linewidth = 0.9) + ggplot2::scale_colour_manual(values = colours, name = "Compound") + ggplot2::labs( x = "Time (h)", y = "Concentration (\u00b5mol/L)", title = sprintf( "Capecitabine %g mg/m\u00b2 BID (\u2248%g \u00b5mol, BSA = %.2f m\u00b2)", input$dose_bsa, dose_umol, input$bsa ), caption = "Note: Metabolite concentrations are relative (V = 1 L fixed in model; not identifiable)" ) + ggplot2::theme_minimal(base_size = 14) + ggplot2::theme( legend.position = "top", legend.key.width = grid::unit(1.5, "cm"), plot.caption = ggplot2::element_text(colour = "#7f8c8d", size = 10) ) if (input$log_scale) { p <- p + ggplot2::scale_y_log10( labels = scales::label_number(accuracy = 0.01) ) } p }) } shinyApp(ui = ui, server = server)