Age-Stratified BRCA Testing and Olaparib Population Impact

First-pass aggregate reconstruction of the 2024 single-cohort lifetime analysis

Author

Prepared as a reproducible R/Quarto analysis

Published

August 14, 2026

Code
library(dplyr)
library(ggplot2)
library(knitr)
library(readr)
library(scales)
library(tidyr)
Code
model_inputs <- read_csv("inputs/model_inputs.csv", show_col_types = FALSE)
life_table <- read_csv("inputs/us_female_life_table_2023.csv", show_col_types = FALSE)

scalar_input <- function(input_id) {
  value <- model_inputs$value[model_inputs$input_id == input_id]
  if (length(value) != 1) {
    stop("Expected exactly one input for ", input_id, call. = FALSE)
  }
  as.numeric(value)
}

text_input <- function(input_id, column) {
  value <- model_inputs[[column]][model_inputs$input_id == input_id]
  if (length(value) != 1) {
    stop("Expected exactly one input for ", input_id, call. = FALSE)
  }
  value
}

fmt_count <- function(x) comma(round(x, 0), accuracy = 1)
fmt_decimal <- function(x, digits = 3) formatC(x, format = "f", digits = digits)
fmt_pct <- function(x, accuracy = 0.1) percent(x, accuracy = accuracy)
fmt_pp <- function(x, digits = 1) {
  paste(formatC(100 * x, format = "f", digits = digits), "percentage points")
}
fmt_years <- function(x) comma(round(x, 1), accuracy = 0.1)

source_link <- function(label, url) {
  paste0("[", label, "](", url, ")")
}

1 Purpose

This report is a first-pass R/Quarto aggregate reconstruction of Kasle et al.’s population impact model for BRCA testing and adjuvant olaparib in high-risk HER2-negative early breast cancer. The source paper estimated 3,938 olaparib-eligible patients in the 2024 US cohort and reported lifetime reductions of 272 metastatic breast cancer (mBC) recurrences, 68 non-mBC recurrences, and 8,677 life-years gained under testing and olaparib treatment versus no testing and no adjuvant treatment.

The extension here keeps that published analysis as a reproduction benchmark and layers in an exploratory age-stratified treatment-effect modifier for <45 years and >=45 years. The age-stratified evidence is uncertain and should not be read as a definitive age-specific olaparib effect.

2 Source Inputs

Code
source_summary <- model_inputs |>
  count(category, source_url, name = "inputs") |>
  arrange(category, source_url)

kable(
  source_summary,
  col.names = c("Input category", "Source", "Rows"),
  align = c("l", "l", "r")
)
Input category Source Rows
age_distribution https://doi.org/10.1038/s41523-024-00632-8 4
age_modifier info/BRCA_pilot_age_group_het_trials.docx 6
benchmark https://doi.org/10.1080/13696998.2025.2583870 9
clinical_rate https://doi.org/10.1080/13696998.2025.2583870 12
population https://doi.org/10.1080/13696998.2025.2583870 3
representative_age https://doi.org/10.1038/s41523-024-00632-8 1
representative_age https://doi.org/10.1080/13696998.2025.2583870 1

Key sources:

  • Kasle et al. 2025, Journal of Medical Economics, “The estimated long-term clinical effects of BRCA testing and olaparib treatment of early breast cancer in the US population: a population impact model”: DOI 10.1080/13696998.2025.2583870.
  • Age-stratified treatment-effect modifier: local meta-regression report at info/BRCA_pilot_age_group_het_trials.docx and extracted subgroup data at info/d.human.trials.subgroup.data.csv.
  • Age-distribution proxy: Morganti et al. 2024, npj Breast Cancer, “Clinicopathological characteristics and eligibility for adjuvant olaparib of germline BRCA1/2 mutation carriers with HER2-negative early breast cancer” (n=205, Dana-Farber): median age at diagnosis 43 years overall, 39 years among patients meeting olaparib-eligibility criteria: DOI 10.1038/s41523-024-00632-8. This is matched on gBRCA1/2 status and HER2-negative early breast cancer, unlike the previous general-population ACS proxy, but does not report an exact <45/>=45 split or subtype-specific age moments, so 50%/50% is used as a round proxy consistent with the sub-45 median.
  • Female life expectancy: CDC/NCHS United States Life Tables, 2023, Table 3: CDC life tables and Table03.xlsx.

3 Published Model Benchmark

The published model splits the eligible cohort into HR-positive and TNBC subpopulations. This report reconstructs those pieces from the published subtype population sizes and rounded clinical inputs.

Code
subtype_inputs <- tibble(
  subtype = c("HR-positive", "TNBC"),
  patients = c(
    scalar_input("eligible_hr_positive"),
    scalar_input("eligible_tnbc")
  ),
  no_mbc_rate = c(
    scalar_input("hrp_no_treatment_mbc_rate"),
    scalar_input("tnbc_no_treatment_mbc_rate")
  ),
  no_non_mbc_rate = c(
    scalar_input("hrp_no_treatment_non_mbc_rate"),
    scalar_input("tnbc_no_treatment_non_mbc_rate")
  ),
  no_life_years_per_patient = c(
    scalar_input("hrp_no_treatment_life_years"),
    scalar_input("tnbc_no_treatment_life_years")
  ),
  olaparib_mbc_rate = c(
    scalar_input("hrp_olaparib_mbc_rate"),
    scalar_input("tnbc_olaparib_mbc_rate")
  ),
  olaparib_non_mbc_rate = c(
    scalar_input("hrp_olaparib_non_mbc_rate"),
    scalar_input("tnbc_olaparib_non_mbc_rate")
  ),
  olaparib_life_years_per_patient = c(
    scalar_input("hrp_olaparib_life_years"),
    scalar_input("tnbc_olaparib_life_years")
  )
) |>
  mutate(
    no_mbc = patients * no_mbc_rate,
    olaparib_mbc = patients * olaparib_mbc_rate,
    mbc_prevented = no_mbc - olaparib_mbc,
    no_non_mbc = patients * no_non_mbc_rate,
    olaparib_non_mbc = patients * olaparib_non_mbc_rate,
    non_mbc_prevented = no_non_mbc - olaparib_non_mbc,
    no_life_years = patients * no_life_years_per_patient,
    olaparib_life_years = patients * olaparib_life_years_per_patient,
    life_years_gained = olaparib_life_years - no_life_years
  )

