library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ── mrgsolve model ───────────────────────────────────────────────────────────── # 2-compartment IV continuous infusion model # Source: Jonckheere et al. 2019, Antimicrob Agents Chemother 63:e01655-19 # All parameters allometrically scaled to 70 kg reference body weight # CL_renal driven by estimated creatinine clearance (eCrCL, Cockcroft-Gault) model_code <- " $PARAM @annotated THETA1 : 2.29 : CL_renal coefficient (L/h/70kg) THETA2 : 0.943 : CL_renal eCrCL power exponent CLOTHER : 0.795 : Non-renal clearance (L/h/70kg) V1 : 10.7 : Central volume of distribution (L/70kg) V2 : 12.2 : Peripheral volume of distribution (L/70kg) Q : 11.0 : Intercompartmental clearance (L/h/70kg) WT : 70 : Body weight (kg) ECRCL : 60 : Estimated creatinine clearance (mL/min) $CMT @annotated CENT : Central compartment (mg) PERIPH : Peripheral compartment (mg) $MAIN // Allometric weight scaling (reference = 70 kg, exponent = 1) double WTSCALE = WT / 70.0; // Renal clearance with Cockcroft-Gault eCrCL power covariate double CLrenal_i = THETA1 * pow(ECRCL / 60.0, THETA2) * WTSCALE; double CLi = CLrenal_i + CLOTHER * WTSCALE; double V1i = V1 * WTSCALE; double V2i = V2 * WTSCALE; double Qi = Q * WTSCALE; $ODE // mrgsolve adds zero-order infusion rate to CENT automatically dxdt_CENT = -(CLi / V1i) * CENT - (Qi / V1i) * CENT + (Qi / V2i) * PERIPH; dxdt_PERIPH = (Qi / V1i) * CENT - (Qi / V2i) * PERIPH; $TABLE double CP = CENT / V1i; // plasma concentration (mg/L) $CAPTURE CP CLi V1i " mod <- mcode("cefepime_jonckheere2019", 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; margin-top: 4px; } .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 = "Cefepime PK Simulator — ICU Continuous Infusion", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing (Continuous IV Infusion)"), sliderInput("dose_g", "Daily Dose (g/day)", min = 0.5, max = 6, value = 1.5, step = 0.5), numericInput("n_days", "Duration (days)", value = 3, min = 1, max = 7), hr(), h6("Patient Characteristics"), sliderInput("wt", "Weight (kg)", min = 40, max = 120, value = 70, step = 5), sliderInput("ecrcl", "eCrCL (mL/min)", min = 10, max = 150, value = 60, step = 5), hr(), h6("PK/PD Target"), selectInput("mic", "Pathogen MIC (mg/L)", choices = c("0.25" = 0.25, "0.5" = 0.5, "1" = 1, "2" = 2, "4" = 4, "8" = 8), selected = 1 ), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE) ), navset_card_underline( title = "Cefepime IV Continuous Infusion 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("css")), div(class = "metric-label", "Predicted Css (mg/L)") ), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("tmic")), div(class = "metric-label", "T > MIC (%)") ), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc24")), div(class = "metric-label", "AUC (mg·h/L per day)") ), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "Terminal t½ (h)") ) ), card( full_screen = TRUE, plotOutput("pkPlot", height = "500px") ) ), # ── Model Information tab ───────────────────────────────────────────────── nav_panel("Model Information", card( card_body( markdown(" ## Cefepime — 2-Compartment IV Population PK Model **Source:** Jonckheere et al. (2019). *Antimicrobial Agents and Chemotherapy* 63(9):e01655-19. **Fitting method:** NONMEM FOCE-I **Population:** 21 critically ill ICU patients; combined dataset (n = 2 studies) --- ### Population PK Parameter Estimates (Reference: 70 kg, eCrCL = 60 mL/min) | Parameter | Estimate | Units | RSE (%) | Interpretation | |-----------|----------|-------|---------|----------------| | θ₁ (CL_renal coeff.) | 2.29 | L/h/70kg | 5.4 | Renal CL at reference eCrCL | | θ₂ (eCrCL exponent) | 0.943 | — | 9.6 | Power relationship with eCrCL | | CL_other | 0.795 | L/h/70kg | 9.0 | Non-renal clearance | | V₁ (central) | 10.7 | L/70kg | 8.1 | Central volume | | V₂ (peripheral) | 12.2 | L/70kg | 7.2 | Peripheral volume | | Q₂ | 11.0 | L/h/70kg | 14 | Intercompartmental clearance | **Total CL** (70 kg, eCrCL = 60 mL/min) ≈ 3.09 L/h --- ### Covariate Model ``` CL_renal = 2.29 × (eCrCL / 60)^0.943 × (WT / 70) CL_total = CL_renal + CL_other × (WT / 70) ``` All PK parameters allometrically scaled to body weight (exponent = 1 for L parameters, 1 for L/h parameters per allometric theory). --- ### Between-Subject Variability (BSV, CV%) | Parameter | BSV (CV%) | |-----------|-----------| | CL_renal | 24.6% | | V₁ | 45.7% | | CL_other | 69.4% | --- ### Therapeutic Drug Monitoring - **TCI target:** 16 mg/L (continuous infusion steady state) - **Therapeutic range:** 11.2–20.8 mg/L (±30% around target) - **Toxicity threshold:** >35 mg/L (neurotoxicity risk) - **PK-PD index:** T > MIC (time-dependent killing) - **fT > MIC thresholds:** ≥50–70% for bacteriostatic/bactericidal effect ") ) ) ), # ── References tab ──────────────────────────────────────────────────────── nav_panel("References", card( card_body( div(class = "ref-box", tags$h5("📚 Key References"), tags$ol( tags$li( "Jonckheere S et al. (2019). Target-Controlled Infusion of Cefepime in Critically Ill Patients. ", tags$em("Antimicrobial Agents and Chemotherapy"), " 63(9):e01655-19. ", tags$a(href = "https://doi.org/10.1128/AAC.01655-19", target = "_blank", "DOI") ), tags$li( "Jonckheere S et al. (2018). Development and external evaluation of a population pharmacokinetic model for cefepime dosing optimization. ", tags$em("J Antimicrob Chemother") ), tags$li( "Roberts JA et al. (2014). DALI study — Defining antibiotic levels in intensive care unit patients: are current beta-lactam antibiotic doses sufficient for critically ill patients? ", tags$em("Clin Infect Dis"), " 58(8):1072-1083." ), tags$li( "Tam VH et al. (2003). Pharmacodynamics of cefepime in patients with Gram-negative infections. ", tags$em("J Antimicrob Chemother") ) ), tags$h5("💊 Drug Context"), tags$ul( tags$li(tags$strong("Drug class:"), " 4th-generation cephalosporin (beta-lactam)"), tags$li(tags$strong("Spectrum:"), " Broad-spectrum Gram-negative (Enterobacterales, Pseudomonas aeruginosa), limited Gram-positive"), tags$li(tags$strong("Indications:"), " Febrile neutropenia, pneumonia, UTI, sepsis in ICU patients"), tags$li(tags$strong("Renal elimination:"), " ~70–80% renally excreted unchanged; dose adjustment required for renal impairment"), tags$li(tags$strong("EUCAST breakpoints:"), " Enterobacterales S ≤1 mg/L; Pseudomonas S ≤8 mg/L"), tags$li(tags$strong("Toxicity concern:"), " Cefepime-induced neurotoxicity (CEFN) at unbound trough >22 mg/L") ) ) ) ) ) ), # ── 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" ), " • Cefepime ICU PopPK Simulator (Jonckheere 2019)", br(), tags$span( style = "font-size: 10px;", "For research and educational purposes only. Not for clinical decision-making." ) ) ) # ── Server ────────────────────────────────────────────────────────────────────── server <- function(input, output, session) { # thematic removed — not available on shinyapps.io # ── Reactive simulation ──────────────────────────────────────────────────── sim_data <- reactive({ shiny::req(input$dose_g, input$n_days, input$wt, input$ecrcl) inf_rate_mg_h <- input$dose_g * 1000 / 24 # mg/h total_mg <- input$dose_g * 1000 * input$n_days # total mg over full duration ev1 <- ev(amt = total_mg, rate = inf_rate_mg_h, cmt = 1, time = 0) mod |> param(WT = input$wt, ECRCL = input$ecrcl) |> ev(ev1) |> mrgsim(end = input$n_days * 24, delta = 0.25) |> as.data.frame() |> mutate(time_h = time) }) # ── Metrics ──────────────────────────────────────────────────────────────── output$css <- renderText({ d <- sim_data() last_h <- max(d$time_h) css_val <- d |> dplyr::filter(time_h >= last_h - 1) |> dplyr::pull(CP) |> mean(na.rm = TRUE) sprintf("%.1f", css_val) }) output$tmic <- renderText({ d <- sim_data() mic_v <- as.numeric(input$mic) last24 <- dplyr::filter(d, time_h >= (input$n_days - 1) * 24) pct <- mean(last24$CP > mic_v, na.rm = TRUE) * 100 sprintf("%.0f%%", pct) }) output$auc24 <- renderText({ d <- sim_data() last_start <- (input$n_days - 1) * 24 seg <- dplyr::filter(d, time_h >= last_start, time_h <= last_start + 24) auc <- sum(diff(seg$time_h) * (head(seg$CP, -1) + tail(seg$CP, -1)) / 2) sprintf("%.1f", auc) }) output$thalf <- renderText({ shiny::req(input$wt, input$ecrcl) wt_s <- input$wt / 70.0 cl_renal <- 2.29 * (input$ecrcl / 60.0)^0.943 * wt_s cl_tot <- cl_renal + 0.795 * wt_s v1i <- 10.7 * wt_s v2i <- 12.2 * wt_s qi <- 11.0 * wt_s # 2-compartment terminal beta half-life k10 <- cl_tot / v1i k12 <- qi / v1i k21 <- qi / v2i s <- k10 + k12 + k21 beta <- 0.5 * (s - sqrt(max(s^2 - 4 * k10 * k21, 0))) if (beta <= 0) return("N/A") sprintf("%.1f", log(2) / beta) }) # ── Plot ────────────────────────────────────────────────────────────────── output$pkPlot <- renderPlot({ d <- sim_data() mic_v <- as.numeric(input$mic) d_plot <- if (input$log_scale) dplyr::filter(d, CP > 0) else d max_t <- max(d_plot$time_h) max_cp <- max(d_plot$CP, na.rm = TRUE) y_tox <- 35 y_tci_lo <- 11.2 y_tci_hi <- 20.8 p <- ggplot(d_plot, aes(x = time_h, y = CP)) + # Therapeutic window band (±30% around 16 mg/L TCI target) annotate("rect", xmin = -Inf, xmax = Inf, ymin = y_tci_lo, ymax = y_tci_hi, fill = "#10b981", alpha = 0.12) + geom_hline(yintercept = c(y_tci_lo, y_tci_hi), linetype = "dashed", color = "#10b981", alpha = 0.6) + annotate("text", x = max_t * 0.02, y = y_tci_hi * 1.06, label = "TCI target range (\u00b130%, 11.2\u201320.8 mg/L)", hjust = 0, size = 3.5, color = "#10b981") + # Toxicity threshold geom_hline(yintercept = y_tox, linetype = "dotted", color = "#ef4444", alpha = 0.7) + annotate("text", x = max_t * 0.02, y = y_tox * 1.04, label = "Neurotoxicity threshold (35 mg/L)", hjust = 0, size = 3.5, color = "#ef4444") + # MIC line geom_hline(yintercept = mic_v, linetype = "dashed", color = "#f59e0b", alpha = 0.8) + annotate("text", x = max_t * 0.02, y = mic_v * 1.12 + 0.2, label = paste0("MIC = ", mic_v, " mg/L"), hjust = 0, size = 3.5, color = "#f59e0b") + # PK concentration-time curve geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( x = "Time (hours)", y = "Cefepime Plasma Concentration (mg/L)", title = paste0( "Cefepime ", input$dose_g, " g/day continuous IV infusion", " | ", input$wt, " kg, eCrCL = ", input$ecrcl, " mL/min" ) ) + theme_minimal(base_size = 14) + theme(plot.title = element_text(size = 13, face = "bold")) if (input$log_scale) p <- p + scale_y_log10() p }) } shinyApp(ui = ui, server = server)