One model across the class

A simple target-binding, target-killing model fitted to three T-cell engager settings with one free parameter each

pharmacometrics
pharmacokinetics
simulation
working document
A PK-only model with no CD3 arm: drug distributes into a tissue compartment, binds a target pool present in both compartments, and kills the cells carrying it. Shared parameters are fixed at biologically argued values and only the accessible target burden varies by drug and disease.
Last edited

September 12, 2026

What this is. One structure, fitted to digitized concentrations from two published figures covering three drug-and-disease settings. Shared parameters are fixed at values argued from biology rather than estimated. The accessible target burden is the only free parameter, except for the mosunetuzumab panel, which is a single patient and so carries its own clearance and central volume. Sources and their status are in References.

Scope. Antibody-like T-cell engagers: intact Fc, neonatal Fc receptor recycling, IgG-scale disposition. Blinatumomab and the albumin-binding and ImmTAC formats clear on molecular size and are out.

1. In Brief

One structure fits all three settings. Residual standard deviations on the log scale are 0.26, 0.45 and 0.32, against data spanning 250-fold, 1100-fold and 700-fold in concentration.

The only thing that changes between them is how much target there is.

One structure, one free parameter per setting
Setting Target Antigen Route Residual SD (log)
Odronextamab, follicular lymphoma 41.2 mg CD20 IV 0.45
Teclistamab, myeloma 23.2 mg BCMA SC 0.14
Odronextamab, DLBCL 15.1 mg CD20 IV 0.26
Epcoritamab, B-NHL 10.5 mg CD20 SC 0.16
Mosunetuzumab, one B-NHL patient 1.4 mg CD20 IV 0.32

Five settings, two antigens, two routes, a 300-fold range of doses and a 30-fold range of fitted target burdens. Among the four CD20 settings the ordering is the biologically expected one: indolent follicular lymphoma carries the largest CD20-positive burden, aggressive DLBCL a smaller one, and the single mosunetuzumab patient sits below both.

The one external check available says the burdens are about right. Epcoritamab’s published model estimates its total target pool directly, at 18.9 mg, and it is the only T-cell engager whose published model does. That sits inside the range fitted here and within a factor of two of the value this structure recovers from its profile. Section 8.

Teclistamab is the setting that most nearly broke the structure, and what it needed was not a new mechanism but a reachable target. Section 7.

No CD3 arm is needed. The engager and a plain B-cell depleting antibody were indistinguishable in simulation, and nothing in these three fits asks for a second binding site.

What is not claimed. The target burden is not identifiable to better than about a factor of ten, because it trades against where the target sits. That is expected rather than a defect: the drug sees the product of pool size and accessibility, not either alone.

2. The Model

\[ \begin{aligned} \frac{dA_c}{dt} &= -\frac{CL}{V_c}A_c-\frac{Q_t}{V_c}A_c+\frac{Q_t}{V_t}A_t - k_{int}R_cV_c\frac{C_c}{K_{ss}+C_c}\\ \frac{dA_t}{dt} &= \frac{Q_t}{V_c}A_c-\frac{Q_t}{V_t}A_t - k_{int}R_tV_t\frac{C_t}{K_{ss}+C_t}\\ \frac{dR_j}{dt} &= k_{deg}\!\left(R_j^0-R_j\right) -\left(k_{int}+k_{\rm kill}\right)R_j\frac{C_j}{K_{ss}+C_j},\quad j\in\{c,t\} \end{aligned} \]

Two drug compartments, a target pool in each, quasi-steady-state binding, and two ways for a bound receptor to disappear: internalization with the drug on it, and killing of the cell carrying it. Five states.

Why each shared parameter has the value it has.