subtype_display <- subtype_inputs |>
  transmute(
    Subtype = subtype,
    Patients = fmt_count(patients),
    `mBC avoided` = fmt_count(mbc_prevented),
    `non-mBC avoided` = fmt_count(non_mbc_prevented),
    `Life-years gained` = fmt_count(life_years_gained)
  )

kable(subtype_display, align = c("l", "r", "r", "r", "r"))
Subtype Patients mBC avoided non-mBC avoided Life-years gained
HR-positive 1,433 77 19 2,579
TNBC 2,505 195 48 6,012
Code
calculated_totals <- subtype_inputs |>
  summarise(
    `mBC recurrences, no testing` = sum(no_mbc),
    `mBC recurrences, olaparib` = sum(olaparib_mbc),
    `mBC recurrences avoided` = sum(mbc_prevented),
    `non-mBC recurrences, no testing` = sum(no_non_mbc),
    `non-mBC recurrences, olaparib` = sum(olaparib_non_mbc),
    `non-mBC recurrences avoided` = sum(non_mbc_prevented),
    `Life-years, no testing` = sum(no_life_years),
    `Life-years, olaparib` = sum(olaparib_life_years),
    `Life-years gained` = sum(life_years_gained)
  ) |>
  pivot_longer(everything(), names_to = "metric", values_to = "calculated")

published_totals <- tibble(
  metric = c(
    "mBC recurrences, no testing",
    "mBC recurrences, olaparib",
    "mBC recurrences avoided",
    "non-mBC recurrences, no testing",
    "non-mBC recurrences, olaparib",
    "non-mBC recurrences avoided",
    "Life-years, no testing",
    "Life-years, olaparib",
    "Life-years gained"
  ),
  published = c(
    scalar_input("published_mbc_no_testing"),
    scalar_input("published_mbc_olaparib"),
    scalar_input("published_mbc_prevented"),
    scalar_input("published_non_mbc_no_testing"),
    scalar_input("published_non_mbc_olaparib"),
    scalar_input("published_non_mbc_prevented"),
    scalar_input("published_life_years_no_testing"),
    scalar_input("published_life_years_olaparib"),
    scalar_input("published_life_years_gained")
  )
)

benchmark_comparison <- calculated_totals |>
  left_join(published_totals, by = "metric") |>
  mutate(
    difference = calculated - published,
    relative_difference = difference / published
  )

kable(
  benchmark_comparison |>
    transmute(
      Metric = metric,
      Calculated = fmt_count(calculated),
      Published = fmt_count(published),
      Difference = fmt_count(difference),
      `Relative difference` = fmt_pct(relative_difference, accuracy = 0.1)
    ),
  align = c("l", "r", "r", "r", "r")
)
Metric Calculated Published Difference Relative difference
mBC recurrences, no testing 1,220 1,218 2 0.2%
mBC recurrences, olaparib 947 946 1 0.1%
mBC recurrences avoided 273 272 1 0.3%
non-mBC recurrences, no testing 304 304 0 0.0%
non-mBC recurrences, olaparib 238 236 2 0.7%
non-mBC recurrences avoided 66 68 -2 -2.6%
Life-years, no testing 99,055 99,084 -29 0.0%
Life-years, olaparib 107,646 107,761 -115 -0.1%
Life-years gained 8,591 8,677 -86 -1.0%

The reconstruction is close for recurrence outputs because the published table gives subtype-specific recurrence rates. Life-years differ modestly because the report only exposes rounded life-year inputs and aggregate published totals.

4 Mathematical Model

This report uses an aggregate population-impact model rather than a cycle-based health-state model. The model first reconstructs all-age recurrence outputs from subtype-specific published inputs, then redistributes the treatment effect across age strata while preserving the published all-age treatment effect on the log-hazard-ratio scale.

Let \(s \in \mathcal{S}\) index subtype strata, where \(\mathcal{S} = \{\text{HR-positive}, \text{TNBC}\}\), and let \(a \in \mathcal{A}\) index age strata, where \(\mathcal{A} = \{<45, \ge 45\}\). Let \(e \in \mathcal{E}\) index recurrence endpoints, where \(\mathcal{E} = \{\text{mBC recurrence}, \text{non-mBC recurrence}\}\).

For each subtype \(s\):

  • \(N_s\) is the number of olaparib-eligible patients.
  • \(w_{s,a}\) is the share of subtype \(s\) in age stratum \(a\), with \(\sum_a w_{s,a} = 1\).
  • \(r^0_{s,e}\) is the no-testing/no-olaparib recurrence rate for endpoint \(e\).

The expected untreated recurrence count in each subtype-age-endpoint cell is:

\[ B_{s,a,e} = N_s w_{s,a} r^0_{s,e}. \]

4.1 Calibrating Age-Specific Treatment Effects

The published all-age recurrence effect is represented as a base hazard ratio:

\[ H_0 = 1 - \frac{P^{\text{pub}}_{\text{mBC}} + P^{\text{pub}}_{\text{non-mBC}}} {B^{\text{pub}}_{\text{mBC}} + B^{\text{pub}}_{\text{non-mBC}}}, \]

