library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # MgSO4·7H2O conversion constant (MW = 246.47 g/mol) MMOL_PER_G <- 1000 / 246.47 # 4.057 mmol per gram of MgSO4·7H2O # ── mrgsolve model ────────────────────────────────────────────────────────── model_code <- ' $PARAM @annotated CL : 4.81 : Typical clearance (L/h) V : 15.6 : Typical volume of distribution (L) KA : 0.317 : IM absorption rate constant (1/h) F1 : 0.862 : IM bioavailability (fraction) BL : 0.85 : Endogenous baseline magnesium (mmol/L) WT : 55 : Body weight (kg) CRCONC : 0.8 : Serum creatinine (mg/dl) H1 : 0.692 : Weight exponent on V H2 : 1.48 : Creatinine exponent on CL $CMT @annotated DEPOT : IM absorption depot (mmol) CENT : Central compartment (mmol) $MAIN double Vi = V * pow(WT / 55.0, H1); double CLi = CL * pow(0.8 / CRCONC, H2); F_DEPOT = F1; $ODE dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CLi / Vi) * CENT; $TABLE double CP_exog = CENT / Vi; double CP_total = CP_exog + BL; $CAPTURE @annotated CP_total : Total serum magnesium (mmol/L) CP_exog : Exogenous drug-derived magnesium (mmol/L) ' mod <- mcode("magnesium_sulfate", 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 = "Magnesium Sulphate PK Simulator — Pre-eclampsia / Eclampsia", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 350, h6("Regimen"), radioButtons("route", NULL, choices = c("IV only" = "iv", "IM only" = "im", "Compare IV vs IM" = "both"), selected = "both" ), hr(), h6("IV Settings"), conditionalPanel("input.route !== 'im'", sliderInput("iv_load_g", "IV Loading Dose (g MgSO\u2084)", min = 4, max = 8, value = 4, step = 1), sliderInput("iv_maint_g_h", "IV Maintenance Rate (g/h)", min = 0.5, max = 2.0, value = 1.0, step = 0.25) ), conditionalPanel("input.route === 'im'", tags$p(style = "color:#6c757d; font-size:12px;", "IV-only settings hidden for IM regimen.") ), hr(), h6("IM Settings"), conditionalPanel("input.route !== 'iv'", sliderInput("im_load_iv_g", "IM Protocol IV Load (g)", min = 2, max = 6, value = 4, step = 1), sliderInput("im_load_im_g", "IM Loading Dose (g MgSO\u2084)", min = 5, max = 15, value = 10, step = 1), sliderInput("im_maint_g", "IM Maintenance Dose (g Q4H)", min = 3, max = 7, value = 5, step = 0.5) ), conditionalPanel("input.route === 'iv'", tags$p(style = "color:#6c757d; font-size:12px;", "IM settings hidden for IV regimen.") ), sliderInput("sim_hours", "Simulation Duration (h)", min = 12, max = 24, value = 20, step = 2), hr(), h6("Patient Characteristics"), sliderInput("wt", "Body Weight (kg)", min = 40, max = 100, value = 55, step = 5), sliderInput("crconc", "Serum Creatinine (mg/dl)", min = 0.4, max = 1.5, value = 0.8, step = 0.1), hr(), checkboxInput("show_therapeutic", "Show Therapeutic Window", value = TRUE), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE) ), # ── Metric cards ──────────────────────────────────────────────────────── tags$p(id = "metrics_note", style = "font-size: 11px; color: #6c757d; margin: 4px 0 0 6px;", textOutput("metrics_label", inline = TRUE) ), layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax")), div(class = "metric-label", "Peak Mg (mmol/L)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Mg at 12h (mmol/L)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUC\u2080\u208b\u00b9\u00b2h (mmol\u00b7h/L)")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "t\u00bd (h)")) ), # ── PK Plot ───────────────────────────────────────────────────────────── card( card_header("Serum Magnesium Concentration-Time Profile"), full_screen = TRUE, plotOutput("pkPlot", height = "500px") ), # ── Info tabs ──────────────────────────────────────────────────────────── navset_card_underline( title = "Drug Information", nav_panel("Model Information", markdown(" ## Magnesium Sulphate (MgSO\u2084) \u2014 Population PK Model **Study:** Salinger et al. (2013), *BJOG* 120:894\u2013900 **Subjects:** 258 pregnant women with pre-eclampsia (India) **Structure:** 1-compartment + IM first-order absorption **Software:** NONMEM 7 (mixed-effects, proportional residual error) ### Population PK Parameters (Final Model) | Parameter | Value | Units | %SE | |-----------|-------|-------|-----| | Clearance (CL) | 4.81 | L/h | 10% | | Volume of distribution (V) | 15.6 | L | 8% | | IM absorption rate (K\u2090) | 0.317 | h\u207b\u00b9 | 16% | | IM bioavailability (F) | 86.2% | \u2014 | 3.2% | | Endogenous baseline (BL) | 0.85 | mmol/L | \u2014 | | Residual CV | 22.9% | \u2014 | \u2014 | ### Covariate Effects - **Weight \u2192 Volume:** V\u1d62 = V \u00d7 (WT\u1d62/55)\u2070\u02b7\u2076\u2079\u00b2 - **Creatinine \u2192 Clearance:** CL\u1d62 = CL \u00d7 (0.8/SCr\u1d62)\u00b9\u02b7\u2074\u2078 (SCr in mg/dl; reference = 0.8 mg/dl = 61 \u00b5mol/L) Reference weight = **55 kg** (median study population) ### Therapeutic Serum Magnesium Levels | Level (mmol/L) | mg/dl | mEq/L | Clinical significance | |----------------|-------|-------|----------------------| | < 2.0 | < 4.8 | < 4 | Potentially sub-therapeutic | | **2.0 \u2013 3.5** | **4.8 \u2013 8.5** | **4 \u2013 7** | **Therapeutic (seizure prophylaxis)** | | 3.5 \u2013 5.0 | 8.5 \u2013 12 | 7 \u2013 10 | Loss of patellar reflexes | | 5.0 \u2013 6.5 | 12 \u2013 15.8 | 10 \u2013 13 | Respiratory paralysis risk | | > 7.5 | > 18 | > 15 | Cardiac conduction changes | *Note: 1 mmol/L \u2248 2.43 mg/dl \u2248 2 mEq/L for Mg\u00b2\u207a* ### Standard MAGPIE Trial Dosing - **IV:** 4 g loading over 20 min \u2192 1 g/h maintenance infusion - **IM:** 4 g IV loading + 10 g IM loading \u2192 5 g IM Q4H ")), nav_panel("References", div(class = "ref-box", tags$h5("\U0001F4DA Primary Reference"), tags$ol( tags$li( tags$strong("Salinger DH, Mundle S, Regi A, Bracken H, Winikoff B, Vicini P, Easterling T. (2013)"), " Magnesium sulphate for prevention of eclampsia: are intramuscular and intravenous regimens equivalent? A population pharmacokinetic study. ", tags$em("BJOG"), " 120:894\u2013900. ", tags$a(href = "https://doi.org/10.1111/1471-0528.12222", target = "_blank", "doi:10.1111/1471-0528.12222") ), tags$li( "The Magpie Trial Collaboration Group. (2002) Do women with pre-eclampsia, and their babies, benefit from magnesium sulphate? The Magpie Trial. ", tags$em("Lancet"), " 359:1877\u201390." ), tags$li( "Lu J, Pfister M, Ferrari P, Chen G, Sheiner L. (2002) Pharmacokinetic-pharmacodynamic modelling of magnesium plasma concentration and blood pressure in preeclampsia. ", tags$em("Clin Pharmacokinet"), " 41:1105\u201313." ), tags$li( "Chuan FS, Charles BG, Boyle RK, Rasiah RL. (2001) Population pharmacokinetics of magnesium in preeclampsia. ", tags$em("Am J Obstet Gynecol"), " 185:593\u20139." ) ), tags$h5("\U0001F48A Therapeutic Context"), tags$ul( tags$li(tags$strong("Class:"), " Anticonvulsant / electrolyte replenisher"), tags$li(tags$strong("Indication:"), " Prevention and treatment of seizures in pre-eclampsia/eclampsia"), tags$li(tags$strong("Routes:"), " IV infusion, deep IM injection (gluteal)"), tags$li(tags$strong("Elimination:"), " Renal filtration (no hepatic metabolism); CL \u2248 GFR"), tags$li(tags$strong("Monitoring:"), " Serum Mg, deep tendon reflexes, respiratory rate (\u226512/min), urine output (\u226525 mL/h)"), tags$li(tags$strong("Antidote:"), " Calcium gluconate 1 g IV (for overdose)") ) )) ), 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_one_route <- function(route_type) { shiny::req(input$wt, input$crconc, input$sim_hours) end_h <- input$sim_hours load_dur_h <- 20 / 60 # 20-min IV bolus if (route_type == "iv") { shiny::req(input$iv_load_g, input$iv_maint_g_h) load_mmol <- input$iv_load_g * MMOL_PER_G maint_rate <- input$iv_maint_g_h * MMOL_PER_G # mmol/h maint_dur <- max(0, end_h - load_dur_h) ev_load <- ev(time = 0, amt = load_mmol, cmt = 2, rate = load_mmol / load_dur_h) ev_maint <- ev(time = load_dur_h, amt = maint_rate * maint_dur, cmt = 2, rate = maint_rate) evs <- c(ev_load, ev_maint) } else { shiny::req(input$im_load_iv_g, input$im_load_im_g, input$im_maint_g) iv_load_mmol <- input$im_load_iv_g * MMOL_PER_G im_load_mmol <- input$im_load_im_g * MMOL_PER_G im_maint_mmol <- input$im_maint_g * MMOL_PER_G n_addl <- max(0L, as.integer(floor((end_h - 4) / 4)) - 1L) ev_iv_load <- ev(time = 0, amt = iv_load_mmol, cmt = 2, rate = iv_load_mmol / load_dur_h) ev_im_load <- ev(time = 0, amt = im_load_mmol, cmt = 1) ev_im_maint <- ev(time = 4, amt = im_maint_mmol, cmt = 1, ii = 4, addl = n_addl) evs <- c(ev_iv_load, ev_im_load, ev_im_maint) } mod %>% param(WT = input$wt, CRCONC = input$crconc) %>% ev(evs) %>% mrgsim(end = end_h, delta = 0.1) %>% as.data.frame() |> dplyr::mutate(route = route_type) } sim_data <- reactive({ shiny::req(input$route) if (input$route == "iv") { sim_one_route("iv") } else if (input$route == "im") { sim_one_route("im") } else { bind_rows(sim_one_route("iv"), sim_one_route("im")) } }) primary_data <- reactive({ d <- sim_data() if (input$route == "both") d |> dplyr::filter(route == "iv") else d }) output$metrics_label <- renderText({ if (input$route == "both") "Metrics shown for IV regimen" else "" }) output$cmax <- renderText({ d <- primary_data() shiny::req(nrow(d) > 0) sprintf("%.2f", max(d$CP_total, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- primary_data() shiny::req(nrow(d) > 0) d12 <- d |> dplyr::filter(abs(time - 12) < 0.15) if (nrow(d12) == 0) return("\u2014") sprintf("%.2f", d12$CP_total[1]) }) output$auc <- renderText({ d <- primary_data() shiny::req(nrow(d) > 0) d_auc <- d |> dplyr::filter(time <= 12) auc <- sum(diff(d_auc$time) * (head(d_auc$CP_total, -1) + tail(d_auc$CP_total, -1)) / 2) sprintf("%.1f", auc) }) output$thalf <- renderText({ shiny::req(input$wt, input$crconc) Vi <- 15.6 * (input$wt / 55)^0.692 CLi <- 4.81 * (0.8 / max(input$crconc, 0.1))^1.48 sprintf("%.1f", log(2) / (CLi / Vi)) }) output$pkPlot <- renderPlot({ d <- sim_data() shiny::req(nrow(d) > 0) d_plot <- if (input$log_scale) d |> dplyr::filter(CP_total > 0.01) else d route_colors <- c("iv" = "#8b5cf6", "im" = "#f59e0b") route_labels <- c("iv" = "IV Regimen", "im" = "IM Regimen") p <- ggplot(d_plot, aes(x = time, y = CP_total, color = route, group = route)) if (input$show_therapeutic) { p <- p + annotate("rect", xmin = -Inf, xmax = Inf, ymin = 2.0, ymax = 3.5, fill = "#10b981", alpha = 0.12) + annotate("rect", xmin = -Inf, xmax = Inf, ymin = 3.5, ymax = 5.0, fill = "#f59e0b", alpha = 0.10) + geom_hline(yintercept = 2.0, linetype = "dashed", color = "#10b981", alpha = 0.8, linewidth = 0.5) + geom_hline(yintercept = 3.5, linetype = "dashed", color = "#f59e0b", alpha = 0.8, linewidth = 0.5) + annotate("text", x = max(d$time, na.rm = TRUE) * 0.98, y = 2.1, label = "Therapeutic \u2265 2.0 mmol/L", hjust = 1, size = 3.2, color = "#10b981") + annotate("text", x = max(d$time, na.rm = TRUE) * 0.98, y = 3.6, label = "Reflex loss \u2265 3.5 mmol/L", hjust = 1, size = 3.2, color = "#f59e0b") } p <- p + geom_line(linewidth = 1.0) + scale_color_manual(values = route_colors, labels = route_labels, breaks = names(route_colors)) + labs( x = "Time (hours)", y = "Serum Magnesium (mmol/L)", title = paste0("MgSO\u2084 PK \u2014 WT: ", input$wt, " kg | SCr: ", input$crconc, " mg/dl"), color = NULL ) + theme_minimal(base_size = 14) + theme(legend.position = "top", legend.text = element_text(size = 12)) if (input$log_scale) p <- p + scale_y_log10() p }) } shinyApp(ui = ui, server = server)