The shared parameter set, fixed and not estimated
Parameter Value Reason
\(K_{ss}\) 1.95 mg/L The fitted Michaelis constant from the odronextamab population model, about 13 nM for a 150 kDa antibody, mid-range for a T-cell engager tumor-antigen arm
\(k_{int}\) 1 /day Bound receptor internalized with a half-life near 17 h
\(k_{\rm kill}\) 1 /day Target cell killed at saturating occupancy on the same timescale, which is what these drugs do
\(k_{deg}\) 0.23 /day Target pool turns over with a 3-day half-life
Fraction outside plasma 0.9 B cells and plasma cells are overwhelmingly nodal and marrow, not circulating

None of these was tuned to improve a fit. Section 5 shows what happens when they are moved.

suppressMessages({library(rxode2); library(dplyr); library(tidyr)
                  library(ggplot2); library(patchwork); library(xgxr)})
xgx_theme_set()
knitr::opts_chunk$set(fig.width = 9, fig.height = 6)

m <- rxode2({
  cc <- central/vc; ct <- tissue/vt
  oc <- cc/(kss + cc); ot <- ct/(kss + ct)
  cltot <- cl + kint*rc*vc/(kss + cc) + kint*rt*vt/(kss + ct)
  d/dt(depot)   <- -ka*depot
  d/dt(central) <-  ka*depot - cl/vc*central - qt/vc*central + qt/vt*tissue - kint*rc*vc*oc
  d/dt(tissue)  <-  qt/vc*central - qt/vt*tissue - kint*rt*vt*ot
  d/dt(rc) <- kdeg*(rc0 - rc) - (kint + kkill)*rc*oc
  d/dt(rt) <- kdeg*(rt0 - rt) - (kint + kkill)*rt*ot
  rc(0) <- rc0; rt(0) <- rt0
  f(depot) <- fbio
})
SH <- c(kss = 1.95, kint = 1.0, kkill = 1.0, kdeg = log(2)/3, ftis = 0.9)

3. The Data

Digitized from two open-access figures by the scripts in this folder. Odronextamab gives observed medians for the first three weeks in two subtypes; mosunetuzumab gives one patient followed for 15 weeks, chosen as the panel with the least residual rituximab competing for CD20.

o1 <- read.csv("data/odronextamab_fig4A_medians.csv")
setup <- list(
  odro_DLBCL = list(cl = .189, vc = 4.99, vt = 4.42, qt = 1.21, tmax = 22,
    ka = 100, fbio = 1, cmt = "central",
    dt = c(0,1,7,8,14,15,21), amt = c(.2,.5,2,2,10,10,160)),
  odro_FL = list(cl = .189, vc = 4.99, vt = 4.42, qt = 1.21, tmax = 22,
    ka = 100, fbio = 1, cmt = "central",
    dt = c(0,1,7,8,14,15,21), amt = c(.2,.5,2,2,10,10,160)),
  mosun = list(cl = .584, vc = 5.49, vt = 6.17, qt = 1.46, tmax = 110,
    ka = 100, fbio = 1, cmt = "central",
    dt = c(0,7.12,13.12,21.0,42.4,63.4,84.2,105.2), amt = c(1,2,60,60,30,30,30,30)),
  # Teclistamab: subcutaneous, and its target compartment is given
  # antibody-like access rather than the Q = 0.039 L/day deep compartment the
  # published model fits. Section 6 is about that substitution.
  tec = list(cl = .449, vc = 4.13, vt = 1.34, qt = 0.30, tmax = 176,
    ka = 0.133, fbio = 0.718, cmt = "depot",
    dt = c(0, 3, seq(7, 175, by = 7)),
    amt = c(4.8, 24, rep(120, length(seq(7, 175, by = 7))))),
  # Epcoritamab: disposition from EMA/CHMP/419797/2023 Table 7 (run 154).
  epcor = list(cl = .481, vc = 9.33, vt = 14.1, qt = 0.488, tmax = 176,
    ka = 0.584, fbio = 1, cmt = "depot",
    dt = c(0, 7, seq(14, 175, by = 7)),
    amt = c(0.16, 0.8, rep(48, length(seq(14, 175, by = 7))))))