where \(P^{\text{pub}}_e\) is the published number of recurrences avoided for endpoint \(e\), and \(B^{\text{pub}}_e\) is the published no-testing recurrence count for endpoint \(e\). Let \(L = \log(H_0)\).

The age-modifier meta-regression estimates the older-versus-younger log-hazard-ratio contrast:

\[ \delta = \log(H_{\ge 45}) - \log(H_{<45}). \]

Let \(W_a\) be the total olaparib-eligible cohort share in age stratum \(a\):

\[ W_a = \frac{\sum_s N_s w_{s,a}}{\sum_s N_s}. \]

The age-specific hazard ratios are centered around the published all-age effect:

\[ \log(H_{<45}) = L - W_{\ge 45}\delta, \]

\[ \log(H_{\ge 45}) = L + W_{<45}\delta. \]

This construction preserves both the estimated age contrast and the all-age anchor:

\[ \log(H_{\ge 45}) - \log(H_{<45}) = \delta, \]

\[ W_{<45}\log(H_{<45}) + W_{\ge 45}\log(H_{\ge 45}) = L. \]

4.2 Recurrences Avoided

For the age-stratified treatment-effect analysis, expected olaparib-treated recurrences are:

\[ T_{s,a,e} = B_{s,a,e} H_a. \]

Recurrences avoided are therefore:

\[ P_{s,a,e} = B_{s,a,e} - T_{s,a,e} = B_{s,a,e}(1 - H_a). \]

Aggregate age-stratified recurrence outputs are:

\[ P_{a,e} = \sum_s P_{s,a,e}. \]

The homogeneous-effect counterfactual used in the HTE communication section applies the same all-age hazard ratio \(H_0\) to every age stratum:

\[ P^{\text{hom}}_{s,a,e} = B_{s,a,e}(1 - H_0). \]

This counterfactual keeps age-specific life expectancy but removes age-specific treatment-effect heterogeneity.

4.3 Life-Years Saved

The published model gives an aggregate life-year gain per recurrence avoided:

\[ c_0 = \frac{Y^{\text{pub}}} {P^{\text{pub}}_{\text{mBC}} + P^{\text{pub}}_{\text{non-mBC}}}, \]

where \(Y^{\text{pub}}\) is the published total life-years gained.

Let \(\ell_a\) be the remaining life expectancy for the representative age of stratum \(a\), taken from the US female life table. The age-specific life-year gain per recurrence avoided is scaled from \(c_0\) by relative remaining life expectancy:

\[ c_a = c_0 \frac{\ell_a}{\sum_a W_a \ell_a}. \]

Finally, life-years saved in age stratum \(a\) are:

\[ Y_a = c_a \sum_e P_{a,e}. \]

This life-year translation is directional: it is designed to illustrate how age-specific recurrence prevention changes expected population impact, not to replace a disease-specific survival model.

5 Age-Modifier Calibration

The local meta-regression estimates an older-versus-younger age contrast of 0.2073 on the log-hazard-ratio scale, where >=45 years has the higher log-HR and therefore the smaller recurrence benefit. The deterministic base case uses the point estimate and communicates the uncertainty separately: standard error 0.0984, model p-value 0.0352, and permutation-test p-value 0.0604.

Code
published_total_recurrences_no_testing <-
  scalar_input("published_mbc_no_testing") +
  scalar_input("published_non_mbc_no_testing")

published_total_recurrences_prevented <-
  scalar_input("published_mbc_prevented") +
  scalar_input("published_non_mbc_prevented")

base_recurrence_hr <- 1 - (
  published_total_recurrences_prevented /
    published_total_recurrences_no_testing
)

age_distribution <- model_inputs |>
  filter(category == "age_distribution") |>
  transmute(
    subtype,
    age_group,
    age_share = as.numeric(value)
  )

age_weights <- subtype_inputs |>
  select(subtype, patients) |>
  inner_join(age_distribution, by = "subtype") |>
  group_by(age_group) |>
  summarise(
    patients = sum(patients * age_share),
    cohort_share = patients / sum(subtype_inputs$patients),
    .groups = "drop"
  ) |>
  arrange(factor(age_group, levels = c("<45", ">=45")))

delta_log_hr <- scalar_input("age_contrast_log_hr")
weight_young <- age_weights$cohort_share[age_weights$age_group == "<45"]
weight_older <- age_weights$cohort_share[age_weights$age_group == ">=45"]
base_log_hr <- log(base_recurrence_hr)

age_effects <- age_weights |>
  mutate(
    log_hazard_ratio = if_else(
      age_group == "<45",
      base_log_hr - weight_older * delta_log_hr,
      base_log_hr + weight_young * delta_log_hr
    ),
    hazard_ratio = exp(log_hazard_ratio),
    recurrence_reduction = 1 - hazard_ratio
  )

age_calibration_check <- age_effects |>
  summarise(
    weighted_log_hr = sum(cohort_share * log_hazard_ratio),
    base_log_hr = base_log_hr,
    difference = weighted_log_hr - base_log_hr,
    observed_delta = log_hazard_ratio[age_group == ">=45"] -
      log_hazard_ratio[age_group == "<45"]
  )

kable(
  age_effects |>
    transmute(
      `Age group` = age_group,
      `Cohort share` = fmt_pct(cohort_share),
      `Log-HR` = fmt_decimal(log_hazard_ratio, 3),
      HR = fmt_decimal(hazard_ratio, 3),
      `Relative recurrence reduction` = fmt_pct(recurrence_reduction)
    ),
  align = c("l", "r", "r", "r", "r")
)
Age group Cohort share Log-HR HR Relative recurrence reduction
<45 50.0% -0.356 0.700 30.0%
>=45 50.0% -0.149 0.861 13.9%

