library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # Moxifloxacin pediatric PopPK model: 2-compartment + transit absorption # Based on Palmer et al. 2025, Br J Clin Pharmacol (CATALYST trial) # Allometric scaling with body weight on all disposition parameters # Parameters referenced to 16 kg child model_code <- ' $PARAM @annotated CL : 6.90 : Apparent clearance CL/F at 16 kg (L/h) VC : 61.1 : Central volume Vc/F at 16 kg (L) QP : 0.86 : Intercompartmental CL Q/F at 16 kg (L/h) VP : 44.5 : Peripheral volume Vp/F at 16 kg (L) MAT : 1.01 : Mean absorption time (h) F1 : 1.0 : Bioavailability (fixed) WT : 16 : Body weight (kg) $CMT @annotated DEPOT : Oral depot (mg) TRANS1 : Transit 1 (mg) TRANS2 : Transit 2 (mg) TRANS3 : Transit 3 (mg) CENT : Central (mg) PERIPH : Peripheral (mg) $MAIN double CLi = CL * pow(WT / 16.0, 0.75); double VCi = VC * (WT / 16.0); double QPi = QP * pow(WT / 16.0, 0.75); double VPi = VP * (WT / 16.0); double KTR = 4.0 / MAT; F_DEPOT = F1; $ODE dxdt_DEPOT = -KTR * DEPOT; dxdt_TRANS1 = KTR * DEPOT - KTR * TRANS1; dxdt_TRANS2 = KTR * TRANS1 - KTR * TRANS2; dxdt_TRANS3 = KTR * TRANS2 - KTR * TRANS3; dxdt_CENT = KTR * TRANS3 - (CLi / VCi) * CENT - (QPi / VCi) * CENT + (QPi / VPi) * PERIPH; dxdt_PERIPH = (QPi / VCi) * CENT - (QPi / VPi) * PERIPH; $TABLE double CP = CENT / VCi; $CAPTURE CP ' mod <- mcode("moxifloxacin_peds", 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; } .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; } .dose-box { background: #fff3e0; border-left: 4px solid #f59e0b; padding: 12px 16px; border-radius: 4px; margin-top: 8px; font-size: 13px; } ") # WHO dosing function who_dose <- function(wt) { if (wt < 5) return(NA) if (wt < 7) return(80) if (wt < 10) return(150) if (wt < 16) return(200) if (wt < 24) return(300) return(400) } proposed_dose <- function(wt) { if (wt < 5) return(NA) if (wt < 7) return(125) if (wt < 10) return(150) if (wt < 16) return(200) if (wt < 24) return(300) return(400) } ui <- page_sidebar( title = "Moxifloxacin Pediatric PK Simulator \u2014 RR-TB", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), sliderInput("wt", "Body Weight (kg)", min = 5, max = 70, value = 16, step = 1), uiOutput("dose_recommendation"), numericInput("dose", "Dose (mg)", value = 200, min = 25, max = 400, step = 25), numericInput("n_days", "Duration (days)", value = 5, min = 1, max = 14), hr(), h6("Display Options"), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), checkboxInput("show_target", "Show Adult Reference Range", value = TRUE), hr(), div(class = "dose-box", tags$strong("\U0001F4CB WHO Weight-Band Dosing"), br(), tags$small( "5\u2013<7 kg: 80 mg (WHO) / 125 mg (proposed)", br(), "7\u2013<10 kg: 150 mg", br(), "10\u2013<16 kg: 200 mg", br(), "16\u2013<24 kg: 300 mg", br(), "\u226524 kg: 400 mg" ) ) ), navset_card_tab( title = "Moxifloxacin Pediatric PK Simulator", full_screen = TRUE, 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,ss (mg/L)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "C24h (mg/L)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUC\u2080\u208B\u2082\u2084,ss (mg\u00B7h/L)")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "t\u00BD (h)")) ), plotOutput("pkPlot", height = "500px") ), nav_panel("Model Information", markdown(" ## Moxifloxacin \u2014 Pediatric Population PK Model (CATALYST Trial) **Drug:** Moxifloxacin (fluoroquinolone antibiotic) **Brand:** Avelox\u00AE (adults), dispersible 100 mg scored tablet (pediatric) **Class:** Fluoroquinolone antibiotic (VEGFR Group A drug for RR-TB) **Indication:** Rifampicin-resistant tuberculosis (RR-TB) in children **Route:** Oral, once daily ### Model Structure - **Type:** Two-compartment disposition with first-order elimination - **Absorption:** 3 transit compartments with mean absorption time (MAT = 1.01 h) - **Scaling:** Allometric with body weight (exponent 0.75 for CL/Q, 1.0 for V) - **Software:** NONMEM 7.5 (original); reproduced with mrgsolve - **Data:** 438 observations from 36 children (0.4\u201315 years, 6.9\u201342.1 kg) ### Population PK Parameters (Reference: 16 kg child) | Parameter | Value | RSE (%) | Description | |-----------|-------|---------|-------------| | CL/F | 6.90 L/h | 3.5 | Apparent clearance | | Vc/F | 61.1 L | 3.7 | Apparent central volume | | Q/F | 0.86 L/h | 19 | Intercompartmental clearance | | Vp/F | 44.5 L | 36 | Peripheral volume | | MAT | 1.01 h | 5.5 | Mean absorption time | | F | 1 (fixed) | \u2014 | Bioavailability | ### Allometric Scaling - CL/F and Q/F scale with (WT/16)\u2070\u00B7\u2077\u2075 - Vc/F and Vp/F scale with (WT/16)\u00B9 ### Formulation - **Dispersible 100 mg** vs **standard 400 mg** tablet: bioequivalent (ratio 1.05, 90% CI 0.95\u20131.15) - Same dosing recommendations apply to both formulations ### Adult Reference Exposures (400 mg QD) - AUC\u2080\u208B\u2082\u2084: median 37 mg\u00B7h/L (2.5th\u201397.5th: 15\u201376 mg\u00B7h/L) - Cmax: median 3.1 mg/L (2.5th\u201397.5th: 1.4\u20136.7 mg/L) ")), nav_panel("References", div(class = "ref-box", tags$h5("\U0001F4DA Key References"), tags$ol( tags$li("Palmer M, Zou Y, Hesseling AC, et al. Population pharmacokinetics and dosing of dispersible moxifloxacin formulation in children with rifampicin-resistant tuberculosis. Br J Clin Pharmacol. 2025;91(6):1853-1864."), tags$li("Radtke KK, Hesseling AC, Winckler JL, et al. Moxifloxacin pharmacokinetics, cardiac safety, and dosing for the treatment of rifampicin-resistant tuberculosis in children. Clin Infect Dis. 2022;74(8):1372-1381."), tags$li("Zvada SP, Denti P, Geldenhuys H, et al. Moxifloxacin population pharmacokinetics in patients with pulmonary tuberculosis. Antimicrob Agents Chemother. 2012;56(8):4471-4473."), tags$li(tags$a(href = "https://www.accessdata.fda.gov/drugsatfda_docs/label/2016/021085s063lbl.pdf", target = "_blank", "Avelox (moxifloxacin) FDA Prescribing Information")) ), tags$h5("\U0001F48A Therapeutic Context"), tags$ul( tags$li(tags$strong("Class:"), " Fluoroquinolone antibiotic (WHO Group A drug for RR-TB)"), tags$li(tags$strong("Mechanism:"), " Inhibits bacterial DNA gyrase and topoisomerase IV"), tags$li(tags$strong("Adult dose:"), " 400 mg orally once daily"), tags$li(tags$strong("Bioavailability:"), " ~90% (unaffected by food)"), tags$li(tags$strong("Protein binding:"), " 30\u201350%"), tags$li(tags$strong("Half-life:"), " ~12 h in adults"), tags$li(tags$strong("Metabolism:"), " Phase II: sulfate conjugation (~38%, SULT2A1) and glucuronidation (~14%, UGT1A1); ~20% renal excretion unchanged") ), tags$h5("\U0001F52C Key Findings"), tags$ul( tags$li("Dispersible 100 mg formulation is bioequivalent to standard 400 mg tablet in children"), tags$li("WHO-recommended doses achieve adult-like exposures in children >10 kg"), tags$li("Children <10 kg may need 33\u201356% higher doses than current WHO recommendations"), tags$li("Study: CATALYST trial \u2014 36 children across South Africa, India, and Philippines") ) )) ), 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 <- function(input, output, session) { output$dose_recommendation <- renderUI({ wt <- input$wt who <- who_dose(wt) prop <- proposed_dose(wt) dose_text <- paste0("WHO dose: ", who, " mg") if (who != prop) dose_text <- paste0(dose_text, " | Proposed: ", prop, " mg") tags$div( style = "background: #e8f5e9; padding: 6px 10px; border-radius: 4px; font-size: 12px; margin-bottom: 8px;", tags$strong(paste0("\u2705 ", input$wt, " kg \u2192 ")), dose_text ) }) observe({ updateNumericInput(session, "dose", value = who_dose(input$wt)) }) sim_data <- reactive({ total_hours <- input$n_days * 24 n_doses <- input$n_days ev1 <- ev(amt = input$dose, cmt = 1, ii = 24, addl = max(n_doses - 1, 0)) mod %>% param(WT = input$wt) %>% ev(ev1) %>% mrgsim(end = total_hours, delta = 0.25) %>% as.data.frame() %>% mutate(time_h = time, time_days = time / 24) }) last_interval_data <- reactive({ d <- sim_data() total_hours <- input$n_days * 24 last_dose_time <- total_hours - 24 d %>% filter(time_h >= last_dose_time, time_h <= total_hours) }) output$cmax <- renderText({ li <- last_interval_data() if (nrow(li) < 2) return("--") sprintf("%.2f", max(li$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ li <- last_interval_data() if (nrow(li) < 2) return("--") sprintf("%.2f", min(li$CP, na.rm = TRUE)) }) output$auc <- renderText({ li <- last_interval_data() if (nrow(li) < 2) return("--") auc_val <- sum(diff(li$time_h) * (head(li$CP, -1) + tail(li$CP, -1)) / 2) sprintf("%.1f", auc_val) }) output$thalf <- renderText({ CLi <- 6.90 * (input$wt / 16)^0.75 VCi <- 61.1 * (input$wt / 16) sprintf("%.1f", log(2) / (CLi / VCi)) }) output$pkPlot <- renderPlot({ d <- sim_data() p <- ggplot(d, aes(x = time_h, y = CP)) + geom_line(color = "#8b5cf6", linewidth = 0.8) if (input$show_target) { # Adult reference Cmax range p <- p + annotate("rect", xmin = -Inf, xmax = Inf, ymin = 1.4, ymax = 6.7, fill = "#10b981", alpha = 0.07) + geom_hline(yintercept = 3.1, linetype = "dashed", color = "#10b981", alpha = 0.6) + annotate("text", x = max(d$time_h) * 0.02, y = 6.9, label = "Adult Cmax range: 1.4\u20136.7 mg/L (median 3.1)", hjust = 0, size = 3.2, color = "#10b981") } dose_per_kg <- round(input$dose / input$wt, 1) p <- p + labs( x = "Time (hours)", y = "Concentration (mg/L)", title = paste0("Moxifloxacin ", input$dose, " mg QD (", dose_per_kg, " mg/kg) \u2014 ", input$wt, " kg child"), subtitle = paste0("CL/F = ", round(6.90 * (input$wt/16)^0.75, 1), " L/h | ", "Vc/F = ", round(61.1 * (input$wt/16), 0), " L") ) + theme_minimal(base_size = 14) + theme(plot.title = element_text(face = "bold")) if (input$log_scale) p <- p + scale_y_log10() p }) } shinyApp(ui = ui, server = server)