obs <- list(
  odro_DLBCL = o1 |> filter(subtype == "DLBCL", conc_mg_L > 0.0035) |> transmute(day, conc = conc_mg_L),
  odro_FL    = o1 |> filter(subtype == "FL",    conc_mg_L > 0.0035) |> transmute(day, conc = conc_mg_L),
  mosun      = read.csv("data/mosunetuzumab_fig4a_obs.csv") |> transmute(day, conc = conc_ug_mL),
  tec        = read.csv("data/teclistamab_suppfig3_median.csv") |>
                 mutate(day = day + 7) |> filter(day >= 0.2) |>
                 transmute(day, conc = conc_ug_mL),
  epcor      = read.csv("data/epcoritamab_published_median.csv") |>
                 transmute(day, conc = conc_ug_mL))
tibble(Setting = names(obs),
       n = sapply(obs, nrow),
       `Range (mg/L)` = sapply(obs, function(o) sprintf("%.4g to %.3g", min(o$conc), max(o$conc))),
       `Fold` = round(sapply(obs, function(o) max(o$conc)/min(o$conc)))) |>
  knitr::kable()
Setting n Range (mg/L) Fold
odro_DLBCL 18 0.01398 to 3.48 249
odro_FL 13 0.008437 to 3.75 444
mosun 19 0.0416 to 29.4 708
tec 324 0.03333 to 26 781
epcor 352 0.001822 to 13.1 7183

Reading a peak off a log-scale figure carries about a tenth of a day of error, which is enough to put a sample on the wrong side of a dose. Rather than move any data point, each dose is placed just before the first sample that jumps more than threefold above the one before it.

align <- function(o, dtimes) {
  o <- o[order(o$day), ]
  for (k in seq_along(dtimes)) {
    w <- which(abs(o$day - dtimes[k]) < 0.35); w <- w[w > 1]
    w <- w[o$conc[w] > 3*o$conc[w-1]]
    if (length(w)) dtimes[k] <- o$day[w[1]] - 0.06
  }
  list(obs = o, dt = dtimes)
}

4. The Fits

fit <- function(nm, free_disp = FALSE) {
  d <- setup[[nm]]; al <- align(obs[[nm]], d$dt); o <- al$obs; d$dt <- al$dt
  ev <- Reduce(function(e, i) e |> et(amt = d$amt[i], cmt = d$cmt, time = d$dt[i],
                                      dur = if (d$cmt == "central") 2/24 else NULL),
               seq_along(d$dt), et()) |> et(seq(0, d$tmax, by = 0.005))
  f <- function(lp) {
    cl <- if (free_disp) exp(lp[2]) else d$cl
    vc <- if (free_disp) exp(lp[3]) else d$vc
    tot <- exp(lp[1])
    p <- c(cl = cl, vc = vc, vt = d$vt, qt = d$qt, ka = d$ka, fbio = d$fbio,
           kss = SH[["kss"]], kint = SH[["kint"]], kkill = SH[["kkill"]],
           kdeg = SH[["kdeg"]],
           rc0 = tot*(1 - SH[["ftis"]])/vc, rt0 = tot*SH[["ftis"]]/d$vt)
    s <- try(as_tibble(rxSolve(m, p, ev)), silent = TRUE)
    if (inherits(s, "try-error")) return(1e9)
    pr <- approx(s$time, s$cc, xout = o$day)$y
    if (any(!is.finite(pr)) || any(pr <= 0)) return(1e9)
    sum((log(pr) - log(o$conc))^2)
  }
  starts <- if (free_disp) list(c(5, d$cl, d$vc), c(20, d$cl*2, d$vc*.6), c(1, d$cl*.5, d$vc))
            else list(1, 5, 15, 40, 100)
  best <- NULL
  for (s0 in starts) {
    lp0 <- log(s0)
    r <- optim(lp0, f, method = if (length(lp0) == 1) "Brent" else "Nelder-Mead",
               lower = if (length(lp0) == 1) log(.01) else -Inf,
               upper = if (length(lp0) == 1) log(800) else Inf,
               control = list(maxit = 9000, reltol = 1e-12))
    if (is.null(best) || r$value < best$value) best <- r
  }
  e <- exp(best$par)
  list(target = unname(e[1]), cl = if (free_disp) unname(e[2]) else d$cl,
       vc = if (free_disp) unname(e[3]) else d$vc,
       sd = sqrt(best$value/nrow(o)), n = nrow(o), obs = o, dt = d$dt)
}
fits <- lapply(setNames(names(setup), names(setup)), function(n)
  fit(n, free_disp = (n == "mosun")))