Calibration check: the weighted log-HR is -0.252817, the base log-HR is -0.252817, and the age contrast is 0.2073.

6 Age-Stratified Recurrences

The age-stratified extension uses subtype-specific untreated recurrence rates from the published model, then applies the age-specific olaparib recurrence HRs above. This keeps subtype-specific baseline recurrence behavior while making treatment benefit age-stratified.

Code
baseline_recurrence_rates <- subtype_inputs |>
  select(subtype, patients, no_mbc_rate, no_non_mbc_rate) |>
  pivot_longer(
    cols = c(no_mbc_rate, no_non_mbc_rate),
    names_to = "endpoint",
    values_to = "baseline_rate"
  ) |>
  mutate(
    endpoint = recode(
      endpoint,
      no_mbc_rate = "mBC recurrences",
      no_non_mbc_rate = "non-mBC recurrences"
    )
  )

age_recurrence_cells <- baseline_recurrence_rates |>
  inner_join(age_distribution, by = "subtype") |>
  inner_join(
    age_effects |> select(age_group, hazard_ratio, recurrence_reduction),
    by = "age_group"
  ) |>
  mutate(
    baseline_recurrences = patients * age_share * baseline_rate,
    olaparib_recurrences = baseline_recurrences * hazard_ratio,
    recurrences_prevented = baseline_recurrences - olaparib_recurrences
  )

age_recurrence_summary <- age_recurrence_cells |>
  group_by(age_group, endpoint) |>
  summarise(
    baseline_recurrences = sum(baseline_recurrences),
    olaparib_recurrences = sum(olaparib_recurrences),
    recurrences_prevented = sum(recurrences_prevented),
    .groups = "drop"
  )

age_output_wide <- age_recurrence_summary |>
  select(age_group, endpoint, recurrences_prevented) |>
  pivot_wider(names_from = endpoint, values_from = recurrences_prevented)

