library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ============================================================================= # Datopotamab Deruxtecan (Dato-DXd / DATROWAY) Population PK Simulator # Based on: Hong et al. (2025) CPT: Pharmacometrics & Systems Pharmacology # DOI: 10.1002/psp4.70118 # # ADC: 2-compartment + parallel linear & Michaelis-Menten elimination # DXd payload: 1-compartment with linear clearance, semi-mechanistic release # Time unit: days | Amounts: µg | Volumes: L | Concentrations: µg/L (= ng/mL) # ============================================================================= model_code <- ' $PARAM @annotated WT : 66 : Body weight (kg) SEX : 0 : Sex (0=Male, 1=Female) ALB : 38 : Albumin (g/L) AGE : 62 : Age (years) TSIZE : 66 : Tumor size (mm) TAU : 21 : Dosing interval (days) NCYC : 4 : Number of dosing cycles $CMT @annotated ADC_C : Dato-DXd central (ug) ADC_P : Dato-DXd peripheral (ug) DXD_C : DXd central (ug) $MAIN // --- Dato-DXd PK parameters with covariates (L/day) --- // Eq 8: CL with allometric WT, albumin, age, sex double CL_ADC = 0.386 * pow(WT/66.0, 0.75) * pow(ALB/38.0, -0.788) * pow(AGE/62.0, -0.306) * (1.0 - 0.263 * SEX); // Eq 9: Vc with WT and sex double Vc_ADC = 3.06 * pow(WT/66.0, 0.415) * (1.0 - 0.160 * SEX); // Q (no significant covariates retained) double Q_ADC = 0.422; // Eq 10: Vp with WT double Vp_ADC = 2.88 * pow(WT/66.0, 0.311); // Eq 11-12: Nonlinear elimination (Michaelis-Menten) double Vmax_ADC = 8410.0 * pow(TSIZE/66.0, 0.125); // ug/day double Km_ADC = 4490.0; // ug/L (= ng/mL) // --- DXd PK parameters (converted to days) --- // CLDXd = 2.66 L/h -> 63.84 L/day; Eq 14 with WT + albumin double CL_DXd = 63.84 * pow(WT/66.0, 0.298) * pow(ALB/38.0, 0.343); // VcDXd = 25.1 L; Eq 15 with WT + sex double Vc_DXd = 25.1 * pow(WT/66.0, 0.530) * (1.0 - 0.185 * SEX); // DAR decline constants (beta = 0.259/h -> 6.216/day) double BETA_DAR = 6.216; double FACTOR1 = 0.696; // MW ratio for mass conversion: DXd (493.5 Da) / ADC (~150 kDa) double MW_RATIO = 493.5 / 150000.0; $ODE // Dato-DXd central concentration (ug/L) double CP_ADC = ADC_C / Vc_ADC; // Michaelis-Menten elimination rate (ug/day) // rate = Vmax * A / (Km*V + A) to keep amount-based ODE consistent double rate_MM = Vmax_ADC * ADC_C / (Km_ADC * Vc_ADC + ADC_C); // DAR: time-dependent decline within cycle + between-cycle reduction // Eq 1: DAR = 4 * (0.25 + 0.75 * exp(-beta * t_after_dose)) * factor // After last dose, t_cycle keeps increasing (no reset) so DAR stays low double last_dose_time = (NCYC - 1.0) * TAU; double cycle_n = floor(SOLVERTIME / TAU) + 1.0; double t_cycle; if (SOLVERTIME > last_dose_time) { t_cycle = SOLVERTIME - last_dose_time; // no reset after last dose } else { t_cycle = fmod(SOLVERTIME, TAU); } double f1 = (cycle_n <= 1.0) ? 1.0 : FACTOR1; double DAR_val = 4.0 * (0.25 + 0.75 * exp(-BETA_DAR * t_cycle)) * f1; // Total ADC elimination rate (ug/day) double total_elim = (CL_ADC / Vc_ADC) * ADC_C + rate_MM; // DXd release = total ADC elim * DAR * MW ratio (ug/day of DXd) double dxd_release = total_elim * DAR_val * MW_RATIO; // Dato-DXd ODEs: 2-compartment + MM elimination dxdt_ADC_C = -(CL_ADC/Vc_ADC) * ADC_C -(Q_ADC/Vc_ADC) * ADC_C +(Q_ADC/Vp_ADC) * ADC_P - rate_MM; dxdt_ADC_P = (Q_ADC/Vc_ADC) * ADC_C - (Q_ADC/Vp_ADC) * ADC_P; // DXd ODE: 1-compartment with release input dxdt_DXD_C = dxd_release - (CL_DXd/Vc_DXd) * DXD_C; $TABLE double CPADC_ugmL = ADC_C / Vc_ADC / 1000.0; // ug/L -> ug/mL double CPDXd_ngmL = DXD_C / Vc_DXd; // ug/L = ng/mL $CAPTURE CPADC_ugmL CPDXd_ngmL ' mod <- mcode("datodxd", model_code) # ============================================================================= # 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 = "Datopotamab Deruxtecan (Dato-DXd) PopPK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), sliderInput("dose_mgkg", "Dose (mg/kg)", min = 1, max = 10, value = 6, step = 0.5), helpText("Approved dose: 6 mg/kg IV Q3W"), numericInput("n_cycles", "Number of Cycles", value = 4, min = 1, max = 12), hr(), h6("Patient Characteristics"), sliderInput("wt", "Body Weight (kg)", min = 37, max = 156, value = 66, step = 1), radioButtons("sex", "Sex", choices = c("Male" = 0, "Female" = 1), inline = TRUE), sliderInput("alb", "Albumin (g/L)", min = 19, max = 50, value = 38, step = 1), sliderInput("age", "Age (years)", min = 26, max = 84, value = 62, step = 1), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = TRUE) ), navset_card_tab( title = "Dato-DXd PopPK Simulator", full_screen = TRUE, # --- Simulation Tab --- nav_panel("Simulation", layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax_adc")), div(class = "metric-label", "Dato-DXd Cmax (µg/mL)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough_adc")), div(class = "metric-label", "Dato-DXd Ctrough (µg/mL)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("cmax_dxd")), div(class = "metric-label", "DXd Cmax (ng/mL)")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf_adc")), div(class = "metric-label", "ADC t½β (days)")) ), plotOutput("pkPlot", height = "500px") ), # --- Model Information Tab --- nav_panel("Model Information", markdown(" ## Datopotamab Deruxtecan (Dato-DXd, DATROWAY®) — Population PK Model ### ADC Component (Dato-DXd) **Structure:** Two-compartment with parallel linear and nonlinear (Michaelis-Menten) elimination | Parameter | Typical Value | Description | |-----------|--------------|-------------| | CL_lin | 0.386 L/day | Linear clearance (66 kg male reference) | | Vc | 3.06 L | Central volume of distribution | | Q | 0.422 L/day | Inter-compartmental clearance | | Vp | 2.88 L | Peripheral volume of distribution | | Vmax | 8410 µg/day | Maximum nonlinear elimination capacity | | Km | 4490 ng/mL | Concentration at half-maximum elimination | **Covariates retained:** - Body weight → allometric on CL (0.75 fixed), Vc (0.415), Vp (0.311) - Albumin → power model on CL (−0.788) - Age → power model on CL (−0.306) - Female sex → −26.3% on CL, −16.0% on Vc - Tumor size → power model on Vmax (0.125) ### DXd Payload Component **Structure:** One-compartment with linear clearance; release = total ADC elimination rate | Parameter | Typical Value | Description | |-----------|--------------|-------------| | CL_DXd | 2.66 L/h | DXd clearance | | Vc_DXd | 25.1 L | DXd volume of distribution | | DAR₀ | 4 | Initial drug-to-antibody ratio | | β | 0.259 h⁻¹ | Within-cycle DAR exponential decline | | Factor1 | 0.696 | Between-cycle DAR reduction (cycles ≥2) | ### Key Clinical Findings - **Linear CL dominates** at doses ≥ 4 mg/kg Q3W (>50% of total elimination) - **Minimal accumulation** for AUC and Cmax (Rac < 1.2); moderate Ctrough accumulation (Rac ~1.5 at 6 mg/kg) - **Weight-based dosing** supported — body weight is the most influential covariate - **No dose adjustment** required based on age, sex, albumin, or hepatic/renal function - **Dose cap option:** 540 mg flat dose for patients >90 kg achieves comparable exposure ### Drug Information - **Brand name:** DATROWAY® - **Class:** TROP2-directed antibody-drug conjugate (ADC) - **Payload:** DXd (exatecan derivative), topoisomerase I inhibitor - **Linker:** Tetrapeptide-based plasma-stable cleavable - **Approved dose:** 6 mg/kg IV Q3W - **Indication:** HR+/HER2− metastatic breast cancer (prior endocrine + chemo) - **Developer:** Daiichi Sankyo / AstraZeneca ")), # --- References Tab --- nav_panel("References", div(class = "ref-box", tags$h5("📚 Key References"), tags$ol( tags$li("Hong Y, Peigné S, Pan Y, et al. (2025) Population Pharmacokinetic Analysis of Datopotamab Deruxtecan (Dato-DXd), a TROP2-Directed Antibody-Drug Conjugate, in Patients With Advanced Solid Tumors. ", tags$em("CPT: Pharmacometrics & Systems Pharmacology"), " 14:2149–2160. ", tags$a(href = "https://doi.org/10.1002/psp4.70118", target = "_blank", "DOI: 10.1002/psp4.70118")), tags$li("Naito Y, et al. (2022) TROPION-PanTumor01: Phase 1 study of Dato-DXd in patients with advanced solid tumors. ", tags$em("J Clin Oncol"), " 40:3003–3012."), tags$li("Meric-Bernstam F, et al. (2024) TROPION-Lung05: Dato-DXd in NSCLC with actionable genomic alterations. ", tags$em("J Clin Oncol"), " 42:3013–3023."), tags$li("Goto K, et al. (2024) TROPION-Lung01: Dato-DXd vs docetaxel in advanced NSCLC. ", tags$em("NEJM"), " 391:1301–1312."), tags$li(tags$a(href = "https://www.accessdata.fda.gov/drugsatfda_docs/label/2025/761394s000lbl.pdf", target = "_blank", "FDA Prescribing Information — DATROWAY® (datopotamab deruxtecan-dlnk)")) ), tags$h5("💊 Therapeutic Context"), tags$ul( tags$li(tags$strong("Class:"), " TROP2-directed antibody-drug conjugate (ADC)"), tags$li(tags$strong("Approved dose:"), " 6 mg/kg IV every 3 weeks"), tags$li(tags$strong("Indication:"), " HR+/HER2− metastatic breast cancer"), tags$li(tags$strong("Payload:"), " DXd (deruxtecan) — topoisomerase I inhibitor"), tags$li(tags$strong("Developer:"), " Daiichi Sankyo / AstraZeneca") ), tags$p(tags$em("NSCLC indication under regulatory review in multiple jurisdictions.")) )) ), # 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) { sim_data <- reactive({ dose_ug <- input$dose_mgkg * input$wt * 1000 # mg/kg * kg * 1000 ug/mg tau <- 21 # Q3W in days ev1 <- ev(amt = dose_ug, cmt = 1, ii = tau, addl = input$n_cycles - 1) mod %>% param( WT = input$wt, SEX = as.numeric(input$sex), ALB = input$alb, AGE = input$age, TSIZE = 66, TAU = tau, NCYC = input$n_cycles ) %>% ev(ev1) %>% mrgsim(end = input$n_cycles * tau + 7, delta = 0.1) %>% as.data.frame() %>% mutate(time_days = time) }) # --- Metric: Dato-DXd Cmax cycle 1 --- output$cmax_adc <- renderText({ d <- sim_data() %>% filter(time_days <= 21) sprintf("%.1f", max(d$CPADC_ugmL, na.rm = TRUE)) }) # --- Metric: Dato-DXd Ctrough before cycle 2 --- output$ctrough_adc <- renderText({ d <- sim_data() trough_val <- d %>% filter(time_days >= 20, time_days < 21) %>% pull(CPADC_ugmL) %>% min(na.rm = TRUE) if (is.infinite(trough_val)) trough_val <- 0 sprintf("%.2f", trough_val) }) # --- Metric: DXd Cmax cycle 1 --- output$cmax_dxd <- renderText({ d <- sim_data() %>% filter(time_days <= 21) sprintf("%.1f", max(d$CPDXd_ngmL, na.rm = TRUE)) }) # --- Metric: ADC terminal half-life (linear 2-CMT) --- output$thalf_adc <- renderText({ sex <- as.numeric(input$sex) CL <- 0.386 * (input$wt / 66)^0.75 * (input$alb / 38)^(-0.788) * (input$age / 62)^(-0.306) * (1 - 0.263 * sex) Vc <- 3.06 * (input$wt / 66)^0.415 * (1 - 0.160 * sex) Q <- 0.422 Vp <- 2.88 * (input$wt / 66)^0.311 k10 <- CL / Vc k12 <- Q / Vc k21 <- Q / Vp s <- k10 + k12 + k21 beta <- (s - sqrt(s^2 - 4 * k10 * k21)) / 2 sprintf("%.1f", log(2) / beta) }) # --- PK Plot: Dato-DXd + DXd stacked panels --- output$pkPlot <- renderPlot({ d <- sim_data() # Long format using base R (avoids tidyr dependency) d_adc <- d %>% transmute(time_days, analyte = "Dato-DXd (µg/mL)", conc = CPADC_ugmL) d_dxd <- d %>% transmute(time_days, analyte = "DXd Payload (ng/mL)", conc = CPDXd_ngmL) d_long <- bind_rows(d_adc, d_dxd) %>% mutate(analyte = factor(analyte, levels = c("Dato-DXd (µg/mL)", "DXd Payload (ng/mL)"))) # Filter out zero/negative for log scale if (input$log_scale) { d_long <- d_long %>% filter(conc > 1e-6) } # Cycle dose lines tau <- 21 dose_times <- seq(0, (input$n_cycles - 1) * tau, by = tau) p <- ggplot(d_long, aes(x = time_days, y = conc)) + geom_vline(xintercept = dose_times, linetype = "dotted", color = "grey60", alpha = 0.5) + geom_line(color = "#8b5cf6", linewidth = 0.9) + facet_wrap(~analyte, scales = "free_y", ncol = 1) + labs( x = "Time (days)", y = "Concentration", title = paste0("Dato-DXd ", input$dose_mgkg, " mg/kg IV Q3W • ", input$wt, " kg ", ifelse(as.numeric(input$sex) == 0, "male", "female"), " • ", input$n_cycles, " cycles") ) + scale_x_continuous( breaks = seq(0, (input$n_cycles + 1) * tau, by = tau), labels = function(x) paste0("D", x) ) + theme_minimal(base_size = 14) + theme( strip.text = element_text(face = "bold", size = 13), plot.title = element_text(size = 13, face = "bold"), panel.grid.minor = element_blank() ) if (input$log_scale) { p <- p + scale_y_log10(labels = scales::label_number()) } p }) } shinyApp(ui = ui, server = server)