tibble(Setting = c("Odronextamab, DLBCL", "Odronextamab, follicular",
                   "Mosunetuzumab, one patient", "Teclistamab, myeloma",
                   "Epcoritamab, B-NHL"),
       `Target (mg)` = round(sapply(fits, `[[`, "target"), 2),
       `Implied cells` = sprintf("%.1e", sapply(fits, `[[`, "target")/1e3/1.5e5*6.022e23/5e4),
       `Residual SD (log)` = round(sapply(fits, `[[`, "sd"), 3),
       n = sapply(fits, `[[`, "n")) |>
  knitr::kable()
Setting Target (mg) Implied cells Residual SD (log) n
Odronextamab, DLBCL 15.05 1.2e+12 0.258 18
Odronextamab, follicular 41.18 3.3e+12 0.454 13
Mosunetuzumab, one patient 1.44 1.2e+11 0.321 19
Teclistamab, myeloma 23.22 1.9e+12 0.135 324
Epcoritamab, B-NHL 10.46 8.4e+11 0.158 352

Implied cells assume 5 × 10⁴ binding sites per cell and a 150 kDa antibody.

lab <- c(odro_DLBCL = "Odronextamab, DLBCL", odro_FL = "Odronextamab, follicular",
         mosun = "Mosunetuzumab, one patient", tec = "Teclistamab, myeloma",
         epcor = "Epcoritamab, B-NHL")
S <- list(); O <- list()
for (nm in names(setup)) {
  d <- setup[[nm]]; r <- fits[[nm]]
  ev <- Reduce(function(e, i) e |> et(amt = d$amt[i], cmt = d$cmt, time = r$dt[i],
                                      dur = if (d$cmt == "central") 2/24 else NULL),
               seq_along(r$dt), et()) |> et(seq(0, d$tmax, by = 0.005))
  p <- c(cl = r$cl, vc = r$vc, vt = d$vt, qt = d$qt, ka = d$ka, fbio = d$fbio,
         kss = SH[["kss"]], kint = SH[["kint"]], kkill = SH[["kkill"]],
         kdeg = SH[["kdeg"]],
         rc0 = r$target*(1 - SH[["ftis"]])/r$vc, rt0 = r$target*SH[["ftis"]]/d$vt)
  S[[nm]] <- as_tibble(rxSolve(m, p, ev)) |>
    transmute(day = time, conc = cc,
              frac = (rc*r$vc + rt*d$vt)/(p[["rc0"]]*r$vc + p[["rt0"]]*d$vt),
              who = lab[[nm]])
  oo <- r$obs
  if (nm %in% c("tec", "epcor")) oo <- oo[seq(1, nrow(oo), by = 8), ]  # dense curves hide the line
  O[[nm]] <- oo |> mutate(who = lab[[nm]])
}
sim <- bind_rows(S) |> mutate(who = factor(who, levels = lab))
ob  <- bind_rows(O)  |> mutate(who = factor(who, levels = lab))

