library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ── Levamisole Two-Compartment Oral PK Model ── # Based on allometric 2CM joint fitting across 8 species (2 bird, 6 mammalian) # Source: Cheng CF, Jeong YS, Jusko WJ (2026) J Pharm Invest 56:171-183 # Human parameters from Table 1 (CL) and Table 2 (V1, V2, Q allometric) # Ka and F estimated/fixed in joint model fitting model_code <- ' $PARAM @annotated CL : 17.5 : Clearance (L/h) at 70 kg V1 : 88.1 : Central volume (L) at 70 kg V2 : 40.3 : Peripheral volume (L) at 70 kg Q : 17.5 : Intercompartmental clearance (L/h) at 70 kg KA : 1.702 : Absorption rate constant (1/h) F1 : 0.66 : Oral bioavailability WT : 70 : Body weight (kg) $CMT @annotated DEPOT : Oral absorption depot (mg) CENT : Central compartment (mg) PERIPH : Peripheral compartment (mg) $MAIN double CLi = CL * pow(WT / 70.0, 0.75); double V1i = V1 * (WT / 70.0); double V2i = V2 * (WT / 70.0); double Qi = Q * pow(WT / 70.0, 0.75); F_DEPOT = F1; $ODE dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CLi / V1i + Qi / V1i) * CENT + (Qi / V2i) * PERIPH; dxdt_PERIPH = (Qi / V1i) * CENT - (Qi / V2i) * PERIPH; $TABLE double CP = CENT / V1i; $CAPTURE @annotated CP : Plasma concentration (mg/L) ' mod <- mcode("levamisole", model_code) # ── PKPDBuilder Brand 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 = "Levamisole PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), numericInput("dose", "Dose (mg)", value = 150, min = 10, max = 500, step = 25), sliderInput("interval", "Dosing Interval (h)", min = 6, max = 24, value = 8, step = 2), numericInput("n_days", "Duration (days)", value = 3, min = 1, max = 14), hr(), h6("Patient"), sliderInput("wt", "Weight (kg)", min = 40, max = 150, value = 70, step = 5), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE) ), # ── Metric Cards ── 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", paste0("AUC", "\u2080", "-", "\u03C4", " (mg\u00B7h/L)")) ), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", paste0("t\u00BD", " (h)")) ) ), # ── PK Plot ── card( card_header("Concentration-Time Profile"), full_screen = TRUE, plotOutput("pkPlot", height = "500px") ), # ── Tabbed Info ── navset_card_underline( title = "Drug Information", nav_panel("Model Information", markdown(" ## Levamisole — Two-Compartment Oral PK Model **Structure:** Two-compartment with first-order absorption and allometric weight scaling **Source:** Meta-analysis across 8 species (duck, rabbit, chicken, goat, dog, sheep, pig, human) ### Human PK Parameters (at 70 kg) | Parameter | Value | Description | |-----------|-------|-------------| | CL | 17.5 L/h | Systemic clearance | | V1 (Vp) | 88.1 L | Central volume of distribution | | V2 (Vt) | 40.3 L | Peripheral volume of distribution | | Q (CLd) | 17.5 L/h | Distributional clearance | | Ka | 1.702 h\u207B\u00B9 | Absorption rate constant | | F | 0.66 | Oral bioavailability | | t\u00BD | ~5.7 h | Terminal elimination half-life | ### Allometric Scaling (Cross-Species) - **V1** = 0.339 \u00D7 BW^1.31 (exponent from joint fitting) - **V2** = 0.937 \u00D7 BW^0.885 - **Q** = 0.352 \u00D7 BW^0.920 - **CL** scaled with standard 0.75 exponent (within-human range) ### Pharmacological Context - **Immunomodulant** — enhances T-cell function, used adjunctively in colon cancer (with 5-FU) - **Antiparasitic** — broad-spectrum anthelmintic across veterinary and human medicine - **Absorption:** Rapid (Tmax ~1.5 h), high solubility (210 mg/mL), BCS debated (Class I vs III) - **Metabolism:** Extensive hepatic; p-hydroxylation is key pathway in humans; renal CL only ~5% of total - **Bioavailability:** Remarkably consistent across species (50-80%), except goats ### Clinical Dosing - **Antiparasitic:** 150 mg single oral dose (2.5 mg/kg) - **Cancer adjuvant:** 50 mg TID \u00D7 3 days, every 2 weeks (with 5-FU) - **Immunomodulation:** 50 mg/day or 2.5 mg/kg intermittently ")), nav_panel("References", div(class = "ref-box", tags$h5("\U0001F4DA Key References"), tags$ol( tags$li("Cheng CF, Jeong YS, Jusko WJ (2026) Meta-analysis of levamisole absorption and disposition across diverse species using a minimal physiologically-based pharmacokinetic model. J Pharm Invest 56:171-183. ", tags$a(href = "https://doi.org/10.1007/s40005-025-00770-6", target = "_blank", "DOI")), tags$li("Kouassi E et al. (1986) Novel assay and pharmacokinetics of levamisole and p-hydroxylevamisole in human plasma and urine. Biopharm Drug Dispos 7(1):71-89"), tags$li("Luyckx M et al. (1982) Pharmacokinetics of levamisole in healthy subjects and cancer patients. Eur J Drug Metab Pharmacokinet 7:247-254"), tags$li("Kambayashi A et al. (2023) Biowaiver monograph for immediate-release dosage forms: levamisole hydrochloride. J Pharm Sci 112(3):634-639") ), tags$h5("\U0001F48A Therapeutic Context"), tags$ul( tags$li(tags$strong("Class:"), " Imidazothiazole; immunomodulant / anthelmintic"), tags$li(tags$strong("MW:"), " 204.29 g/mol; pKa = 6.98"), tags$li(tags$strong("Route:"), " Oral (high solubility, rapid absorption)"), tags$li(tags$strong("Metabolism:"), " Hepatic oxidation & hydrolysis; p-hydroxylation (major in humans)"), tags$li(tags$strong("Note:"), " FDA-approved in 1990 for colon cancer adjuvant (withdrawn 2000); widely used in veterinary medicine worldwide") ) )) ), # ── 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"), " \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_data <- reactive({ shiny::req(input$dose > 0, input$interval > 0, input$n_days >= 1) n_doses <- max(1, floor(input$n_days * 24 / input$interval)) ev1 <- ev(amt = input$dose, cmt = 1, ii = input$interval, addl = n_doses - 1) mod %>% param(WT = input$wt) %>% ev(ev1) %>% mrgsim(end = input$n_days * 24, delta = 0.1) %>% as.data.frame() |> mutate(time_h = time) }) # Steady-state interval data (last dosing interval) last_interval <- reactive({ d <- sim_data() last_start <- max(0, (input$n_days - 1) * 24) d |> dplyr::filter(time >= last_start) }) output$cmax <- renderText({ d <- last_interval() shiny::req(nrow(d) > 0) sprintf("%.3f", max(d$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- last_interval() shiny::req(nrow(d) > 0) sprintf("%.4f", min(d$CP[d$CP > 0], na.rm = TRUE)) }) output$auc <- renderText({ d <- sim_data() shiny::req(nrow(d) > 1) last_start <- max(0, (input$n_days - 1) * 24) last <- d |> dplyr::filter(time >= last_start, time <= last_start + input$interval) shiny::req(nrow(last) > 1) auc_val <- sum(diff(last$time) * (head(last$CP, -1) + tail(last$CP, -1)) / 2) sprintf("%.2f", auc_val) }) output$thalf <- renderText({ wt <- input$wt CLi <- 17.5 * (wt / 70)^0.75 V1i <- 88.1 * (wt / 70) V2i <- 40.3 * (wt / 70) Qi <- 17.5 * (wt / 70)^0.75 k10 <- CLi / V1i k12 <- Qi / V1i k21 <- Qi / V2i s <- k10 + k12 + k21 p <- k10 * k21 disc <- s^2 - 4 * p disc <- max(disc, 0) beta_val <- (s - sqrt(disc)) / 2 if (beta_val > 0) { sprintf("%.1f", log(2) / beta_val) } else { "N/A" } }) output$pkPlot <- renderPlot({ d <- sim_data() shiny::req(nrow(d) > 0) d_plot <- if (input$log_scale) d |> dplyr::filter(CP > 0.0001) else d shiny::req(nrow(d_plot) > 0) p <- ggplot(d_plot, aes(x = time_h, y = CP)) + geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( x = "Time (hours)", y = "Concentration (mg/L)", title = paste0("Levamisole ", input$dose, " mg Q", input$interval, "H \u00D7 ", input$n_days, " days (", input$wt, " kg)") ) + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold", size = 15), panel.grid.minor = element_line(color = "#f0f0f0") ) if (input$log_scale) { p <- p + scale_y_log10( labels = scales::label_number(drop0trailing = TRUE) ) } p }) } shinyApp(ui = ui, server = server)