library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) paper_reference <- list( title = "Population pharmacokinetics of intravenous ampicillin in awake and anesthetised dogs", citation = "Pressiat C, Audrac C, Marotto S, Verwaerde P, Maurey C, Hulin A, Kohlhauer M. The Veterinary Journal. 2025;314:106435.", doi = "10.1016/j.tvjl.2025.106435" ) study_constants <- list( typical_cl_ml_min_kg = 8.8, typical_v1_l_kg = 0.10, typical_v2_l_kg = 0.23, typical_q_ml_min_kg = 7.7, beta_creatinine = -0.074, beta_anesthesia = -0.39, mean_creatinine_mg_l = 8.8, mean_weight_kg = 25.1, protein_binding = 0.259, free_fraction = 0.741 ) pta_table <- tibble::tribble( ~state, ~target_label, ~regimen, ~mic, ~pta, "Awake", "40% fT>MIC", "Bolus q8h", 0.0625, 98.8, "Awake", "40% fT>MIC", "Bolus q8h", 0.1250, 97.7, "Awake", "40% fT>MIC", "Bolus q8h", 0.2500, 95.6, "Awake", "40% fT>MIC", "Bolus q8h", 0.5000, 92.0, "Awake", "40% fT>MIC", "Bolus q8h", 1.0000, 80.9, "Awake", "40% fT>MIC", "Bolus q8h", 2.0000, 58.1, "Awake", "40% fT>MIC", "Bolus q8h", 4.0000, 29.5, "Awake", "40% fT>MIC", "Bolus q8h", 8.0000, 9.6, "Awake", "40% fT>MIC", "Bolus q8h", 16.0000, 2.1, "Awake", "40% fT>MIC", "Bolus q8h", 32.0000, 0.1, "Awake", "40% fT>MIC", "Bolus q4h", 0.0625, 100.0, "Awake", "40% fT>MIC", "Bolus q4h", 0.1250, 100.0, "Awake", "40% fT>MIC", "Bolus q4h", 0.2500, 99.9, "Awake", "40% fT>MIC", "Bolus q4h", 0.5000, 99.9, "Awake", "40% fT>MIC", "Bolus q4h", 1.0000, 99.4, "Awake", "40% fT>MIC", "Bolus q4h", 2.0000, 96.0, "Awake", "40% fT>MIC", "Bolus q4h", 4.0000, 80.4, "Awake", "40% fT>MIC", "Bolus q4h", 8.0000, 46.6, "Awake", "40% fT>MIC", "Bolus q4h", 16.0000, 13.3, "Awake", "40% fT>MIC", "Bolus q4h", 32.0000, 2.1, "Awake", "40% fT>MIC", "2h infusion q8h", 0.0625, 100.0, "Awake", "40% fT>MIC", "2h infusion q8h", 0.1250, 100.0, "Awake", "40% fT>MIC", "2h infusion q8h", 0.2500, 99.8, "Awake", "40% fT>MIC", "2h infusion q8h", 0.5000, 99.0, "Awake", "40% fT>MIC", "2h infusion q8h", 1.0000, 95.7, "Awake", "40% fT>MIC", "2h infusion q8h", 2.0000, 83.6, "Awake", "40% fT>MIC", "2h infusion q8h", 4.0000, 54.5, "Awake", "40% fT>MIC", "2h infusion q8h", 8.0000, 18.2, "Awake", "40% fT>MIC", "2h infusion q8h", 16.0000, 2.8, "Awake", "40% fT>MIC", "2h infusion q8h", 32.0000, 0.1, "Awake", "40% fT>MIC", "4h infusion q8h", 0.0625, 100.0, "Awake", "40% fT>MIC", "4h infusion q8h", 0.1250, 100.0, "Awake", "40% fT>MIC", "4h infusion q8h", 0.2500, 100.0, "Awake", "40% fT>MIC", "4h infusion q8h", 0.5000, 100.0, "Awake", "40% fT>MIC", "4h infusion q8h", 1.0000, 100.0, "Awake", "40% fT>MIC", "4h infusion q8h", 2.0000, 100.0, "Awake", "40% fT>MIC", "4h infusion q8h", 4.0000, 99.6, "Awake", "40% fT>MIC", "4h infusion q8h", 8.0000, 43.7, "Awake", "40% fT>MIC", "4h infusion q8h", 16.0000, 3.8, "Awake", "40% fT>MIC", "4h infusion q8h", 32.0000, 0.1, "Awake", "50% fT>MIC", "Bolus q8h", 0.0625, 96.8, "Awake", "50% fT>MIC", "Bolus q8h", 0.1250, 94.9, "Awake", "50% fT>MIC", "Bolus q8h", 0.2500, 91.7, "Awake", "50% fT>MIC", "Bolus q8h", 0.5000, 82.7, "Awake", "50% fT>MIC", "Bolus q8h", 1.0000, 67.3, "Awake", "50% fT>MIC", "Bolus q8h", 2.0000, 43.2, "Awake", "50% fT>MIC", "Bolus q8h", 4.0000, 18.4, "Awake", "50% fT>MIC", "Bolus q8h", 8.0000, 5.9, "Awake", "50% fT>MIC", "Bolus q8h", 16.0000, 1.2, "Awake", "50% fT>MIC", "Bolus q8h", 32.0000, 0.1, "Awake", "50% fT>MIC", "Bolus q4h", 0.0625, 100.0, "Awake", "50% fT>MIC", "Bolus q4h", 0.1250, 100.0, "Awake", "50% fT>MIC", "Bolus q4h", 0.2500, 99.9, "Awake", "50% fT>MIC", "Bolus q4h", 0.5000, 99.6, "Awake", "50% fT>MIC", "Bolus q4h", 1.0000, 97.9, "Awake", "50% fT>MIC", "Bolus q4h", 2.0000, 91.3, "Awake", "50% fT>MIC", "Bolus q4h", 4.0000, 68.2, "Awake", "50% fT>MIC", "Bolus q4h", 8.0000, 32.6, "Awake", "50% fT>MIC", "Bolus q4h", 16.0000, 8.3, "Awake", "50% fT>MIC", "Bolus q4h", 32.0000, 1.4, "Awake", "50% fT>MIC", "2h infusion q8h", 0.0625, 99.8, "Awake", "50% fT>MIC", "2h infusion q8h", 0.1250, 98.9, "Awake", "50% fT>MIC", "2h infusion q8h", 0.2500, 97.8, "Awake", "50% fT>MIC", "2h infusion q8h", 0.5000, 94.8, "Awake", "50% fT>MIC", "2h infusion q8h", 1.0000, 87.1, "Awake", "50% fT>MIC", "2h infusion q8h", 2.0000, 64.8, "Awake", "50% fT>MIC", "2h infusion q8h", 4.0000, 33.9, "Awake", "50% fT>MIC", "2h infusion q8h", 8.0000, 10.6, "Awake", "50% fT>MIC", "2h infusion q8h", 16.0000, 1.9, "Awake", "50% fT>MIC", "2h infusion q8h", 32.0000, 0.1, "Awake", "50% fT>MIC", "4h infusion q8h", 0.0625, 100.0, "Awake", "50% fT>MIC", "4h infusion q8h", 0.1250, 100.0, "Awake", "50% fT>MIC", "4h infusion q8h", 0.2500, 100.0, "Awake", "50% fT>MIC", "4h infusion q8h", 0.5000, 100.0, "Awake", "50% fT>MIC", "4h infusion q8h", 1.0000, 100.0, "Awake", "50% fT>MIC", "4h infusion q8h", 2.0000, 99.8, "Awake", "50% fT>MIC", "4h infusion q8h", 4.0000, 78.4, "Awake", "50% fT>MIC", "4h infusion q8h", 8.0000, 20.3, "Awake", "50% fT>MIC", "4h infusion q8h", 16.0000, 2.2, "Awake", "50% fT>MIC", "4h infusion q8h", 32.0000, 0.1, "Anesthetized", "100% fT>MIC", "Bolus q90min", 0.0625, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q90min", 0.1250, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q90min", 0.2500, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q90min", 0.5000, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q90min", 1.0000, 99.99, "Anesthetized", "100% fT>MIC", "Bolus q90min", 2.0000, 99.77, "Anesthetized", "100% fT>MIC", "Bolus q90min", 4.0000, 98.7, "Anesthetized", "100% fT>MIC", "Bolus q90min", 8.0000, 85.1, "Anesthetized", "100% fT>MIC", "Bolus q90min", 16.0000, 37.2, "Anesthetized", "100% fT>MIC", "Bolus q90min", 32.0000, 2.2, "Anesthetized", "100% fT>MIC", "Bolus q120min", 0.0625, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q120min", 0.1250, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q120min", 0.2500, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q120min", 0.5000, 100.0, "Anesthetized", "100% fT>MIC", "Bolus q120min", 1.0000, 99.9, "Anesthetized", "100% fT>MIC", "Bolus q120min", 2.0000, 99.5, "Anesthetized", "100% fT>MIC", "Bolus q120min", 4.0000, 95.9, "Anesthetized", "100% fT>MIC", "Bolus q120min", 8.0000, 71.3, "Anesthetized", "100% fT>MIC", "Bolus q120min", 16.0000, 22.1, "Anesthetized", "100% fT>MIC", "Bolus q120min", 32.0000, 0.9, "Anesthetized", "100% fT>MIC", "90min infusion", 0.0625, 100.0, "Anesthetized", "100% fT>MIC", "90min infusion", 0.1250, 100.0, "Anesthetized", "100% fT>MIC", "90min infusion", 0.2500, 100.0, "Anesthetized", "100% fT>MIC", "90min infusion", 0.5000, 100.0, "Anesthetized", "100% fT>MIC", "90min infusion", 1.0000, 99.9, "Anesthetized", "100% fT>MIC", "90min infusion", 2.0000, 1.5, "Anesthetized", "100% fT>MIC", "90min infusion", 4.0000, 0.0, "Anesthetized", "100% fT>MIC", "90min infusion", 8.0000, 0.0, "Anesthetized", "100% fT>MIC", "90min infusion", 16.0000, 0.0, "Anesthetized", "100% fT>MIC", "90min infusion", 32.0000, 0.0 ) pkpd_cutoffs <- pta_table |> dplyr::filter(pta >= 90) |> summarise(pkpdco = max(mic), .by = c(state, target_label, regimen)) awake_regimens <- c("Bolus q8h", "Bolus q4h", "2h infusion q8h", "4h infusion q8h") anesthesia_regimens <- c("Bolus q90min", "Bolus q120min", "90min infusion") build_regimen_event <- function(state, regimen, dose_mg_kg, weight_kg) { dose_mg <- dose_mg_kg * weight_kg switch( regimen, "Bolus q8h" = ev(amt = dose_mg, ii = 8, addl = 2, cmt = 1), "Bolus q4h" = ev(amt = dose_mg, ii = 4, addl = 5, cmt = 1), "2h infusion q8h" = ev(amt = dose_mg, ii = 8, addl = 2, rate = dose_mg / 2, cmt = 1), "4h infusion q8h" = ev(amt = dose_mg, ii = 8, addl = 2, rate = dose_mg / 4, cmt = 1), "Bolus q90min" = ev(amt = dose_mg, ii = 1.5, addl = 1, cmt = 1), "Bolus q120min" = ev(amt = dose_mg, ii = 2, addl = 1, cmt = 1), "90min infusion" = ev(amt = dose_mg, rate = dose_mg / 1.5, cmt = 1), stop("Unsupported regimen: ", regimen) ) } simulation_horizon_h <- function(state) { if (identical(state, "Anesthetized")) 3 else 24 } metric_window_h <- function(regimen, state) { if (identical(regimen, "Bolus q8h")) return(8) if (identical(regimen, "Bolus q4h")) return(4) if (identical(regimen, "2h infusion q8h")) return(8) if (identical(regimen, "4h infusion q8h")) return(8) if (identical(regimen, "Bolus q90min")) return(1.5) if (identical(regimen, "Bolus q120min")) return(2) if (identical(state, "Anesthetized")) return(3) 24 } trap_auc <- function(time, conc) { if (length(time) < 2) { return(NA_real_) } sum(diff(time) * (head(conc, -1) + tail(conc, -1)) / 2) } terminal_half_life_h <- function(cl_l_h, q_l_h, v1_l, v2_l) { k10 <- cl_l_h / v1_l k12 <- q_l_h / v1_l k21 <- q_l_h / v2_l beta <- 0.5 * ((k10 + k12 + k21) - sqrt((k10 + k12 + k21)^2 - 4 * k21 * k10)) log(2) / beta } lookup_pta <- function(state, target_label, regimen, mic) { row <- pta_table |> dplyr::filter(state == !!state, target_label == !!target_label, regimen == !!regimen, mic == !!mic) if (nrow(row) == 0) { return(NA_real_) } row$pta[[1]] } lookup_pkpdco <- function(state, target_label, regimen) { row <- pkpd_cutoffs |> dplyr::filter(state == !!state, target_label == !!target_label, regimen == !!regimen) if (nrow(row) == 0) { return(NA_real_) } row$pkpdco[[1]] } model_code <- " $PARAM @annotated TVCL : 8.8 : Typical elimination clearance parameter (mL/min/kg) TVV1 : 0.10 : Central volume (L/kg) TVV2 : 0.23 : Peripheral volume (L/kg) TVQ : 7.7 : Intercompartmental clearance (mL/min/kg) WT : 25.1 : Body weight (kg) CREAT : 8.8 : Plasma creatinine (mg/L) ANES : 0 : Anesthesia indicator (0 = awake, 1 = anesthetized) BCREAT : -0.074: Creatinine effect on log(CL) BANES : -0.39 : Anesthesia effect on log(CL) FU : 0.741 : Free fraction in plasma $CMT @annotated CENT : Central compartment (mg) PERIPH : Peripheral compartment (mg) $MAIN double CLkg = TVCL * exp(BCREAT * CREAT + BANES * ANES); double Qkg = TVQ; double CLi = CLkg * WT * 0.06; double Qi = Qkg * WT * 0.06; double V1i = TVV1 * WT; double V2i = TVV2 * WT; $ODE dxdt_CENT = -(CLi / V1i) * CENT - (Qi / V1i) * CENT + (Qi / V2i) * PERIPH; dxdt_PERIPH = (Qi / V1i) * CENT - (Qi / V2i) * PERIPH; $TABLE double CP = CENT / V1i; double CPFREE = CP * FU; $CAPTURE CP CPFREE CLi Qi V1i V2i " mod <- mcode("ampicillin_pressiat_2025", model_code) simulate_profile <- function(state, regimen, dose_mg_kg, weight_kg, creatinine_mg_l, delta = 0.05) { horizon_h <- simulation_horizon_h(state) anes_flag <- if (identical(state, "Anesthetized")) 1 else 0 dose_event <- build_regimen_event(state, regimen, dose_mg_kg, weight_kg) mod |> param( WT = weight_kg, CREAT = creatinine_mg_l, ANES = anes_flag, FU = study_constants$free_fraction ) %>% ev(dose_event) %>% mrgsim(end = horizon_h, delta = delta) %>% as.data.frame() |> mutate( time_h = time, CP = pmax(CP, 1e-6), CPFREE = pmax(CPFREE, 1e-6) ) } 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; min-height: 108px; } .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; } .summary-box { background: #f8f9ff; border: 1px solid #d8ddff; border-radius: 8px; padding: 14px 16px; font-size: 13px; line-height: 1.5; } .pk-note { background: #f1f3f5; border-radius: 6px; padding: 8px 10px; font-size: 11px; color: #495057; font-family: monospace; } .ref-box { background: #f0f4ff; border-left: 4px solid #8b5cf6; padding: 12px 16px; border-radius: 4px; margin-top: 10px; font-size: 13px; } .footer-note { text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #e9ecef; color: #6c757d; font-size: 12px; } ") ui <- page_sidebar( title = "Ampicillin Dog PopPK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, selectInput("state", "Clinical setting", choices = c("Awake", "Anesthetized"), selected = "Awake"), selectInput("regimen", "Paper regimen", choices = awake_regimens, selected = "Bolus q8h"), sliderInput("dose_mg_kg", "Dose (mg/kg)", min = 10, max = 40, value = 20, step = 1), sliderInput("weight_kg", "Body weight (kg)", min = 5, max = 60, value = 25.1, step = 0.5), sliderInput("creatinine", "Plasma creatinine (mg/L)", min = 2, max = 18, value = 8.8, step = 0.1), uiOutput("target_ui"), selectInput( "mic", "MIC for PK/PD summary (mg/L)", choices = c("0.0625", "0.125", "0.25", "0.5", "1", "2", "4", "8", "16", "32"), selected = "0.5" ), checkboxInput("show_free", "Show free concentration line", value = TRUE), checkboxInput("log_scale", "Log scale (Y-axis)", value = FALSE), div( class = "pk-note", "Paper constants: V1 0.10 L/kg, V2 0.23 L/kg, Q 7.7 mL/min/kg, mean protein binding 25.9%." ) ), navset_card_underline( title = "Ampicillin IV PopPK Simulator", 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 (mg/L)") ), div( class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Ctrough (mg/L)") ), div( class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUC over paper horizon") ), div( class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "Terminal t1/2 (h)") ) ), card( full_screen = TRUE, plotOutput("pkPlot", height = "500px") ), card( style = "margin-top: 10px;", card_body( div(class = "summary-box", htmlOutput("study_summary")) ) ) ), nav_panel( "PK/PD Targets", layout_column_wrap( width = 1/3, fill = FALSE, div( class = "metric-card metric-primary", div(class = "metric-value", textOutput("published_pta")), div(class = "metric-label", "Published PTA at selected MIC") ), div( class = "metric-card metric-success", div(class = "metric-value", textOutput("published_pkpdco")), div(class = "metric-label", "Published PK/PDCO") ), div( class = "metric-card metric-info", div(class = "metric-value", textOutput("deterministic_ftmic")), div(class = "metric-label", "Deterministic free T > MIC") ) ), card( full_screen = TRUE, plotOutput("ptaPlot", height = "450px") ), card( style = "margin-top: 10px;", card_body(tableOutput("pkpd_table")) ) ), nav_panel( "Model Information", card( card_body( markdown(paste0( "## Ampicillin in Dogs: Two-Compartment Population PK **Source:** ", paper_reference$citation, " **DOI:** https://doi.org/", paper_reference$doi, " **Population:** 20 client-owned dogs, 10 awake and 10 anesthetized **Drug:** Intravenous ampicillin-sulbactam, modeled on ampicillin total plasma concentrations ### Structural Model - Two-compartment IV model with linear elimination - Monolix nonlinear mixed-effects analysis - Sparse clinical sampling in awake and peri-anesthetic dogs ### Paper Parameter Estimates (Table 1) | Parameter | Estimate | Units | |-----------|----------|-------| | CL | 8.8 | mL/min/kg | | V1 | 0.10 | L/kg | | V2 | 0.23 | L/kg | | Q | 7.7 | mL/min/kg | | beta_creatinine | -0.074 | log(CL) per mg/L | | beta_anesthesia | -0.39 | log(CL) shift | ### Covariate Model The paper expresses clearance covariates on the log scale: log(CL_i) = log(CL_typical) + beta_creatinine * creatinine + beta_anesthesia * anesthesia where anesthesia = 0 for awake dogs and 1 for anesthetized dogs. ### PK/PD Details - Mean plasma protein binding in the study: 25.9% - Free fraction used here: 74.1% - Awake Monte Carlo targets: 40% or 50% fT>MIC over 24 h - Anesthetized target: 100% fT>MIC over 3 h ### Study Conclusions Embedded in This App - Standard 20 mg/kg q8h bolus was weak for higher-MIC Gram-negative soft tissue infections. - Prolonged 4 h infusion q8h materially improved target attainment in awake dogs. - Clearance fell during anesthesia and with higher creatinine, increasing exposure. " )) ) ) ), nav_panel( "References", card( card_body( div( class = "ref-box", tags$h5("Primary manuscript"), tags$p( paper_reference$citation, " ", tags$a( href = paste0("https://doi.org/", paper_reference$doi), target = "_blank", "DOI link" ) ), tags$h5("Drug context"), tags$ul( tags$li("Class: aminopenicillin / beta-lactam"), tags$li("Primary PK-PD index: free time above MIC"), tags$li("Main elimination path: renal clearance"), tags$li("Clinical point from this paper: prolonged infusion improves target attainment in awake dogs") ), tags$h5("Supporting literature cited in the paper"), tags$ul( tags$li("Goggs et al. 2025 - ampicillin-sulbactam in critically ill dogs"), tags$li("Monaghan et al. 2021 - ampicillin PK in dogs with impaired kidney function"), tags$li("Stewart et al. 2022 - continuous vs intermittent ampicillin-sulbactam infusion in septic peritonitis") ) ) ) ) ) ), div( class = "footer-note", "Powered by ", tags$a( href = "https://www.pkpdbuilder.com", target = "_blank", style = "color: #8b5cf6; font-weight: 500;", "PKPDBuilder.com" ), " • Built by Sunny for research and educational use only" ) ) server <- function(input, output, session) { observeEvent(input$state, { if (identical(input$state, "Awake")) { updateSelectInput(session, "regimen", choices = awake_regimens, selected = "Bolus q8h") updateSelectInput(session, "mic", selected = "0.5") } else { updateSelectInput(session, "regimen", choices = anesthesia_regimens, selected = "Bolus q90min") updateSelectInput(session, "mic", selected = "4") } }, ignoreInit = TRUE) output$target_ui <- renderUI({ if (identical(input$state, "Awake")) { selectInput( "target_label", "PK/PD target", choices = c("40% fT>MIC", "50% fT>MIC"), selected = "40% fT>MIC" ) } else { selectInput( "target_label", "PK/PD target", choices = c("100% fT>MIC"), selected = "100% fT>MIC" ) } }) sim_data <- reactive({ shiny::req(input$state, input$regimen, input$dose_mg_kg, input$weight_kg, input$creatinine) simulate_profile( state = input$state, regimen = input$regimen, dose_mg_kg = input$dose_mg_kg, weight_kg = input$weight_kg, creatinine_mg_l = input$creatinine ) }) metric_data <- reactive({ shiny::req(input$state, input$regimen, input$mic) d <- sim_data() horizon_h <- simulation_horizon_h(input$state) window_h <- metric_window_h(input$regimen, input$state) window_start <- max(0, horizon_h - window_h) window_data <- d |> dplyr::filter(time_h >= window_start) pk_row <- d[nrow(d), ] auc_val <- trap_auc(window_data$time_h, window_data$CP) ftmic <- mean(window_data$CPFREE > as.numeric(input$mic)) * 100 thalf <- terminal_half_life_h(pk_row$CLi, pk_row$Qi, pk_row$V1i, pk_row$V2i) list( cmax = max(window_data$CP, na.rm = TRUE), ctrough = dplyr::last(window_data$CP), auc = auc_val, ftmic = ftmic, thalf = thalf, current_cl = pk_row$CLi, current_q = pk_row$Qi, current_v1 = pk_row$V1i, current_v2 = pk_row$V2i ) }) output$cmax <- renderText(sprintf("%.1f", metric_data()$cmax)) output$ctrough <- renderText(sprintf("%.1f", metric_data()$ctrough)) output$auc <- renderText(sprintf("%.1f mg*h/L", metric_data()$auc)) output$thalf <- renderText(sprintf("%.2f", metric_data()$thalf)) output$published_pta <- renderText({ shiny::req(input$target_label) pta <- lookup_pta(input$state, input$target_label, input$regimen, as.numeric(input$mic)) sprintf("%.1f%%", pta) }) output$published_pkpdco <- renderText({ shiny::req(input$target_label) cutoff <- lookup_pkpdco(input$state, input$target_label, input$regimen) sprintf("%.2f mg/L", cutoff) }) output$deterministic_ftmic <- renderText({ sprintf("%.1f%%", metric_data()$ftmic) }) output$study_summary <- renderUI({ shiny::req(input$target_label) published_pta <- lookup_pta(input$state, input$target_label, input$regimen, as.numeric(input$mic)) cutoff <- lookup_pkpdco(input$state, input$target_label, input$regimen) m <- metric_data() HTML(sprintf( paste0( "Current structural PK under selected covariates
", "CL = %.2f L/h, V1 = %.2f L, V2 = %.2f L, Q = %.2f L/h.

", "Deterministic free-drug exposure
", "Free T>MIC over the paper horizon = %.1f%% at MIC %.4g mg/L.

", "Published study anchor for this regimen
", "PTA at the same MIC = %.1f%% and PK/PDCO = %.2f mg/L using the paper's Monte Carlo table.

", "This app intentionally separates the mechanistic typical-profile simulation from the paper-reported PTA outputs, ", "because the manuscript reports regimen-level Monte Carlo target attainment but not the full subject-level simulation inputs needed to recreate those distributions exactly." ), m$current_cl, m$current_v1, m$current_v2, m$current_q, m$ftmic, as.numeric(input$mic), published_pta, cutoff )) }) output$pkPlot <- renderPlot({ d <- sim_data() mic_value <- as.numeric(input$mic) horizon_h <- simulation_horizon_h(input$state) p <- ggplot(d, aes(x = time_h, y = CP)) + geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( x = "Time (h)", y = "Concentration (mg/L)", title = paste0("Ampicillin ", input$dose_mg_kg, " mg/kg - ", input$regimen), subtitle = paste0( input$state, " setting | horizon ", horizon_h, " h | ", "creatinine ", sprintf("%.1f", input$creatinine), " mg/L" ) ) + geom_hline(yintercept = mic_value, linetype = "dashed", color = "#dc3545") + annotate( "text", x = 0.05 * horizon_h, y = mic_value * 1.12, label = paste0("MIC = ", mic_value, " mg/L"), hjust = 0, color = "#dc3545", size = 3.5 ) + theme_minimal(base_size = 14) if (isTRUE(input$show_free)) { p <- p + geom_line(aes(y = CPFREE), color = "#10b981", linewidth = 0.8, linetype = "solid") } if (isTRUE(input$log_scale)) { p <- p + scale_y_log10() } p }) output$ptaPlot <- renderPlot({ shiny::req(input$target_label) d <- pta_table |> dplyr::filter(state == input$state, target_label == input$target_label, regimen == input$regimen) cutoff <- lookup_pkpdco(input$state, input$target_label, input$regimen) ggplot(d, aes(x = mic, y = pta)) + geom_line(color = "#8b5cf6", linewidth = 1) + geom_point(color = "#8b5cf6", size = 2.5) + geom_hline(yintercept = 90, color = "#10b981", linetype = "dashed") + geom_vline(xintercept = cutoff, color = "#10b981", linetype = "dotted") + annotate( "text", x = cutoff, y = 95, label = paste0("PK/PDCO = ", cutoff, " mg/L"), hjust = -0.05, color = "#10b981", size = 3.5 ) + scale_x_log10(breaks = d$mic, labels = d$mic) + scale_y_continuous(limits = c(0, 100)) + labs( x = "MIC (mg/L)", y = "Probability of target attainment (%)", title = paste0(input$regimen, " - ", input$target_label), subtitle = "Values digitized directly from Table 2 of the paper" ) + theme_minimal(base_size = 14) }) output$pkpd_table <- renderTable({ shiny::req(input$target_label) pta_table |> dplyr::filter(state == input$state, target_label == input$target_label) |> left_join(pkpd_cutoffs, by = c("state", "target_label", "regimen")) |> mutate( MIC_mg_L = mic, PTA_percent = pta, PKPDCO_mg_L = pkpdco ) |> dplyr::select(regimen, MIC_mg_L, PTA_percent, PKPDCO_mg_L) }, striped = TRUE, digits = 2) } shinyApp(ui = ui, server = server)