---
title: "Brigatinib (Hanley 2024)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Brigatinib (Hanley 2024)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(nlmixr2lib)
library(PKNCA)
library(rxode2)
library(dplyr)
library(tidyr)
library(ggplot2)
```

# Model and source

* Citation: `r rxode2::rxode(readModelDb("Hanley_2024_brigatinib"))$reference`
* Description: `r rxode2::rxode(readModelDb("Hanley_2024_brigatinib"))$description`
* Article: <https://doi.org/10.1002/psp4.13106>
* Supplement (Appendix S1): <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC11015081/>

# What this model is, and what it is not

Hanley 2024 built a **minimal PBPK model with a single adjusting compartment
(SAC)** for brigatinib in the Simcyp Population-based Simulator (versions 15
and 17), and used it to predict drug-drug interactions (DDIs) with CYP3A
inhibitors and inducers and with transporter substrates.

Simcyp's whole-body mass-balance equations are proprietary and are not printed
in the paper or in Appendix S1 -- Figure 1b is a box-and-arrow schematic, and
the seven numbered equations in the supplement are all *input-derivation*
formulae (partition-coefficient prediction, retrograde clearance, ISEF scaling,
Cheng-Prusoff, population-file calibration), never the disposition ODEs. The
platform model therefore cannot be encoded as published.

What *is* completely reported is the brigatinib **compound layer**: the
absorption parameters, the distribution parameters that define the SAC, the
unbound hepatic intrinsic clearance, the renal clearance, and the physiological
scalars needed to turn those into a systemic clearance. Those are enough to
rebuild the disposition as an ordinary two-compartment oral model. That
reduction is what this package ships, and the sections below show that it
reproduces the paper's own predicted Cmax, Tmax, and AUC to within 6.4%,
without a single fitted parameter.

**The DDI predictions are not reproducible.** Every result in Tables 2 (test
arms), 3, and 4 depends on Simcyp compound files for itraconazole,
hydroxy-itraconazole, rifampin, diltiazem, verapamil, efavirenz, digoxin,
dabigatran, rosuvastatin, and metformin that are proprietary to Certara. This
model reproduces the brigatinib control arms only. See
[Assumptions and deviations](#assumptions-and-deviations).

# Population

The model inputs were assembled from six clinical studies of brigatinib
(Hanley 2024 Methods, "Clinical data for brigatinib"; Appendix S1):

* **AP26113-15-106** -- an open-label, randomized, two-period bioequivalence
  crossover study in 36 healthy participants. This is the study the disposition
  parameters (`ka`, Vss, Vsac, `kin`, `kout`) were optimized against, and the
  study whose observed oral clearance of 13.47 L/h anchors the retrograde
  clearance calculation.
* **AP26113-13-104** -- a human ADME / mass balance study in six healthy male
  participants given a single 180 mg / 100 uCi [14C]-brigatinib dose. The
  recovery split (89.75% total; 24.99% urine, 64.76% faeces; 40.91% of faecal
  radioactivity as unchanged brigatinib) fixes the fraction absorbed at 0.63
  and the renal fraction at 21.38% of the dose.
* **AP26113-15-105** -- a three-arm, open-label, randomized, fixed-sequence
  crossover DDI study (n = 20 per arm) with gemfibrozil, itraconazole, and
  rifampin.
* **AP26113-15-107** (hepatic impairment) and **AP26113-15-108** (renal
  impairment) -- contributed ex vivo plasma protein binding from 17 pooled
  healthy participants, giving a mean bound fraction of 91.17% and hence
  fu = 0.0883.
* **AP26113-11-101** (NCT01449461) -- the phase I/II study in patients with
  ALK-positive advanced malignancies used for the multiple-dose verification
  arm.

Simulations of healthy participants used the Simcyp default North European
Caucasian population; the cancer arm used the Simcyp Sim-Cancer population
described in Appendix S1.

The same information is available programmatically via
`readModelDb("Hanley_2024_brigatinib")()$population`.

# Source trace

Every value in `ini()` is either a verbatim Hanley 2024 Table 1 / Appendix S1
input or an arithmetic consequence of one. Nothing was fitted.

| Parameter / equation | Value | Source location |
|---|---|---|
| `lka` | 1.8 1/h | Table 1 (`ka`); Appendix S1 "Determination of absorption parameters" -- sensitivity analysis to recover the observed ~2 h Tmax |
| `lk12` | 28.13 1/h | Table 1 (`kin`, "first-order rate constant that acts on the masses of drug within the systemic compartment"); Figure 1 legend |
| `lk21` | 20.00 1/h | Table 1 (`kout`, "acts on the masses of drug within the SAC"); Figure 1 legend |
| `lvc` | 62.05 L | Derived: (Vss 6.91 - Vsac 6.0) L/kg x 70 kg - 1.648 L liver. Table 1 for the two volumes; Appendix S1 Eq. S2 for the 1648 g default liver weight |
| `lcl` | 7.1816 L/h | Derived: well-stirred hepatic clearance from Table 1 (CLuH,int 12.21 uL/min/mg, fu 0.0883, B:P 0.69) plus Table 1 renal clearance 3.21 L/h. Appendix S1 Eq. S2 supplies QH = 90 L/h, liver 1648 g, 39.8 mg protein/g liver |
| `lfdepot` | 0.5307 | Derived: fa 0.63 (Table 1) x fG 0.9 (Appendix S1 Eq. S2 text) x fH 0.936 (from the same well-stirred calculation) |
| `propSd` | 0 (fixed) | Not reported. Hanley 2024 is a PBPK simulation analysis with no residual-error model and no estimated variance components |
| `d/dt(depot)`, `d/dt(central)`, `d/dt(peripheral1)` | n/a | Figure 1b schematic plus the Figure 1 legend definitions of `kin` and `kout`, with the liver and portal-vein compartments lumped into the systemic compartment |

## Reproducing the two derivations

The clearance and volume derivations are the only places where this model
departs from transcription, so they are recomputed here from the published
inputs and checked against the packaged values.

```{r derivation}
# --- Published inputs (Hanley 2024 Table 1 and Appendix S1) ---------------
fu             <- 0.0883   # Table 1, fraction unbound in plasma
bp             <- 0.69     # Table 1, blood-to-plasma ratio
clint_per_mg   <- 12.21    # Table 1, CLuH,int (uL/min/mg microsomal protein)
clr            <- 3.21     # Table 1, renal clearance (L/h)
qh             <- 90       # Appendix S1 Eq. S2, hepatic blood flow (L/h)
liver_g        <- 1648     # Appendix S1 Eq. S2, default liver weight (g)
mppgl          <- 39.8     # Appendix S1 Eq. S2, mg microsomal protein / g liver
fa             <- 0.63     # Table 1, fraction absorbed
fg             <- 0.9      # Appendix S1 Eq. S2 text, fraction escaping the gut
vss_per_kg     <- 6.91     # Table 1, whole-body steady-state volume (L/kg)
vsac_per_kg    <- 6.0      # Table 1, SAC volume (L/kg)
bw_ref         <- 70       # reference body weight (kg) for the L/kg inputs
clf_observed   <- 13.47    # Appendix S1 Eq. S2 text, observed CL/F (L/h)

