Summary
Add a preprocessing layer that expands a serotype name (e.g. A2, HLA-B7) into its member MHC class I alleles, so users can request a prediction for a serotype and get a pooled answer across member alleles. Today mhcflurry has no serotype support beyond collapse_singleton_serotypes=True passed to mhcgnomes.parse; a serotype name that maps to more than one allele fails to parse and is rejected.
Motivation
Users (and IEDB-style inputs) sometimes specify alleles only at serotype resolution. There's no way to ask mhcflurry "what does serotype A2 present?" without manually enumerating member alleles.
Note: this is prediction-only value — it does not recover training data (see the sibling "serotypes in training" issue; serotype rows are ≈0% of the curated training set).
Feasibility (already scoped)
mhcgnomes does the hard part:
mhcgnomes.parse("A2") / parse("HLA-A2") → mhcgnomes.Serotype with a .alleles tuple of member Allele objects. [a.to_string() for a in st.alleles] yields strings mhcflurry's canonicalize_allele_name already consumes.
- Full enumerable table:
mhcgnomes.data.serotypes (170 HLA names; 122 class I). parse(..., only_class1=True) filters class II to None.
Critical gotcha: normalize_allele_name (mhcflurry/common.py) restricts results to [Allele, AlleleWithoutGene, Gene], so parse("A2") returns None and normalize_allele_name("A2") raises today. A serotype expander must run before normalization, not inside it.
Proposed approach
- New utility
expand_serotype(name) -> list[str] in mhcflurry/common.py (next to normalize_allele_name), wrapping parse(name, only_class1=True) and returning member allele strings when the result is a Serotype, else falling through to single-allele behavior.
- Wire into
mhcflurry/predict_command.py _split_allele_string (≈ line 196), so a serotype cell expands into the existing genotype min-affinity pooling path (predict already reports the tightest-nM binder across an allele set, with optional best_allele column).
- Do not expand inside
Class1AffinityPredictor.predict_to_dataframe — it requires alleles length-aligned with peptides, and expansion changes row cardinality.
Open design questions
- Pooling: default to min affinity (tightest binder = "presented by any member"); offer mean/max as options.
- Percentile ranks: ranks aren't directly comparable across alleles — pool on raw nM, then optionally re-rank. (The one genuinely subtle modeling detail.)
- Unsupported members: drop/NaN members not in
predictor.supported_alleles.
- Non-serotype coarse labels: gate explicitly —
"HLA class I" doesn't parse to a serotype; "HLA-A" parses to a Gene (a much larger, different expansion); A2=47 members, Bw4=36, so cap/dedup to bound cost.
Effort
Small for the predict path (expander + wiring + tests) — the serotype data, class-I filtering, and min-pooling all already exist.
Key files
mhcflurry/common.py (normalize_allele_name, new expand_serotype)
mhcflurry/predict_command.py (_split_allele_string)
mhcflurry/class1_affinity_predictor.py (canonicalize_allele_name, predict_to_dataframe)
Summary
Add a preprocessing layer that expands a serotype name (e.g.
A2,HLA-B7) into its member MHC class I alleles, so users can request a prediction for a serotype and get a pooled answer across member alleles. Today mhcflurry has no serotype support beyondcollapse_singleton_serotypes=Truepassed tomhcgnomes.parse; a serotype name that maps to more than one allele fails to parse and is rejected.Motivation
Users (and IEDB-style inputs) sometimes specify alleles only at serotype resolution. There's no way to ask mhcflurry "what does serotype A2 present?" without manually enumerating member alleles.
Note: this is prediction-only value — it does not recover training data (see the sibling "serotypes in training" issue; serotype rows are ≈0% of the curated training set).
Feasibility (already scoped)
mhcgnomes does the hard part:
mhcgnomes.parse("A2")/parse("HLA-A2")→mhcgnomes.Serotypewith a.allelestuple of memberAlleleobjects.[a.to_string() for a in st.alleles]yields strings mhcflurry'scanonicalize_allele_namealready consumes.mhcgnomes.data.serotypes(170 HLA names; 122 class I).parse(..., only_class1=True)filters class II toNone.Critical gotcha:
normalize_allele_name(mhcflurry/common.py) restricts results to[Allele, AlleleWithoutGene, Gene], soparse("A2")returnsNoneandnormalize_allele_name("A2")raises today. A serotype expander must run before normalization, not inside it.Proposed approach
expand_serotype(name) -> list[str]inmhcflurry/common.py(next tonormalize_allele_name), wrappingparse(name, only_class1=True)and returning member allele strings when the result is aSerotype, else falling through to single-allele behavior.mhcflurry/predict_command.py_split_allele_string(≈ line 196), so a serotype cell expands into the existing genotype min-affinity pooling path (predict already reports the tightest-nM binder across an allele set, with optionalbest_allelecolumn).Class1AffinityPredictor.predict_to_dataframe— it requiresalleleslength-aligned withpeptides, and expansion changes row cardinality.Open design questions
predictor.supported_alleles."HLA class I"doesn't parse to a serotype;"HLA-A"parses to aGene(a much larger, different expansion);A2=47 members,Bw4=36, so cap/dedup to bound cost.Effort
Small for the predict path (expander + wiring + tests) — the serotype data, class-I filtering, and min-pooling all already exist.
Key files
mhcflurry/common.py(normalize_allele_name, newexpand_serotype)mhcflurry/predict_command.py(_split_allele_string)mhcflurry/class1_affinity_predictor.py(canonicalize_allele_name,predict_to_dataframe)