library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) library(tidyr) # ── Combined Intranasal PK Model: Nalmefene + Naloxone ── # 2-compartment with parallel zero/first-order absorption + lag time # Source: Laffont et al. Front Psychiatry 2024; 15:1399803 model_code <- ' $PARAM @annotated // Nalmefene IN parameters (Table 1) NMF_CL : 63.7 : Nalmefene CL/F (L/h) NMF_Vc : 15.2 : Nalmefene Vc/F (L) NMF_Q : 81.3 : Nalmefene Q/F (L/h) NMF_Vp : 522 : Nalmefene Vp/F (L) NMF_KA : 0.497 : Nalmefene first-order Ka (1/h) NMF_D2 : 0.302 : Nalmefene zero-order duration (h) NMF_FK0 : 0.0485 : Nalmefene fraction zero-order NMF_LAG : 0.0615 : Nalmefene absorption lag (h) // Naloxone IN parameters (Table 2) NLX_CL : 396 : Naloxone CL/F (L/h) NLX_Vc : 65.7 : Naloxone Vc/F (L) NLX_Q : 284 : Naloxone Q/F (L/h) NLX_Vp : 102 : Naloxone Vp/F (L) NLX_KA : 0.998 : Naloxone first-order Ka (1/h) NLX_D2 : 0.689 : Naloxone zero-order duration (h) NLX_FK0 : 0.183 : Naloxone fraction zero-order NLX_LAG : 0.0717 : Naloxone absorption lag (h) $CMT @annotated NMF_DEPOT : Nalmefene first-order depot NMF_CENT : Nalmefene central NMF_PERI : Nalmefene peripheral NLX_DEPOT : Naloxone first-order depot NLX_CENT : Naloxone central NLX_PERI : Naloxone peripheral $MAIN F_NMF_DEPOT = (1.0 - NMF_FK0); ALAG_NMF_DEPOT = NMF_LAG; F_NMF_CENT = NMF_FK0; D_NMF_CENT = NMF_D2; F_NLX_DEPOT = (1.0 - NLX_FK0); ALAG_NLX_DEPOT = NLX_LAG; F_NLX_CENT = NLX_FK0; D_NLX_CENT = NLX_D2; $ODE dxdt_NMF_DEPOT = -NMF_KA * NMF_DEPOT; dxdt_NMF_CENT = NMF_KA * NMF_DEPOT - (NMF_CL / NMF_Vc) * NMF_CENT - (NMF_Q / NMF_Vc) * NMF_CENT + (NMF_Q / NMF_Vp) * NMF_PERI; dxdt_NMF_PERI = (NMF_Q / NMF_Vc) * NMF_CENT - (NMF_Q / NMF_Vp) * NMF_PERI; dxdt_NLX_DEPOT = -NLX_KA * NLX_DEPOT; dxdt_NLX_CENT = NLX_KA * NLX_DEPOT - (NLX_CL / NLX_Vc) * NLX_CENT - (NLX_Q / NLX_Vc) * NLX_CENT + (NLX_Q / NLX_Vp) * NLX_PERI; dxdt_NLX_PERI = (NLX_Q / NLX_Vc) * NLX_CENT - (NLX_Q / NLX_Vp) * NLX_PERI; $TABLE double CP_NMF = NMF_CENT / NMF_Vc * 1000.0; double CP_NLX = NLX_CENT / NLX_Vc * 1000.0; $CAPTURE CP_NMF CP_NLX ' mod <- mcode("nmf_nlx_pk", model_code) # ── Cardiac arrest simulation data from Tables 3 & 4 ── ca_data <- bind_rows( # Chronic opioid users (Table 3) data.frame(pop = "Chronic Opioid Users", opioid = "Fentanyl 1.63 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(52.1, 29.5, 19.2, 10.5, 6.6, 3.8, 2.2, 0.35), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), data.frame(pop = "Chronic Opioid Users", opioid = "Fentanyl 2.97 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(77.9, 54.2, 47.1, 31.8, 22.6, 17.0, 11.6, 3.8), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), data.frame(pop = "Chronic Opioid Users", opioid = "Carfentanil 0.012 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(59.2, 36.6, 27.5, 15.8, 10.4, 6.8, 3.8, 0.70), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), data.frame(pop = "Chronic Opioid Users", opioid = "Carfentanil 0.022 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(90.2, 73.7, 70.6, 57.0, 46.6, 38.9, 32.0, 14.5), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), # Opioid naive (Table 4) data.frame(pop = "Opioid Na\u00efve", opioid = "Fentanyl 1.63 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(74.7, 48.3, 39.1, 23.8, 16.3, 11.8, 7.6, 1.7), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), data.frame(pop = "Opioid Na\u00efve", opioid = "Fentanyl 2.97 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(90.1, 71.8, 67.6, 53.0, 42.4, 33.4, 25.7, 10.1), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), data.frame(pop = "Opioid Na\u00efve", opioid = "Carfentanil 0.012 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(75.9, 56.6, 50.5, 37.1, 26.8, 20.6, 14.9, 5.8), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE), data.frame(pop = "Opioid Na\u00efve", opioid = "Carfentanil 0.022 mg", treatment = c("No Treatment","2mg IM Naloxone","1\u00d74mg IN Naloxone","2\u00d74mg IN Naloxone", "3\u00d74mg IN Naloxone","4\u00d74mg IN Naloxone","1\u00d73mg IN Nalmefene","2\u00d73mg IN Nalmefene"), rate = c(96.4, 86.4, 85.8, 77.1, 69.8, 63.5, 55.0, 36.8), drug = c("None","Naloxone","Naloxone","Naloxone","Naloxone","Naloxone","Nalmefene","Nalmefene"), stringsAsFactors = FALSE) ) # ── 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; } .nmf-card .metric-value { color: #2563eb; } .nlx-card .metric-value { color: #dc2626; } .ratio-card .metric-value { color: #059669; } .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; } .key-finding { background: #fef3c7; border-left: 4px solid #f59e0b; padding: 12px 16px; border-radius: 4px; margin: 10px 0; } ") # ── UI ── ui <- page_sidebar( title = "IN Nalmefene vs IN Naloxone \u2014 Opioid Overdose Translational Model", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("PK Comparison"), sliderInput("nmf_doses", "Nalmefene Doses (3 mg HCl each)", min = 1, max = 2, value = 1, step = 1), sliderInput("nlx_doses", "Naloxone Doses (4 mg HCl each)", min = 1, max = 4, value = 1, step = 1), sliderInput("sim_time", "Simulation Duration (h)", min = 2, max = 24, value = 8, step = 1), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), hr(), h6("Cardiac Arrest Simulations"), selectInput("ca_opioid", "Opioid Scenario", choices = c("Fentanyl 1.63 mg", "Fentanyl 2.97 mg", "Carfentanil 0.012 mg", "Carfentanil 0.022 mg")), selectInput("ca_pop", "Population", choices = c("Chronic Opioid Users", "Opioid Na\u00efve")) ), navset_card_tab( title = "Nalmefene vs Naloxone \u2014 Translational PK-PD Comparison", full_screen = TRUE, nav_panel("PK Comparison", layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card nmf-card", div(class = "metric-value", textOutput("nmf_cmax")), div(class = "metric-label", "Nalmefene Cmax (ng/mL)")), div(class = "metric-card nlx-card", div(class = "metric-value", textOutput("nlx_cmax")), div(class = "metric-label", "Naloxone Cmax (ng/mL)")), div(class = "metric-card nmf-card", div(class = "metric-value", textOutput("nmf_tmax")), div(class = "metric-label", "Nalmefene Tmax (h)")), div(class = "metric-card nlx-card", div(class = "metric-value", textOutput("nlx_tmax")), div(class = "metric-label", "Naloxone Tmax (h)")) ), layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card nmf-card", div(class = "metric-value", textOutput("nmf_thalf")), div(class = "metric-label", "Nalmefene t\u00bd (h)")), div(class = "metric-card nlx-card", div(class = "metric-value", textOutput("nlx_thalf")), div(class = "metric-label", "Naloxone t\u00bd (h)")), div(class = "metric-card nmf-card", div(class = "metric-value", textOutput("nmf_auc1h")), div(class = "metric-label", "Nalmefene AUC\u2080\u208b\u2081h")), div(class = "metric-card nlx-card", div(class = "metric-value", textOutput("nlx_auc1h")), div(class = "metric-label", "Naloxone AUC\u2080\u208b\u2081h")) ), plotOutput("pkPlot", height = "500px") ), nav_panel("Cardiac Arrest", div(class = "key-finding", tags$strong("Key Finding: "), "A single dose of IN nalmefene (3 mg) produces cardiac arrest ", "reductions comparable to 4 simultaneous doses of IN naloxone (16 mg total). ", "Data from 2,000 virtual patients using a validated translational model." ), plotOutput("caPlot", height = "500px") ), nav_panel("Model Information", markdown(" ## Translational Model Overview This simulator implements population pharmacokinetic (PopPK) models for **intranasal nalmefene** and **intranasal naloxone** developed by Laffont et al. (2024), embedded within the validated translational model framework of Mann et al. (2022). ### Model Structure Both drugs follow **2-compartment disposition** with **parallel zero-order and first-order intranasal absorption** and a lag time on the first-order process. ### Nalmefene IN Parameters (3 mg HCl = 2.7 mg free base) | Parameter | Value | Description | |-----------|-------|-------------| | CL/F | 63.7 L/h | Apparent clearance | | Vc/F | 15.2 L | Apparent central volume | | Q/F | 81.3 L/h | Intercompartmental clearance | | Vp/F | 522 L | Peripheral volume | | Ka | 0.497 h\u207b\u00b9 | First-order absorption rate | | D2 | 0.302 h | Zero-order absorption duration | | FK0 | 0.0485 | Fraction zero-order absorption | | ALAG | 0.0615 h | Lag time | | t\u00bd | ~7\u201311 h | Terminal half-life | ### Naloxone IN Parameters (4 mg HCl) | Parameter | Value | Description | |-----------|-------|-------------| | CL/F | 396 L/h | Apparent clearance | | Vc/F | 65.7 L | Apparent central volume | | Q/F | 284 L/h | Intercompartmental clearance (fixed) | | Vp/F | 102 L | Peripheral volume (fixed) | | Ka | 0.998 h\u207b\u00b9 | First-order absorption rate | | D2 | 0.689 h | Zero-order absorption duration | | FK0 | 0.183 | Fraction zero-order absorption | | ALAG | 0.0717 h | Lag time | | t\u00bd | ~2 h | Terminal half-life | ### Key Pharmacological Differences - **Nalmefene** has higher \u00b5-opioid receptor affinity (lower Kd) than naloxone - **Nalmefene** achieves higher early plasma concentrations (faster onset) - **Nalmefene** has a ~5\u00d7 longer half-life (reduced risk of renarcotization) - Plasma-to-brain equilibration t\u00bd \u2248 6.5 min (ke0 = 0.001774 s\u207b\u00b9) for both ### Cardiac Arrest Simulations - 2,000 virtual patients per scenario (bootstrapped 2,500\u00d7) - Cardiac arrest defined as total blood flow \u2264 0.01 L/min - Antagonist administered 1 min after ventilation drops to 40% of baseline - Fentanyl doses (1.63 mg, 2.97 mg) based on fatal overdose postmortem data ")), nav_panel("References", div(class = "ref-box", tags$h5("Key References"), tags$ol( tags$li("Laffont CM, Purohit P, Delcamp N, et al. (2024) ", tags$em("Comparison of intranasal naloxone and intranasal nalmefene in a translational model assessing the impact of synthetic opioid overdose on respiratory depression and cardiac arrest. "), "Front Psychiatry 15:1399803. ", tags$a(href = "https://doi.org/10.3389/fpsyt.2024.1399803", target = "_blank", "DOI")), tags$li("Mann SP, et al. (2022) ", tags$em("A translational model to predict the effectiveness of naloxone in reversing opioid overdose. "), "Clin Pharmacol Ther 112:1059\u20131070."), tags$li("Crystal R, et al. (2024) ", tags$em("Pharmacokinetic properties of an FDA-approved intranasal nalmefene formulation. "), "Clin Pharmacol Drug Dev 13:167\u2013177."), tags$li("Ellison T, et al. (2024) Comparison of IN nalmefene and IN naloxone in reversing remifentanil-induced respiratory depression."), tags$li("Yassen A, et al. Naloxone pharmacokinetics. (Parameters for Q/F and Vp/F in naloxone model.)") ), tags$h5("Clinical Context"), tags$ul( tags$li(tags$strong("Nalmefene (OPVEE\u00ae):"), " 3 mg IN nalmefene HCl, FDA-approved 2023 for opioid overdose"), tags$li(tags$strong("Naloxone (Narcan\u00ae):"), " 4 mg IN naloxone HCl, FDA-approved 2015 (OTC 2023)"), tags$li(tags$strong("Context:"), " >90% of opioid overdose deaths linked to synthetic opioids (fentanyl)"), tags$li(tags$strong("EC50 (nalmefene):"), " 1.5 ng/mL (FDA label, time to EC50 = 4 min)"), tags$li(tags$strong("Funded by:"), " Indivior Inc. (acquired Opiant Pharmaceuticals)") ) )) ), # 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 ── server <- function(input, output, session) { sim_data <- reactive({ dose_nmf <- 3 * input$nmf_doses dose_nlx <- 4 * input$nlx_doses # Build events: first-order (bolus into depot) + zero-order (rate=-2 into central) ev_nmf <- ev(amt = dose_nmf, cmt = 1, time = 0) + ev(amt = dose_nmf, cmt = 2, rate = -2, time = 0) ev_nlx <- ev(amt = dose_nlx, cmt = 4, time = 0) + ev(amt = dose_nlx, cmt = 5, rate = -2, time = 0) all_ev <- ev_nmf + ev_nlx mod %>% ev(all_ev) %>% mrgsim(end = input$sim_time, delta = 0.01) %>% as.data.frame() %>% mutate(time_h = time, time_min = time * 60) }) # ── Metric Computations ── nmf_metrics <- reactive({ d <- sim_data() cmax <- max(d$CP_NMF, na.rm = TRUE) tmax <- d$time_h[which.max(d$CP_NMF)] # AUC 0-1h d1h <- d %>% filter(time_h <= 1) auc1h <- sum(diff(d1h$time_h) * (head(d1h$CP_NMF, -1) + tail(d1h$CP_NMF, -1)) / 2) # Terminal half-life from beta eigenvalue k10 <- 63.7 / 15.2; k12 <- 81.3 / 15.2; k21 <- 81.3 / 522 s <- k10 + k12 + k21 beta <- (s - sqrt(s^2 - 4 * k10 * k21)) / 2 thalf <- 0.693 / beta list(cmax = cmax, tmax = tmax, auc1h = auc1h, thalf = thalf) }) nlx_metrics <- reactive({ d <- sim_data() cmax <- max(d$CP_NLX, na.rm = TRUE) tmax <- d$time_h[which.max(d$CP_NLX)] d1h <- d %>% filter(time_h <= 1) auc1h <- sum(diff(d1h$time_h) * (head(d1h$CP_NLX, -1) + tail(d1h$CP_NLX, -1)) / 2) k10 <- 396 / 65.7; k12 <- 284 / 65.7; k21 <- 284 / 102 s <- k10 + k12 + k21 beta <- (s - sqrt(s^2 - 4 * k10 * k21)) / 2 thalf <- 0.693 / beta list(cmax = cmax, tmax = tmax, auc1h = auc1h, thalf = thalf) }) output$nmf_cmax <- renderText(sprintf("%.2f", nmf_metrics()$cmax)) output$nmf_tmax <- renderText(sprintf("%.2f", nmf_metrics()$tmax)) output$nmf_thalf <- renderText(sprintf("%.1f", nmf_metrics()$thalf)) output$nmf_auc1h <- renderText(sprintf("%.2f", nmf_metrics()$auc1h)) output$nlx_cmax <- renderText(sprintf("%.2f", nlx_metrics()$cmax)) output$nlx_tmax <- renderText(sprintf("%.2f", nlx_metrics()$tmax)) output$nlx_thalf <- renderText(sprintf("%.2f", nlx_metrics()$thalf)) output$nlx_auc1h <- renderText(sprintf("%.2f", nlx_metrics()$auc1h)) # ── PK Comparison Plot ── output$pkPlot <- renderPlot({ d <- sim_data() d_long <- d %>% select(time_h, CP_NMF, CP_NLX) %>% pivot_longer(cols = c(CP_NMF, CP_NLX), names_to = "Drug", values_to = "Conc") %>% mutate(Drug = ifelse(Drug == "CP_NMF", paste0("Nalmefene ", input$nmf_doses, "\u00d73 mg IN"), paste0("Naloxone ", input$nlx_doses, "\u00d74 mg IN"))) p <- ggplot(d_long, aes(x = time_h, y = Conc, color = Drug)) + geom_line(linewidth = 1.2) + scale_color_manual(values = c( setNames("#2563eb", paste0("Nalmefene ", input$nmf_doses, "\u00d73 mg IN")), setNames("#dc2626", paste0("Naloxone ", input$nlx_doses, "\u00d74 mg IN")) )) + labs( x = "Time (hours)", y = "Plasma Concentration (ng/mL)", title = paste0("Intranasal PK Comparison: Nalmefene ", input$nmf_doses, "\u00d73 mg vs Naloxone ", input$nlx_doses, "\u00d74 mg"), subtitle = "Population PK models from Laffont et al. (2024)", color = NULL ) + theme_minimal(base_size = 14) + theme( legend.position = "top", legend.text = element_text(size = 12), plot.title = element_text(face = "bold"), plot.subtitle = element_text(color = "gray50") ) if (input$log_scale) { p <- p + scale_y_log10() } p }) # ── Cardiac Arrest Bar Chart ── output$caPlot <- renderPlot({ d <- ca_data %>% filter(pop == input$ca_pop, opioid == input$ca_opioid) %>% mutate(treatment = factor(treatment, levels = rev(treatment))) ggplot(d, aes(x = treatment, y = rate, fill = drug)) + geom_col(width = 0.7) + geom_text(aes(label = paste0(rate, "%")), hjust = -0.1, size = 4, fontface = "bold") + coord_flip(ylim = c(0, max(d$rate) * 1.15)) + scale_fill_manual(values = c("None" = "#6b7280", "Naloxone" = "#dc2626", "Nalmefene" = "#2563eb")) + labs( x = NULL, y = "Cardiac Arrest Rate (%)", title = paste0("Simulated Cardiac Arrest: ", input$ca_opioid, " IV Overdose"), subtitle = paste0("Population: ", input$ca_pop, " | N = 2,000 virtual patients"), fill = NULL ) + theme_minimal(base_size = 14) + theme( legend.position = "top", plot.title = element_text(face = "bold"), plot.subtitle = element_text(color = "gray50"), panel.grid.major.y = element_blank() ) }) } shinyApp(ui = ui, server = server)