library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ────────────────────────────────────────────────────────────────────────────── # Tacrolimus Extended-Release (Advagraf®) — Population PK Simulator # Based on: Benkali et al. (2010) Clin Pharmacokinet 49(10):683-692 # Model: 2-compartment + Erlang absorption (n=3 transit compartments) # Covariate: CYP3A5*1/*3 genotype on apparent clearance (CL/F) # ────────────────────────────────────────────────────────────────────────────── model_code <- ' $PARAM @annotated KTR : 3.3 : Transit rate constant — Erlang absorption (1/h) V1F : 486 : Apparent central volume V1/F (L) CLF_NEXP : 19 : Apparent CL/F for CYP3A5 non-expressers (L/h) THETA2 : 1.15 : CYP3A5 expressor multiplier on CL/F (dimensionless) K56 : 0.13 : Rate constant central-to-peripheral (1/h) K65 : 0.09 : Rate constant peripheral-to-central (1/h) CYP35 : 0 : CYP3A5 status (0=non-expressor 3/*3, 1=expressor) $CMT @annotated DEPOT : Oral dose depot (mg) TRANSIT1 : Erlang transit compartment 1 (mg) TRANSIT2 : Erlang transit compartment 2 (mg) TRANSIT3 : Erlang transit compartment 3 (mg) CENT : Central compartment (mg) PERIPH : Peripheral compartment (mg) $ODE double CLF = CLF_NEXP * (1.0 + THETA2 * CYP35); dxdt_DEPOT = -KTR * DEPOT; dxdt_TRANSIT1 = KTR * DEPOT - KTR * TRANSIT1; dxdt_TRANSIT2 = KTR * TRANSIT1 - KTR * TRANSIT2; dxdt_TRANSIT3 = KTR * TRANSIT2 - KTR * TRANSIT3; dxdt_CENT = KTR * TRANSIT3 - (CLF / V1F + K56) * CENT + K65 * PERIPH; dxdt_PERIPH = K56 * CENT - K65 * PERIPH; $TABLE double CP = (CENT / V1F) * 1000.0; $CAPTURE @annotated CP : Tacrolimus whole-blood concentration (ng/mL) ' mod <- mcode("tacrolimus_er", model_code) TARGET_TROUGH_LOW <- 4 # ng/mL (stable renal transplant maintenance) TARGET_TROUGH_HIGH <- 8 # ng/mL # ── PKPDBuilder Brand 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: 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 ─────────────────────────────────────────────────────────────────────── ui <- page_sidebar( title = "Tacrolimus ER (Advagraf\u00ae) PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("\U0001f48a Dosing"), sliderInput("dose", "Once-Daily Dose (mg)", min = 0.5, max = 12, value = 5, step = 0.5), numericInput("n_days", "Simulation Duration (days)", value = 21, min = 7, max = 60), hr(), h6("\U0001f9ec CYP3A5 Genotype"), selectInput("cyp35", "CYP3A5 Status", choices = c( "Non-expressor — CYP3A5*3/*3" = "0", "Expressor — CYP3A5*1/*1 or *1/*3" = "1" ), selected = "0" ), helpText("Expressors have ~2\u00d7 higher CL/F than non-expressors and", " need higher doses to reach target troughs."), hr(), h6("\U0001f4ca Display"), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), checkboxInput("show_target", "Show Target Trough Range (4\u20138 ng/mL)", value = TRUE), checkboxInput("show_comparison", "Compare Both CYP3A5 Genotypes", value = FALSE) ), # ── Metric Cards ──────────────────────────────────────────────────────────── layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax")), div(class = "metric-label", "SS Cmax (ng/mL)") ), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "SS Trough / C\u2080 (ng/mL)") ), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc24")), div(class = "metric-label", "AUC\u2082\u2084 (ng\u00b7h/mL)") ), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "Terminal t\u00bd (h)") ) ), # ── PK Plot ───────────────────────────────────────────────────────────────── card( card_header("Whole-Blood Concentration-Time Profile"), full_screen = TRUE, plotOutput("pkPlot", height = "500px") ), # ── Info Tabs ─────────────────────────────────────────────────────────────── navset_card_underline( title = "Drug Information", nav_panel("Model Information", markdown(" ## Tacrolimus Extended-Release (Advagraf\u00ae) \u2014 Population PK Model **Based on:** Benkali K et al. (2010), *Clinical Pharmacokinetics* 49(10):683\u2013692 **Drug:** Tacrolimus (FK506) \u2014 Advagraf\u00ae once-daily extended-release capsules **Indication:** Immunosuppression in adult renal transplant recipients (stable, \u22656 months post-switch) --- ### Model Structure | Feature | Details | |---------|---------| | Structural model | 2-compartment | | Absorption | Erlang distribution (n = 3 transit compartments) | | Elimination | First-order | | Software | NONMEM\u00ae VI (ADVAN5 SS5), FOCE | | Population | 41 stable adult renal transplant recipients | | Biosampling | Whole blood (12-point profiles, LC-MS/MS) | --- ### Population PK Parameters (Whole Dataset, n = 41) | Parameter | Value | IIV (%) | Units | |-----------|-------|---------|-------| | Transit rate constant (k\u209c\u1d63) | 3.3 | 52 | h\u207b\u00b9 | | Apparent central volume (V\u2081/F) | 486 | 53 | L | | CL/F \u2014 CYP3A5 non-expressers (\u03b8\u2081) | 19 | 35 | L/h | | CYP3A5 expressor multiplier (\u03b8\u2082) | 1.15 | \u2014 | dimensionless | | CL/F \u2014 CYP3A5 expressers | ~41 | \u2014 | L/h | | k central\u2192peripheral (k\u2085\u2086) | 0.13 | \u2014 | h\u207b\u00b9 | | k peripheral\u2192central (k\u2086\u2085) | 0.09 | \u2014 | h\u207b\u00b9 | | Residual error (proportional) | 8% | \u2014 | \u2014 | | Residual error (additive) | 0.7 | \u2014 | ng/mL | --- ### CYP3A5 Pharmacogenomics - CYP3A5 expressers (*1/*1 or *1/*3) have **~2-fold higher apparent clearance** - This SNP explains **~35%** of interindividual variability in CL/F - No other covariates were retained (weight, sex, age, haematocrit, creatinine, prednisolone) - **Clinical implication:** Expressers require higher doses to achieve the same trough --- ### Therapeutic Drug Monitoring (TDM) - **Target trough (C\u2080):** 4\u20138 ng/mL (stable maintenance, \u22656 months) - **Measurement:** Whole blood (tacrolimus ~80% erythrocyte-bound) - **Key insight:** AUC\u2082\u2084 can vary up to 50% for the same C\u2080 value - **Bayesian estimation:** 3 samples (C\u2080, C\u2081h, C\u2083h) provides AUC\u2082\u2084 with RMSE 9% Available at: [ISBA ABIS \u2014 ImmunoSuppressant Bayesian Dose Adjustment](https://pharmaco.chu-limoges.fr/) ")), nav_panel("References", div(class = "ref-box", tags$h5("\U0001f4da Primary Reference"), tags$ol( tags$li( "Benkali K, Pr\u00e9maud A, Picard N et al. (2010) Tacrolimus population pharmacokinetic-pharmacogenetic", " analysis and Bayesian estimation in renal transplant recipients on a new once-daily formulation. ", tags$em("Clinical Pharmacokinetics"), " 49(10):683\u2013692. ", tags$a(href = "https://doi.org/10.2165/11535950-000000000-00000", target = "_blank", "DOI: 10.2165/11535950-000000000-00000") ) ), tags$h5("\U0001f4cb Supporting Literature"), tags$ol(start = 2, tags$li("Staatz CE, Tett SE. (2004) Clinical pharmacokinetics and pharmacodynamics of tacrolimus. ", tags$em("Clin Pharmacokinet"), " 43(10):623\u2013653."), tags$li("Press RR et al. (2009) Explaining variability in tacrolimus pharmacokinetics. ", tags$em("Ann Transplant"), " 14(3):57\u201367."), tags$li("Barraclough KA et al. (2012) Predicting tacrolimus exposure. ", tags$em("Transplantation"), " 93(6):627\u2013633.") ), tags$h5("\U0001f48a Drug Summary"), tags$ul( tags$li(tags$strong("Brand names:"), " Advagraf\u00ae (ER, once-daily); Prograf\u00ae (IR, twice-daily)"), tags$li(tags$strong("Class:"), " Calcineurin inhibitor (macrolide immunosuppressant)"), tags$li(tags$strong("Route:"), " Oral (extended-release capsules)"), tags$li(tags$strong("Metabolism:"), " CYP3A4/CYP3A5 (hepatic and intestinal)"), tags$li(tags$strong("Molecular weight:"), " 822.05 g/mol"), tags$li(tags$strong("Measurement:"), " Whole blood (80% bound to erythrocytes)") ) )) ), # ── Footer ─────────────────────────────────────────────────────────────────── 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"), " \u2022 Built by Sunny \u2600\ufe0f (Husain Attarwala's AI Assistant)", br(), tags$span( style = "font-size: 10px;", "For research and educational purposes only. Not for clinical decision-making." ) ) ) # ── Server ─────────────────────────────────────────────────────────────────── server <- function(input, output, session) { run_sim <- function(cyp35_val) { shiny::req(input$dose, input$n_days) n_doses <- input$n_days ev1 <- ev(amt = input$dose, cmt = 1, ii = 24, addl = n_doses - 1) mod %>% param(CYP35 = cyp35_val) %>% ev(ev1) %>% mrgsim(end = input$n_days * 24, delta = 0.5) %>% as.data.frame() } sim_data <- reactive({ shiny::req(input$dose, input$n_days, input$cyp35) cyp35_val <- as.numeric(input$cyp35) d <- run_sim(cyp35_val) label <- if (cyp35_val == 0) "Non-expressor (*3/*3)" else "Expressor (*1/*3 or *1/*1)" d$genotype <- label d }) sim_data_compare <- reactive({ shiny::req(input$dose, input$n_days) d0 <- run_sim(0) d0$genotype <- "Non-expressor (*3/*3)" d1 <- run_sim(1) d1$genotype <- "Expressor (*1/*3 or *1/*1)" rbind(d0, d1) }) last_interval <- reactive({ d <- sim_data() last_start <- (input$n_days - 1) * 24 d |> dplyr::filter(time >= last_start) }) output$cmax <- renderText({ d <- last_interval() sprintf("%.1f", max(d$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- last_interval() sprintf("%.1f", min(d$CP, na.rm = TRUE)) }) output$auc24 <- renderText({ # Analytical SS AUC: Dose / CL/F (mg / (L/h)) × 1000 = ng·h/mL cyp35_val <- as.numeric(input$cyp35) clf <- 19 * (1 + 1.15 * cyp35_val) auc <- input$dose / clf * 1000 sprintf("%.0f", auc) }) output$thalf <- renderText({ cyp35_val <- as.numeric(input$cyp35) clf <- 19 * (1 + 1.15 * cyp35_val) k10 <- clf / 486 # elimination micro-rate constant k12 <- 0.13 # k central → peripheral k21 <- 0.09 # k peripheral → central sum_k <- k10 + k12 + k21 prod_k <- k10 * k21 discriminant <- pmax(sum_k^2 - 4 * prod_k, 0) beta <- (sum_k - sqrt(discriminant)) / 2 t_half <- log(2) / beta sprintf("%.0f", t_half) }) output$pkPlot <- renderPlot({ shiny::req(input$dose, input$n_days, input$cyp35) color_vals <- c( "Non-expressor (*3/*3)" = "#8b5cf6", "Expressor (*1/*3 or *1/*1)" = "#ef4444" ) if (isTRUE(input$show_comparison)) { d_all <- sim_data_compare() d_plot <- if (input$log_scale) d_all |> dplyr::filter(CP > 0.01) else d_all p <- ggplot(d_plot, aes(x = time, y = CP, color = genotype)) + geom_line(linewidth = 0.9) + scale_color_manual(values = color_vals, name = "CYP3A5 Genotype") + theme(legend.position = "bottom") title_text <- paste0("Tacrolimus ER ", input$dose, " mg QD \u2014 CYP3A5 Genotype Comparison") } else { d <- sim_data() d_plot <- if (input$log_scale) d |> dplyr::filter(CP > 0.01) else d cyp35_val <- as.numeric(input$cyp35) geno_label <- if (cyp35_val == 0) "CYP3A5 Non-expressor (*3/*3)" else "CYP3A5 Expressor (*1/*3 or *1/*1)" geno_color <- if (cyp35_val == 0) "#8b5cf6" else "#ef4444" p <- ggplot(d_plot, aes(x = time, y = CP)) + geom_line(color = geno_color, linewidth = 0.9) + theme(legend.position = "none") title_text <- paste0("Tacrolimus ER ", input$dose, " mg QD \u2014 ", geno_label) } x_max <- max(d_plot$time, na.rm = TRUE) if (isTRUE(input$show_target)) { p <- p + annotate("rect", xmin = -Inf, xmax = Inf, ymin = TARGET_TROUGH_LOW, ymax = TARGET_TROUGH_HIGH, fill = "#10b981", alpha = 0.12) + geom_hline( yintercept = c(TARGET_TROUGH_LOW, TARGET_TROUGH_HIGH), linetype = "dashed", color = "#059669", linewidth = 0.5, alpha = 0.8) + annotate("text", x = x_max * 0.02, y = TARGET_TROUGH_HIGH + 0.5, label = "Target trough: 4\u20138 ng/mL", hjust = 0, size = 3.5, color = "#059669") } p <- p + labs( x = "Time (hours)", y = "Whole-blood tacrolimus concentration (ng/mL)", title = title_text ) + theme_minimal(base_size = 14) if (input$log_scale) p <- p + scale_y_log10() p }) } shinyApp(ui = ui, server = server)