library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) library(tidyr) # ───────────────────────────────────────────────────────────────────────────── # PF-06463922 (Lorlatinib) Precursor PKPD + Tumor Growth Model # Yamazaki et al. (2014) JPET 351:67–76 # # Model structure: # 1-CMT oral PK → Precursor/Modulator → ALK inhibition (with rebound) + Tumor TGI # # PK: 1-CMT oral (ADVAN2), naïve-pooled, Study 2 parameters # CL/F = 1.1 L/h/kg V/F = 7.0 L/kg Ka = 1.3 h-1 # # ALK Precursor PD (accounts for rebound): # dM/dt = kin - kmd * M # dR/dt = kmd * M * (1 - Emax*Cp^g/(EC50^g + Cp^g)) - kout * R # EC50 = 58 ng/mL kout = 1.8 h-1 kmd = 0.021 h-1 Emax = 1 (fixed) # # Tumor TGI (logistic growth, ALK model; Study 2): # dT/dt = kng*T*(1-T/Tss) - Kmax*Cp/(KC50+Cp)*T # KC50 = 33 ng/mL Kmax = 0.011 h-1 kng = 0.0094 h-1 Tss = 1530 mm3 # ───────────────────────────────────────────────────────────────────────────── model_code <- ' $PARAM @annotated CLF : 1.1 : Oral clearance CL/F (L/h/kg) VF : 7.0 : Oral volume V/F (L/kg) KA : 1.3 : Absorption rate constant (h-1) WT : 0.025 : Body weight (kg) [25g mouse default] // ALK Precursor PD parameters EC50 : 58.0 : EC50 for ALK inhibition (ng/mL) EMAX : 1.0 : Emax (fixed = 1) KOUT : 1.8 : ALK degradation rate constant (h-1) KMD : 0.021 : Modulator degradation rate (h-1) KIN : 0.021 : Zero-order modulator formation rate (h-1); at SS: KIN = KMD // Tumor TGI parameters (ALK logistic model; Study 2) KC50 : 33.0 : KC50 for tumor killing (ng/mL) KMAX : 0.011 : Maximal tumor killing rate (h-1) KNG : 0.0094: Net tumor growth rate constant (h-1) TSS : 1530 : Maximum sustainable tumor volume (mm3) [logistic] T0 : 200 : Initial tumor volume (mm3) $CMT @annotated DEPOT : Oral depot (mg/kg) CENT : Central compartment (mg/kg) MOD : Modulator (M) ALK : ALK phosphorylation response (R; ratio to baseline) TUM : Tumor volume (mm3) $MAIN // kel = (CLF/VF); Vi is only used for notation — keep consistent per-kg units double kel = CLF / VF; // h-1: (L/h/kg) / (L/kg) // Initial conditions (true SS without drug) // M_ss = kin/kmd; ALK_ss = kin/kout (since kmd*M_ss = kin, so ALK_ss = kin/kout) if (NEWIND <= 1) { TUM_0 = T0; MOD_0 = KIN / KMD; // M SS = 1.0 (since KIN=KMD by default) ALK_0 = KIN / KOUT; // ALK SS in absolute units = 0.021/1.8 = 0.01167 } $ODE // CENT is in mg/kg (amt entered as mg/kg); VF is L/kg → Cp in mg/L = ug/mL double Cp = CENT / VF; // Convert mg/L to ng/mL (* 1000) double Cp_ngml = Cp * 1000.0; // PK dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * DEPOT - kel * CENT; // Inhibitory term double Inh = EMAX * pow(Cp_ngml, 1.0) / (pow(EC50, 1.0) + pow(Cp_ngml, 1.0)); // Precursor modulator: // Drug inhibits kmd (modulator degradation), causing M to accumulate during treatment. // After drug washout, elevated M drives a burst of ALK formation → REBOUND above baseline. // This is the precursor mechanism (Sharma et al. 1998). dxdt_MOD = KIN - KMD * (1.0 - Inh) * MOD; // ALK response: formation = kmd*(1-Inh)*M; degradation = kout*R dxdt_ALK = KMD * (1.0 - Inh) * MOD - KOUT * ALK; // Tumor: logistic growth - Kmax killing driven by Cp double TGI = KMAX * Cp_ngml / (KC50 + Cp_ngml); dxdt_TUM = KNG * TUM * (1.0 - TUM / TSS) - TGI * TUM; $TABLE double CP_NGML = CENT / VF * 1000.0; // mg/kg / (L/kg) * 1000 = ng/mL // Normalize ALK to its drug-free SS (KIN/KOUT) to get ratio to baseline double ALK_SS_NODRUG = KIN / KOUT; double ALK_RATIO = (ALK_SS_NODRUG > 0) ? ALK / ALK_SS_NODRUG : ALK; double TUMOR_VOL = TUM; double ALK_INH_PCT = 100.0 * EMAX * CP_NGML / (EC50 + CP_NGML); double TGI_PCT = 0.0; // placeholder (from diff eqn at end of sim) $CAPTURE CP_NGML ALK_RATIO TUMOR_VOL ALK_INH_PCT ' mod <- mcode("lorlatinib_pkpd", model_code, quiet = TRUE) 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: 22px; font-weight: bold; color: #2c3e50; } .metric-label { font-size: 11px; color: #7f8c8d; margin-top: 4px; } .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: #dc3545; } .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; } .param-table td { padding: 4px 8px; font-size: 13px; } ") ui <- page_sidebar( title = "Lorlatinib (PF-06463922) PKPD Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 320, h6("Dosing"), sliderInput("dose_mgkg", "Dose (mg/kg)", min = 0.1, max = 25, value = 3, step = 0.1), selectInput("regimen", "Dosing Regimen", choices = c("Once Daily (QD)"="qd", "Twice Daily (BID, 7h apart)"="bid"), selected = "bid"), numericInput("n_days", "Duration (days)", value = 13, min = 1, max = 30), hr(), h6("Animal Weight"), sliderInput("wt_g", "Mouse Weight (g)", min = 15, max = 35, value = 25, step = 1), hr(), h6("PK Parameters (Study 2)"), sliderInput("clf", "CL/F (L/h/kg)", min = 0.5, max = 3.0, value = 1.1, step = 0.1), sliderInput("vf", "V/F (L/kg)", min = 2.0, max = 15.0, value = 7.0, step = 0.5), hr(), h6("PD: ALK Precursor Model"), sliderInput("ec50", "EC50 (ng/mL)", min = 10, max = 200, value = 58, step = 1), sliderInput("kout", "kout (h⁻¹)", min = 0.1, max = 5.0, value = 1.8, step = 0.1), sliderInput("kmd", "kmd (h⁻¹)", min = 0.005, max = 0.10, value = 0.021, step = 0.001), hr(), h6("Tumor TGI Model"), sliderInput("kng", "kng (h⁻¹)", min = 0.001, max = 0.05, value = 0.0094, step = 0.0001), sliderInput("kmax", "Kmax (h⁻¹)", min = 0.005, max = 0.05, value = 0.011, step = 0.001), sliderInput("kc50", "KC50 (ng/mL)", min = 5, max = 200, value = 33, step = 1), sliderInput("t0", "Initial Tumor Volume (mm³)", min = 50, max = 500, value = 200, step = 10), hr(), checkboxInput("log_scale", "Log Scale (Conc. Y-axis)", value = FALSE) ), layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("cmax_out")), div(class = "metric-label", "Cmax SS (ng/mL)")), div(class = "metric-card metric-success", div(class = "metric-value", textOutput("tmax_out")), div(class = "metric-label", "Tmax (h, 1st dose)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("alk_inh_out")), div(class = "metric-label", "Peak ALK Inh (%, SS)")), div(class = "metric-card metric-danger", div(class = "metric-value", textOutput("tgi_out")), div(class = "metric-label", "Tumor Vol Final (mm³)")) ), navset_card_underline( full_screen = TRUE, nav_panel("PK — Plasma Concentrations", plotOutput("pkPlot", height = "480px")), nav_panel("PD — ALK Inhibition + Rebound", plotOutput("alkPlot", height = "480px")), nav_panel("Efficacy — Tumor Growth Inhibition", plotOutput("tgiPlot", height = "480px")), nav_panel("Model Information", div(style = "max-width: 820px; padding: 12px;", markdown(" ## PF-06463922 (Lorlatinib) — Precursor PKPD + Tumor Growth Model **Drug:** Lorlatinib (PF-06463922) — 2nd-generation ALK/ROS1 inhibitor (Pfizer) **Indication:** Crizotinib-resistant ALK+ NSCLC (EML4-ALK L1196M mutation) **Species:** Mouse (nonclinical xenograft; H3122 NSCLC) **Reference:** Yamazaki et al. (2014) *JPET* 351:67–76 --- ### 1. Pharmacokinetic Model (1-CMT Oral) | Parameter | Study 2 Value | Units | |-----------|:------------:|-------| | CL/F | 1.1 | L/h/kg | | V/F | 7.0 | L/kg | | Ka | 1.3 | h⁻¹ | | fu (plasma) | 0.25 | — | Model: ADVAN2 (NONMEM), naïve-pooled, proportional residual error (CV 12–24%). --- ### 2. ALK Inhibition — Precursor (Modulator) Model The **precursor model** captures the ALK phosphorylation rebound observed at 24–36h post-dose (rebound exceeding baseline at later dosing days): ``` dM/dt = kin − kmd·M dR/dt = kmd·M·(1 − Emax·Cp/(EC50+Cp)) − kout·R ``` | Parameter | Estimate | Units | |-----------|:--------:|-------| | EC50,in vivo | 58 | ng/mL | | Emax | 1 (fixed) | — | | kout | 1.8 | h⁻¹ | | kmd | 0.021 | h⁻¹ | > The precursor model fit ALK rebounds significantly better than the standard IDR (ΔOFV = 22 units, p < 0.01). > EC50,in vivo = 58 ng/mL corresponds to **36 nM free** (fu = 0.25, MW = 406.4 g/mol). --- ### 3. Tumor Growth Inhibition Model **Logistic tumor growth** (Study 2, ALK H3122 model): ``` dT/dt = kng·T·(1 − T/Tss) − Kmax·Cp/(KC50+Cp)·T ``` | Parameter | ALK Study 2 | Units | |-----------|:-----------:|-------| | KC50 | 33 | ng/mL | | Kmax | 0.011 | h⁻¹ | | kng | 0.0094 | h⁻¹ | | Tss | 1530 | mm³ | | Tsc* | 83 | ng/mL | > *Tumor stasis concentration (Tsc): Cp required for zero net tumor growth (dT/dt = 0). > At EC60,in vivo for ALK inhibition (~52 nM free), tumor stasis is achieved. --- ### Key Translational Conclusions - **>60% ALK inhibition → tumor stasis** in EML4-ALK L1196M model - **Proposed minimum target exposure:** EC60,in vivo ≈ 50 nM free (~83 ng/mL total) - Clinical lorlatinib dose (100 mg QD) achieves Cmax ~573 ng/mL in patients — well above Tsc - ROS1-tumor model ~10x more sensitive (KC50 = 13 ng/mL, Tsc = 10 ng/mL) ") ) ), nav_panel("References", div(class = "ref-box", style = "max-width: 780px; padding: 16px;", tags$h5("📚 Primary Reference"), tags$p( "Yamazaki S, Lam JL, Zou HY, Wang H, Smeal T, Vicini P (2014). ", tags$em("Translational pharmacokinetic-pharmacodynamic modeling for an orally available novel inhibitor of anaplastic lymphoma kinase and c-Ros oncogene 1. "), "J Pharmacol Exp Ther 351(1):67–76. ", tags$a(href="https://doi.org/10.1124/jpet.114.215319", target="_blank", "DOI: 10.1124/jpet.114.215319") ), tags$h5("💊 Drug Information"), tags$ul( tags$li(tags$strong("Generic name:"), " Lorlatinib"), tags$li(tags$strong("Brand name:"), " Lorbrena® (Pfizer)"), tags$li(tags$strong("Approval:"), " FDA approved 2018 (ALK+ NSCLC, 3rd line); 2021 (1st line)"), tags$li(tags$strong("Clinical dose:"), " 100 mg orally once daily"), tags$li(tags$strong("Targets:"), " ALK, ROS1 (2nd-generation, penetrates CNS)"), tags$li(tags$strong("Compound code:"), " PF-06463922") ), tags$h5("📖 Additional References"), tags$ol( tags$li("Yamazaki S (2013) Translational PKPD modeling from nonclinical to clinical: crizotinib case study. AAPS J 15:354–366."), tags$li("Yamazaki et al. (2012) PKPD modeling of crizotinib for ALK inhibition and antitumor efficacy. JPET 340:549–557."), tags$li("Solomon et al. (2018) Lorlatinib in patients with ALK-positive NSCLC. Lancet Oncol 19:1654–1667."), tags$li("Sharma et al. (1998) Precursor-dependent indirect response model. J Theor Biol 190:261–270.") ) ) ) ), div(style = "text-align: center; padding: 16px; margin-top: 24px; 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"), " • For research and educational purposes only — not for clinical decision-making.", br(), tags$span(style = "font-size: 10px;", "PF-06463922 PKPD model based on Yamazaki et al. (2014) JPET 351:67–76")) ) # ───────────────────────────────────────────────────────────────────────────── # Server # ───────────────────────────────────────────────────────────────────────────── server <- function(input, output, session) { sim_data <- reactive({ shiny::req(input$dose_mgkg, input$wt_g, input$clf, input$vf, input$ec50, input$kout, input$kmd, input$kng, input$kmax, input$kc50, input$t0, input$n_days) dose_mgkg <- input$dose_mgkg wt_kg <- input$wt_g / 1000 ii <- if (input$regimen == "bid") 7 else 24 addl <- ceiling(input$n_days * 24 / ii) - 1 end_h <- input$n_days * 24 ev1 <- ev(amt = dose_mgkg, cmt = 1, ii = ii, addl = addl) out <- mod |> param( WT = wt_kg, CLF = input$clf, VF = input$vf, KA = 1.3, EC50 = input$ec50, EMAX = 1.0, KOUT = input$kout, KMD = input$kmd, KIN = input$kmd, # At SS: KIN = KMD (modulator baseline = 1) KC50 = input$kc50, KMAX = input$kmax, KNG = input$kng, TSS = 1530, T0 = input$t0 ) |> ev(ev1) |> mrgsim(end = end_h, delta = 0.25) |> as.data.frame() out }) # ── Metric: Cmax at SS ─────────────────────────────────────────────────── output$cmax_out <- renderText({ d <- sim_data() ss_start <- max(0, (input$n_days - 2) * 24) last <- dplyr::filter(d, time >= ss_start) sprintf("%.1f", max(last$CP_NGML, na.rm = TRUE)) }) # ── Metric: Tmax (1st dose) ────────────────────────────────────────────── output$tmax_out <- renderText({ d <- sim_data() first <- dplyr::filter(d, time <= 24) tmax_h <- first$time[which.max(first$CP_NGML)] sprintf("%.1f", tmax_h) }) # ── Metric: Peak ALK inhibition at SS ──────────────────────────────────── output$alk_inh_out <- renderText({ d <- sim_data() ss_start <- max(0, (input$n_days - 2) * 24) last <- dplyr::filter(d, time >= ss_start) sprintf("%.0f%%", max(last$ALK_INH_PCT, na.rm = TRUE)) }) # ── Metric: Final tumor volume ─────────────────────────────────────────── output$tgi_out <- renderText({ d <- sim_data() last_val <- tail(d$TUMOR_VOL, 1) sprintf("%.0f", max(0, last_val)) }) # ── PK Plot ─────────────────────────────────────────────────────────────── output$pkPlot <- renderPlot({ d <- sim_data() # Tsc reference lines tsc_alk <- 83 tsc_ros <- 10 p <- ggplot(d, aes(x = time / 24, y = CP_NGML)) + annotate("rect", xmin = -Inf, xmax = Inf, ymin = tsc_alk, ymax = Inf, fill = "#10b981", alpha = 0.08) + geom_hline(yintercept = tsc_alk, linetype = "dashed", color = "#10b981", linewidth = 0.6) + geom_hline(yintercept = tsc_ros, linetype = "dotted", color = "#f59e0b", linewidth = 0.6) + annotate("text", x = input$n_days * 0.98, y = tsc_alk * 1.3, label = "ALK Tsc (83 ng/mL)", hjust = 1, size = 3.2, color = "#10b981") + annotate("text", x = input$n_days * 0.98, y = tsc_ros * 1.8, label = "ROS1 Tsc (10 ng/mL)", hjust = 1, size = 3.2, color = "#f59e0b") + geom_line(color = "#8b5cf6", linewidth = 0.9) + labs( x = "Time (days)", y = "Plasma Concentration (ng/mL)", title = paste0("Lorlatinib PK — ", input$dose_mgkg, " mg/kg ", toupper(input$regimen), " × ", input$n_days, "d"), subtitle = "Dashed = ALK tumor stasis conc; Dotted = ROS1 tumor stasis conc" ) + theme_minimal(base_size = 14) + theme(plot.subtitle = element_text(size = 11, color = "#6c757d")) if (input$log_scale) { p <- p + scale_y_log10() } p }) # ── ALK Inhibition / Rebound Plot ───────────────────────────────────────── output$alkPlot <- renderPlot({ d <- sim_data() alk_long <- d |> dplyr::select(time, ALK_RATIO, ALK_INH_PCT) |> tidyr::pivot_longer( cols = c(ALK_RATIO, ALK_INH_PCT), names_to = "measure", values_to = "value" ) |> dplyr::mutate( panel = dplyr::case_when( measure == "ALK_RATIO" ~ "ALK Phosphorylation (ratio to baseline)", measure == "ALK_INH_PCT" ~ "ALK Inhibition (%)" ), time_day = time / 24 ) ggplot(alk_long, aes(x = time_day, y = value)) + geom_hline(data = data.frame( panel = "ALK Phosphorylation (ratio to baseline)", yint = 1), aes(yintercept = yint), linetype = "dashed", color = "#6c757d", alpha = 0.5) + geom_hline(data = data.frame( panel = "ALK Inhibition (%)", yint = 60), aes(yintercept = yint), linetype = "dashed", color = "#10b981", alpha = 0.6) + annotate("text", x = input$n_days * 0.98, y = 60 * 1.05, label = "60% inh. = tumor stasis", hjust = 1, size = 3.0, color = "#10b981") + geom_line(color = "#8b5cf6", linewidth = 0.9) + facet_wrap(~ panel, scales = "free_y", ncol = 1) + labs( x = "Time (days)", y = NULL, title = "ALK Response — Precursor Modulator Model (includes rebound)", subtitle = paste0("EC50 = ", input$ec50, " ng/mL | kout = ", input$kout, " h⁻¹ | kmd = ", input$kmd, " h⁻¹") ) + theme_minimal(base_size = 14) + theme( strip.text = element_text(face = "bold", size = 11), plot.subtitle = element_text(size = 10, color = "#6c757d") ) }) # ── Tumor Volume Plot ───────────────────────────────────────────────────── output$tgiPlot <- renderPlot({ d <- sim_data() # Vehicle simulation (no drug) wt_kg <- input$wt_g / 1000 end_h <- input$n_days * 24 vehicle <- mod |> param( WT = wt_kg, CLF = input$clf, VF = input$vf, KA = 1.3, EC50 = input$ec50, EMAX = 1.0, KOUT = input$kout, KMD = input$kmd, KIN = input$kmd, KC50 = input$kc50, KMAX = input$kmax, KNG = input$kng, TSS = 1530, T0 = input$t0 ) |> ev(ev(amt = 0, cmt = 1)) |> mrgsim(end = end_h, delta = 0.25) |> as.data.frame() |> dplyr::mutate(group = "Vehicle (no drug)") treated <- d |> dplyr::mutate(group = paste0(input$dose_mgkg, " mg/kg ", toupper(input$regimen))) both <- dplyr::bind_rows( dplyr::select(vehicle, time, TUMOR_VOL, group), dplyr::select(treated, time, TUMOR_VOL, group) ) |> dplyr::mutate(time_day = time / 24) # TGI % at last day last_day <- max(both$time_day) vol_veh_final <- both |> dplyr::filter(abs(time_day - last_day) < 0.1, group == "Vehicle (no drug)") |> dplyr::pull(TUMOR_VOL) |> mean() vol_tx_first <- both |> dplyr::filter(time_day < 0.1, group != "Vehicle (no drug)") |> dplyr::pull(TUMOR_VOL) |> mean() vol_tx_final <- both |> dplyr::filter(abs(time_day - last_day) < 0.1, group != "Vehicle (no drug)") |> dplyr::pull(TUMOR_VOL) |> mean() delta_t <- vol_tx_final - vol_tx_first delta_c <- vol_veh_final - vol_tx_first tgi_pct <- if (abs(delta_c) > 1) round(100 * (1 - delta_t / delta_c)) else NA_real_ ggplot(both, aes(x = time_day, y = TUMOR_VOL, color = group, linetype = group)) + geom_line(linewidth = 0.9) + scale_color_manual(values = c("Vehicle (no drug)" = "#dc3545", setNames("#8b5cf6", paste0(input$dose_mgkg, " mg/kg ", toupper(input$regimen))))) + scale_linetype_manual(values = c("Vehicle (no drug)" = "dashed", setNames("solid", paste0(input$dose_mgkg, " mg/kg ", toupper(input$regimen))))) + annotate("text", x = last_day * 0.5, y = max(both$TUMOR_VOL, na.rm = TRUE) * 0.9, label = if (!is.na(tgi_pct)) paste0("TGI = ", tgi_pct, "% on day ", round(last_day)) else "", size = 4.5, color = "#2c3e50", fontface = "bold") + labs( x = "Time (days)", y = "Tumor Volume (mm³)", color = NULL, linetype = NULL, title = "Tumor Growth Inhibition — Logistic Model", subtitle = paste0("KC50 = ", input$kc50, " ng/mL | Kmax = ", input$kmax, " h⁻¹ | kng = ", input$kng, " h⁻¹ | T0 = ", input$t0, " mm³") ) + theme_minimal(base_size = 14) + theme( legend.position = "top", plot.subtitle = element_text(size = 10, color = "#6c757d") ) }) } shinyApp(ui = ui, server = server)