library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ── mrgsolve model ────────────────────────────────────────────────────────── model_code <- ' $PARAM @annotated CL : 10.3 : Apparent clearance (L/h) Vc : 16.9 : Central volume of distribution (L) Vp : 166 : Peripheral volume of distribution (L) Q : 17.4 : Intercompartmental clearance (L/h) KA : 0.0718 : First-order absorption rate constant (1/h) F1 : 0.372 : Oral bioavailability (fraction, fed state) D1 : 0.619 : Duration of zero-order absorption (h) CLCR : 90 : Creatinine clearance (mL/min) ALB : 4.0 : Albumin (g/dL) $CMT @annotated DEPOT : Oral absorption depot [mg] CENT : Central compartment [mg] PERIPH : Peripheral compartment [mg] $MAIN // Covariate-adjusted clearance (renal function + albumin) double CLi = CL * pow(CLCR/90.0, 0.30) * pow(ALB/4.0, 0.25); F_DEPOT = F1; D_DEPOT = D1; $ODE dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CLi/Vc) * CENT - (Q/Vc) * CENT + (Q/Vp) * PERIPH; dxdt_PERIPH = (Q/Vc) * CENT - (Q/Vp) * PERIPH; $TABLE double CP = (CENT / Vc) * 1000.0; // ng/mL $CAPTURE CP CLi ' mod <- mcode("rucaparib", model_code, quiet = TRUE) # ── App 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 = "Rucaparib (Rubraca) PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("💊 Dosing"), sliderInput("dose", "Dose (mg)", min = 200, max = 800, value = 600, step = 200), selectInput("interval", "Dosing Interval", choices = c("BID — Q12H" = "12", "QD — Q24H" = "24"), selected = "12"), numericInput("n_days", "Duration (days)", value = 14, min = 1, max = 28), hr(), h6("👤 Patient Characteristics"), sliderInput("clcr", "Creatinine Clearance (mL/min)", min = 30, max = 120, value = 90, step = 5), sliderInput("alb", "Albumin (g/dL)", min = 2.5, max = 5.0, value = 4.0, step = 0.1), hr(), h6("🍽️ Food Effect"), selectInput("food", "Dosing Condition", choices = c( "Fed — typical" = "fed", "Fasted" = "fasted", "High-fat meal (>480 mg)" = "hfm" ), selected = "fed"), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), checkboxInput("show_ref", "Show 600 mg BID Cavg reference", value = TRUE) ), navset_card_underline( title = "Rucaparib Population PK Simulator", # ── Tab 1: Simulation ────────────────────────────────────────────────── 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,ss (ng/mL)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Ctrough,ss (ng/mL)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUC\u2080-\u03c4,ss (ng\u00b7h/mL)")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "t\u00bd terminal (h)")) ), card( full_screen = TRUE, plotOutput("pkPlot", height = "500px") ) ), # ── Tab 2: Model Information ─────────────────────────────────────────── nav_panel("Model Information", div(class = "ref-box", markdown(" ## Rucaparib (Rubraca) — 2-Compartment Population PK Model **Drug Class:** PARP inhibitor (poly ADP-ribose polymerase) **Indication:** BRCA1/2-mutated advanced ovarian cancer (≥ 2 prior platinum-based regimens) **NDA:** 209115 (FDA approval: December 2016) --- ### Model Structure - **Type:** Two-compartment with sequential zero-order → first-order oral absorption - **Dataset:** 458 patients; 3 studies — A4991014, CO-338-010 (Study 10), ARIEL2 - **Software:** NONMEM 7.3 / R 3.3.0 ### Population PK Parameter Estimates | Parameter | Estimate | Description | |-----------|----------|-------------| | CL | 10.3 L/h | Apparent oral clearance | | Vc | 16.9 L | Central volume of distribution | | Vp | 166 L | Peripheral volume of distribution | | Q | 17.4 L/h | Intercompartmental clearance | | Ka | 0.0718 h⁻¹ | First-order absorption rate constant | | F1 | 37.2% | Oral bioavailability (fed, typical) | | D1 | 0.619 h | Zero-order absorption duration | | t½ | ~17–19 h | Terminal half-life | ### Covariate Effects on Clearance | Covariate | Direction | Magnitude | |-----------|-----------|-----------| | CLCR ↑ | CL ↑ | ~26% across range | | Albumin ↑ | CL ↑ | ~18% across range | | Mild renal impairment | AUC ↑ | ~15% | | Moderate renal impairment | AUC ↑ | ~33% | ### Food Effect | Condition | Dose | F1 | |-----------|------|----| | Fed (typical) | all | 37.2% | | Fasted | ≤480 mg | 29.0% | | Fasted | >480 mg | 32.6% | | High-fat meal | >480 mg | 51.7% | ### Steady-State Exposures at 600 mg BID | Metric | Value | |--------|-------| | Cavg,ss (AUCavg1W) | ~1333 ng/mL | | Predicted 200 mg BID Cavg | ~447 ng/mL | | Predicted 400 mg BID Cavg | ~893 ng/mL | ### Notes - CYP1A2 and CYP2D6 phenotypes did not significantly impact rucaparib PK - PPI co-medication increases F1 by ~15% (not clinically meaningful) - Ka decreased with increasing dose levels ") ) ), # ── Tab 3: References ────────────────────────────────────────────────── nav_panel("References", div(class = "ref-box", tags$h5("\U0001f4da Key References"), tags$ol( tags$li( "FDA NDA 209115 Multi-Discipline Review — Rucaparib (Rubraca), 2016. ", tags$a(href = "https://www.accessdata.fda.gov/drugsatfda_docs/nda/2016/209115Orig1s000MultiDisciplineR.pdf", target = "_blank", "FDA DARRTS") ), tags$li( "Xiao JJ et al. (2022). Population pharmacokinetics of rucaparib in patients with advanced ovarian cancer or other solid tumors. ", tags$em("Cancer Chemother Pharmacol."), " doi:10.1007/s00280-022-04413-7" ), tags$li( "Kristeleit R et al. (2022). Clinical Pharmacokinetics and Pharmacodynamics of Rucaparib. ", tags$em("Clin Pharmacokinet."), " doi:10.1007/s40262-022-01157-8" ), tags$li( "Swisher EM et al. (2017). Rucaparib in relapsed, platinum-sensitive high-grade ovarian carcinoma (ARIEL2 Part 1). ", tags$em("Lancet Oncol."), " 18(1):75-87." ) ), tags$h5("\U0001f48a Drug Information"), tags$ul( tags$li(tags$strong("Brand Name:"), " Rubraca\u00ae (Clovis Oncology)"), tags$li(tags$strong("Class:"), " PARP inhibitor"), tags$li(tags$strong("Approved dose:"), " 600 mg BID orally (with or without food)"), tags$li(tags$strong("Dose reductions:"), " 500 → 400 → 300 mg BID for toxicity"), tags$li(tags$strong("Metabolism:"), " CYP1A2, CYP2D6, CYP3A4 (minor); also glucuronidation"), tags$li(tags$strong("Protein binding:"), " ~70%"), tags$li(tags$strong("Time to Cmax:"), " ~1.9 h (median)"), tags$li(tags$strong("Indication:"), " BRCA1/2-mutated advanced ovarian cancer (≥2 prior platinum-based regimens)") ) ) ) ), # ── Footer ───────────────────────────────────────────────────────────────── 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\u2019s 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) { # Bioavailability adjusted for food and dose f1_reactive <- reactive({ dose <- input$dose food <- input$food if (food == "fasted") { if (dose > 480) 0.326 else 0.290 } else if (food == "hfm") { if (dose > 480) 0.517 else 0.290 } else { 0.372 } }) # Full PK simulation sim_data <- reactive({ shiny::req(input$dose, input$interval, input$n_days, input$clcr, input$alb) ii <- as.numeric(input$interval) n_doses <- ceiling(input$n_days * 24 / ii) ev1 <- ev(amt = input$dose, cmt = 1, ii = ii, addl = n_doses - 1) mod %>% param(CLCR = input$clcr, ALB = input$alb, F1 = f1_reactive()) %>% ev(ev1) %>% mrgsim(end = input$n_days * 24, delta = 0.1) %>% as.data.frame() }) # Last-day (steady-state) slice ss_data <- reactive({ d <- sim_data() last_start <- (input$n_days - 1) * 24 dplyr::filter(d, time >= last_start) }) # Terminal half-life (analytical) thalf_val <- reactive({ shiny::req(input$clcr, input$alb) CLi <- 10.3 * (input$clcr / 90)^0.30 * (input$alb / 4.0)^0.25 k10 <- CLi / 16.9 k12 <- 17.4 / 16.9 k21 <- 17.4 / 166 s <- k10 + k12 + k21 disc <- sqrt(s^2 - 4 * k10 * k21) beta <- (s - disc) / 2 log(2) / beta }) output$cmax <- renderText({ shiny::req(ss_data()) sprintf("%.0f", max(ss_data()$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ shiny::req(ss_data()) sprintf("%.0f", min(ss_data()$CP, na.rm = TRUE)) }) output$auc <- renderText({ shiny::req(sim_data()) ii <- as.numeric(input$interval) last_start <- (input$n_days - 1) * 24 last <- dplyr::filter(sim_data(), time >= last_start, time <= last_start + ii) if (nrow(last) < 2) return("\u2014") auc <- sum(diff(last$time) * (head(last$CP, -1) + tail(last$CP, -1)) / 2) sprintf("%.0f", auc) }) output$thalf <- renderText({ sprintf("%.1f", thalf_val()) }) output$pkPlot <- renderPlot({ shiny::req(sim_data()) d <- sim_data() ii <- as.numeric(input$interval) dose_label <- paste0(input$dose, " mg Q", ii, "H") p <- ggplot(d, aes(x = time, y = CP)) + geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( x = "Time (hours)", y = "Rucaparib Concentration (ng/mL)", title = paste0("Rucaparib ", dose_label, " \u2014 Population PK Simulation"), caption = paste0( "2-CMT sequential zero-order/first-order absorption | ", "CLCR = ", input$clcr, " mL/min | ALB = ", input$alb, " g/dL | ", "Source: FDA NDA 209115" ) ) + theme_minimal(base_size = 14) + theme(plot.caption = element_text(size = 9, color = "#6c757d")) # Reference Cavg,ss for 600 mg BID if (isTRUE(input$show_ref) && !(input$dose == 600 && ii == 12)) { p <- p + geom_hline(yintercept = 1333, linetype = "dashed", color = "#6c757d", linewidth = 0.6, alpha = 0.8) + annotate("text", x = max(d$time, na.rm = TRUE) * 0.02, y = 1460, label = "600 mg BID Cavg,ss ref (1333 ng/mL)", hjust = 0, size = 3.2, color = "#6c757d") } if (isTRUE(input$log_scale)) { d_pos <- dplyr::filter(d, CP > 0) p <- p %+% d_pos + scale_y_log10( labels = scales::label_number(accuracy = 1) ) } p }) } shinyApp(ui = ui, server = server)