kable(
  age_recurrence_summary |>
    transmute(
      `Age group` = age_group,
      Endpoint = endpoint,
      `No testing recurrences` = fmt_count(baseline_recurrences),
      `Testing and olaparib recurrences` = fmt_count(olaparib_recurrences),
      `Recurrences avoided` = fmt_count(recurrences_prevented)
    ),
  align = c("l", "l", "r", "r", "r")
)
Age group Endpoint No testing recurrences Testing and olaparib recurrences Recurrences avoided
<45 mBC recurrences 610 427 183
<45 non-mBC recurrences 152 106 46
>=45 mBC recurrences 610 525 85
>=45 non-mBC recurrences 152 131 21
Code
ggplot(
  age_recurrence_summary,
  aes(x = age_group, y = recurrences_prevented, fill = endpoint)
) +
  geom_col(position = "dodge", width = 0.7) +
  scale_y_continuous(labels = comma) +
  scale_fill_manual(values = c("#1b7f79", "#be6a15")) +
  labs(
    x = "Age group",
    y = "Recurrences avoided",
    fill = "Endpoint",
    title = "Exploratory age-stratified recurrence impact"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

7 Age-Specific Life-Year Translation

The published base case reports 8,677 life-years gained for 340 total recurrences avoided, or 25.5 life-years per recurrence avoided. This aggregate reconstruction scales that relationship by age-specific remaining life expectancy from the female US life table.

Code
representative_ages <- model_inputs |>
  filter(category == "representative_age") |>
  transmute(
    age_group,
    representative_age = as.integer(round(as.numeric(value))),
    source_note,
    assumption_note
  )

life_expectancy_by_age <- representative_ages |>
  left_join(
    life_table |> select(age, life_expectancy_years),
    by = c("representative_age" = "age")
  )

reference_life_expectancy <- age_effects |>
  select(age_group, cohort_share) |>
  left_join(life_expectancy_by_age, by = "age_group") |>
  summarise(
    value = sum(cohort_share * life_expectancy_years)
  ) |>
  pull(value)

base_life_years_per_recurrence <-
  scalar_input("published_life_years_gained") /
  published_total_recurrences_prevented

life_year_translation <- age_effects |>
  select(age_group, cohort_share) |>
  left_join(life_expectancy_by_age, by = "age_group") |>
  mutate(
    life_years_per_recurrence =
      base_life_years_per_recurrence *
      life_expectancy_years /
      reference_life_expectancy
  )

life_years_by_age <- age_recurrence_summary |>
  group_by(age_group) |>
  summarise(
    total_recurrences_prevented = sum(recurrences_prevented),
    .groups = "drop"
  ) |>
  left_join(life_year_translation, by = "age_group") |>
  mutate(
    life_years_saved = total_recurrences_prevented * life_years_per_recurrence
  )

kable(
  life_year_translation |>
    transmute(
      `Age group` = age_group,
      `Representative age` = representative_age,
      `Female life expectancy` = fmt_years(life_expectancy_years),
      `Life-years per recurrence avoided` = fmt_years(life_years_per_recurrence)
    ),
  align = c("l", "r", "r", "r")
)
Age group Representative age Female life expectancy Life-years per recurrence avoided
<45 42 40.8 30.2
>=45 56 28.2 20.8
Code
main_outputs <- age_output_wide |>
  left_join(
    life_years_by_age |> select(age_group, life_years_saved),
    by = "age_group"
  )

main_outputs <- bind_rows(
  main_outputs,
  main_outputs |>
    summarise(
      age_group = "Total",
      `mBC recurrences` = sum(`mBC recurrences`),
      `non-mBC recurrences` = sum(`non-mBC recurrences`),
      life_years_saved = sum(life_years_saved)
    )
)

kable(
  main_outputs |>
    transmute(
      `Age group` = age_group,
      `mBC recurrences avoided` = fmt_count(`mBC recurrences`),
      `non-mBC recurrences avoided` = fmt_count(`non-mBC recurrences`),
      `Life-years saved` = fmt_count(life_years_saved)
    ),
  align = c("l", "r", "r", "r")
)
Age group mBC recurrences avoided non-mBC recurrences avoided Life-years saved
<45 183 46 6,899
>=45 85 21 2,201
Total 267 67 9,100

8 Subtype-by-Age Detail

Code
subtype_age_detail <- age_recurrence_cells |>
  group_by(subtype, age_group, endpoint) |>
  summarise(recurrences_prevented = sum(recurrences_prevented), .groups = "drop") |>
  pivot_wider(names_from = endpoint, values_from = recurrences_prevented)

kable(
  subtype_age_detail |>
    transmute(
      Subtype = subtype,
      `Age group` = age_group,
      `mBC recurrences avoided` = fmt_count(`mBC recurrences`),
      `non-mBC recurrences avoided` = fmt_count(`non-mBC recurrences`)
    ),
  align = c("l", "l", "r", "r")
)
Subtype Age group mBC recurrences avoided non-mBC recurrences avoided
HR-positive <45 88 22
HR-positive >=45 41 10
TNBC <45 95 24
TNBC >=45 44 11

9 Why Age-Stratified Treatment Effects Matter

The age-stratified treatment-effect estimates are used here to improve population-impact estimation, not to define an age-based treatment rule. In this first-pass analysis, olaparib eligibility and treatment uptake are unchanged: eligible patients in both age groups remain candidates for olaparib, the >=45 years stratum still has a positive predicted recurrence reduction, and no better alternative treatment strategy for >=45 years is modeled.

Two distinct age-related mechanisms affect projected life-years saved. First, if recurrence is avoided earlier in life, more remaining life-years can be preserved even when the treatment effect is identical at every age. Second, if treatment benefit is heterogeneous by age, the model avoids a different number of recurrences in each age group before life expectancy is even applied. The counterfactual below separates those mechanisms.

Code
lt45_impact_share <- main_outputs |>
  filter(age_group == "<45") |>
  transmute(
    recurrence_share =
      (`mBC recurrences` + `non-mBC recurrences`) /
      sum(main_outputs$`mBC recurrences`[main_outputs$age_group != "Total"] +
            main_outputs$`non-mBC recurrences`[main_outputs$age_group != "Total"]),
    life_year_share =
      life_years_saved /
      sum(main_outputs$life_years_saved[main_outputs$age_group != "Total"])
  )

communication_table <- tibble(
  Question = c(
    "Does this change who should receive olaparib?",
    "Does this change predicted recurrences avoided?",
    "Does this change predicted life-years saved?",
    "Does this help when applying the model to another population?",
    "Does this change how uncertainty should be communicated?",
    "Does this show value in collecting subgroup evidence?"
  ),
  Answer = c(
    "No. The analysis is not an age-based rule for withholding treatment from eligible patients in the >=45 years stratum.",
    "Yes. The same all-age treatment benefit is redistributed across age strata according to the calibrated treatment-effect contrast.",
    "Yes. Life-years depend on both recurrence reductions and remaining life expectancy, so age-stratified effects can materially change the life-year estimate.",
    "Yes. A population with a different age mix may not have the same all-age population impact, even under the same treatment strategy.",
    "Yes. The report can separate the published-model benchmark from the uncertain age-stratified extension.",
    "Yes. Subgroup evidence can improve population-impact estimates even when it does not justify a different treatment rule."
  )
)

kable(communication_table, align = c("l", "l"))
Question Answer
Does this change who should receive olaparib? No. The analysis is not an age-based rule for withholding treatment from eligible patients in the >=45 years stratum.
Does this change predicted recurrences avoided? Yes. The same all-age treatment benefit is redistributed across age strata according to the calibrated treatment-effect contrast.
Does this change predicted life-years saved? Yes. Life-years depend on both recurrence reductions and remaining life expectancy, so age-stratified effects can materially change the life-year estimate.
Does this help when applying the model to another population? Yes. A population with a different age mix may not have the same all-age population impact, even under the same treatment strategy.
Does this change how uncertainty should be communicated? Yes. The report can separate the published-model benchmark from the uncertain age-stratified extension.
Does this show value in collecting subgroup evidence? Yes. Subgroup evidence can improve population-impact estimates even when it does not justify a different treatment rule.
Code
homogeneous_recurrence_summary <- age_recurrence_cells |>
  group_by(age_group, endpoint) |>
  summarise(
    baseline_recurrences = sum(baseline_recurrences),
    .groups = "drop"
  ) |>
  mutate(
    recurrences_prevented = baseline_recurrences * (1 - base_recurrence_hr)
  )

homogeneous_life_years_by_age <- homogeneous_recurrence_summary |>
  group_by(age_group) |>
  summarise(
    homogeneous_recurrences_prevented = sum(recurrences_prevented),
    .groups = "drop"
  ) |>
  left_join(
    life_year_translation |> select(age_group, life_years_per_recurrence),
    by = "age_group"
  ) |>
  mutate(
    homogeneous_life_years_saved =
      homogeneous_recurrences_prevented * life_years_per_recurrence
  )

hte_decomposition <- life_years_by_age |>
  select(
    age_group,
    hte_recurrences_prevented = total_recurrences_prevented,
    hte_life_years_saved = life_years_saved
  ) |>
  left_join(homogeneous_life_years_by_age, by = "age_group") |>
  mutate(
    recurrence_increment_from_hte =
      hte_recurrences_prevented - homogeneous_recurrences_prevented,
    life_year_increment_from_hte =
      hte_life_years_saved - homogeneous_life_years_saved
  )

hte_decomposition_display <- hte_decomposition |>
  bind_rows(
    hte_decomposition |>
      summarise(
        age_group = "Total",
        hte_recurrences_prevented = sum(hte_recurrences_prevented),
        homogeneous_recurrences_prevented =
          sum(homogeneous_recurrences_prevented),
        recurrence_increment_from_hte =
          sum(recurrence_increment_from_hte),
        hte_life_years_saved = sum(hte_life_years_saved),
        homogeneous_life_years_saved =
          sum(homogeneous_life_years_saved),
        life_year_increment_from_hte =
          sum(life_year_increment_from_hte)
      )
  )

lt45_homogeneous_life_year_share <- hte_decomposition |>
  filter(age_group == "<45") |>
  summarise(
    share = homogeneous_life_years_saved /
      sum(hte_decomposition$homogeneous_life_years_saved)
  ) |>
  pull(share)

lt45_life_year_share_increment_from_hte <-
  lt45_impact_share$life_year_share - lt45_homogeneous_life_year_share

lt45_recurrence_increment_from_hte <- hte_decomposition |>
  filter(age_group == "<45") |>
  pull(recurrence_increment_from_hte)

total_life_year_increment_from_hte <- hte_decomposition |>
  summarise(total = sum(life_year_increment_from_hte)) |>
  pull(total)

kable(
  hte_decomposition_display |>
    transmute(
      `Age group` = age_group,
      `Recurrences avoided with homogeneous effect` =
        fmt_count(homogeneous_recurrences_prevented),
      `Recurrences avoided with age-specific effect` =
        fmt_count(hte_recurrences_prevented),
      `Recurrence difference from HTE` =
        fmt_count(recurrence_increment_from_hte),
      `Life-years saved with homogeneous effect` =
        fmt_count(homogeneous_life_years_saved),
      `Life-years saved with age-specific effect` =
        fmt_count(hte_life_years_saved),
      `Life-year difference from HTE` =
        fmt_count(life_year_increment_from_hte)
    ),
  align = c("l", "r", "r", "r", "r", "r", "r")
)
Age group Recurrences avoided with homogeneous effect Recurrences avoided with age-specific effect Recurrence difference from HTE Life-years saved with homogeneous effect Life-years saved with age-specific effect Life-year difference from HTE
<45 170 228 58 5,139 6,899 1,759
>=45 170 106 -65 3,548 2,201 -1,347
Total 340 334 -6 8,688 9,100 412

In this run, <45 years represents 50.0% of the modeled eligible population. With age-specific life expectancy but a homogeneous treatment effect, that group accounts for 59.2% of life-years saved. With age-specific treatment effects, it accounts for 68.4% of avoided recurrences and 75.8% of life-years saved. That is an additional 16.7 percentage points shift toward <45 years attributable to treatment-effect heterogeneity, over and above the fact that younger patients have more life-years left. In counts, the age-specific treatment effect moves about 58 additional avoided recurrences into <45 years and adds 412 life-years in this aggregate approximation.

10 Sensitivity to the Age Contrast

The base-case age contrast is a single point estimate from an underpowered, marginally significant meta-regression (see Age-Modifier Calibration). This section recomputes the age-stratified recurrence and life-year pipeline at the lower and upper bounds of the age contrast’s 95% confidence interval, and at a null (no-heterogeneity) benchmark, to show how much of the reported <45 years shift depends on the exact point estimate used in the base case.

Code
run_age_scenario <- function(delta_log_hr) {
  log_h_lt45 <- base_log_hr - weight_older * delta_log_hr
  log_h_ge45 <- base_log_hr + weight_young * delta_log_hr

  scenario_age_effects <- age_weights |>
    mutate(
      log_hazard_ratio = if_else(age_group == "<45", log_h_lt45, log_h_ge45),
      hazard_ratio = exp(log_hazard_ratio)
    )

  scenario_cells <- baseline_recurrence_rates |>
    inner_join(age_distribution, by = "subtype", relationship = "many-to-many") |>
    inner_join(
      scenario_age_effects |> select(age_group, hazard_ratio),
      by = "age_group"
    ) |>
    mutate(
      baseline_recurrences = patients * age_share * baseline_rate,
      recurrences_prevented = baseline_recurrences * (1 - hazard_ratio)
    )

  scenario_recurrence_summary <- scenario_cells |>
    group_by(age_group) |>
    summarise(recurrences_prevented = sum(recurrences_prevented), .groups = "drop")

  scenario_reference_le <- scenario_age_effects |>
    select(age_group, cohort_share) |>
    left_join(life_expectancy_by_age, by = "age_group") |>
    summarise(value = sum(cohort_share * life_expectancy_years)) |>
    pull(value)

  scenario_life_years <- scenario_age_effects |>
    select(age_group, cohort_share) |>
    left_join(life_expectancy_by_age, by = "age_group") |>
    mutate(
      life_years_per_recurrence =
        base_life_years_per_recurrence * life_expectancy_years / scenario_reference_le
    ) |>
    left_join(scenario_recurrence_summary, by = "age_group") |>
    mutate(life_years_saved = recurrences_prevented * life_years_per_recurrence)

  tibble(
    lt45_hazard_ratio =
      scenario_age_effects$hazard_ratio[scenario_age_effects$age_group == "<45"],
    ge45_hazard_ratio =
      scenario_age_effects$hazard_ratio[scenario_age_effects$age_group == ">=45"],
    lt45_recurrence_share =
      scenario_recurrence_summary$recurrences_prevented[
        scenario_recurrence_summary$age_group == "<45"
      ] / sum(scenario_recurrence_summary$recurrences_prevented),
    lt45_life_year_share =
      scenario_life_years$life_years_saved[scenario_life_years$age_group == "<45"] /
      sum(scenario_life_years$life_years_saved),
    total_life_years_saved = sum(scenario_life_years$life_years_saved)
  )
}

sensitivity_scenarios <- tibble(
  scenario = c(
    "No heterogeneity (life expectancy only)",
    "Age contrast: lower 95% CI bound",
    "Age contrast: point estimate (base case)",
    "Age contrast: upper 95% CI bound"
  ),
  delta_log_hr = c(
    0,
    scalar_input("age_contrast_ci_low"),
    scalar_input("age_contrast_log_hr"),
    scalar_input("age_contrast_ci_high")
  )
)

sensitivity_scenarios <- bind_cols(
  sensitivity_scenarios,
  bind_rows(lapply(sensitivity_scenarios$delta_log_hr, run_age_scenario))
) |>
  mutate(
    life_year_share_increment =
      lt45_life_year_share -
      lt45_life_year_share[scenario == "No heterogeneity (life expectancy only)"]
  )

kable(
  sensitivity_scenarios |>
    transmute(
      Scenario = scenario,
      `Log-HR contrast` = fmt_decimal(delta_log_hr, 4),
      `HR <45` = fmt_decimal(lt45_hazard_ratio, 3),
      `HR >=45` = fmt_decimal(ge45_hazard_ratio, 3),
      `<45 recurrence share` = fmt_pct(lt45_recurrence_share),
      `<45 life-year share` = fmt_pct(lt45_life_year_share),
      `Shift vs. no heterogeneity` = fmt_pp(life_year_share_increment)
    ),
  align = c("l", "r", "r", "r", "r", "r", "r")
)
Scenario Log-HR contrast HR <45 HR >=45 <45 recurrence share <45 life-year share Shift vs. no heterogeneity
No heterogeneity (life expectancy only) 0.0000 0.777 0.777 50.0% 59.2% 0.0 percentage points
Age contrast: lower 95% CI bound 0.0144 0.771 0.782 51.3% 60.4% 1.2 percentage points
Age contrast: point estimate (base case) 0.2073 0.700 0.861 68.4% 75.8% 16.7 percentage points
Age contrast: upper 95% CI bound 0.4002 0.636 0.949 87.6% 91.1% 32.0 percentage points

At the base-case point estimate, treatment-effect heterogeneity shifts an additional 16.7 percentage points of life-years saved toward <45 years, on top of the life-expectancy-only baseline. At the lower bound of the age contrast’s 95% confidence interval, that additional shift falls to 1.2 percentage points — close to zero — while at the upper bound it grows to 32.0 percentage points. Because the age contrast comes from a meta-regression of 21 observations drawn from only 9 trials (model p = 0.0352; permutation p = 0.0604), the treatment-effect-heterogeneity component of the <45 years life-year shift should be read as this wide range rather than as the fixed point-estimate value reported above.

11 Sensitivity to the Age-Distribution Proxy

The published Kasle et al. model is not age-stratified: it uses a single representative age (42, the OlympiA trial average) for the whole eligible cohort’s lifetime horizon, so there is no <45/>=45 population split in the original paper to reproduce. The 10%/90% split used in an earlier version of this exploratory extension was this project’s own first-pass proxy (general-population ACS statistics), not a published input. The Published Model Benchmark section above is unaffected by any age-distribution assumption and remains the directly comparable-to-Kasle output.

This section instead compares the exploratory age-stratified extension’s own outputs under that earlier general-population proxy and the current gBRCA-matched proxy, to make clear how much of the extension’s conclusions follow from this replaceable assumption rather than from the age-contrast calibration itself.

Code
run_proxy_scenario <- function(lt45_share, representative_age_ge45_years) {
  alt_age_distribution <- tibble(
    subtype = rep(c("HR-positive", "TNBC"), each = 2),
    age_group = rep(c("<45", ">=45"), times = 2),
    age_share = rep(c(lt45_share, 1 - lt45_share), times = 2)
  )

  alt_age_weights <- subtype_inputs |>
    select(subtype, patients) |>
    inner_join(alt_age_distribution, by = "subtype") |>
    group_by(age_group) |>
    summarise(
      patients = sum(patients * age_share),
      cohort_share = patients / sum(subtype_inputs$patients),
      .groups = "drop"
    )

  alt_weight_young <- alt_age_weights$cohort_share[alt_age_weights$age_group == "<45"]
  alt_weight_older <- alt_age_weights$cohort_share[alt_age_weights$age_group == ">=45"]

  alt_age_effects <- alt_age_weights |>
    mutate(
      log_hazard_ratio = if_else(
        age_group == "<45",
        base_log_hr - alt_weight_older * delta_log_hr,
        base_log_hr + alt_weight_young * delta_log_hr
      ),
      hazard_ratio = exp(log_hazard_ratio)
    )

  alt_cells <- baseline_recurrence_rates |>
    inner_join(alt_age_distribution, by = "subtype", relationship = "many-to-many") |>
    inner_join(
      alt_age_effects |> select(age_group, hazard_ratio),
      by = "age_group"
    ) |>
    mutate(
      baseline_recurrences = patients * age_share * baseline_rate,
      recurrences_prevented = baseline_recurrences * (1 - hazard_ratio)
    )

  alt_recurrence_summary <- alt_cells |>
    group_by(age_group) |>
    summarise(recurrences_prevented = sum(recurrences_prevented), .groups = "drop")

  alt_life_expectancy_by_age <- tibble(
    age_group = c("<45", ">=45"),
    representative_age = c(
      as.integer(round(scalar_input("representative_age_lt45"))),
      as.integer(round(representative_age_ge45_years))
    )
  ) |>
    left_join(life_table |> select(age, life_expectancy_years), by = c("representative_age" = "age"))

  alt_reference_le <- alt_age_effects |>
    select(age_group, cohort_share) |>
    left_join(alt_life_expectancy_by_age, by = "age_group") |>
    summarise(value = sum(cohort_share * life_expectancy_years)) |>
    pull(value)

  alt_life_years <- alt_age_effects |>
    select(age_group, cohort_share) |>
    left_join(alt_life_expectancy_by_age, by = "age_group") |>
    mutate(
      life_years_per_recurrence =
        base_life_years_per_recurrence * life_expectancy_years / alt_reference_le
    ) |>
    left_join(alt_recurrence_summary, by = "age_group") |>
    mutate(life_years_saved = recurrences_prevented * life_years_per_recurrence)

  tibble(
    lt45_population_share = alt_weight_young,
    lt45_recurrence_share =
      alt_recurrence_summary$recurrences_prevented[alt_recurrence_summary$age_group == "<45"] /
      sum(alt_recurrence_summary$recurrences_prevented),
    lt45_life_year_share =
      alt_life_years$life_years_saved[alt_life_years$age_group == "<45"] /
      sum(alt_life_years$life_years_saved),
    total_life_years_saved = sum(alt_life_years$life_years_saved)
  )
}

distribution_scenarios <- tibble(
  scenario = c(
    "Previous version: ACS general-population proxy (10% / 90%, representative age 63)",
    "Current base case: gBRCA-matched proxy (Morganti et al. 2024; 50% / 50%, representative age 56)"
  ),
  lt45_share = c(0.10, 0.50),
  representative_age_ge45_years = c(63, 56)
)

distribution_scenarios <- bind_cols(
  distribution_scenarios,
  bind_rows(
    Map(
      run_proxy_scenario,
      distribution_scenarios$lt45_share,
      distribution_scenarios$representative_age_ge45_years
    )
  )
)

kable(
  distribution_scenarios |>
    transmute(
      Scenario = scenario,
      `<45 population share` = fmt_pct(lt45_population_share),
      `<45 recurrence share` = fmt_pct(lt45_recurrence_share),
      `<45 life-year share` = fmt_pct(lt45_life_year_share),
      `Total life-years saved` = fmt_count(total_life_years_saved)
    ),
  align = c("l", "r", "r", "r", "r")
)
Scenario <45 population share <45 recurrence share <45 life-year share Total life-years saved
Previous version: ACS general-population proxy (10% / 90%, representative age 63) 10.0% 16.0% 25.9% 9,030
Current base case: gBRCA-matched proxy (Morganti et al. 2024; 50% / 50%, representative age 56) 50.0% 68.4% 75.8% 9,100

Under the previous ACS-based proxy, <45 years accounted for 25.9% of life-years saved; under the current gBRCA-matched proxy, that rises to 75.8%. Both figures use the same base-case age contrast (0.2073); the difference between them is attributable entirely to the age-distribution and representative-age assumptions, not to the treatment-effect calibration. Because the age-distribution proxy is now matched on gBRCA1/2 status rather than drawn from the general breast-cancer population, the current base case is judged more representative of this model’s population — but readers who want the prior version’s assumptions for their own comparison can reconstruct it from this table.

12 Interpretation

Under the first-pass age-distribution proxy, the calibrated age-specific recurrence HR is lower for <45 years than for >=45 years, so more benefit concentrates in the younger stratum than its population share alone would imply. The total age-stratified recurrence outputs remain close to the published aggregate benchmark because the age contrast is centered around the published all-age recurrence effect on the log-HR scale.

The life-year output is more exploratory than the recurrence output. It combines the published aggregate life-year-per-recurrence relationship with female general-population life expectancy, so it should be treated as a directional translation rather than a disease-specific survival projection. The main interpretive claim is therefore about estimating expected population impact more accurately and transparently, not changing olaparib treatment recommendations by age.

13 Limitations and Replaceable Inputs

  • This is an aggregate reconstruction, not the original Excel health-state model.
  • Published clinical input percentages are rounded, so exact reproduction of paper totals is not expected.
  • The age distribution is now matched on gBRCA1/2 status and HER2-negative early breast cancer (Morganti et al. 2024), but the source reports only overall-cohort median/mean age, not an exact <45/>=45 split or subtype-specific age moments, so 50%/50% is a round approximation rather than a directly reported figure. It is also not restricted to the olaparib-eligible subgroup specifically, which had a younger median age (39) in the same source.
  • Representative ages are placeholders and should be replaced with stratum-specific mean or median ages when available. representative_age_ge45 is now derived (56) from the same Morganti et al. 2024 mean/SD used for the age-distribution proxy, via a normal-distribution approximation, rather than a directly reported figure. representative_age_lt45 (42) is kept from the OlympiA trial average because it is directly reported rather than derived; applying the same normal-approximation method used for representative_age_ge45 to the <45 stratum would instead give about 34.5, so the two representative ages currently rest on different methods and a single consistent source would be an improvement.
  • The age-stratified treatment-effect evidence comes from a small meta-regression and should be communicated as uncertain: 21 observations from 9 trials of metastatic/advanced-disease PARP-inhibitor PFS (not adjuvant olaparib-specific, disease-free-survival evidence), several trials contributing multiple correlated rows. See Sensitivity to the Age Contrast for how much the headline <45 years life-year shift depends on this estimate’s confidence interval.
  • The recurrence translation uses 1 - HR as an aggregate approximation.
  • Life-years use a female general-population life table, not breast-cancer-specific survival.