library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ─── Semi-Mechanistic Uric Acid Disposition Model ──────────────────────── # Based on: Aksenov et al. (2018) Physiol Rep 6(5):e13614 # Model: dSUA/dt = kP - CLI*[sUA] - GFR*FE*[sUA] # Drugs: Allopurinol (-> Oxypurinol XOI), Febuxostat (XOI), Lesinurad (Uricosuric) model_code <- ' $PARAM @annotated GFR_ml : 90 : Glomerular filtration rate (mL/min) FE0 : 0.04 : Baseline fractional excretion coefficient CLI : 0.27 : Intestinal clearance of UA (L/h) VUA : 19.0 : Volume of distribution of UA (L) BSUA : 9.0 : Baseline serum UA (mg/dL) KA_OXP : 0.5 : Oxypurinol absorption rate (1/h) CL_OXP : 0.9 : Oxypurinol clearance (L/h) V_OXP : 20.0 : Oxypurinol volume (L) KA_FBX : 3.0 : Febuxostat absorption rate (1/h) CL_FBX : 10.0 : Febuxostat clearance (L/h) V_FBX : 50.0 : Febuxostat volume (L) KA_LSN : 2.5 : Lesinurad absorption rate (1/h) CL_LSN : 6.0 : Lesinurad clearance (L/h) V_LSN : 25.0 : Lesinurad volume (L) RMAX_OXP : 0.84 : Max production inhibition by oxypurinol P50_OXP : 14000 : EC50 oxypurinol (ng per mL) RMAX_FBX : 1.0 : Max production inhibition by febuxostat P50_FBX : 120 : EC50 febuxostat (ng per mL) FMAX_LSN : 0.56 : Max FE increase by lesinurad P50_LSN : 23000 : EC50 lesinurad (ng per mL) $CMT @annotated OXP_D : Oxypurinol depot (mg) OXP_C : Oxypurinol central (mg) FBX_D : Febuxostat depot (mg) FBX_C : Febuxostat central (mg) LSN_D : Lesinurad depot (mg) LSN_C : Lesinurad central (mg) SUA : Uric acid amount in body (mg) UUA : Cumulative uric acid in urine (mg) $MAIN double GFR_Lh = GFR_ml * 0.06; double BSUA_mgL = BSUA * 10.0; double kP0 = BSUA_mgL * (CLI + GFR_Lh * FE0); SUA_0 = BSUA_mgL * VUA; $ODE double CP_OXP = (OXP_C / V_OXP) * 1000.0; double CP_FBX = (FBX_C / V_FBX) * 1000.0; double CP_LSN = (LSN_C / V_LSN) * 1000.0; double inhib_oxp = RMAX_OXP * CP_OXP / (CP_OXP + P50_OXP); double inhib_fbx = RMAX_FBX * CP_FBX / (CP_FBX + P50_FBX); double kP = kP0 * (1.0 - inhib_oxp) * (1.0 - inhib_fbx); double FE = FE0 + FMAX_LSN * CP_LSN / (CP_LSN + P50_LSN); double SUA_conc = SUA / VUA; dxdt_OXP_D = -KA_OXP * OXP_D; dxdt_OXP_C = KA_OXP * OXP_D - (CL_OXP / V_OXP) * OXP_C; dxdt_FBX_D = -KA_FBX * FBX_D; dxdt_FBX_C = KA_FBX * FBX_D - (CL_FBX / V_FBX) * FBX_C; dxdt_LSN_D = -KA_LSN * LSN_D; dxdt_LSN_C = KA_LSN * LSN_D - (CL_LSN / V_LSN) * LSN_C; dxdt_SUA = kP - CLI * SUA_conc - GFR_Lh * FE * SUA_conc; dxdt_UUA = GFR_Lh * FE * SUA_conc; $TABLE double CP_OXP_t = (OXP_C / V_OXP) * 1000.0; double CP_FBX_t = (FBX_C / V_FBX) * 1000.0; double CP_LSN_t = (LSN_C / V_LSN) * 1000.0; double sUA_mgdL = (SUA / VUA) / 10.0; double FE_curr = FE0 + FMAX_LSN * CP_LSN_t / (CP_LSN_t + P50_LSN); double inhib_oxp_t = RMAX_OXP * CP_OXP_t / (CP_OXP_t + P50_OXP); double inhib_fbx_t = RMAX_FBX * CP_FBX_t / (CP_FBX_t + P50_FBX); double inhib_total = 1.0 - (1.0 - inhib_oxp_t) * (1.0 - inhib_fbx_t); double UA_excr_rate = GFR_Lh * FE_curr * (SUA / VUA); $CAPTURE sUA_mgdL FE_curr inhib_total UA_excr_rate ' mod <- mcode("uric_acid_dynamics", 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; } .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 = "Uric Acid Dynamics \u2014 Hyperuricemia Treatment Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 350, h6("\U0001f9ec Patient Parameters"), sliderInput("bsua", "Baseline Serum UA (mg/dL)", min = 6, max = 14, value = 9, step = 0.5), sliderInput("gfr", "GFR (mL/min)", min = 30, max = 150, value = 90, step = 5), sliderInput("fe0", "Fractional Excretion (FE)", min = 0.01, max = 0.12, value = 0.04, step = 0.01), helpText("Normal FE: 0.07\u20130.12 | Underexcretor: 0.01\u20130.05"), hr(), h6("\U0001f48a Treatment"), radioButtons("xoi", "Xanthine Oxidase Inhibitor", choices = c("None" = "none", "Allopurinol" = "allopurinol", "Febuxostat" = "febuxostat"), selected = "allopurinol", inline = TRUE), conditionalPanel( condition = "input.xoi == 'allopurinol'", sliderInput("allo_dose", "Allopurinol (mg QD)", min = 100, max = 900, value = 300, step = 100) ), conditionalPanel( condition = "input.xoi == 'febuxostat'", selectInput("fbx_dose", "Febuxostat (mg QD)", choices = c(40, 80), selected = 40) ), selectInput("lsn_dose", "Lesinurad \u2014 Uricosuric (mg QD)", choices = c("None" = 0, "200 mg" = 200, "400 mg" = 400), selected = 0), helpText("Lesinurad approved only in combination with a XOI"), hr(), h6("\u2699\ufe0f Options"), sliderInput("n_days", "Duration (days)", min = 7, max = 28, value = 14, step = 1), checkboxInput("show_target", "Show Target (< 6 mg/dL)", value = TRUE), checkboxInput("show_cryst", "Show Crystallization (6.8 mg/dL)", value = TRUE) ), navset_card_tab( title = "Uric Acid PK/PD Simulator", full_screen = TRUE, nav_panel("Simulation", layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("baseline_sua")), div(class = "metric-label", "Baseline sUA (mg/dL)")), div(class = "metric-card metric-success", div(class = "metric-value", textOutput("treated_sua")), div(class = "metric-label", "Treated sUA (mg/dL)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("pct_reduction")), div(class = "metric-label", "% Reduction")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("urine_ua")), div(class = "metric-label", "Urine UA (mg/day)")) ), plotOutput("pkPlot", height = "500px") ), nav_panel("Treatment Nomogram", div(style = "padding: 10px 0;", p("Contour lines show the steady-state % reduction in serum UA for combinations of production rate inhibition (XOI effect) and fractional excretion increase (uricosuric effect), calculated for the current patient\u2019s GFR and baseline FE. The red diamond marks the current treatment regimen."), plotOutput("nomogramPlot", height = "500px") ) ), nav_panel("Model Information", markdown(" ## Semi-Mechanistic Uric Acid Disposition Model **Source:** Aksenov, Peck, Eriksson & Stanski (2018) *Physiological Reports* 6(5):e13614 ### Model Structure One-compartment model for uric acid in serum with three clearance pathways: - **Production** (constant rate kP, mg/h) \u2014 from purine metabolism - **Intestinal elimination** (clearance CLI = 0.27 L/h) - **Renal elimination** (GFR \u00d7 FE \u00d7 [sUA]) **Governing Equation:** > dSUA/dt = kP \u2212 CLI \u00d7 [sUA] \u2212 GFR \u00d7 FE \u00d7 [sUA] **Steady State:** > [sUA]ss = kP / (CLI + GFR \u00d7 FE) ### Drug Effect Models - **XO Inhibitors** (allopurinol \u2192 oxypurinol, febuxostat): Emax model decreasing kP - **Uricosurics** (lesinurad): Emax model increasing FE ### Key Parameters (Table 1) | Parameter | Value | Description | |-----------|-------|-------------| | CLI | 0.27 L/h | Intestinal clearance of UA | | VUA | 19 L | Volume of distribution of UA | | Rmax (oxypurinol) | 0.84 | Max production inhibition | | Rmax (febuxostat) | 1.0 | Max production inhibition | | EC50 oxypurinol | 14,000 ng/mL | Half-maximal inhibition | | EC50 febuxostat | 120 ng/mL | Half-maximal (hyperuricemia) | | Fmax (lesinurad) | 0.56 | Max FE increase | | EC50 lesinurad | 23,000 ng/mL | Half-maximal (hyperuricemia) | ### Key Clinical Insights 1. **XOI effect is independent of GFR and FE** \u2014 % sUA reduction by XOI alone depends only on dose 2. **Uricosurics are more effective in underexcreters** (low FE) and patients with normal GFR 3. **Combination XOI + uricosuric** allows lower XOI doses for the same target sUA 4. **Higher urinary UA excretion** with uricosurics \u2014 monitor nephrolithiasis risk 5. **Patients with sUA > 9 mg/dL** unlikely to reach target with standard XOI alone \u2014 consider early combination ### Pharmacokinetics (Simplified) Drug PK modeled as one-compartment oral with published parameters: - **Oxypurinol** (from allopurinol, ~70% conversion): t\u00bd \u2248 15 h - **Febuxostat**: t\u00bd \u2248 3.5 h - **Lesinurad**: t\u00bd \u2248 3 h (transient within-day FE increase) ")), nav_panel("References", div(class = "ref-box", tags$h5("\U0001f4da Key References"), tags$ol( tags$li("Aksenov S, Peck CC, Eriksson UG, Stanski DR. (2018) ", tags$em("Individualized treatment strategies for hyperuricemia informed by a semi-mechanistic exposure-response model of uric acid dynamics."), " Physiol Rep 6(5):e13614. ", tags$a(href = "https://doi.org/10.14814/phy2.13614", target = "_blank", "DOI: 10.14814/phy2.13614")), tags$li("Shen Z et al. (2015) ", tags$em("Pharmacokinetics, pharmacodynamics, and safety of lesinurad."), " Drug Des Devel Ther 9:3423\u20133434."), tags$li("Wright DFB et al. (2013) ", tags$em("Population pharmacokinetics of allopurinol and oxypurinol."), " Eur J Clin Pharmacol 69:1411\u20131421."), tags$li("Khanna D et al. (2012) ", tags$em("American College of Rheumatology guidelines for management of gout. Part 2."), " Arthritis Care Res 64:1447\u20131461."), tags$li("Graham S et al. (1996) ", tags$em("Pharmacodynamics of oxypurinol after administration of allopurinol."), " Br J Clin Pharmacol 41:299\u2013304.") ), tags$h5("\U0001f48a Therapeutic Context"), tags$ul( tags$li(tags$strong("Drug Classes:"), " Xanthine oxidase inhibitors (XOI), Uricosurics (URAT1 inhibitors)"), tags$li(tags$strong("Allopurinol:"), " 100\u2013900 mg QD (prodrug \u2192 active metabolite oxypurinol)"), tags$li(tags$strong("Febuxostat (Uloric):"), " 40\u201380 mg QD (non-purine selective XOI)"), tags$li(tags$strong("Lesinurad (Zurampic):"), " 200 mg QD (approved in combination with XOI only)"), tags$li(tags$strong("Indication:"), " Chronic hyperuricemia associated with gout"), tags$li(tags$strong("Treatment Target:"), " Serum UA < 6 mg/dL (ACR guideline); < 5 mg/dL often recommended"), tags$li(tags$strong("Crystallization:"), " UA precipitates above ~6.8 mg/dL in physiological fluids") ), tags$h5("\U0001f52c Model Insights from Paper"), tags$ul( tags$li("At allopurinol 300 mg/day: ~35% reduction in sUA, independent of patient GFR/FE"), tags$li("Patients with sUA > 9 mg/dL will not reach 6 mg/dL target on 300 mg allopurinol alone"), tags$li("Adding lesinurad 200 mg increases FE by ~0.05 on average, allowing reduced XOI dose"), tags$li("Model validated against Phase I (renal impairment) and Phase III data (647 hyperuricemic patients)"), tags$li("Patient 'parametypes' (GFR \u00d7 FE space) determine optimal treatment strategy") ) )) ), 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)", tags$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({ n_days <- input$n_days n_doses <- n_days - 1 ev_all <- NULL # XOI dosing if (input$xoi == "allopurinol") { oxyp_dose <- input$allo_dose * 0.7 ev_all <- ev(amt = oxyp_dose, cmt = 1, ii = 24, addl = n_doses) } else if (input$xoi == "febuxostat") { ev_all <- ev(amt = as.numeric(input$fbx_dose), cmt = 3, ii = 24, addl = n_doses) } # Lesinurad dosing lsn <- as.numeric(input$lsn_dose) if (lsn > 0) { ev_lsn <- ev(amt = lsn, cmt = 5, ii = 24, addl = n_doses) if (is.null(ev_all)) { ev_all <- ev_lsn } else { ev_all <- c(ev_all, ev_lsn) } } m <- mod %>% param(GFR_ml = input$gfr, FE0 = input$fe0, BSUA = input$bsua) if (!is.null(ev_all)) { m <- m %>% ev(ev_all) } m %>% mrgsim(end = n_days * 24, delta = 0.5) %>% as.data.frame() %>% mutate(time_d = time / 24) }) # ─── Metric Cards ────────────────────────────────────────────────────── output$baseline_sua <- renderText(sprintf("%.1f", input$bsua)) output$treated_sua <- renderText({ d <- sim_data() last_day <- d %>% filter(time >= (input$n_days - 1) * 24) sprintf("%.1f", mean(last_day$sUA_mgdL, na.rm = TRUE)) }) output$pct_reduction <- renderText({ d <- sim_data() last_day <- d %>% filter(time >= (input$n_days - 1) * 24) avg_treated <- mean(last_day$sUA_mgdL, na.rm = TRUE) pct <- (input$bsua - avg_treated) / input$bsua * 100 sprintf("%.0f%%", pct) }) output$urine_ua <- renderText({ d <- sim_data() last_day <- d %>% filter(time >= (input$n_days - 1) * 24) avg_rate_h <- mean(last_day$UA_excr_rate, na.rm = TRUE) sprintf("%.0f", avg_rate_h * 24) }) # ─── Main Simulation Plot ────────────────────────────────────────────── output$pkPlot <- renderPlot({ d <- sim_data() # Build treatment label parts <- c() if (input$xoi == "allopurinol") parts <- c(parts, paste0("Allopurinol ", input$allo_dose, " mg")) if (input$xoi == "febuxostat") parts <- c(parts, paste0("Febuxostat ", input$fbx_dose, " mg")) lsn <- as.numeric(input$lsn_dose) if (lsn > 0) parts <- c(parts, paste0("Lesinurad ", lsn, " mg")) if (length(parts) == 0) parts <- "No Treatment" title_txt <- paste0("Serum Uric Acid \u2014 ", paste(parts, collapse = " + "), " QD") ymax <- max(c(d$sUA_mgdL, 8), na.rm = TRUE) * 1.1 p <- ggplot(d, aes(x = time_d, y = sUA_mgdL)) + geom_line(color = "#8b5cf6", linewidth = 1) + labs(x = "Time (days)", y = "Serum UA (mg/dL)", title = title_txt) + theme_minimal(base_size = 14) + theme(plot.title = element_text(face = "bold")) + coord_cartesian(ylim = c(0, ymax)) if (input$show_target) { p <- p + annotate("rect", xmin = -Inf, xmax = Inf, ymin = 0, ymax = 6, fill = "#10b981", alpha = 0.08) + geom_hline(yintercept = 6, linetype = "dashed", color = "#10b981", alpha = 0.7) + annotate("text", x = max(d$time_d) * 0.02, y = 5.6, label = "Target: < 6 mg/dL", hjust = 0, size = 3.5, color = "#10b981") } if (input$show_cryst) { p <- p + geom_hline(yintercept = 6.8, linetype = "dotted", color = "#f59e0b", linewidth = 0.6) + annotate("text", x = max(d$time_d) * 0.98, y = 7.1, label = "Crystallization: 6.8 mg/dL", hjust = 1, size = 3.2, color = "#f59e0b") } # Baseline reference line p <- p + geom_hline(yintercept = input$bsua, linetype = "solid", color = "#adb5bd", linewidth = 0.4) + annotate("text", x = max(d$time_d) * 0.98, y = input$bsua + ymax * 0.02, label = paste0("Baseline: ", input$bsua, " mg/dL"), hjust = 1, size = 3, color = "#adb5bd") p }) # ─── Nomogram Plot ───────────────────────────────────────────────────── output$nomogramPlot <- renderPlot({ GFR_Lh <- input$gfr * 0.06 CLI_val <- 0.27 FE0_val <- input$fe0 inhib_seq <- seq(0, 0.95, length.out = 100) dFE_seq <- seq(0, 0.15, length.out = 100) grid <- expand.grid(inhib = inhib_seq, dFE = dFE_seq) grid$pct_red <- with(grid, { (1 - (1 - inhib) * (CLI_val + GFR_Lh * FE0_val) / (CLI_val + GFR_Lh * (FE0_val + dFE))) * 100 }) # Compute current treatment position on nomogram curr_inhib <- 0 curr_dFE <- 0 if (input$xoi == "allopurinol") { css_oxp <- (input$allo_dose * 0.7) / (0.9 * 24) * 1000 # ng/mL curr_inhib <- 0.84 * css_oxp / (css_oxp + 14000) } else if (input$xoi == "febuxostat") { css_fbx <- as.numeric(input$fbx_dose) / (10 * 24) * 1000 curr_inhib <- 1.0 * css_fbx / (css_fbx + 120) } lsn <- as.numeric(input$lsn_dose) if (lsn > 0) { css_lsn <- lsn / (6 * 24) * 1000 curr_dFE <- 0.56 * css_lsn / (css_lsn + 23000) } curr_pct <- (1 - (1 - curr_inhib) * (CLI_val + GFR_Lh * FE0_val) / (CLI_val + GFR_Lh * (FE0_val + curr_dFE))) * 100 curr_pt <- data.frame(x = curr_dFE, y = curr_inhib * 100, pct = curr_pct) ggplot(grid, aes(x = dFE, y = inhib * 100)) + geom_contour_filled(aes(z = pct_red), breaks = seq(0, 100, by = 10), alpha = 0.7) + geom_contour(aes(z = pct_red), breaks = seq(10, 90, by = 10), color = "grey30", linewidth = 0.3, linetype = "solid") + geom_contour(aes(z = pct_red), breaks = 50, color = "white", linewidth = 0.8, linetype = "dashed") + geom_point(data = curr_pt, aes(x = x, y = y), color = "red", size = 5, shape = 18, inherit.aes = FALSE) + annotate("label", x = min(curr_pt$x + 0.008, 0.12), y = min(curr_pt$y + 5, 90), label = paste0("Current Rx\n", round(curr_pct, 0), "% reduction"), color = "red", size = 3.5, fontface = "bold", fill = "white", alpha = 0.85) + scale_fill_viridis_d(name = "% sUA\nReduction", option = "plasma") + labs(x = "Increase in Fractional Excretion (\u0394FE from uricosuric)", y = "% Inhibition of UA Production Rate (from XOI)", title = paste0("Treatment Nomogram \u2014 GFR ", input$gfr, " mL/min, Baseline FE = ", input$fe0), subtitle = "White dashed line = 50% reduction target") + theme_minimal(base_size = 14) + theme(plot.title = element_text(face = "bold"), plot.subtitle = element_text(color = "grey50")) }) } shinyApp(ui = ui, server = server)