library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) model_code <- ' $PARAM @annotated CL : 0.808 : Apparent clearance CL/F (L/h) V : 50.4 : Apparent volume of distribution V/F (L) KA : 2.52 : Absorption rate constant (1/h) TLAG : 0.463 : Absorption lag time (h) F1 : 1.0 : Bioavailability (relative) WT : 73 : Body weight (kg) PPI : 0 : Proton-pump inhibitor coadministration (0=No, 1=Yes) HLTH : 0 : Health status (0=Cancer patient, 1=Healthy) $CMT @annotated DEPOT : Oral depot (mg) CENT : Central (mg) $MAIN double CLi = CL * pow(WT / 73.0, 0.43); double Vi = V * pow(WT / 73.0, 0.924); if (HLTH == 1) Vi = Vi * (1.0 - 0.0908); double KAi = KA; if (PPI == 1) KAi = KA * (1.0 - 0.607); ALAG_DEPOT = TLAG; F_DEPOT = F1; $ODE dxdt_DEPOT = -KAi * DEPOT; dxdt_CENT = KAi * DEPOT - (CLi / Vi) * CENT; $TABLE double CP = CENT / Vi * 1000; // mg/L to ng/mL $CAPTURE CP ' mod <- mcode("fruquintinib", 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; } ") ui <- page_sidebar( title = "Fruquintinib (FRUZAQLA) PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), sliderInput("dose", "Dose (mg)", min = 1, max = 6, value = 5, step = 1), selectInput("schedule", "Dosing Schedule", choices = c("3 weeks on / 1 week off (3/1)" = "3_1", "Continuous daily" = "continuous"), selected = "3_1"), numericInput("n_cycles", "Number of Cycles", value = 3, min = 1, max = 6), hr(), h6("Patient Characteristics"), sliderInput("wt", "Weight (kg)", min = 36, max = 158, value = 73, step = 1), checkboxInput("ppi", "Concurrent PPI Use", value = FALSE), checkboxInput("healthy", "Healthy Subject (vs Cancer Patient)", value = FALSE), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), checkboxInput("show_target", "Show VEGFR-2 Target (176 ng/mL)", value = TRUE) ), navset_card_tab( title = "Fruquintinib 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 (ng/mL)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Cmin,ss (ng/mL)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUCss,\u03C4 (ng\u00B7h/mL)")), 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(" ## Fruquintinib (FRUZAQLA\u2122) \u2014 One-Compartment Population PK Model **Drug:** Fruquintinib (6-[6,7-dimethoxyquinazolin-4-yloxy]-N,2-dimethyl benzofuran-3-carboxamide) **Brand Name:** FRUZAQLA\u2122 **Class:** Selective VEGFR-1, -2, -3 Tyrosine Kinase Inhibitor **Indication:** Previously treated metastatic colorectal cancer (mCRC) **Route:** Oral capsule, once daily ### Model Structure - **Type:** One-compartment with first-order absorption, lag time, and linear elimination - **Software:** NONMEM 7.4.3 (original); reproduced with mrgsolve - **Data:** 6,668 fruquintinib concentrations from 557 subjects across 6 clinical trials ### Population PK Parameters (Typical Values, 73 kg Cancer Patient) | Parameter | Value | RSE (%) | Description | |-----------|-------|---------|-------------| | CL/F | 0.808 L/h | 1.2 | Apparent clearance | | V/F | 50.4 L | 0.9 | Apparent volume of distribution | | Ka | 2.52 h\u207B\u00B9 | 7.6 | Absorption rate constant | | Tlag | 0.463 h | 1.1 | Absorption lag time | | t\u00BD | 43.2 h | \u2014 | Elimination half-life | ### Covariate Effects - **Body weight on CL/F:** Allometric exponent 0.43 - **Body weight on V/F:** Allometric exponent 0.924 - **PPI coadministration on Ka:** 60.7% reduction (no meaningful effect on systemic exposure) - **Healthy status on V/F:** 9.08% lower (no meaningful effect on exposure) ### Dosing Information - **Approved dose:** 5 mg QD, 3 weeks on / 1 week off (28-day cycles) - **Linear PK** over 1\u20136 mg dose range - **Steady state** reached by Day 14 of repeated dosing - **VEGFR-2 target:** Cmin,ss > 176 ng/mL for >80% VEGFR-2 phosphorylation inhibition ")), nav_panel("References", div(class = "ref-box", tags$h5("\U0001F4DA Key References"), tags$ol( tags$li("Zhou X, Yang X, Grinshpun B, et al. Population Pharmacokinetics of Fruquintinib, a Selective Oral Inhibitor of VEGFR-1, -2, and -3, in Patients with Refractory Metastatic Colorectal Cancer. J Clin Pharmacol. 2025;65(7):873-884. PMID: 39969131."), tags$li("Dasari A, Lonardi S, Garcia-Carbonero R, et al. Fruquintinib versus placebo in patients with refractory metastatic colorectal cancer (FRESCO-2). Lancet. 2023;402(10395):41-53."), tags$li(tags$a(href = "https://www.accessdata.fda.gov/drugsatfda_docs/label/2023/217564s000lbl.pdf", target = "_blank", "FRUZAQLA (fruquintinib) FDA Prescribing Information (2023)")) ), tags$h5("\U0001F48A Therapeutic Context"), tags$ul( tags$li(tags$strong("Class:"), " Selective VEGFR-1/2/3 tyrosine kinase inhibitor"), tags$li(tags$strong("Mechanism:"), " Inhibits angiogenesis by blocking VEGFR signaling, restricting tumor growth"), tags$li(tags$strong("Approved dose:"), " 5 mg QD orally, 3 weeks on / 1 week off"), tags$li(tags$strong("FDA approval:"), " 2023 for adult mCRC patients previously treated with fluoropyrimidine-, oxaliplatin-, irinotecan-based chemo, anti-VEGF, and (if RAS WT) anti-EGFR therapy"), tags$li(tags$strong("Key trial:"), " FRESCO-2 \u2014 OS 7.4 vs 4.8 months (HR 0.66, P < .0001)"), tags$li(tags$strong("Metabolism:"), " CYP450 and non-CYP450 enzymes; M11 is major circulating metabolite (~17% of total radioactivity)"), tags$li(tags$strong("Protein binding:"), " ~95%") ), tags$h5("\U0001F52C Model Validation"), tags$ul( tags$li("Based on 6,668 concentration observations from 557 subjects in 6 clinical trials"), tags$li("Validated via prediction-corrected VPCs, GOF plots, and bootstrap analysis"), tags$li("BSV on CL/F: 26.2% CV; all fixed-effect RSEs < 30%") ) )) ), 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) { sim_data <- reactive({ cycle_days <- 28 total_days <- input$n_cycles * cycle_days total_hours <- total_days * 24 if (input$schedule == "3_1") { # 3 weeks on / 1 week off per cycle events <- lapply(seq_len(input$n_cycles), function(cyc) { start_h <- (cyc - 1) * cycle_days * 24 ev(amt = input$dose, cmt = 1, ii = 24, addl = 20, time = start_h) }) ev_combined <- do.call(c, events) } else { # Continuous daily ev_combined <- ev(amt = input$dose, cmt = 1, ii = 24, addl = total_days - 1) } mod %>% param(WT = input$wt, PPI = as.numeric(input$ppi), HLTH = as.numeric(input$healthy)) %>% ev(ev_combined) %>% mrgsim(end = total_hours, delta = 0.5) %>% as.data.frame() %>% mutate( time_h = time, time_days = time / 24 ) }) last_dosing_data <- reactive({ d <- sim_data() cycle_days <- 28 total_days <- input$n_cycles * cycle_days if (input$schedule == "3_1") { # Last on-treatment interval: last cycle, day 20 to day 21 (last dose interval) last_cycle_start <- (input$n_cycles - 1) * cycle_days last_dose_day <- last_cycle_start + 20 # 21st day of dosing (0-indexed: day 20) start_h <- last_dose_day * 24 end_h <- start_h + 24 } else { start_h <- (total_days - 1) * 24 end_h <- total_days * 24 } d %>% filter(time_h >= start_h, time_h <= end_h) }) output$cmax <- renderText({ li <- last_dosing_data() if (nrow(li) < 2) return("--") sprintf("%.0f", max(li$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ li <- last_dosing_data() if (nrow(li) < 2) return("--") sprintf("%.0f", min(li$CP, na.rm = TRUE)) }) output$auc <- renderText({ li <- last_dosing_data() if (nrow(li) < 2) return("--") auc_val <- sum(diff(li$time_h) * (head(li$CP, -1) + tail(li$CP, -1)) / 2) sprintf("%.0f", auc_val) }) output$thalf <- renderText({ CLi <- 0.808 * (input$wt / 73)^0.43 Vi <- 50.4 * (input$wt / 73)^0.924 if (input$healthy) Vi <- Vi * (1 - 0.0908) sprintf("%.1f", log(2) / (CLi / Vi)) }) output$pkPlot <- renderPlot({ d <- sim_data() p <- ggplot(d, aes(x = time_days, y = CP)) + geom_line(color = "#8b5cf6", linewidth = 0.8) if (input$show_target) { p <- p + geom_hline(yintercept = 176, linetype = "dashed", color = "#10b981", alpha = 0.7) + annotate("text", x = max(d$time_days) * 0.02, y = 190, label = "VEGFR-2 target: 176 ng/mL", hjust = 0, size = 3.5, color = "#10b981") } # Shade off-treatment weeks for 3/1 schedule if (input$schedule == "3_1") { for (cyc in seq_len(input$n_cycles)) { off_start <- ((cyc - 1) * 28 + 21) off_end <- cyc * 28 p <- p + annotate("rect", xmin = off_start, xmax = off_end, ymin = -Inf, ymax = Inf, fill = "#f59e0b", alpha = 0.08) } } sched_label <- ifelse(input$schedule == "3_1", "QD 3/1", "QD continuous") p <- p + labs( x = "Time (days)", y = "Concentration (ng/mL)", title = paste0("Fruquintinib ", input$dose, " mg ", sched_label, " \u2014 ", input$n_cycles, " Cycle(s)") ) + 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)