suppressPackageStartupMessages({ library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) }) model_code <- ' $PARAM @annotated WT : 22.8 : Body weight (kg) ALT : 26.5 : Alanine aminotransferase (IU/L) GENO : 0 : CYP2C19 genotype (0 = EM, 1 = HEM/PM) CL_EMKG : 0.582 : Clearance in EMs at reference ALT (L/h/kg) HEM_DROP : 0.355 : Fractional clearance reduction in HEM/PM ALT_THETA : -0.0931: ALT effect exponent on clearance ALT_REF : 26.5 : Reference ALT (IU/L) KM_MG_L : 3.03 : Michaelis-Menten constant (mg/L) VC_KG : 0.807 : Central volume (L/kg) Q_KG : 0.609 : Intercompartmental clearance (L/h/kg) VP_KG : 2.17 : Peripheral volume (L/kg) KA : 0.849 : Absorption rate constant (1/h) F1 : 0.446 : Oral bioavailability $CMT @annotated DEPOT : Oral depot (mg) CENT : Central compartment (mg) PERIPH : Peripheral compartment (mg) AUC : Area under the concentration-time curve (mg*h/L) $MAIN double cl_factor = pow(ALT / ALT_REF, ALT_THETA); double geno_factor = (GENO > 0.5) ? (1.0 - HEM_DROP) : 1.0; double CLi = CL_EMKG * cl_factor * geno_factor * WT; double VCi = VC_KG * WT; double Qi = Q_KG * WT; double VPi = VP_KG * WT; double VMAX = CLi * KM_MG_L; double K12 = Qi / VCi; double K21 = Qi / VPi; F_DEPOT = F1; $ODE double CP = CENT / VCi; double ELIM = (CP > 0.0) ? (VMAX * CP / (KM_MG_L + CP)) : 0.0; dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - ELIM - K12 * CENT + K21 * PERIPH; dxdt_PERIPH = K12 * CENT - K21 * PERIPH; dxdt_AUC = CP; $TABLE double CP_MG_L = CENT / VCi; double AUCOUT = AUC; double CL_L_H = CLi; double VMAX_MG_H = VMAX; double VC_L = VCi; double VP_L = VPi; double Q_L_H = Qi; $CAPTURE CP_MG_L AUCOUT CL_L_H VMAX_MG_H VC_L VP_L Q_L_H ' mod <- mcode("pkpd_app_348_voriconazole", 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; min-height: 108px; } .metric-value { font-size: 24px; font-weight: bold; color: #2c3e50; } .metric-label { font-size: 12px; color: #7f8c8d; margin-top: 4px; } .metric-subtext { font-size: 11px; color: #6c757d; margin-top: 6px; } .metric-success .metric-value { color: #10b981; } .metric-warning .metric-value { color: #f59e0b; } .metric-primary .metric-value { color: #8b5cf6; } .metric-info .metric-value { color: #0dcaf0; } .info-box { background: #f8f9ff; border: 1px solid #d8ddff; border-radius: 8px; padding: 12px 14px; margin-top: 10px; font-size: 13px; } .ref-box { background: #f0f4ff; border-left: 4px solid #8b5cf6; padding: 12px 16px; border-radius: 4px; margin-top: 10px; font-size: 13px; } ") trap_auc <- function(time, conc) { if (length(time) < 2) { return(NA_real_) } sum(diff(time) * (head(conc, -1) + tail(conc, -1)) / 2) } calc_linear_half_life <- function(weight, alt, genotype) { cl_per_kg <- 0.582 * (alt / 26.5)^(-0.0931) if (identical(genotype, "HEM/PM")) { cl_per_kg <- cl_per_kg * (1 - 0.355) } cl <- cl_per_kg * weight vc <- 0.807 * weight q <- 0.609 * weight vp <- 2.17 * weight k10 <- cl / vc k12 <- q / vc k21 <- q / vp disc <- (k10 + k12 + k21)^2 - 4 * k21 * k10 beta <- 0.5 * ((k10 + k12 + k21) - sqrt(max(disc, 0))) log(2) / beta } preset_values <- function(preset) { switch( preset, "Recommended IV: 7 mg/kg BID" = list( route = "iv", oral_mode = "fixed", iv_dose_mgkg = 7, oral_dose_mg = 200, oral_dose_mgkg = 6, interval_h = 12, infusion_h = 1.5 ), "Recommended oral: 200 mg BID" = list( route = "oral", oral_mode = "fixed", iv_dose_mgkg = 7, oral_dose_mg = 200, oral_dose_mgkg = 6, interval_h = 12, infusion_h = 1.5 ), "Study IV: 8 mg/kg BID" = list( route = "iv", oral_mode = "fixed", iv_dose_mgkg = 8, oral_dose_mg = 200, oral_dose_mgkg = 6, interval_h = 12, infusion_h = 1.5 ), "Study oral: 4 mg/kg BID" = list( route = "oral", oral_mode = "mgkg", iv_dose_mgkg = 7, oral_dose_mg = 200, oral_dose_mgkg = 4, interval_h = 12, infusion_h = 1.5 ), "Study oral: 6 mg/kg BID" = list( route = "oral", oral_mode = "mgkg", iv_dose_mgkg = 7, oral_dose_mg = 200, oral_dose_mgkg = 6, interval_h = 12, infusion_h = 1.5 ), list( route = "iv", oral_mode = "fixed", iv_dose_mgkg = 7, oral_dose_mg = 200, oral_dose_mgkg = 6, interval_h = 12, infusion_h = 1.5 ) ) } ui <- page_sidebar( title = "Voriconazole Pediatric PopPK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, selectInput( "preset", "Paper preset", choices = c( "Custom", "Recommended IV: 7 mg/kg BID", "Recommended oral: 200 mg BID", "Study IV: 8 mg/kg BID", "Study oral: 4 mg/kg BID", "Study oral: 6 mg/kg BID" ), selected = "Recommended IV: 7 mg/kg BID" ), radioButtons( "route", "Route", choices = c("IV infusion" = "iv", "Oral" = "oral"), inline = TRUE ), conditionalPanel( "input.route === 'iv'", sliderInput("iv_dose_mgkg", "IV dose (mg/kg)", min = 3, max = 10, value = 7, step = 0.5), numericInput("infusion_h", "Infusion duration (h)", value = 1.5, min = 0.5, max = 4, step = 0.5) ), conditionalPanel( "input.route === 'oral'", radioButtons( "oral_mode", "Oral dose mode", choices = c("Fixed dose (mg)" = "fixed", "Weight-based (mg/kg)" = "mgkg"), inline = TRUE ), conditionalPanel( "input.oral_mode === 'fixed'", numericInput("oral_dose_mg", "Oral dose (mg)", value = 200, min = 50, max = 400, step = 25) ), conditionalPanel( "input.oral_mode === 'mgkg'", sliderInput("oral_dose_mgkg", "Oral dose (mg/kg)", min = 3, max = 12, value = 6, step = 0.5) ) ), numericInput("interval_h", "Dosing interval (h)", value = 12, min = 8, max = 24, step = 1), numericInput("duration_days", "Simulation duration (days)", value = 14, min = 2, max = 21, step = 1), hr(), h6("Patient characteristics"), sliderInput("wt", "Body weight (kg)", min = 10, max = 55, value = 22.8, step = 0.5), selectInput( "genotype", "CYP2C19 genotype group", choices = c("Extensive metabolizer (EM)" = "EM", "Heterozygous/poor metabolizer (HEM/PM)" = "HEM/PM"), selected = "EM" ), numericInput("alt", "ALT (IU/L)", value = 26.5, min = 5, max = 300, step = 1), hr(), checkboxInput("log_scale", "Log scale", value = FALSE), div( class = "info-box", tags$strong("Paper recommendation:"), tags$div("7 mg/kg BID IV or 200 mg BID oral, without loading doses."), tags$div(style = "margin-top: 6px;", "Model uses the published final PopPK parameterization from Karlsson et al. 2009.") ) ), 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-subtext", textOutput("dose_summary")) ), div( class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Ctrough (mg/L)"), div(class = "metric-subtext", "End of final dosing interval") ), div( class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUC0-tau (mg*h/L)"), div(class = "metric-subtext", "Final dosing interval") ), div( class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "Approx. terminal t1/2 (h)"), div(class = "metric-subtext", "Linearized around low concentrations") ) ), navset_card_underline( title = "Voriconazole Pediatric PopPK Simulator", nav_panel( "Simulation", card( full_screen = TRUE, plotOutput("pk_plot", height = "500px") ) ), nav_panel( "Model Information", div( class = "info-box", tags$h5("Published structural model"), tags$ul( tags$li("Two-compartment disposition with Michaelis-Menten elimination."), tags$li("First-order oral absorption with no lag time."), tags$li("Weight scales CL, Vc, Q, and Vp linearly."), tags$li("Published final model covariates: CYP2C19 genotype group and ALT on clearance.") ), tags$h5("Final model parameters"), tags$ul( tags$li("Km = 3,030 ng/mL (3.03 mg/L)"), tags$li("Vc = 0.807 L/kg, Q = 0.609 L/h/kg, Vp = 2.17 L/kg"), tags$li("Ka = 0.849 h^-1, oral bioavailability = 44.6%"), tags$li("CL in EMs = 0.582 L/h/kg at ALT reference 26.5 IU/L"), tags$li("HEM/PM clearance reduction = 35.5%"), tags$li("ALT exponent on clearance = -0.0931") ), tags$h5("Implementation notes"), tags$ul( tags$li("The CYP2C9 inhibitor effect reported in the paper's alternative final model is intentionally excluded here."), tags$li("This simulator focuses on the typical-subject final model used for the manuscript's dose recommendation."), tags$li("Use the presets to reproduce the paper's recommended IV and oral maintenance regimens.") ) ) ), nav_panel( "References", div( class = "ref-box", tags$h5("Primary reference"), tags$ol( tags$li( "Karlsson MO, Lutsar I, Milligan PA. Population Pharmacokinetic Analysis of Voriconazole Plasma Concentration Data from Pediatric Studies. Antimicrob Agents Chemother. 2009;53(3):935-944. doi:10.1128/AAC.00751-08." ) ), tags$h5("Clinical context from the manuscript"), tags$ul( tags$li("Population: pediatric patients 2 to <12 years of age."), tags$li("Recommended maintenance regimens: 7 mg/kg BID IV or 200 mg BID oral."), tags$li("No loading dose or baseline-covariate dose adjustment recommended by the paper.") ) ) ) ), 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" ), " • Built by Sunny (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) { observeEvent(input$preset, { if (identical(input$preset, "Custom")) { return() } vals <- preset_values(input$preset) updateRadioButtons(session, "route", selected = vals$route) updateRadioButtons(session, "oral_mode", selected = vals$oral_mode) updateSliderInput(session, "iv_dose_mgkg", value = vals$iv_dose_mgkg) updateNumericInput(session, "oral_dose_mg", value = vals$oral_dose_mg) updateSliderInput(session, "oral_dose_mgkg", value = vals$oral_dose_mgkg) updateNumericInput(session, "interval_h", value = vals$interval_h) updateNumericInput(session, "infusion_h", value = vals$infusion_h) }, ignoreInit = TRUE) dose_amount <- reactive({ shiny::req(input$route) if (identical(input$route, "iv")) { input$iv_dose_mgkg * input$wt } else if (identical(input$oral_mode, "mgkg")) { input$oral_dose_mgkg * input$wt } else { input$oral_dose_mg } }) regimen_label <- reactive({ shiny::req(input$route) if (identical(input$route, "iv")) { sprintf("IV infusion %.1f mg/kg Q%sh", input$iv_dose_mgkg, input$interval_h) } else if (identical(input$oral_mode, "mgkg")) { sprintf("Oral %.1f mg/kg Q%sh", input$oral_dose_mgkg, input$interval_h) } else { sprintf("Oral %s mg Q%sh", format(round(input$oral_dose_mg, 1), trim = TRUE), input$interval_h) } }) sim_data <- reactive({ shiny::req(input$interval_h, input$duration_days, input$wt, input$alt) n_doses <- max(1L, as.integer(round(input$duration_days * 24 / input$interval_h))) end_time <- input$duration_days * 24 genotype_flag <- if (identical(input$genotype, "HEM/PM")) 1 else 0 ev_obj <- if (identical(input$route, "iv")) { shiny::req(input$infusion_h > 0) ev( amt = dose_amount(), cmt = 2, ii = input$interval_h, addl = n_doses - 1L, rate = dose_amount() / input$infusion_h ) } else { ev( amt = dose_amount(), cmt = 1, ii = input$interval_h, addl = n_doses - 1L ) } mod %>% param(WT = input$wt, ALT = input$alt, GENO = genotype_flag) %>% ev(ev_obj) %>% mrgsim(end = end_time, delta = 0.05) %>% as.data.frame() |> mutate(time_h = time) }) interval_metrics <- reactive({ data <- sim_data() last_start <- max(0, max(data$time_h, na.rm = TRUE) - input$interval_h) last_interval <- data |> dplyr::filter(time_h >= last_start, time_h <= max(time_h, na.rm = TRUE)) list( cmax = max(last_interval$CP_MG_L, na.rm = TRUE), ctrough = dplyr::last(last_interval$CP_MG_L), auc = trap_auc(last_interval$time_h, last_interval$CP_MG_L) ) }) output$dose_summary <- renderText({ sprintf("Dose = %.1f mg", dose_amount()) }) output$cmax <- renderText({ sprintf("%.2f", interval_metrics()$cmax) }) output$ctrough <- renderText({ sprintf("%.2f", interval_metrics()$ctrough) }) output$auc <- renderText({ sprintf("%.1f", interval_metrics()$auc) }) output$thalf <- renderText({ sprintf("%.1f", calc_linear_half_life(input$wt, input$alt, input$genotype)) }) output$pk_plot <- renderPlot({ data <- sim_data() plot_data <- if (isTRUE(input$log_scale)) { data |> dplyr::filter(CP_MG_L > 0) } else { data } plot_obj <- ggplot(plot_data, aes(x = time_h, y = CP_MG_L)) + geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( title = regimen_label(), subtitle = "Final pediatric PopPK model from Karlsson et al. (2009)", x = "Time (hours)", y = "Voriconazole concentration (mg/L)" ) + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold"), plot.subtitle = element_text(color = "#6c757d") ) if (isTRUE(input$log_scale)) { plot_obj <- plot_obj + scale_y_log10() } plot_obj }) } shinyApp(ui = ui, server = server)