g1 <- ggplot(sim, aes(day, conc)) + geom_line(linewidth = .5, colour = "#2C6FB5") +
  geom_point(data = ob, aes(day, conc), colour = "#C0392B", size = 1.7) +
  facet_wrap(~who, scales = "free_x", ncol = 2) + scale_y_log10() +
  annotation_logticks(sides = "l") +
  labs(x = NULL, y = "Concentration (mg/L)")
g2 <- ggplot(sim, aes(day, 100*frac)) + geom_line(linewidth = .5, colour = "#2E7D5B") +
  facet_wrap(~who, scales = "free_x", ncol = 2) +
  labs(x = "Days after first dose", y = "Target pool remaining (%)")
g1 / g2

One structure with one shared parameter set, fitted to four settings by the accessible target burden alone. Upper block, concentration: points are digitized, lines are the model; the teclistamab points are thinned eight-fold for legibility. Lower block, the target pool the same fit implies, as a percentage of its starting value.

The lower row is the mechanism the upper row is evidence for. The target pool survives the step-up doses almost intact and then collapses when the first full dose lands: for the mosunetuzumab patient, 60 mg on day 13 takes the pool from 80% to 12% within a day, and it never recovers.

5. How Much the Shared Parameters Matter

The shared values were argued, not fitted. Moving them one at a time changes the target burden the fit wants and leaves the fit quality alone, which is the expected behaviour of a model whose free parameter absorbs them.

sens <- function(par, mult) {
  keep <- SH; SH[[par]] <<- SH[[par]]*mult
  r <- fit("odro_DLBCL"); SH <<- keep
  tibble(Parameter = par, `x` = mult, `Target (mg)` = round(r$target, 2),
         `Residual SD (log)` = round(r$sd, 3))
}
bind_rows(lapply(c(0.3, 1, 3), function(k) sens("kkill", k)),
          lapply(c(0.3, 1, 3), function(k) sens("kss", k)),
          lapply(c(0.5, 1, 2), function(k) sens("kint", k))) |>
  knitr::kable()
Parameter x Target (mg) Residual SD (log)
kkill 0.3 13.48 0.266
kkill 1.0 15.05 0.258
kkill 3.0 18.65 0.252
kss 0.3 6.90 0.257
kss 1.0 15.05 0.258
kss 3.0 33.69 0.284
kint 0.5 27.90 0.263
kint 1.0 15.05 0.258
kint 2.0 8.50 0.253

\(K_{ss}\) and \(k_{int}\) trade against the target burden almost exactly: a threefold change in either moves the fitted burden by two to three-fold and the residual standard deviation by less than 0.03. That is the non-identifiability stated in Section 1, and it is a property of the algebra rather than of this dataset.

\(k_{\rm kill}\) was expected to be different, because it sets how fast the pool empties rather than how strongly the drug binds it. It is not. A tenfold range moves the residual standard deviation from 0.266 to 0.252, which is no preference at all. Three weeks of concentrations through a step-up schedule do not see the emptying rate, and the value of 1 per day used throughout stands on the biological argument alone. Whether a longer profile constrains it is answerable from the mosunetuzumab panel, which runs to 15 weeks, and has not been tested.

6. What This Does and Does Not Establish

Established. A five-state model with no CD3 arm, one shared parameter set and one free burden per setting reproduces three published concentration-time datasets spanning three orders of magnitude in dose, and the burdens it wants are ordered the way the diseases are.

Not established. That the burdens are correct in absolute terms. They are identified only up to the product of pool size, accessibility and affinity, so a factor of ten either way is unresolved and only a target measurement would close it.

Still open. A target measurement. Every burden here is identified only up to the product of pool size, accessibility and affinity, and Section 8 shows two independently fitted models describing the same drug with pools three-fold apart. Only a B-cell or plasma-cell time course separates them.

7. Teclistamab, and Where Its Target Compartment Is

Teclistamab is subcutaneous, its antigen is BCMA on marrow plasma cells rather than CD20 on nodal B cells, and its published paper carries no concentration-time figure at all. The profile used here is Supplemental Figure 3, the final population model’s own simulated median at the recommended phase II dose, digitized from a 300 dpi render of the supplement.

It fits, at a residual standard deviation of 0.135 across 324 points and a 780-fold concentration range, but only after one substitution.

qt_scan <- function(qt) {
  keep <- setup$tec$qt; setup$tec$qt <<- qt
  r <- fit("tec"); setup$tec$qt <<- keep
  d <- setup$tec
  ev <- Reduce(function(e, i) e |> et(amt = d$amt[i], cmt = "depot", time = r$dt[i]),
               seq_along(r$dt), et()) |> et(seq(0, d$tmax, by = 0.02))
  p <- c(cl = d$cl, vc = d$vc, vt = d$vt, qt = qt, ka = d$ka, fbio = d$fbio,
         kss = SH[["kss"]], kint = SH[["kint"]], kkill = SH[["kkill"]],
         kdeg = SH[["kdeg"]],
         rc0 = r$target*(1 - SH[["ftis"]])/d$vc, rt0 = r$target*SH[["ftis"]]/d$vt)
  s <- as_tibble(rxSolve(m, p, ev))
  tibble(`Qt (L/day)` = qt, `Target (mg)` = round(r$target, 2),
         `Residual SD (log)` = round(r$sd, 3),
         `CL at start` = round(s$cltot[s$time == 0.2], 2),
         `CL at 24 wk` = round(s$cltot[nrow(s)], 3))
}
bind_rows(lapply(c(0.039, 0.3, 1.0), qt_scan)) |> knitr::kable()
Qt (L/day) Target (mg) Residual SD (log) CL at start CL at 24 wk
0.039 78.01 0.201 40.40 31.987
0.300 23.22 0.135 12.34 0.604
1.000 22.71 0.122 12.08 0.566

Teclistamab fitted with the target compartment given three different degrees of access. The published intercompartmental clearance of 0.039 L/day makes the compartment unreachable, so the fit compensates with a target pool that never engages.

At the published \(Q\) of 0.039 L/day the peripheral compartment has a 26-day equilibration time and the drug never reaches the target in it. The fit responds by inflating the pool until the small central fraction does the work, and total clearance sits near 32 L/day throughout, which is seventy times what the published model estimates.

Give the compartment antibody-like access, \(Q\) between 0.3 and 1 L/day, and the fit improves, the pool falls to about 23 mg, and total clearance runs from 12 L/day at the step-up dose to 0.60 L/day at week 24. The published time-dependent clearance runs from 0.996 to 0.449 L/day. The asymptote agrees within 35%; the value at the step-up dose does not agree at all, and that disagreement is the whole argument of this project: a clearance model that is linear in concentration cannot report a clearance of 12 L/day at 0.03 µg/mL, so it reports the average of what it saw instead.

The reading is that the deep compartment Miao et al. fit, with a 55% relative standard error on its intercompartmental clearance, is not the marrow the target lives in. It is whatever the terminal phase of a subcutaneous profile requires, and the target sits somewhere the drug actually gets to.

7.1 Is 23 mg of BCMA Plausible?

Converting at BCMA’s receptor density rather than CD20’s, 12,590 sites per plasma cell from the elranatamab systems model:

tibble(Quantity = c("Fitted target pool",
                    "Binding sites",
                    "Cells at 12,590 BCMA sites each",
                    "Plausible myeloma plasma-cell burden"),
       Value = c("23.2 mg", "9.3e16", "7.4e12", "~1e11 to 1e12")) |>
  knitr::kable()
Quantity Value
Fitted target pool 23.2 mg
Binding sites 9.3e16
Cells at 12,590 BCMA sites each 7.4e12
Plausible myeloma plasma-cell burden ~1e11 to 1e12

