# Concizumab PK/PD Simulator # Systems PK/PD model for anti-TFPI monoclonal antibody (Alhemo®) # Based on: Yuan et al. Eur J Pharm Sci 138 (2019) 105032 # Drug: Concizumab (humanized IgG4, targets TFPI for hemophilia treatment) library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) # ── mrgsolve Model: Concizumab TMDD with sTFPI PD ── # Two-compartment model with target-mediated drug disposition (TMDD) # Non-linear clearance due to membrane-bound TFPI (mTFPI) # PD: suppression of soluble TFPI (sTFPI) model_code <- ' $PARAM @annotated // PK Parameters - calibrated to match clinical data // Based on Yuan et al. 2019 with adjustments for clinical relevance CL_linear : 0.15 : Linear clearance (L/day) - at saturation VC : 3.0 : Central volume (L) VP : 2.5 : Peripheral volume (L) Q : 0.3 : Intercompartmental CL (L/day) KA : 0.25 : SC absorption rate (1/day) F1 : 0.93 : SC bioavailability // TMDD Parameters (simplified QSS approximation) // Represents mTFPI-mediated non-linear clearance VMAX : 1.5 : Max non-linear elimination rate (nmol/day/L) KM : 5.0 : Concentration at half-max elimination (nM) // PD Parameters (sTFPI suppression - indirect response) STFPI0 : 1.6 : Baseline sTFPI (nM) KDEGS : 1.18 : sTFPI degradation rate (1/day) IC50 : 2.0 : IC50 for sTFPI production inhibition (nM) IMAX : 0.95 : Maximum inhibition of sTFPI production // Patient covariates WT : 70 : Body weight (kg) REF_WT : 70 : Reference weight (kg) $CMT @annotated DEPOT : SC depot (nmol) CENT : Central/plasma (nmol) PERIPH : Peripheral (nmol) STFPI : Soluble TFPI (nM) $MAIN // Allometric scaling (standard for mAbs) double WTF = WT / REF_WT; double CLi = CL_linear * pow(WTF, 0.75); double VCi = VC * pow(WTF, 1.0); double VPi = VP * pow(WTF, 1.0); double Qi = Q * pow(WTF, 0.75); double VMAXi = VMAX * pow(WTF, 0.75); F_DEPOT = F1; STFPI_0 = STFPI0; // Initialize sTFPI at baseline $ODE // Plasma concentration (nM) double CP = CENT / VCi; double CP_safe = CP > 0.001 ? CP : 0.001; // Avoid division issues // TMDD: Non-linear clearance (Michaelis-Menten approximation) // Represents saturable mTFPI-mediated elimination double CL_nonlin = VMAXi / (KM + CP_safe); // Total clearance = linear + non-linear double CL_total = CLi + CL_nonlin; // Two-compartment PK dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - (CL_total/VCi) * CENT - (Qi/VCi) * CENT + (Qi/VPi) * PERIPH; dxdt_PERIPH = (Qi/VCi) * CENT - (Qi/VPi) * PERIPH; // PD: sTFPI indirect response (inhibition of production) double KSYN = KDEGS * STFPI0; // Synthesis rate double INH = (IMAX * CP_safe) / (IC50 + CP_safe); // Inhibition dxdt_STFPI = KSYN * (1.0 - INH) - KDEGS * STFPI; $TABLE double DV = CENT / VCi; // Plasma concentration (nM) // Convert to clinical units: ng/mL (MW ~ 150 kDa for IgG4) // 1 nM = 150 ng/mL for 150 kDa antibody double CP_ngml = DV * 150.0; double CP_ugml = CP_ngml / 1000.0; // microg/mL double STFPI_out = STFPI; double STFPI_pct = (STFPI / STFPI0) * 100.0; // % of baseline $CAPTURE @annotated DV : Plasma concentration (nM) CP_ngml : Plasma concentration (ng/mL) CP_ugml : Plasma concentration (ug/mL) STFPI_out : Soluble TFPI (nM) STFPI_pct : sTFPI (% of baseline) ' # Compile model mod <- mcode("concizumab_tmdd", model_code) # ── Theme with custom metric cards ── 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; } .metric-danger .metric-value { color: #ef4444; } ") # ── UI ── ui <- page_sidebar( title = "Concizumab (Alhemo®) PK/PD Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, h6("Patient Characteristics"), sliderInput("weight", "Body Weight (kg)", min = 40, max = 150, value = 70, step = 5), hr(), h6("Dosing Regimen"), selectInput("route", "Administration Route", choices = c("Subcutaneous (SC)" = "SC", "Intravenous (IV)" = "IV"), selected = "SC"), selectInput("regimen", "Dosing Regimen", choices = c("FDA-Approved Loading + Maintenance" = "fda", "Loading Only (Days 1-7)" = "loading", "Maintenance Only (Weekly)" = "maint", "Custom" = "custom"), selected = "fda"), conditionalPanel( condition = "input.regimen == 'custom'", numericInput("dose_mgkg", "Dose (mg/kg)", value = 0.5, min = 0.05, max = 9, step = 0.05), selectInput("interval", "Dosing Interval", choices = c("Daily" = 1, "Every 3 Days" = 3, "Weekly" = 7, "Every 2 Weeks" = 14), selected = "7"), numericInput("n_doses", "Number of Doses", value = 12, min = 1, max = 52) ), numericInput("sim_days", "Simulation Duration (days)", value = 84, min = 7, max = 365), checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE), hr(), card( card_header("Model Parameters", class = "bg-light"), p(strong("CL (linear):"), "0.15 L/day"), p(strong("Vc:"), "3.0 L"), p(strong("Vp:"), "2.5 L"), p(strong("F (SC):"), "93%"), p(em("Target: sTFPI < LLOQ for efficacy")) ), hr(), card( card_header("References", class = "bg-light"), tags$small( tags$a(href = "https://www.accessdata.fda.gov/drugsatfda_docs/label/2024/761315s000lbl.pdf", "FDA Label (Alhemo®)", target = "_blank"), tags$br(), tags$a(href = "https://doi.org/10.1016/j.ejps.2019.105032", "Yuan et al. 2019 (Model)", target = "_blank"), tags$br(), tags$a(href = "https://www.nejm.org/doi/full/10.1056/NEJMoa2216455", "Phase 3 Trial (NEJM)", target = "_blank") ) ) ), # Metrics row layout_columns( col_widths = c(3, 3, 3, 3), div(class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax")), div(class = "metric-label", "Cmax (μg/mL)") ), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Ctrough (μg/mL)") ), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("stfpi_nadir")), div(class = "metric-label", "sTFPI Nadir (% baseline)") ), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "t½ (days)") ) ), # PK Plot card( card_header("Concizumab Plasma Concentration"), full_screen = TRUE, plotOutput("pkPlot", height = "420px") ), # PD Plot card( card_header("Soluble TFPI Suppression (Pharmacodynamic Response)"), full_screen = TRUE, plotOutput("pdPlot", height = "420px") ), # Tabbed content navset_card_tab( title = "Analysis", nav_panel("Compare Regimens", plotOutput("comparePlot", height = "450px") ), nav_panel("Dosing Guidelines", markdown(" ## FDA-Approved Dosing (Alhemo®) **Indication:** Routine prophylaxis to prevent or reduce bleeding episodes in adults and children ≥12 years with hemophilia A or B (with or without inhibitors). ### Recommended Dosing | Phase | Dose | Frequency | Duration | |-------|------|-----------|----------| | Loading | 0.5 mg/kg | Daily | Days 1-7 | | Maintenance | 0.15-0.25 mg/kg | Weekly | Ongoing | ### Administration - **Route:** Subcutaneous injection only - **Sites:** Abdomen, thigh, or upper arm (rotate sites) - **Volume:** Up to 2 mL per injection site ### Dose Adjustments - If ≥2 spontaneous bleeding episodes occur in any 4-week period, increase maintenance dose by 0.05 mg/kg - Maximum recommended dose: 0.25 mg/kg weekly ### Monitoring - sTFPI levels should be suppressed below LLOQ (~0.2 nM) - Monitor for thromboembolic events ") ), nav_panel("Model Information", markdown(" ## Systems PK/PD Model This simulator is based on the systems pharmacokinetics/pharmacodynamics model developed by **Yuan et al. (2019)** for concizumab, an anti-TFPI monoclonal antibody. ### Mechanism of Action Concizumab is a humanized IgG4 monoclonal antibody that binds to tissue factor pathway inhibitor (TFPI), a negative regulator of coagulation. By neutralizing TFPI, concizumab promotes thrombin generation and reduces bleeding in hemophilia patients. ### Model Structure - **PK:** Two-compartment model with target-mediated drug disposition (TMDD) - **TMDD:** Non-linear clearance due to binding to membrane-bound TFPI (mTFPI) - **PD:** Indirect response model for soluble TFPI (sTFPI) suppression ### Key Parameters | Parameter | Value | Description | |-----------|-------|-------------| | CL (linear) | 0.15 L/day | Non-target-mediated clearance | | Vc | 3.0 L | Central volume | | Vp | 2.5 L | Peripheral volume | | Q | 0.3 L/day | Distribution clearance | | F (SC) | 93% | Subcutaneous bioavailability | | Ka | 0.25 /day | Absorption rate constant | | Vmax | 1.5 nmol/day/L | Max TMDD elimination | | Km | 5.0 nM | TMDD half-saturation | ### Clinical PK Characteristics - Non-linear pharmacokinetics (faster clearance at low doses) - Half-life: ~14-18 days at therapeutic doses - Steady-state achieved by Week 4-6 of maintenance dosing ### Reference Yuan D, Rode F, Cao Y. *A systems pharmacokinetic/pharmacodynamic model for concizumab to explore the potential of anti-TFPI recycling antibodies.* Eur J Pharm Sci. 2019;138:105032. ") ) ), # ── PKPDBuilder Branding 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"), " • Built by Sunny ☀️ (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) { # Build dosing regimen dosing_events <- reactive({ wt <- input$weight sim_days <- input$sim_days route <- input$route cmt <- if (route == "SC") 1 else 2 # DEPOT or CENT # Convert mg/kg to nmol (MW ~ 150 kDa) # 1 mg = 1000 ug = 1000/150 nmol = 6.67 nmol mg_to_nmol <- 1000 / 150 if (input$regimen == "fda") { # FDA-approved: Loading 0.5 mg/kg daily x7, then 0.2 mg/kg weekly loading_dose <- 0.5 * wt * mg_to_nmol maint_dose <- 0.2 * wt * mg_to_nmol # Loading phase: Days 0-6 (7 doses) loading <- ev(amt = loading_dose, cmt = cmt, time = 0, addl = 6, ii = 1) # Maintenance phase: Starting Day 14, weekly n_maint <- max(1, floor((sim_days - 14) / 7) + 1) maint <- ev(amt = maint_dose, cmt = cmt, time = 14, addl = n_maint - 1, ii = 7) ev_seq(loading, maint) } else if (input$regimen == "loading") { # Loading only loading_dose <- 0.5 * wt * mg_to_nmol ev(amt = loading_dose, cmt = cmt, time = 0, addl = 6, ii = 1) } else if (input$regimen == "maint") { # Maintenance only (0.2 mg/kg weekly) maint_dose <- 0.2 * wt * mg_to_nmol n_doses <- max(1, floor(sim_days / 7)) ev(amt = maint_dose, cmt = cmt, time = 0, addl = n_doses - 1, ii = 7) } else { # Custom regimen dose_mg <- input$dose_mgkg * wt dose_nmol <- dose_mg * mg_to_nmol ii <- as.numeric(input$interval) n_doses <- input$n_doses ev(amt = dose_nmol, cmt = cmt, time = 0, addl = n_doses - 1, ii = ii) } }) # Run simulation sim_data <- reactive({ shiny::req(dosing_events()) wt <- input$weight sim_days <- input$sim_days out <- mod %>% param(WT = wt) %>% ev(dosing_events()) %>% mrgsim(end = sim_days, delta = 0.5) %>% as.data.frame() out }) # PK Metrics output$cmax <- renderText({ d <- sim_data() shiny::req(nrow(d) > 0) sprintf("%.1f", max(d$CP_ugml, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- sim_data() shiny::req(nrow(d) > 0) # Get trough at end of simulation (or last week) end_time <- max(d$time) trough_window <- d %>% dplyr::filter(time >= end_time - 7) sprintf("%.2f", min(trough_window$CP_ugml, na.rm = TRUE)) }) output$stfpi_nadir <- renderText({ d <- sim_data() shiny::req(nrow(d) > 0) sprintf("%.0f%%", min(d$STFPI_pct, na.rm = TRUE)) }) output$thalf <- renderText({ # Approximate half-life at high concentrations (linear phase) cl <- 0.15 * (input$weight / 70)^0.75 vc <- 3.0 * (input$weight / 70) thalf <- log(2) * vc / cl # days sprintf("%.1f", thalf) }) # PK Plot output$pkPlot <- renderPlot({ d <- sim_data() shiny::req(nrow(d) > 0) # For log scale, filter out zeros if (input$log_scale) { d <- d %>% dplyr::filter(CP_ugml > 0.001) } p <- ggplot(d, aes(x = time, y = CP_ugml)) + geom_line(color = "#8b5cf6", linewidth = 1.2) + geom_hline(yintercept = 0.665, linetype = "dashed", color = "#10b981", linewidth = 0.8) + annotate("text", x = max(d$time) * 0.95, y = 0.8, label = "Target trough: ~0.7 μg/mL", hjust = 1, size = 3.5, color = "#10b981") + labs(x = "Time (days)", y = "Concizumab Concentration (μg/mL)", title = "Plasma Concentration-Time Profile") + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold"), panel.grid.minor = element_blank() ) if (input$log_scale) { p <- p + scale_y_log10(labels = scales::comma) } p }) # PD Plot output$pdPlot <- renderPlot({ d <- sim_data() shiny::req(nrow(d) > 0) # LLOQ for sTFPI ~ 0.2 nM (Duckers et al. 2008) lloq_pct <- (0.2 / 1.6) * 100 # ~12.5% of baseline ggplot(d, aes(x = time, y = STFPI_pct)) + geom_line(color = "#f59e0b", linewidth = 1.2) + geom_hline(yintercept = 100, linetype = "dotted", color = "gray50") + geom_hline(yintercept = lloq_pct, linetype = "dashed", color = "#ef4444", linewidth = 0.8) + annotate("text", x = max(d$time) * 0.95, y = lloq_pct + 5, label = "LLOQ (~12.5%)", hjust = 1, size = 3.5, color = "#ef4444") + annotate("text", x = max(d$time) * 0.95, y = 103, label = "Baseline", hjust = 1, size = 3.5, color = "gray50") + scale_y_continuous(limits = c(0, 110)) + labs(x = "Time (days)", y = "Soluble TFPI (% of baseline)", title = "sTFPI Suppression (Pharmacodynamic Response)") + theme_minimal(base_size = 14) + theme( plot.title = element_text(face = "bold"), panel.grid.minor = element_blank() ) }) # Compare regimens plot output$comparePlot <- renderPlot({ wt <- input$weight mg_to_nmol <- 1000 / 150 # Define regimens to compare regimens <- list( "FDA (Loading + 0.2 mg/kg QW)" = { loading <- ev(amt = 0.5 * wt * mg_to_nmol, cmt = 1, time = 0, addl = 6, ii = 1) maint <- ev(amt = 0.2 * wt * mg_to_nmol, cmt = 1, time = 14, addl = 10, ii = 7) ev_seq(loading, maint) }, "0.15 mg/kg QW (Lower Maintenance)" = { loading <- ev(amt = 0.5 * wt * mg_to_nmol, cmt = 1, time = 0, addl = 6, ii = 1) maint <- ev(amt = 0.15 * wt * mg_to_nmol, cmt = 1, time = 14, addl = 10, ii = 7) ev_seq(loading, maint) }, "0.25 mg/kg QW (Higher Maintenance)" = { loading <- ev(amt = 0.5 * wt * mg_to_nmol, cmt = 1, time = 0, addl = 6, ii = 1) maint <- ev(amt = 0.25 * wt * mg_to_nmol, cmt = 1, time = 14, addl = 10, ii = 7) ev_seq(loading, maint) }, "No Loading (0.2 mg/kg QW only)" = { ev(amt = 0.2 * wt * mg_to_nmol, cmt = 1, time = 0, addl = 12, ii = 7) } ) # Simulate all regimens all_sims <- lapply(names(regimens), function(nm) { out <- mod %>% param(WT = wt) %>% ev(regimens[[nm]]) %>% mrgsim(end = 84, delta = 0.5) %>% as.data.frame() %>% mutate(Regimen = nm) out }) combined <- bind_rows(all_sims) # Plot PK comparison p1 <- ggplot(combined, aes(x = time, y = CP_ugml, color = Regimen)) + geom_line(linewidth = 1) + geom_hline(yintercept = 0.665, linetype = "dashed", color = "gray50") + scale_color_brewer(palette = "Set1") + labs(x = "Time (days)", y = "Concentration (μg/mL)", title = "PK: Concizumab Plasma Concentrations") + theme_minimal(base_size = 12) + theme(legend.position = "bottom", legend.direction = "vertical") # Plot PD comparison p2 <- ggplot(combined, aes(x = time, y = STFPI_pct, color = Regimen)) + geom_line(linewidth = 1) + geom_hline(yintercept = 12.5, linetype = "dashed", color = "#ef4444") + scale_color_brewer(palette = "Set1") + scale_y_continuous(limits = c(0, 110)) + labs(x = "Time (days)", y = "sTFPI (% baseline)", title = "PD: sTFPI Suppression") + theme_minimal(base_size = 12) + theme(legend.position = "none") gridExtra::grid.arrange(p1, p2, ncol = 2) }) } shinyApp(ui = ui, server = server)