library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) model_code <- ' $PARAM @annotated CLBASE : 20.0 : Apparent clearance for PM genotype (L/h) V : 300.0 : Apparent volume of distribution (L) KA : 4.48 : Absorption rate constant (1/h) WT : 70 : Body weight (kg) CYP3A : 1 : CYP3A metabolizer status (0=PM 1=IM 2=EM) THETAC : 0.77 : CYP3A genotype effect on concentrations $CMT @annotated DEPOT : Oral depot (mg) CENT : Central (mg) $MAIN double CLi = CLBASE * pow(WT/70.0, 0.75) * pow(1.0/THETAC, CYP3A); double Vi = V * (WT/70.0); $ODE dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CLi/Vi) * CENT; $TABLE double CP = (CENT / Vi) * 1000.0; $CAPTURE CP ' mod <- mcode("tacrolimus", model_code) 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 = "Tacrolimus PopPK Simulator — CYP3A Genotype-Guided Dosing", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), sliderInput("dose", "Dose (mg)", min = 1, max = 20, value = 5, step = 0.5), selectInput("interval", "Dosing Interval", choices = c("BID (every 12h)" = 12, "QD (every 24h)" = 24), selected = 12), numericInput("n_days", "Duration (days)", value = 7, min = 1, max = 14), hr(), h6("Patient Characteristics"), sliderInput("wt", "Weight (kg)", min = 40, max = 120, value = 70, step = 5), selectInput("cyp3a", "CYP3A Metabolizer Status", choices = c("Poor (PM) — CYP3A5*3/*3 + CYP3A4*22" = 0, "Intermediate (IM) — CYP3A5*3/*3, no CYP3A4*22" = 1, "Extensive (EM) — CYP3A5 expresser" = 2), selected = 1), hr(), h6("Therapeutic Target"), selectInput("target", "Target Trough Range", choices = c("10–20 ng/mL (Week 1 post-transplant)" = "week1", "10–15 ng/mL (Maintenance)" = "maint"), selected = "maint"), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), hr(), div(style = "font-size: 11px; color: #6c757d; padding: 5px;", strong("Recommended Starting Doses:"), br(), "PM: 0.07 mg/kg BID", br(), "IM: 0.13 mg/kg BID", br(), "EM: 0.20 mg/kg BID" ) ), navset_card_tab( title = "Tacrolimus PK Simulator", full_screen = TRUE, nav_panel("Simulation", 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 (ng/mL)")), div(class="metric-card metric-warning", div(class="metric-value", textOutput("ctrough")), div(class="metric-label","Ctrough (ng/mL)")), div(class="metric-card metric-primary", div(class="metric-value", textOutput("auc")), div(class="metric-label","AUC0-τ (ng·h/mL)")), div(class="metric-card metric-info", div(class="metric-value", textOutput("thalf")), div(class="metric-label","t½ (h)")) ), plotOutput("pkPlot", height = "500px") ), nav_panel("Model Information", markdown(" ## Tacrolimus — 1-Compartment PopPK Model with CYP3A Genotype Effect **Structure:** One-compartment oral model with first-order absorption **Source:** Woillard et al. (2017), *Frontiers in Pharmacology* 8:358 ### Model Parameters | Parameter | Value | Description | |-----------|-------|-------------| | Ka | 4.48 h⁻¹ | Absorption rate constant | | CL/F (PM) | 20.0 L/h | Apparent clearance — Poor Metabolizers | | CL/F (IM) | 26.0 L/h | Apparent clearance — Intermediate Metabolizers | | CL/F (EM) | 33.7 L/h | Apparent clearance — Extensive Metabolizers | | V/F | 300 L | Apparent volume of distribution | | θ_CYP3A | 0.77 | Genotype effect per metabolizer step | ### CYP3A Genotype Classification - **PM (Poor Metabolizer):** CYP3A5\\*3/\\*3 homozygotes carrying CYP3A4\\*22 → Lowest clearance, highest exposure - **IM (Intermediate Metabolizer):** CYP3A5\\*3/\\*3 homozygotes without CYP3A4\\*22 → ~23% lower concentrations vs PM - **EM (Extensive Metabolizer):** CYP3A5 expressers without CYP3A4\\*22 → ~41% lower concentrations vs PM ### Therapeutic Drug Monitoring - **Target trough (Week 1):** 10–20 ng/mL - **Target trough (Maintenance):** 10–15 ng/mL - **Narrow therapeutic index** — toxicity at high levels (nephrotoxicity, neurotoxicity, diabetes) - **Subtherapeutic** — risk of acute rejection ### Covariate Effects - **CYP3A genotype:** Primary covariate (Δ−2LL = −73) - **Weight:** Allometric scaling (CL: WT^0.75, V: linear) - Hematocrit, PPARα, POR\\*28: Not retained in final model ### Recommended Starting Doses (Woillard et al. 2017) | Genotype | Dose (mg/kg BID) | 70 kg Patient | |----------|-----------------|---------------| | PM | 0.07 | ~5 mg BID | | IM | 0.13 | ~9 mg BID | | EM | 0.20 | ~14 mg BID | ")), nav_panel("References", div(class = "ref-box", tags$h5(HTML("📚 Key References")), tags$ol( tags$li("Woillard J-B, Mourad M, Neely M, et al. (2017) Tacrolimus Updated Guidelines through popPK Modeling: How to Benefit More from CYP3A Pre-emptive Genotyping Prior to Kidney Transplantation. Front Pharmacol 8:358. doi:10.3389/fphar.2017.00358"), tags$li("Staatz CE, Tett SE. (2004) Clinical Pharmacokinetics and Pharmacodynamics of Tacrolimus in Solid Organ Transplantation. Clin Pharmacokinet 43:623-653"), tags$li("Birdwell KA, et al. (2015) CPIC Guidelines for CYP3A5 Genotype and Tacrolimus Dosing. Clin Pharmacol Ther 98:19-24"), tags$li("Elens L, et al. (2011) CYP3A4 intron 6 C>T SNP (CYP3A4*22) significantly affects tacrolimus pharmacokinetics. Clin Chem 57:1574-1583"), tags$li("Thervet E, et al. (2010) Optimization of initial tacrolimus dose using pharmacogenetic testing. Clin Pharmacol Ther 87:721-726") ), tags$h5(HTML("💊 Therapeutic Context")), tags$ul( tags$li(tags$strong("Class:"), " Calcineurin inhibitor (immunosuppressant)"), tags$li(tags$strong("Indication:"), " Prevention of organ rejection after kidney, liver, or heart transplantation"), tags$li(tags$strong("Route:"), " Oral (immediate-release capsules, BID)"), tags$li(tags$strong("Metabolism:"), " CYP3A4/5 in liver and intestine; P-glycoprotein substrate"), tags$li(tags$strong("Key toxicities:"), " Nephrotoxicity, neurotoxicity, new-onset diabetes, hypertension"), tags$li(tags$strong("Population:"), " 59 cadaveric kidney transplant recipients (Brussels, Belgium)") ) )) ), 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 <- function(input, output, session) { sim_data <- reactive({ tau <- as.numeric(input$interval) n_doses <- ceiling(input$n_days * 24 / tau) ev1 <- ev(amt = input$dose, cmt = 1, ii = tau, addl = n_doses - 1) mod %>% param(WT = input$wt, CYP3A = as.numeric(input$cyp3a)) %>% ev(ev1) %>% mrgsim(end = input$n_days * 24, delta = 0.25) %>% as.data.frame() %>% mutate(time_h = time) }) output$cmax <- renderText({ d <- sim_data() tau <- as.numeric(input$interval) last <- d %>% filter(time >= (input$n_days - 1) * 24) sprintf("%.1f", max(last$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- sim_data() tau <- as.numeric(input$interval) last <- d %>% filter(time >= (input$n_days - 1) * 24) sprintf("%.1f", min(last$CP[last$CP > 0], na.rm = TRUE)) }) output$auc <- renderText({ d <- sim_data() tau <- as.numeric(input$interval) last_start <- (input$n_days - 1) * 24 last <- d %>% filter(time >= last_start, time <= last_start + tau) auc <- sum(diff(last$time) * (head(last$CP, -1) + tail(last$CP, -1)) / 2) sprintf("%.0f", auc) }) output$thalf <- renderText({ cyp3a_val <- as.numeric(input$cyp3a) CLi <- 20.0 * (input$wt / 70)^0.75 * (1.0 / 0.77)^cyp3a_val Vi <- 300.0 * (input$wt / 70) sprintf("%.1f", log(2) / (CLi / Vi)) }) output$pkPlot <- renderPlot({ d <- sim_data() tau <- as.numeric(input$interval) target <- input$target if (target == "week1") { ymin <- 10; ymax <- 20; lbl <- "Target trough: 10\u201320 ng/mL" } else { ymin <- 10; ymax <- 15; lbl <- "Target trough: 10\u201315 ng/mL" } genotype_labels <- c("0" = "PM", "1" = "IM", "2" = "EM") geno <- genotype_labels[input$cyp3a] p <- ggplot(d, aes(x = time_h, y = CP)) + annotate("rect", xmin = -Inf, xmax = Inf, ymin = ymin, ymax = ymax, fill = "#10b981", alpha = 0.12) + geom_hline(yintercept = c(ymin, ymax), linetype = "dashed", color = "#10b981", alpha = 0.6) + annotate("text", x = max(d$time_h) * 0.02, y = ymax + 1.5, label = lbl, hjust = 0, size = 3.5, color = "#10b981") + geom_line(color = "#8b5cf6", linewidth = 0.8) + labs( x = "Time (hours)", y = "Concentration (ng/mL)", title = paste0("Tacrolimus ", input$dose, " mg Q", tau, "H \u2014 CYP3A ", geno, " (", input$wt, " kg)") ) + theme_minimal(base_size = 14) if (input$log_scale) p <- p + scale_y_log10() p }) } shinyApp(ui = ui, server = server)