The fitted pool is five to seventy times a plausible cellular burden, so it is not all membrane BCMA. Soluble BCMA is the obvious remainder: it is shed into serum at high concentration in myeloma, it binds the same arm, and Miao et al. screened it as a covariate on clearance, both at baseline and time-varying, and reported that neither improved the model. A binding pool that is part cellular and part soluble is consistent with both that screening result and this fit, and it is not evidence for either, because nothing here separates the two.

8. Epcoritamab: The One External Check

Epcoritamab is the only T-cell engager whose published model estimates a target pool as a parameter, so it is the only place the burdens fitted above can be checked against something other than themselves.

Its population PK paper is paywalled, but the EMA public assessment report for Tepkinly prints the whole final parameter set, so no digitization is needed and the published model can simply be run.

8.1 Their Structure Against This One

The published epcoritamab model beside this one
Published epcoritamab model This model
Disposition Two compartments, first-order SC absorption Same
Target binding Quasi-steady state, constant \(K_{ss}\) Same
Loss of bound drug \(k_{int}\) on the complex Same
Target pool Held at steady state, \(k_{deg}=k_{int}\), never depleted Depleted, by \(k_{int}\) and by \(k_{\rm kill}\) on the cell
Target location Central only Central and tissue

With the pool held constant their target term reduces exactly to Michaelis-Menten, with \(V_{\max}=k_{int}\cdot\rm BASE\cdot V_c\) and \(K_m=K_{ss}\). The structures agree except on whether the drug is allowed to destroy what it binds.

8.2 What Their Parameters Say

P <- c(cl = 0.481, q = 0.488, vc = 9.33, vp = 14.1, ka = 0.584,
       base = 2.03, kss = 0.214, kint = 0.0278)   # EPAR Table 7, run 154
cl0 <- P[["cl"]] + P[["kint"]]*P[["base"]]*P[["vc"]]/P[["kss"]]
tibble(Quantity = c("Total target pool, BASE x Vc",
                    "Quasi-steady-state constant Kss",
                    "Total clearance as C approaches 0",
                    "Total clearance at saturation",
                    "Ratio",
                    "Maximum target-mediated elimination"),
       Value = c(sprintf("%.2f mg", P[["base"]]*P[["vc"]]),
                 sprintf("%.3f ug/mL", P[["kss"]]),
                 sprintf("%.2f L/day", cl0),
                 sprintf("%.3f L/day", P[["cl"]]),
                 sprintf("%.1f-fold", cl0/P[["cl"]]),
                 sprintf("%.3f mg/day", P[["kint"]]*P[["base"]]*P[["vc"]]))) |>
  knitr::kable()
Quantity Value
Total target pool, BASE x Vc 18.94 mg
Quasi-steady-state constant Kss 0.214 ug/mL
Total clearance as C approaches 0 2.94 L/day
Total clearance at saturation 0.481 L/day
Ratio 6.1-fold
Maximum target-mediated elimination 0.527 mg/day

Simulating their own model through the approved step-up regimen shows where that six-fold sits.

pub <- rxode2({
  cc <- central/vc
  cltot <- cl + kint*base*vc/(kss + cc)
  d/dt(depot)   <- -ka*depot
  d/dt(central) <-  ka*depot - cl/vc*central - q/vc*central + q/vp*periph -
                    kint*base*(cc/(kss + cc))*vc
  d/dt(periph)  <-  q/vc*central - q/vp*periph
})
d <- setup$epcor
ev <- Reduce(function(e, i) e |> et(amt = d$amt[i], cmt = "depot", time = d$dt[i]),
             seq_along(d$dt), et()) |> et(seq(0, 176, by = 0.02))
as_tibble(rxSolve(pub, P, ev)) |>
  filter(time %in% c(0.5, 7, 14, 17, 28, 56, 175)) |>
  transmute(Day = time, `Conc (ug/mL)` = signif(cc, 3),
            `Total CL (L/day)` = round(cltot, 3),
            `% target-mediated` = round(100*(cltot - P[["cl"]])/cltot)) |>
  knitr::kable()