# --- Whole-liver unbound intrinsic clearance ------------------------------
# uL/min/mg * mg/g * g -> uL/min; /1e6 -> L/min; *60 -> L/h
clint_lh <- clint_per_mg * mppgl * liver_g / 1e6 * 60
fub      <- fu / bp

# --- Well-stirred liver ---------------------------------------------------
cl_h_blood  <- qh * fub * clint_lh / (qh + fub * clint_lh)
fh          <- qh / (qh + fub * clint_lh)
cl_h_plasma <- cl_h_blood * bp
cl_total    <- cl_h_plasma + clr
fbio        <- fa * fg * fh

# --- Systemic compartment volume -----------------------------------------
vc <- (vss_per_kg - vsac_per_kg) * bw_ref - liver_g / 1000

tibble::tibble(
  Quantity = c("CLuH,int (whole liver, L/h)", "fu,blood", "fH",
               "Hepatic CL (plasma, L/h)", "Total CL (L/h)",
               "F = fa * fG * fH", "vc (L)"),
  Value = round(c(clint_lh, fub, fh, cl_h_plasma, cl_total, fbio, vc), 4)
) |>
  knitr::kable(caption = "Derived parameters recomputed from the published inputs.")
```

These must agree with what the packaged model carries:

```{r derivation-gate}
ini_vals <- rxode2::rxode(readModelDb("Hanley_2024_brigatinib"))$iniDf
packaged <- setNames(exp(ini_vals$est), ini_vals$name)

