# BGB-16673 BTK Degrader PK/PD Simulator # Based on: Wu et al. (2024) Br J Pharmacol 181:4973-4987 # DOI: 10.1111/bph.17332 # Translational PK/PD modeling for BTK-targeted protein degrader library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ── mrgsolve Model Code ── # Simplified mechanistic PK/PD model for targeted protein degraders (TPDs) # Model: 2-compartment PK + BTK degradation dynamics model_code <- ' $PARAM @annotated // PK Parameters (from mouse studies, scaled for human) CL : 1.2 : Clearance (L/h) V1 : 50 : Central volume (L) V2 : 100 : Peripheral volume (L) Q : 5 : Intercompartmental CL (L/h) KA : 0.5 : Absorption rate constant (1/h) F1 : 0.7 : Oral bioavailability // Protein binding FU : 0.00017 : Unbound fraction (0.017% from paper) // PD Parameters - BTK Degradation (Table 2 - blood, human in vivo predicted) KOUT : 0.017 : BTK turnover rate (1/h) - T1/2 ~41h KDEG : 1.0 : Proteasome-mediated degradation rate (1/h) KAB : 0.35 : Binding affinity TPD-BTK (nM) - in vivo scaled from 0.08nM in vitro // Initial conditions BTK0 : 100 : Baseline BTK (% of normal) // Patient WT : 70 : Body weight (kg) $CMT @annotated DEPOT : Oral depot (mg) CENT : Central compartment (mg) PERIPH : Peripheral compartment (mg) BTK : BTK protein level (% of baseline) $MAIN // Allometric scaling double CL_i = CL * pow(WT / 70.0, 0.75); double V1_i = V1 * pow(WT / 70.0, 1.0); double V2_i = V2 * pow(WT / 70.0, 1.0); double Q_i = Q * pow(WT / 70.0, 0.75); // Bioavailability for depot F_DEPOT = F1; // Initial BTK level BTK_0 = BTK0; $ODE // Drug PK - 2-compartment with first-order absorption double CP_total = CENT / V1_i; // Total plasma conc (mg/L = ug/mL) double CP_ngml = CP_total * 1000.0; // Convert to ng/mL double CP_nM = CP_ngml / 0.832; // Convert to nM (MW ~832 g/mol estimated) double CP_unbound_nM = CP_nM * FU; // Unbound concentration in nM dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CL_i/V1_i + Q_i/V1_i) * CENT + (Q_i/V2_i) * PERIPH; dxdt_PERIPH = (Q_i/V1_i) * CENT - (Q_i/V2_i) * PERIPH; // BTK Degradation PD - Simplified mechanistic model // d[Atotal]/dt = [A0]*kout - [Atotal]*kout - [AB]*kdeg // where [AB] = [Atotal]*[B] / (KAB + [B]) (quasi-equilibrium) double BTK_norm = BTK; // BTK as % of baseline double B = CP_unbound_nM; // Unbound drug concentration (nM) // Calculate bound fraction of BTK (quasi-equilibrium) double frac_bound = B / (KAB + B); double BTK_bound = BTK_norm * frac_bound; // BTK turnover with degradation // Synthesis (kin) = BTK0 * kout (maintains baseline at steady state) double kin = BTK0 * KOUT; dxdt_BTK = kin - BTK_norm * KOUT - BTK_bound * KDEG; $TABLE double CP_tot = CENT / V1_i; // Total plasma (mg/L) double CP_ng = CP_tot * 1000.0; // ng/mL double CP_nM_out = CP_ng / 0.832; // nM double CP_unb_nM = CP_nM_out * FU; // Unbound nM // BTK outputs double BTK_pct = BTK; // BTK as % of baseline double BTK_degradation = 100.0 - BTK; // % degradation // Clamp to valid ranges BTK_pct = BTK_pct > 0 ? BTK_pct : 0; BTK_pct = BTK_pct < 150 ? BTK_pct : 150; CP_ng = CP_ng > 0 ? CP_ng : 0; $CAPTURE @annotated CP_tot : Total plasma concentration (mg/L) CP_ng : Total plasma concentration (ng/mL) CP_nM_out : Total plasma concentration (nM) CP_unb_nM : Unbound plasma concentration (nM) BTK_pct : BTK protein level (% baseline) BTK_degradation : BTK degradation (% from baseline) ' # Compile model mod <- mcode("bgb16673", model_code, compile = TRUE) # ── Theme with custom metric cards ── 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; } .metric-danger .metric-value { color: #ef4444; } ") # ── UI ── ui <- page_sidebar( title = "BGB-16673 BTK Degrader PK/PD Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Patient Characteristics"), sliderInput("weight", "Body Weight (kg)", min = 40, max = 120, value = 70, step = 5), hr(), h6("Dosing Regimen"), numericInput("dose", "Dose (mg)", value = 100, min = 10, max = 500, step = 10), selectInput("frequency", "Dosing Frequency", choices = c("Once daily (QD)" = "24", "Twice daily (BID)" = "12", "Once weekly (QW)" = "168"), selected = "24"), numericInput("n_doses", "Number of Doses", value = 7, min = 1, max = 28), sliderInput("sim_days", "Simulation Duration (days)", min = 1, max = 56, value = 14), hr(), h6("Display Options"), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), checkboxInput("show_unbound", "Show Unbound Concentration", value = TRUE), hr(), card( card_header("Model Parameters", class = "bg-light"), p(strong("BTK t½ (turnover):"), "41 h"), p(strong("KAB (BTK binding):"), "0.35 nM"), p(strong("kdeg' (degradation):"), "1.0 h⁻¹"), p(strong("fu (unbound):"), "0.017%"), p(em("Target: ≥90% BTK degradation")) ), hr(), p(style = "font-size: 10px; color: #7f8c8d;", "Based on: Wu et al. (2024) Br J Pharmacol 181:4973-4987") ), # Metrics row layout_columns( col_widths = c(3, 3, 3, 3), div(class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax_val")), div(class = "metric-label", "Cmax (ng/mL)") ), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough_val")), div(class = "metric-label", "Ctrough (ng/mL)") ), div(class = "metric-card metric-danger", div(class = "metric-value", textOutput("btk_nadir")), div(class = "metric-label", "BTK Nadir (%)") ), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("max_degrad")), div(class = "metric-label", "Max Degradation (%)") ) ), # Main plots layout_columns( col_widths = c(6, 6), card( card_header("Drug Pharmacokinetics"), full_screen = TRUE, plotOutput("pkPlot", height = "420px") ), card( card_header("BTK Protein Degradation"), full_screen = TRUE, plotOutput("pdPlot", height = "420px") ) ), # Additional info tabs navset_card_tab( title = "Analysis", nav_panel("Dose-Response", plotOutput("doseResponsePlot", height = "420px") ), nav_panel("Model Information", markdown(" ## BGB-16673 BTK Degrader **Drug Class:** Targeted Protein Degrader (TPD) / PROTAC **Target:** Bruton's Tyrosine Kinase (BTK) **Developer:** BeiGene **Mechanism:** Cereblon (CRBN) E3 ligase-mediated proteasomal degradation ### Clinical Relevance BTK is critical for B-cell receptor signaling and plays a key role in B-cell malignancies. While BTK inhibitors (ibrutinib, acalabrutinib, pirtobrutinib) have been successful, resistance mutations can develop. BGB-16673 is designed to degrade both wild-type and mutant BTK, potentially overcoming: - **C481S mutation** (ibrutinib resistance) - **T474I mutation** - **L528W mutation** ### Model Description This simulator implements the simplified mechanistic PK/PD model from Wu et al. (2024): **PK Model:** 2-compartment with first-order oral absorption **PD Model:** BTK turnover with degrader-mediated elimination $$\\frac{d[BTK]}{dt} = k_{in} - [BTK] \\cdot k_{out} - [AB] \\cdot k_{deg}'$$ Where [AB] represents the drug-BTK binary complex formed in quasi-equilibrium. ### Key Parameters (Human Predicted) | Parameter | Value | Description | |-----------|-------|-------------| | kout | 0.017 h⁻¹ | BTK turnover rate (t½ = 41 h) | | KAB | 0.35 nM | Drug-BTK binding affinity (in vivo) | | kdeg' | 1.0 h⁻¹ | Degradation rate constant | | fu | 0.017% | Unbound fraction in plasma | ### References 1. Wu Y, et al. (2024) Br J Pharmacol 181:4973-4987 [DOI: 10.1111/bph.17332](https://doi.org/10.1111/bph.17332) 2. Wang H, et al. (2023) BGB-16673 preclinical characterization ") ) ), # ── PKPDBuilder Branding 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"), " • 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 ── server <- function(input, output, session) { # Run simulation sim_data <- reactive({ shiny::req(input$dose, input$n_doses, input$weight) # Parse frequency ii_hours <- as.numeric(input$frequency) sim_hours <- input$sim_days * 24 # Create dosing events ev <- ev(amt = input$dose, cmt = 1, ii = ii_hours, addl = input$n_doses - 1) # Run simulation with patient weight out <- mod %>% param(WT = input$weight) %>% ev(ev) %>% mrgsim(end = sim_hours, delta = 0.5) %>% as.data.frame() out$time_days <- out$time / 24 out }) # Calculate metrics metrics <- reactive({ d <- sim_data() shiny::req(nrow(d) > 0) # Get dosing interval for trough calculation ii_hours <- as.numeric(input$frequency) # Find Cmax (peak after any dose) cmax <- max(d$CP_ng, na.rm = TRUE) # Find Ctrough (just before next dose, after steady state ~5 doses) steady_start <- min(ii_hours * 5, max(d$time) * 0.5) d_ss <- d %>% dplyr::filter(time >= steady_start) ctrough <- if (nrow(d_ss) > 0) min(d_ss$CP_ng, na.rm = TRUE) else NA # BTK metrics btk_nadir <- min(d$BTK_pct, na.rm = TRUE) max_degradation <- max(d$BTK_degradation, na.rm = TRUE) list( cmax = cmax, ctrough = ctrough, btk_nadir = btk_nadir, max_degradation = max_degradation ) }) # Metric outputs output$cmax_val <- renderText({ m <- metrics() sprintf("%.1f", m$cmax) }) output$ctrough_val <- renderText({ m <- metrics() if (is.na(m$ctrough)) return("--") sprintf("%.1f", m$ctrough) }) output$btk_nadir <- renderText({ m <- metrics() sprintf("%.1f", m$btk_nadir) }) output$max_degrad <- renderText({ m <- metrics() sprintf("%.0f%%", m$max_degradation) }) # PK Plot output$pkPlot <- renderPlot({ d <- sim_data() shiny::req(nrow(d) > 0) # Convert unbound nM to ng/mL for display (must be done BEFORE ggplot) d$CP_unb_ng <- d$CP_unb_nM * 0.832 # Filter for log scale if needed if (input$log_scale) { d <- d %>% dplyr::filter(CP_ng > 0.01) } p <- ggplot(d, aes(x = time_days)) + theme_minimal(base_size = 14) + labs(x = "Time (days)", y = "Concentration (ng/mL)", color = "") + theme(legend.position = "bottom") if (input$show_unbound) { p <- p + geom_line(aes(y = CP_ng, color = "Total"), linewidth = 1.2) + geom_line(aes(y = CP_unb_ng * 1000, color = "Unbound (×1000)"), linewidth = 1, linetype = "dashed") + scale_color_manual(values = c("Total" = "#3b82f6", "Unbound (×1000)" = "#ef4444")) } else { p <- p + geom_line(aes(y = CP_ng), color = "#3b82f6", linewidth = 1.2) } if (input$log_scale) { p <- p + scale_y_log10() } p }) # PD Plot (BTK) output$pdPlot <- renderPlot({ d <- sim_data() shiny::req(nrow(d) > 0) # BTK degradation target target_90 <- 10 # 90% degradation = 10% remaining ggplot(d, aes(x = time_days, y = BTK_pct)) + geom_hline(yintercept = target_90, linetype = "dashed", color = "#ef4444", linewidth = 0.8) + geom_hline(yintercept = 100, linetype = "dotted", color = "#6b7280") + geom_line(color = "#10b981", linewidth = 1.5) + geom_ribbon(aes(ymin = 0, ymax = BTK_pct), fill = "#10b981", alpha = 0.2) + annotate("text", x = max(d$time_days) * 0.95, y = target_90 + 5, label = "90% degradation", hjust = 1, size = 3.5, color = "#ef4444") + scale_y_continuous(limits = c(0, 110), breaks = seq(0, 100, 20)) + labs(x = "Time (days)", y = "BTK Protein (% of baseline)") + theme_minimal(base_size = 14) }) # Dose-Response Plot output$doseResponsePlot <- renderPlot({ shiny::req(input$weight) # Simulate multiple doses doses <- c(10, 25, 50, 100, 200, 300, 400, 500) ii_hours <- as.numeric(input$frequency) results <- lapply(doses, function(dose_mg) { ev <- ev(amt = dose_mg, cmt = 1, ii = ii_hours, addl = 6) # 7 doses out <- mod %>% param(WT = input$weight) %>% ev(ev) %>% mrgsim(end = ii_hours * 7, delta = 1) %>% as.data.frame() data.frame( dose = dose_mg, btk_nadir = min(out$BTK_pct, na.rm = TRUE), cmax = max(out$CP_ng, na.rm = TRUE) ) }) dose_resp <- do.call(rbind, results) # Plot ggplot(dose_resp, aes(x = dose, y = btk_nadir)) + geom_hline(yintercept = 10, linetype = "dashed", color = "#ef4444") + geom_line(color = "#8b5cf6", linewidth = 1.5) + geom_point(size = 4, color = "#8b5cf6") + annotate("text", x = max(doses) * 0.9, y = 15, label = "90% degradation target", hjust = 1, size = 3.5, color = "#ef4444") + scale_x_continuous(breaks = doses) + scale_y_continuous(limits = c(0, 100)) + labs(x = "Dose (mg)", y = "BTK Nadir (% of baseline)", title = paste0("BTK Degradation vs Dose (", input$frequency, "h dosing)")) + theme_minimal(base_size = 14) }) } shinyApp(ui = ui, server = server)