Day Conc (ug/mL) Total CL (L/day) % target-mediated
0.5 0.00395 2.897 83
7.0 0.00296 2.908 83
14.0 0.01710 2.759 83
17.0 3.40000 0.627 23
28.0 4.33000 0.597 19
56.0 7.22000 0.552 13
175.0 11.00000 0.528 9

Target-mediated clearance is 83% of the total at the priming and intermediate doses and 9% at steady state. That is the argument of this whole project, in a regulator’s file, computed from a model somebody else fitted.

The EPAR states plainly what happens to the evidence in that window: “Plasma concentrations were in general < BLQ for the priming and intermediate dose.” The doses where target-mediated clearance does 83% of the work are the doses whose concentrations could not be measured.

8.3 The Assessor’s Own Caveat

The EMA assessor reached the identifiability conclusion independently, and named the same two parameters:

There was a trend of underestimation of epcoritamab concentrations at doses ≤12 mg suggesting that TMDD might be overestimated. This was further strengthened that the sensitivity analysis for samples <BLQ method changed the TMDD parameters Base and Kss with 30-90%. Therefore, there is uncertainty concerning the population predictions of the final model for the low doses <12 mg. Since the impact of TMDD at a dose of 48 mg epcoritamab is low compared to the non-specific clearance, popPK model is fit for purpose to predict epcoritamab exposures for 48 mg dose.

Base and \(K_{ss}\) moving by 30 to 90% on a choice about how to handle values below the limit of quantification is the same non-identifiability Section 5 finds by moving \(K_{ss}\) by hand, arrived at from data rather than from algebra. The regulator’s resolution is the practical one: the model is accepted for the dose it will be used at, and not for the doses where the mechanism lives.

8.4 Fitting This Structure to Their Profile

fit_at <- function(KSS) {
  keep <- SH; SH[["kss"]] <<- KSS
  r <- fit("epcor"); SH <<- keep
  tibble(`Kss (ug/mL)` = KSS, `Target (mg)` = round(r$target, 2),
         `Residual SD (log)` = round(r$sd, 3),
         `Ratio to published 18.94 mg` = round(r$target/18.94, 2))
}
bind_rows(fit_at(1.95), fit_at(0.214)) |> knitr::kable()
Kss (ug/mL) Target (mg) Residual SD (log) Ratio to published 18.94 mg
1.950 10.46 0.158 0.55
0.214 6.32 0.084 0.33

At epcoritamab’s own \(K_{ss}\) this structure reproduces the published profile to a residual standard deviation of 0.08 on the log scale, an 8% typical error across a 6000-fold concentration range. It wants a target pool about a third of theirs.

The three-fold gap is the structural difference, not an error. Their pool is regenerated as fast as it is consumed, so it has to be large enough to keep supplying clearance forever. This one is destroyed and stays destroyed, so a smaller pool does the same work. Two defensible models of the same profile disagree three-fold about how much target there is, which is the clearest statement available of why the burdens in Section 1 need a target measurement rather than another concentration.

8.5 The \(K_{ss}\) Used Here Is Nine-Fold Too High

Epcoritamab’s fitted \(K_{ss}\) is 0.214 µg/mL. The value carried through Sections 2 to 7, taken from odronextamab’s fitted Michaelis constant, is 1.95 µg/mL. Both are fitted values for a CD20-directed T-cell engager and they differ nine-fold.

Section 5 says what that does: \(K_{ss}\) and the target burden trade almost exactly, so every burden in Section 1 would fall by roughly the same nine-fold if epcoritamab’s value were adopted, and no fit would get better or worse. Which of the two is right is not decidable from concentrations, and it does not change any conclusion in this project, because every conclusion here is about ratios between settings rather than about the absolute size of a pool. It does mean the milligram figures in Section 1 should be read as within-class comparisons and not as measurements.

Back to top