stopifnot(
  abs(packaged[["lcl"]]     - cl_total) < 0.01,
  abs(packaged[["lvc"]]     - vc)       < 0.01,
  abs(packaged[["lfdepot"]] - fbio)     < 0.001,
  abs(packaged[["lka"]]     - 1.8)      < 1e-8,
  abs(packaged[["lk12"]]    - 28.13)    < 1e-8,
  abs(packaged[["lk21"]]    - 20.00)    < 1e-8
)
cat("Packaged ini() values match the derivation from the published inputs.\n")
```

## Erratum: the 43.02 L/h in Appendix S1 is a transcription error

Appendix S1 states that the retrograde calculation gave a total CLuH,int of
"43.02 L/h", which it then rescales to the 12.21 uL/min/mg value that appears
in Table 1. Those two numbers are not consistent with each other, and it is
43.02 that is wrong: the rescaling scalars printed in the same sentence turn
12.21 uL/min/mg into 48.05 L/h, and only 48.05 L/h reproduces the observed oral
clearance the retrograde equation was solved against.

```{r erratum}
retrograde_clf <- function(clint_lh) {
  x  <- (fu / bp) * clint_lh
  fh <- qh / (qh + x)
  (qh * x / (qh + x) * bp + clr) / (fa * fg * fh)
}

tibble::tibble(
  `CLuH,int (L/h)` = c(48.05, 43.02),
  Source = c("implied by Table 1's 12.21 uL/min/mg",
             "as printed in Appendix S1"),
  `CL/F returned (L/h)` = round(retrograde_clf(c(48.05, 43.02)), 2),
  `vs observed 13.47` = paste0(
    round(100 * (retrograde_clf(c(48.05, 43.02)) / clf_observed - 1), 1), "%")
) |>
  knitr::kable(caption = "Only 48.05 L/h recovers the observed CL/F of 13.47 L/h.")
```

The value actually entered into Simcyp is the Table 1 figure of
12.21 uL/min/mg -- it is also the figure apportioned across metabolic pathways
in Appendix S1 Table S3, whose entries sum to 12.21 -- so the model is
unaffected by the typo. This model uses 48.05 L/h.

# Virtual cohort

The model has no random effects and no covariates, so it is deterministic: one
subject per arm fully characterises each scenario, and there is nothing for a
larger cohort to add. Observed individual concentrations from the source
figures were not digitised, so the comparisons below are against the paper's
*predicted* summary statistics in Table 2.

Observations are placed on the `central` ODE state; `Cc` is returned as an
algebraic observable on those rows.

```{r cohort}
obs_grid <- sort(unique(c(
  seq(0, 6, by = 0.05),      # dense through Tmax
  seq(6, 24, by = 0.25),
  seq(24, 120, by = 1)
)))

make_single_dose <- function(id, dose, label) {
  dplyr::bind_rows(
    tibble::tibble(id = id, time = 0, amt = dose, evid = 1L,
                   cmt = "depot", arm = label),
    tibble::tibble(id = id, time = obs_grid, amt = NA_real_, evid = 0L,
                   cmt = "central", arm = label)
  )
}

events_sd <- dplyr::bind_rows(
  make_single_dose(1L,  90, "90 mg single dose"),
  make_single_dose(2L, 180, "180 mg single dose")
) |>
  dplyr::arrange(id, time, dplyr::desc(evid))

