Skip to content

Safety scoring of vaccine antigen windows: minimize self-peptide presentation + tissue-aware autoimmunity risk #254

Description

@iskandr

Motivation

Multi-antigen vaccine constructs (peptide or mRNA) can present non-mutant self-peptides to the patient's MHC. Three failure modes are currently unmodeled in vaxrank:

  1. Off-target self-peptide presentation inside SLP windows. A 25-mer SLP that flanks a mutation can still slide MHC-I/II k-mers across non-mutant regions. Those k-mers are wild-type and won't drive a tumor-specific response — they dilute the immune response and waste vaccine dose. Today vaxrank ranks windows only by mutant-epitope binding; it never scores the interior of the window for non-mutant binders.

  2. Self-cross-reactivity within ±1 aa. A window may produce a k-mer that differs by ≤1 aa from a strongly-presenting self-peptide. Those neoantigens carry autoimmunity risk: the responding T-cell clone may also recognize the wild-type self-peptide on healthy tissue.

  3. Tissue-aware autoimmunity safety. When self-cross-reactivity is unavoidable, prefer windows whose source proteins are not highly expressed in essential tissues (heart, brain, liver, kidney, lung, pancreas, gut epithelium). Even if some autoimmunity is triggered, hitting a less-essential tissue is far safer.

This issue tracks a safety module that scores candidate vaccine windows on those three axes and uses the scores as ranking tie-breakers (or, optionally, hard filters).

Scope

A. Self-peptide presentation scoring

For each candidate SLP window:

  1. Slide MHC-I (8-11) and MHC-II (13-25) k-mers across the non-mutant residues only.
  2. Score each k-mer with the configured presentation predictor (mhcflurry-presentation by default).
  3. Drop k-mers that already appear in a configurable reference proteome (already-tolerated; not new presentation).
  4. Aggregate per window: `self_strong_count` (rank ≤ 0.5%), `self_mild_count` (rank ≤ 2.0%), `worst_self_rank`.

The reference proteome should be pluggable: default to the patient's matched reference (already in vaxrank), but allow users to substitute a custom k-mer index (e.g. tumor-cell-line proteome, restricted self-proteome, or species-specific for non-human work).

B. Self-cross-reactivity (within ±1 aa)

For each mutant epitope produced by a window:

  1. Look up all reference-proteome peptides within Hamming distance 1.
  2. If any has a strong predicted presentation rank (configurable threshold, default rank ≤ 0.5%), flag the epitope as `autoimmune_risk`.
  3. Aggregate to a window-level `autoimmune_risk_count`.

C. Tissue-expression-aware safety penalty

When at least one autoimmune-risk hit fires for a candidate window, weight it against tissue expression:

  1. Look up the source gene's expression across essential tissues (GTEx is the standard source — bundle a small `gtex_essential_tissue_tpm.tsv` or query a configurable URL/file).
  2. `autoimmune_risk_score = autoimmune_risk_count × max_tpm_in_essential_tissue`.
  3. Use as a tie-breaker (or a configurable weighted term in the combined score) to prefer:
    • windows with zero autoimmune-risk hits, then
    • windows whose source proteins are lowly expressed in essential tissues.

The tissue list and TPM threshold are configurable; default essential-tissue list: heart, brain (cortex / cerebellum / basal ganglia), liver, kidney cortex/medulla, lung, pancreas, small intestine, colon. The TPM threshold for "highly expressed" defaults to 10.

API sketch

```yaml

In vaxrank YAML config

vaccine_peptides:
safety:
enabled: false # off by default; opt-in
presentation_predictor: mhcflurry # uses --mhc-predictor by default
reference_proteome: default # "default" = patient ref proteome already in use
self_kmer_lengths_class_i: [8, 9, 10, 11]
self_kmer_lengths_class_ii: [13, 14, 15]
rank_strong: 0.5
rank_mild: 2.0
cross_reactivity_max_hamming: 1
cross_reactivity_rank_strong: 0.5
tissue_expression:
enabled: false
source: bundled # bundled GTEx file, or path to user-supplied TSV
essential_tissues:
- heart
- brain_cortex
- liver
- kidney
- lung
- pancreas
- small_intestine
- colon
tpm_high_threshold: 10.0
weight_in_combined_score: 0.0 # 0 = tie-breaker only; >0 = additive penalty
```

CLI sketch

```
--vaccine-safety-enabled
--vaccine-safety-cross-reactivity-hamming 1
--vaccine-safety-tissue-source gtex_essential_tissue_tpm.tsv
--vaccine-safety-tpm-high 10.0
```

Manifest fields

Each ranked window gains a `safety` block:

```json
{
"safety": {
"self_strong": 0,
"self_mild": 2,
"worst_self_rank": 8.4,
"autoimmune_risk_count": 0,
"autoimmune_risk_hits": [],
"essential_tissue_tpm_max": 0.3,
"essential_tissue": "none",
"safety_score": 0.0
}
}
```

Acceptance

  • `safety` block on each window's manifest entry.
  • Windows can be ranked / filtered by safety score (configurable weight in the combined score).
  • Pluggable reference-proteome backend; default pulls from existing vaxrank reference proteome.
  • Bundled small GTEx essential-tissue TPM table (or HTTPS-fetchable manifest).
  • Tests: window ordering changes when safety is enabled and one window has a strong self-binder vs another has none; cross-reactivity flag fires for a 1-aa-distance hit; tissue penalty downranks a window whose gene is highly expressed in heart relative to a low-expression peer.

Related

This issue covers the interior of an SLP window and the cross-reactivity / tissue axes — distinct from #247 (junctions) and #249 (boundaries).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions