library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ── Eplontersen PopPK/PD Model ── # 2-compartment PK with indirect response PD (TTR inhibition) # Source: Diep et al., Br J Clin Pharmacol 2022;88:5389-5398 # Drug: Eplontersen (WAINUA) — GalNAc3-conjugated ASO for ATTR amyloidosis model_code <- ' $PARAM @annotated TVCL : 24.1 : Clearance (L/h) TVVc : 50.4 : Central volume (L) TVQ : 3.64 : Intercompartmental clearance (L/h) TVVp : 2790 : Peripheral volume (L) KA : 0.217 : Absorption rate constant - arm (1/h) F1 : 1.0 : Bioavailability (SC) BW : 72.1 : Body weight (kg) LBM : 51.6 : Lean body mass (kg) SITE : 0 : Injection site (0=arm, 1=abdomen) BL : 31.4 : Baseline TTR (mg/dL) KOUT : 0.00398 : TTR loss rate constant (1/h) IMAX : 0.970 : Maximum fractional inhibition IC50 : 0.0283 : Half-max inhibitory concentration (ng/mL) $CMT @annotated DEPOT : SC depot (mg) CENT : Central (mg) PERI : Peripheral (mg) TTR : TTR (mg/dL) $MAIN // Covariate-adjusted PK parameters double CLi = TVCL * pow(LBM / 51.6, 1.42); double Vci = TVVc * pow(BW / 72.1, 1.89); double Qi = TVQ * pow(BW / 72.1, 2.53); double Vpi = TVVp * pow(BW / 72.1, 2.73); // Injection site effect on ka double KAi = SITE == 1 ? 0.282 : 0.217; F_DEPOT = F1; // Initialize TTR at baseline TTR_0 = BL; $ODE double CP = CENT / Vci; // ng/mL (dose in mg, volume in L -> ug/L = ng/mL) dxdt_DEPOT = -KAi * DEPOT; dxdt_CENT = KAi * DEPOT - (CLi / Vci) * CENT - (Qi / Vci) * CENT + (Qi / Vpi) * PERI; dxdt_PERI = (Qi / Vci) * CENT - (Qi / Vpi) * PERI; // Indirect response: inhibition of TTR production double KIN = KOUT * BL; double INH = 1.0 - (IMAX * CP) / (IC50 + CP); dxdt_TTR = KIN * INH - KOUT * TTR; $TABLE double CPout = CENT / Vci; double TTRout = TTR; double TTR_pct = ((TTR - BL) / BL) * 100.0; $CAPTURE CPout TTRout TTR_pct ' mod <- mcode("eplontersen", 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 = "Eplontersen (WAINUA) PK/PD Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), selectInput("dose", "Dose (mg)", choices = c(15, 30, 45, 60, 90, 120), selected = 45), selectInput("interval", "Dosing Interval", choices = list("Every 4 weeks (q4w)" = 28, "Every 8 weeks (q8w)" = 56), selected = 28), numericInput("n_doses", "Number of Doses", value = 6, min = 1, max = 12), hr(), h6("Patient Characteristics"), sliderInput("bw", "Body Weight (kg)", min = 50, max = 100, value = 72, step = 1), sliderInput("lbm", "Lean Body Mass (kg)", min = 30, max = 70, value = 52, step = 1), selectInput("site", "Injection Site", choices = list("Arm" = 0, "Abdomen" = 1), selected = 0), hr(), h6("Baseline"), sliderInput("bl_ttr", "Baseline TTR (mg/dL)", min = 15, max = 60, value = 31, step = 1), hr(), checkboxInput("show_pd", "Show TTR Response", value = TRUE), checkboxInput("log_scale", "Log Scale (PK Y-axis)", value = FALSE) ), navset_card_tab( title = "Eplontersen PK/PD 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","AUCτ (ng·h/mL)")), div(class="metric-card metric-info", div(class="metric-value", textOutput("thalf")), div(class="metric-label","t½ (days)")) ), plotOutput("pkPlot", height = "500px"), conditionalPanel( condition = "input.show_pd", br(), layout_columns( col_widths = c(4,4,4), div(class="metric-card metric-success", div(class="metric-value", textOutput("ttr_nadir")), div(class="metric-label","Max TTR Reduction (%)")), div(class="metric-card metric-warning", div(class="metric-value", textOutput("ttr_trough")), div(class="metric-label","TTR at Trough (mg/dL)")), div(class="metric-card metric-primary", div(class="metric-value", textOutput("ttr_pct_trough")), div(class="metric-label","TTR Change at Trough (%)")) ), plotOutput("pdPlot", height = "500px") ) ), nav_panel("Model Information", markdown(" ## Eplontersen (WAINUA) — Population PK/PD Model ### Drug Overview **Eplontersen** (brand name WAINUA) is a GalNAc3-conjugated antisense oligonucleotide (ASO) targeting transthyretin (TTR) mRNA for the treatment of transthyretin-mediated amyloidosis (ATTR). It is the conjugated form of inotersen (Tegsedi) with ~27-fold higher potency, enabling lower doses and less frequent administration. ### PK Model **Two-compartment model** with first-order absorption and linear elimination: | Parameter | Estimate | Description | |-----------|----------|-------------| | CL | 24.1 L/h | Clearance (LBM covariate, power 1.42) | | Vc | 50.4 L | Central volume (BW covariate, power 1.89) | | Q | 3.64 L/h | Intercompartmental clearance (BW, power 2.53) | | Vp | 2790 L | Peripheral volume (BW, power 2.73) | | ka (arm) | 0.217 h⁻¹ | Absorption rate — arm injection | | ka (abdomen) | 0.282 h⁻¹ | Absorption rate — abdomen injection (29.6% higher) | | t½ | 25.5 days | Terminal elimination half-life | **Covariates:** Lean body mass on CL; body weight on Vc, Q, Vp. Centered on LBM=51.6 kg, BW=72.1 kg. ### PD Model **Indirect response model** — inhibition of TTR production rate: | Parameter | Estimate | Description | |-----------|----------|-------------| | BL | 31.4 mg/dL | Baseline serum TTR | | kout | 0.00398 h⁻¹ | TTR loss rate constant | | Imax | 0.970 | Maximum fractional inhibition | | IC50 | 0.0283 ng/mL | Half-max inhibitory concentration | ### Key Findings - At **45 mg q4w**, median TTR reduction **>88%** at trough - Body weight affects PK exposure but NOT PD response at clinical doses - Injection site (abdomen vs arm) causes ~18% higher Cmax but similar TTR reduction - IC50 is **318-fold lower** than parent compound inotersen (0.0283 vs 9.07 ng/mL) - No dose adjustment needed for weight or injection site ### Clinical Context - **Indication:** Hereditary transthyretin-mediated amyloidosis (hATTR) polyneuropathy - **Recommended dose:** 45 mg SC every 4 weeks - **FDA approved:** December 2023 as WAINUA - **Mechanism:** Targets TTR pre-mRNA → RNase H1-mediated degradation → prevents both mutant and wild-type TTR production ")), nav_panel("References", markdown(" ### Primary Reference Diep JK, Yu RZ, Viney NJ, Schneider E, Guo S, Henry S, Monia B, Geary R, Wang Y. **Population pharmacokinetic/pharmacodynamic modelling of eplontersen, an antisense oligonucleotide in development for transthyretin amyloidosis.** *Br J Clin Pharmacol.* 2022;88(12):5389-5398. [DOI: 10.1111/bcp.15468](https://doi.org/10.1111/bcp.15468) | [PubMed](https://pubmed.ncbi.nlm.nih.gov/35869634/) ### FDA Label - [WAINUA (eplontersen) Prescribing Information](https://www.accessdata.fda.gov/drugsatfda_docs/label/2023/217388s000lbl.pdf) ### Related References - Viney NJ et al. Ligand conjugated antisense oligonucleotide for the treatment of transthyretin amyloidosis: preclinical and phase 1 data. *ESC Heart Fail.* 2021;8(1):652-661. - Benson MD et al. Inotersen treatment for patients with hereditary transthyretin amyloidosis. *N Engl J Med.* 2018;379(1):22-31. - Yu RZ et al. Population pharmacokinetic-pharmacodynamic modeling of inotersen. *Nucleic Acid Ther.* 2020;30(3):153-163. ### ClinicalTrials.gov - [NCT03728634](https://clinicaltrials.gov/study/NCT03728634) — Phase 1 dose escalation (Western subjects) - [NCT04302064](https://clinicaltrials.gov/study/NCT04302064) — Phase 1 single ascending dose (Japanese subjects) - [NCT04136184](https://clinicaltrials.gov/study/NCT04136184) — Phase 3 NEURO-TTRansform (hATTR polyneuropathy) - [NCT04136171](https://clinicaltrials.gov/study/NCT04136171) — Phase 3 CARDIO-TTRansform (ATTR cardiomyopathy) ")) ), # ── 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"), " \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({ dose_mg <- as.numeric(input$dose) interval_days <- as.numeric(input$interval) n_doses <- input$n_doses bw <- input$bw lbm <- input$lbm site <- as.numeric(input$site) bl_ttr <- input$bl_ttr # Convert dose to mg (* 1000 for ug then /1000 = mg; mrgsolve works in mass units) # Concentrations will be in ng/mL since dose in mg and volume in L -> ug/L = ng/mL dose_amt <- dose_mg * 1e6 # Convert mg to ng to get ng/mL concentrations # Actually: dose in mg, V in L -> mg/L = ug/mL = 1000 ng/mL # We need ng/mL output. So dose in mg, V in L gives mg/L. Multiply by 1000 for ng/mL. # OR: put dose as ug (mg * 1000), V in L -> ug/L = ng/mL. Let's do that. dose_amt <- dose_mg * 1000 # mg -> ug, so CENT/V = ug/L = ng/mL interval_h <- interval_days * 24 total_time <- interval_h * n_doses + interval_h # One extra interval after last dose # Build event schedule ev <- ev(amt = dose_amt, cmt = 1, ii = interval_h, addl = n_doses - 1) out <- mod %>% param(BW = bw, LBM = lbm, SITE = site, BL = bl_ttr) %>% ev(ev) %>% mrgsim(end = total_time, delta = 1) %>% as.data.frame() out }) # PK metrics at steady state (last dosing interval) pk_metrics <- reactive({ d <- sim_data() interval_h <- as.numeric(input$interval) * 24 n_doses <- input$n_doses last_dose_time <- (n_doses - 1) * interval_h ss <- d %>% filter(time >= last_dose_time & time <= last_dose_time + interval_h) cmax <- max(ss$CPout, na.rm = TRUE) ctrough <- ss$CPout[nrow(ss)] auc <- sum(diff(ss$time) * (head(ss$CPout, -1) + tail(ss$CPout, -1)) / 2) # Terminal half-life from model parameters bw <- input$bw lbm <- input$lbm CLi <- 24.1 * (lbm / 51.6)^1.42 Vci <- 50.4 * (bw / 72.1)^1.89 Qi <- 3.64 * (bw / 72.1)^2.53 Vpi <- 2790 * (bw / 72.1)^2.73 # Terminal half-life for 2-CMT: use beta phase a <- CLi/Vci + Qi/Vci + Qi/Vpi b <- (CLi/Vci) * (Qi/Vpi) beta <- (a - sqrt(a^2 - 4*b)) / 2 thalf_h <- log(2) / beta thalf_days <- thalf_h / 24 list(cmax = cmax, ctrough = ctrough, auc = auc, thalf = thalf_days) }) # PD metrics pd_metrics <- reactive({ d <- sim_data() bl <- input$bl_ttr interval_h <- as.numeric(input$interval) * 24 n_doses <- input$n_doses last_dose_time <- (n_doses - 1) * interval_h ss <- d %>% filter(time >= last_dose_time & time <= last_dose_time + interval_h) ttr_min <- min(ss$TTRout, na.rm = TRUE) ttr_trough <- ss$TTRout[nrow(ss)] nadir_pct <- ((ttr_min - bl) / bl) * 100 trough_pct <- ((ttr_trough - bl) / bl) * 100 list(ttr_nadir_pct = nadir_pct, ttr_trough = ttr_trough, ttr_trough_pct = trough_pct) }) output$cmax <- renderText(sprintf("%.1f", pk_metrics()$cmax)) output$ctrough <- renderText(sprintf("%.3f", pk_metrics()$ctrough)) output$auc <- renderText(sprintf("%.0f", pk_metrics()$auc)) output$thalf <- renderText(sprintf("%.1f", pk_metrics()$thalf)) output$ttr_nadir <- renderText(sprintf("%.1f%%", pd_metrics()$ttr_nadir_pct)) output$ttr_trough <- renderText(sprintf("%.1f", pd_metrics()$ttr_trough)) output$ttr_pct_trough <- renderText(sprintf("%.1f%%", pd_metrics()$ttr_trough_pct)) output$pkPlot <- renderPlot({ d <- sim_data() d$time_days <- d$time / 24 p <- ggplot(d, aes(x = time_days, y = CPout)) + geom_line(color = "#8b5cf6", linewidth = 1.2) + labs( title = "Eplontersen Plasma Concentration-Time Profile", x = "Time (days)", y = "Plasma Concentration (ng/mL)" ) + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold", size = 16), panel.grid.minor = element_blank() ) if (input$log_scale) { p <- p + scale_y_log10() } p }) output$pdPlot <- renderPlot({ d <- sim_data() d$time_days <- d$time / 24 bl <- input$bl_ttr ggplot(d, aes(x = time_days, y = TTRout)) + geom_hline(yintercept = bl, linetype = "dashed", color = "grey50", linewidth = 0.5) + annotate("text", x = 0, y = bl + 1, label = "Baseline", hjust = 0, color = "grey50", size = 3.5) + # Shade target zone (>80% reduction) annotate("rect", xmin = -Inf, xmax = Inf, ymin = 0, ymax = bl * 0.20, fill = "#10b981", alpha = 0.1) + annotate("text", x = max(d$time_days) * 0.95, y = bl * 0.10, label = ">80% reduction", hjust = 1, color = "#10b981", size = 3, fontface = "italic") + geom_line(color = "#ef4444", linewidth = 1.2) + labs( title = "Serum TTR Concentration Over Time", x = "Time (days)", y = "Serum TTR (mg/dL)" ) + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold", size = 16), panel.grid.minor = element_blank() ) + coord_cartesian(ylim = c(0, bl * 1.1)) }) } shinyApp(ui, server)