stopifnot(!anyDuplicated(events_sd[, c("id", "time", "evid")]))
```

# Simulation

```{r simulate}
mod <- readModelDb("Hanley_2024_brigatinib")
sim_sd <- rxode2::rxSolve(mod, events = events_sd, keep = "arm")
```

# Replicate published figures

```{r figure-2a, fig.width = 7, fig.height = 4.5}
# Replicates the model-predicted mean line of Figure 2a of Hanley 2024
# (single oral 90 mg dose in healthy participants). The observed individual
# concentrations and the 5th/95th percentile band of Figure 2a are driven by
# the Simcyp virtual population and are not reproducible here.
sim_sd |>
  dplyr::filter(arm == "90 mg single dose", Cc > 0) |>
  ggplot(aes(time, Cc)) +
  geom_line(linewidth = 0.8) +
  scale_y_log10() +
  scale_x_continuous(breaks = seq(0, 120, by = 24)) +
  labs(x = "Time (h)", y = "Brigatinib concentration (ng/mL)",
       title = "Predicted plasma brigatinib after a single 90 mg oral dose",
       caption = "Compare with the mean prediction line of Figure 2a of Hanley 2024.")
```

```{r figure-both-doses, fig.width = 7, fig.height = 4.5}
sim_sd |>
  dplyr::filter(Cc > 0) |>
  ggplot(aes(time, Cc, colour = arm)) +
  geom_line(linewidth = 0.8) +
  scale_y_log10() +
  scale_x_continuous(limits = c(0, 48), breaks = seq(0, 48, by = 12)) +
  labs(x = "Time (h)", y = "Brigatinib concentration (ng/mL)",
       colour = NULL,
       title = "Dose proportionality over the two studied single doses")
```

Brigatinib PK was dose-linear over 60-240 mg (Hanley 2024 Discussion), and the
reduction is a linear model, so the 180 mg profile must be exactly twice the
90 mg profile:

```{r dose-linearity}
wide <- sim_sd |>
  dplyr::select(arm, time, Cc) |>
  tidyr::pivot_wider(names_from = arm, values_from = Cc)
ratio <- wide[["180 mg single dose"]] / wide[["90 mg single dose"]]
ratio <- ratio[is.finite(ratio)]
# Tolerance is relative and set by the ODE solver, not by the model: the
# ratio is 2 analytically, and the residual ~2e-6 is integration error.
stopifnot(all(abs(ratio / 2 - 1) < 1e-4))
cat(sprintf(paste("Dose linearity holds: 180 mg / 90 mg concentration ratio",
                  "= 2 to within %.1e (relative).\n"),
            max(abs(ratio / 2 - 1))))
```

# PKNCA validation

```{r pknca-single-dose}
conc_sd <- sim_sd |>
  dplyr::filter(!is.na(Cc)) |>
  dplyr::select(id, time, Cc, arm)

# Guarantee a time-zero record per subject (pre-dose Cc = 0 for oral dosing).
conc_sd <- dplyr::bind_rows(
  conc_sd,
  conc_sd |> dplyr::distinct(id, arm) |> dplyr::mutate(time = 0, Cc = 0)
) |>
  dplyr::distinct(id, arm, time, .keep_all = TRUE) |>
  dplyr::arrange(id, arm, time)

dose_sd <- events_sd |>
  dplyr::filter(evid == 1L) |>
  dplyr::select(id, time, amt, arm)

conc_obj <- PKNCA::PKNCAconc(conc_sd, Cc ~ time | arm + id,
                             concu = "ng/mL", timeu = "h")
dose_obj <- PKNCA::PKNCAdose(dose_sd, amt ~ time | arm + id, doseu = "mg")

intervals_sd <- data.frame(
  start      = c(0, 0),
  end        = c(120, Inf),
  cmax       = c(TRUE,  FALSE),
  tmax       = c(TRUE,  FALSE),
  auclast    = c(TRUE,  FALSE),
  aucinf.obs = c(FALSE, TRUE),
  half.life  = c(FALSE, TRUE)
)

nca_sd <- PKNCA::pk.nca(
  PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals_sd)
)

