library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # --------------------------------------------------------------------------- # mrgsolve 1-compartment model for Dolutegravir (DTG) # Pop PK parameters: Dooley et al. (2015) Br J Clin Pharmacol # CL/F = 0.901 L/h, V/F = 17.4 L, Ka = 2.24 h-1, tlag = 0.263 h # Allometric scaling; pregnancy & DDI modifiers on CL # --------------------------------------------------------------------------- model_code <- ' $PARAM @annotated CL : 0.901 : Apparent oral clearance (L/h) V : 17.4 : Apparent volume of distribution (L) KA : 2.24 : Absorption rate constant (1/h) ALAG : 0.263 : Absorption lag time (h) WT : 70 : Body weight (kg) PREGF : 1.0 : Pregnancy CL multiplier (1.0=none, 1.72=2nd tri, 1.85=3rd tri) DDIF : 1.0 : Drug-interaction CL multiplier $CMT @annotated DEPOT : Oral depot compartment (mg) CENT : Central compartment (mg) $MAIN double CLi = CL * pow(WT / 70.0, 0.75) * PREGF * DDIF; double Vi = V * (WT / 70.0); ALAG_DEPOT = ALAG; F_DEPOT = 1.0; $ODE dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CLi / Vi) * CENT; $TABLE double CP = CENT / Vi; // mg/L = µg/mL $CAPTURE CP ' mod <- mcode("dolutegravir", 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 = "Dolutegravir (DTG) PK Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Dosing"), sliderInput("dose", "Dose (mg)", min = 25, max = 100, value = 50, step = 25), radioButtons("interval", "Dosing Interval", choices = list("Once Daily (Q24H)" = "24", "Twice Daily (Q12H)" = "12"), selected = "24"), numericInput("n_days", "Duration (days)", value = 14, min = 1, max = 28), hr(), h6("Patient Characteristics"), sliderInput("wt", "Weight (kg)", min = 40, max = 120, value = 70, step = 5), selectInput("pregnancy", "Pregnancy Status", choices = list( "Non-Pregnant" = "none", "2nd Trimester (~24 wks)" = "t2", "3rd Trimester (~33 wks)" = "t3" ), selected = "none"), hr(), h6("Drug-Drug Interactions"), selectInput("ddi", "Co-medication", choices = list( "None (no inducer)" = "1.0", "Rifampin (strong inducer)" = "2.3", "Efavirenz / Nevirapine" = "1.25", "Carbamazepine / Phenytoin" = "2.0" ), selected = "1.0"), hr(), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE) ), navset_card_underline( title = "Dolutegravir 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 (µg/mL)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Ctrough / C24h (µg/mL)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "AUCtau,ss (µg·h/mL)")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "t½ (h)")) ), card(full_screen = TRUE, plotOutput("pkPlot", height = "500px") ) ), nav_panel("Model Information", markdown(" ## Dolutegravir (DTG) — 1-Compartment Oral PK Model **Drug:** Dolutegravir (DTG; Tivicay®) — HIV Integrase Strand Transfer Inhibitor (INSTI) **Indication:** HIV-1 infection in adults and adolescents ≥12 years **Model structure:** 1-compartment, first-order absorption with lag time, first-order elimination ### Population PK Parameters | Parameter | Estimate | Units | Source | |-----------|----------|-------|--------| | CL/F | 0.901 | L/h | Dooley et al. 2015 | | V/F | 17.4 | L | Dooley et al. 2015 | | Ka | 2.24 | h⁻¹ | Dooley et al. 2015 | | t_lag | 0.263 | h | Dooley et al. 2015 | | t½ | ~14 | h | FDA label | ### Allometric Scaling - **CL:** CL/F × (WT/70)^0.75 × PREGF × DDIF - **V:** V/F × (WT/70) ### Protein Binding - Highly protein bound: **≥98.9%** (fu = 0.007) - Albumin levels decrease during pregnancy → fraction unbound increases (0.007 → 0.0084 in 2nd, 0.0088 in 3rd trimester) ### Pregnancy Effects (Liu et al. 2020, PBPK Model) Enzyme induction increases total clearance: - **2nd trimester:** UGT1A1 ×1.75 + CYP3A4 ×1.60 → CL ↑ ~72% (PREGF = 1.72) - **3rd trimester:** UGT1A1 ×1.92 + CYP3A4 ×1.60 → CL ↑ ~85% (PREGF = 1.85) - Standard 50 mg QD dose is generally maintained; therapeutic drug monitoring recommended ### Drug-Drug Interactions | Co-medication | Effect on CL | AUC Change | Recommendation | |--------------|-------------|------------|----------------| | Rifampin | ×2.3 | ↓ ~54% | Adjust to 50 mg BID | | Efavirenz/Nevirapine | ×1.25 | ↓ ~20% | 50 mg BID if INSTI-naive | | Carbamazepine | ×2.0 | ↓ ~49% | Avoid or adjust dose | ### Therapeutic Target - IC90-PA (protein-adjusted): **0.064 µg/mL** — shown as dashed red line - Recommended minimum Ctrough > 0.064 µg/mL for viral suppression - Standard dosing maintains trough typically **>1 µg/mL** (>>IC90) ### Metabolism - **UGT1A1:** ~79% of dose (primary) - **CYP3A4:** ~21% of dose - Renal excretion: <1% unchanged ") ), nav_panel("References", div(class = "ref-box", tags$h5("📚 Key References"), tags$ol( tags$li("Liu XQ et al. (2020) Clin Pharmacokinet 59:781-797 — PBPK modeling of DTG and RAL in pregnancy (source paper)"), tags$li("Dooley KE et al. (2015) Br J Clin Pharmacol 80(5):1085-96 — Population PK of dolutegravir in HIV treatment-naive patients"), tags$li("Tivicay® (dolutegravir) FDA Prescribing Information (2023) — FDA.gov"), tags$li("DHHS Panel on Antiretroviral Guidelines (2024) — Recommendations for ARV therapy in adults/adolescents with HIV"), tags$li("Zamek-Gliszczynski MJ et al. (2018) Clin Pharmacol Ther 103:916-925 — DTG and neural tube defects, folate receptor") ), tags$h5("💊 Drug Summary"), tags$ul( tags$li(tags$strong("Class:"), " HIV Integrase Strand Transfer Inhibitor (INSTI)"), tags$li(tags$strong("Brand:"), " Tivicay® (ViiV Healthcare)"), tags$li(tags$strong("Standard dose:"), " 50 mg once daily (treatment-naive/experienced, resistance-free)"), tags$li(tags$strong("Alternative dose:"), " 50 mg twice daily (with certain inducers or INSTI resistance)"), tags$li(tags$strong("Metabolism:"), " UGT1A1 (~79%), CYP3A4 (~21%); substrate of P-gp and BCRP"), tags$li(tags$strong("Protein binding:"), " ≥98.9%"), tags$li(tags$strong("Half-life:"), " ~14 hours"), tags$li(tags$strong("Pregnancy:"), " Recommended in 2nd/3rd trimester; avoid peri-conception (neural tube risk)") ) ) ) ), 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"), " • Simulated with mrgsolve | Liu et al. 2020 & Dooley et al. 2015", br(), tags$span(style = "font-size: 10px;", "For research and educational purposes only. Not for clinical decision-making.") ) ) # --------------------------------------------------------------------------- # Server # --------------------------------------------------------------------------- server <- function(input, output, session) { # thematic::thematic_shiny() preg_factor <- reactive({ switch(input$pregnancy, "none" = 1.0, "t2" = 1.72, "t3" = 1.85, 1.0 ) }) sim_data <- reactive({ shiny::req(input$dose, input$wt, input$n_days) interval <- as.numeric(input$interval) n_doses <- ceiling(as.numeric(input$n_days) * 24 / interval) ev1 <- ev(amt = input$dose, cmt = 1, ii = interval, addl = n_doses - 1) mod |> param(WT = input$wt, PREGF = preg_factor(), DDIF = as.numeric(input$ddi)) |> ev(ev1) |> mrgsim(end = as.numeric(input$n_days) * 24, delta = 0.25) |> as.data.frame() |> dplyr::mutate(time_h = time) }) output$cmax <- renderText({ d <- sim_data() last <- d |> dplyr::filter(time >= (as.numeric(input$n_days) - 1) * 24) sprintf("%.2f", max(last$CP, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- sim_data() last <- d |> dplyr::filter(time >= (as.numeric(input$n_days) - 1) * 24) sprintf("%.2f", min(last$CP, na.rm = TRUE)) }) output$auc <- renderText({ d <- sim_data() interval <- as.numeric(input$interval) t_start <- (as.numeric(input$n_days) - 1) * 24 last <- d |> dplyr::filter(time >= t_start, time <= t_start + interval) auc <- sum(diff(last$time) * (head(last$CP, -1) + tail(last$CP, -1)) / 2) sprintf("%.1f", auc) }) output$thalf <- renderText({ CLi <- 0.901 * (input$wt / 70)^0.75 * preg_factor() * as.numeric(input$ddi) Vi <- 17.4 * (input$wt / 70) sprintf("%.1f", log(2) / (CLi / Vi)) }) output$pkPlot <- renderPlot({ d <- sim_data() ic90pa <- 0.064 # protein-adjusted IC90 in µg/mL interval <- as.numeric(input$interval) preg_labels <- c("none" = "Non-Pregnant", "t2" = "2nd Trimester", "t3" = "3rd Trimester") dose_label <- paste0(input$dose, " mg ", ifelse(interval == 24, "QD", "BID"), " — ", preg_labels[input$pregnancy]) CLi <- 0.901 * (input$wt / 70)^0.75 * preg_factor() * as.numeric(input$ddi) Vi <- 17.4 * (input$wt / 70) p <- ggplot(d, aes(x = time_h, y = CP)) + geom_hline(yintercept = ic90pa, linetype = "dashed", color = "#ef4444", alpha = 0.8, linewidth = 0.8) + annotate("text", x = max(d$time_h) * 0.02, y = ic90pa * 1.25, label = paste0("IC90-PA: ", ic90pa, " µg/mL"), hjust = 0, size = 3.5, color = "#ef4444") + geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( x = "Time (hours)", y = "Plasma Concentration (µg/mL)", title = paste0("Dolutegravir ", dose_label), subtitle = paste0("Weight: ", input$wt, " kg | CL/F: ", round(CLi, 2), " L/h | t½: ", round(log(2) / (CLi / Vi), 1), " h") ) + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold"), plot.subtitle = element_text(color = "#6c757d", size = 11) ) if (input$log_scale) { p <- p + scale_y_log10() } p }) } shinyApp(ui = ui, server = server)