knitr::kable(
  as.data.frame(nca_sd) |>
    dplyr::filter(PPTESTCD %in% c("cmax", "tmax", "auclast",
                                  "aucinf.obs", "half.life")) |>
    dplyr::mutate(PPORRES = signif(PPORRES, 4)) |>
    dplyr::select(arm, start, end, PPTESTCD, PPORRES),
  caption = "PKNCA results for the simulated single-dose arms."
)
```

## Comparison against the published NCA

Hanley 2024 Table 2 reports model-predicted values for both single doses. The
90 mg Cmax, Tmax, and AUC120 come from the bioequivalence-study row
(AP26113-15-106); the 90 mg AUC-infinity is taken from the itraconazole control
arm, which is the only 90 mg AUC-infinity the paper reports, and which was
simulated in a slightly different virtual population (predicted Cmax 279 ng/mL
there, versus 292 ng/mL in the bioequivalence-study row). The 180 mg values are
the rifampin control arm. Tmax and AUC120 were not reported for 180 mg.

```{r nca-compare}
published_sd <- tibble::tribble(
  ~arm,                  ~cmax, ~tmax, ~auclast, ~aucinf.obs,
  "90 mg single dose",     292,   2.1,     6825,        6927,
  "180 mg single dose",    581,    NA,       NA,       14205
)

cmp_sd <- nlmixr2lib::ncaComparisonTable(
  simulated = as.data.frame(nca_sd) |>
    dplyr::filter(PPTESTCD %in% c("cmax", "tmax", "auclast", "aucinf.obs")),
  reference = published_sd,
  by        = "arm",
  units     = c(cmax = "ng/mL", tmax = "h",
                auclast = "ng*h/mL", aucinf.obs = "ng*h/mL"),
  tolerance_pct = 20
)

knitr::kable(
  cmp_sd,
  caption = paste("Simulated vs. Hanley 2024 Table 2 model-predicted values.",
                  "* differs from the reference by >20%."),
  align = c("l", "l", "r", "r", "r")
)
```

Every comparison agrees to within 6.4%, the largest gap being the 180 mg
AUC-infinity. Cmax -- the parameter most sensitive to the systemic-compartment
volume, and hence the sharpest test of the volume derivation above -- agrees to
within 1% at both doses. No parameter was tuned to achieve this.

```{r nca-gate}
# `% diff` is formatted as text (a trailing "*" marks rows over tolerance),
# so strip the formatting before testing.
pct <- suppressWarnings(
  as.numeric(gsub("[^0-9.eE+-]", "", as.character(cmp_sd[["% diff"]])))
)
stopifnot(any(is.finite(pct)), max(abs(pct), na.rm = TRUE) < 10)
cat(sprintf(paste("All %d single-dose NCA comparisons agree with Hanley 2024",
                  "Table 2; largest discrepancy %.1f%%.\n"),
            sum(is.finite(pct)), max(abs(pct), na.rm = TRUE)))
```

# Known limitation: the multiple-dose cancer arm

Hanley 2024 also verified the model against steady-state data from patients
with cancer given 90 mg q.d. for 7 days followed by 180 mg q.d. for 22 days
(study AP26113-11-101, Table 2 bottom block). Those simulations used the Simcyp
Sim-Cancer population, whose lower albumin raises the fraction unbound -- the
paper attributes the agreement specifically to this ("a higher fraction unbound
for brigatinib in the cancer patient population, due to lower albumin
concentrations in patients with cancer, allowed for this good agreement").
Appendix S1 Equation S6 describes the calibration but the resulting cancer
`fu` is never printed, and Equation S6 is one of the formulae the PDF renders
as an undecodable image. This model therefore carries the healthy-participant
`fu = 0.0883` and is expected to *underpredict* the cancer steady-state
exposures.

```{r steady-state}
# 90 mg q.d. days 1-7 (t = 0, 24, ..., 144); 180 mg q.d. days 8-29
# (t = 168, 192, ..., 672). Day 28 spans t = 648 to 672 h.
dose_times_90  <- seq(0, 144, by = 24)
dose_times_180 <- seq(168, 672, by = 24)

ss_obs <- sort(unique(c(
  seq(0, 672, by = 6),                 # coarse background
  seq(648, 672, by = 0.05)             # dense over day 28
)))

events_ss <- dplyr::bind_rows(
  tibble::tibble(id = 1L, time = dose_times_90,  amt = 90,
                 evid = 1L, cmt = "depot"),
  tibble::tibble(id = 1L, time = dose_times_180, amt = 180,
                 evid = 1L, cmt = "depot"),
  tibble::tibble(id = 1L, time = ss_obs, amt = NA_real_,
                 evid = 0L, cmt = "central")
) |>
  dplyr::arrange(time, dplyr::desc(evid))

sim_ss <- rxode2::rxSolve(mod, events = events_ss)

conc_ss <- sim_ss |>
  dplyr::filter(!is.na(Cc)) |>
  dplyr::mutate(id = 1L, arm = "90 mg q.d. x7d then 180 mg q.d.") |>
  dplyr::select(id, time, Cc, arm)

dose_ss <- events_ss |>
  dplyr::filter(evid == 1L) |>
  dplyr::mutate(arm = "90 mg q.d. x7d then 180 mg q.d.") |>
  dplyr::select(id, time, amt, arm)

nca_ss <- PKNCA::pk.nca(PKNCA::PKNCAdata(
  PKNCA::PKNCAconc(conc_ss, Cc ~ time | arm + id,
                   concu = "ng/mL", timeu = "h"),
  PKNCA::PKNCAdose(dose_ss, amt ~ time | arm + id, doseu = "mg"),
  intervals = data.frame(start = 648, end = 672,
                         cmax = TRUE, tmax = TRUE,
                         auclast = TRUE, cmin = TRUE)
))

published_ss <- tibble::tribble(
  ~arm,                                ~cmax, ~tmax, ~auclast, ~cmin,
  "90 mg q.d. x7d then 180 mg q.d.",    1054,  1.88,    16703,   360
)

cmp_ss <- nlmixr2lib::ncaComparisonTable(
  simulated = as.data.frame(nca_ss) |>
    dplyr::filter(PPTESTCD %in% c("cmax", "tmax", "auclast", "cmin")),
  reference = published_ss,
  by        = "arm",
  units     = c(cmax = "ng/mL", tmax = "h",
                auclast = "ng*h/mL", cmin = "ng/mL"),
  tolerance_pct = 20
)

knitr::kable(
  cmp_ss,
  caption = paste("Day-28 steady state vs. Hanley 2024 Table 2 predicted",
                  "values for patients with cancer.",
                  "* differs from the reference by >20%."),
  align = c("l", "l", "r", "r", "r")
)
```

The steady-state exposures come out low, as anticipated: the healthy-participant
`fu` gives a lower unbound hepatic clearance than the Sim-Cancer value the paper
used, and the reduction also carries no renal-function scalar (Appendix S1
Equation S7). This arm is documented rather than corrected -- adjusting `fu`
to close the gap would be tuning to a validation target.

# Terminal phase

```{r terminal}
thalf <- as.data.frame(nca_sd) |>
  dplyr::filter(PPTESTCD == "half.life") |>
  dplyr::pull(PPORRES) |>
  unique()
cat(sprintf("Terminal half-life of the reduction: %.1f h\n", thalf[1]))

frac <- as.data.frame(nca_sd) |>
  dplyr::filter(PPTESTCD %in% c("auclast", "aucinf.obs"), arm == "90 mg single dose")
cat(sprintf("AUC(0-120) captures %.1f%% of AUC(0-Inf).\n",
            100 * frac$PPORRES[frac$PPTESTCD == "auclast"] /
                  frac$PPORRES[frac$PPTESTCD == "aucinf.obs"]))
```

The terminal slope of the reduction is set entirely by the SAC exchange, which
was optimized against a 120 h single-dose profile, so the tail is shorter than
the terminal half-life usually quoted for brigatinib. This is inherited from the
source model rather than introduced by the reduction: the paper's own predicted
AUC120 and AUC-infinity for the same 90 mg dose differ by only 1.5%
(6825 versus 6927 ng*h/mL), so its model also puts almost no exposure beyond
120 h. Do not use this model to extrapolate exposure far past the single-dose
sampling window.

# Assumptions and deviations {#assumptions-and-deviations}

* **The Simcyp platform model is not encoded.** The disposition ODEs of the
  minimal PBPK model with an SAC are not published. This file is a
  compartmental *reduction* fitted by nothing: the liver and portal-vein
  compartments of Figure 1b are lumped into the systemic compartment, and the
  hepatic extraction they represent is folded into a single systemic clearance
  via the well-stirred model. The reduction is validated by reproducing the
  paper's own predictions, not by matching its internal structure.
* **The V_sys convention is imported, not sourced.** Table 1 gives Vss
  (6.91 L/kg) and Vsac (6.0 L/kg) but never states how Simcyp splits the
  remainder between the systemic compartment and the liver. The convention used
  here -- systemic volume = (Vss - Vsac) x body weight - liver volume -- was
  taken from the Table 1 footnote of a sibling Simcyp minimal-PBPK publication
  and is corroborated, not sourced, by the fact that it reproduces the paper's
  predicted Cmax to within 1% at both doses. The alternative of not subtracting
  the liver (63.70 L rather than 62.05 L) predicts Cmax about 3.3% low, so the
  falsifier corroborates the convention but does not sharply prove it. The
  **portal-vein volume of Figure 1b is not reported anywhere in the paper or
  supplement and is not subtracted.**
* **Q_PV and Q_HA are not reported.** Figure 1b splits hepatic inflow into
  portal-vein and hepatic-artery streams, but only the total QH = 90 L/h is
  given, and the paper is internally inconsistent about it (Appendix S1
  Equation S2 calls 90 L/h "blood flow in the hepatic portal vein" while the
  Figure 1 legend defines QH as "blood flow in the liver"). The reduction needs
  only the total, so the ambiguity does not affect it.
* **Appendix S1's 43.02 L/h is treated as a typo**; 48.05 L/h is used. See the
  erratum section above for the arithmetic that settles it.
* **The SAC volume Vsac = 6.0 L/kg is not carried as a parameter.** Because
  `kin` and `kout` act on drug *masses* (Hanley 2024 Figure 1 legend), the SAC
  volume never enters the plasma prediction. Note that the peripheral volume
  implied by `vc * k12 / k21` (87.3 L) is *not* Vsac (420 L at 70 kg), and the
  steady-state volume implied by this reduction is likewise not the Table 1 Vss.
  The reduction reproduces plasma concentrations, not the platform's internal
  volume bookkeeping.
* **No inter-individual variability and no residual error.** The percent-CV
  figures in Tables 2-4 are the spread of a Simcyp virtual population driven by
  unpublished population files, not estimated omegas or sigmas. Rather than
  invent variance components, there are no etas and `propSd` is fixed at zero.
  The model is a typical-value simulation model.
* **No covariates.** Body weight, albumin, and creatinine clearance all act in
  the source through Simcyp population files (Appendix S1 Equations S6 and S7,
  both rendered as undecodable formula images in the PDF). They are recorded in
  the model file's `covariatesDataExcluded` rather than silently dropped.
* **A 70 kg reference weight** is assumed for the L/kg volume inputs; the paper
  does not state the weight its virtual populations were centred on.
* **The cancer steady-state arm underpredicts**, for the reasons given above.
* **The DDI results are not reproducible.** Tables 2 (test arms), 3, and 4 rest
  on proprietary Simcyp compound files for the perpetrators and victims, plus
  induction parameters taken from Yamashita et al. for rifampin. None of that is
  in this package. The transporter inhibition constants for brigatinib
  (Ki 1.65, 10.1, 5.58, and 0.78 uM for P-gp, BCRP, OCT1, and MATE1;
  Appendix S1 Table S4) are reported and could parameterise a future
  interaction model, but there is no victim model here for them to act on, so
  they are not carried.
* **Observed data were not digitised.** All comparisons are against the paper's
  *predicted* summary statistics, which is the correct target for validating a
  reduction of the paper's model.
