Search: resolve protein (p.) HGVS to a variant page
- Dominant language
- Python
- Stars
- 30
- Forks
- 3
- Avg merge
- 9h 28m
- Merged PRs (30d)
- 42
Description
🤖 Written by Claude
Searching for a protein HGVS, eg `NP_000242.1:p.Cys843ValfsTer49`, should land on the variant page.
## Background: biocommons cannot do this for us
biocommons hgvs **parses** p.HGVS fine (verified against our pinned `2.0.0a1.dev50+gdac6a2f`):
```
>>> Parser().parse_hgvs_variant('NP_000242.1:p.Cys843ValfsTer49')
type='p' ac='NP_000242.1' pos=Interval(Cys843) edit=AAFs(alt='V', length=49)
```
But its mapper is one-directional — `VariantMapper` exposes `c_to_g c_to_n c_to_p g_to_c g_to_n g_to_t n_to_c n_to_g t_to_g`. There is `c_to_p` but no `p_to_c`, because back-translation is ambiguous (several codons per amino acid, and `fsTer49` is satisfied by an unbounded set of indels).
## We don't need it — VEP already computed the answer
`VariantTranscriptAnnotation.hgvs_p` (`annotation/models/models.py:1416`) already stores this exact format, RefSeq accession included:
```
'NP_001018074.1:p.Asn760Thr' | 'NM_001018064.3:c.2279A>C'
```
So p.HGVS search is a lookup, not a computation.
## Proposed approach
Ran end-to-end against a dev DB:
1. Parse with the biocommons parser to get accession + posedit.
2. Protein accession → transcript: `TranscriptVersion.objects.filter(data__protein='NP_001018074.1')`. cdot stores `"protein"` in the JSON blob, and holds `ENSP...` for Ensembl transcripts, so both annotation sources work.
3. Annotation lookup filtered on `transcript_id` + `hgvs_p`. Narrowing by transcript first uses the existing `transcript_id` btree on each partition:
```
Bitmap Index Scan on ..._transcript_id_idx9 rows=7859
Execution Time: 19.577 ms
```
4. → `variant_id 2909148`, ie the variant page.
The 40M-row annotation partitions are fine as they are. The one index worth adding is an expression index on `genes_transcriptversion ((data->>'protein'))` — that lookup is currently a 320ms parallel seq scan over ~2M rows.
## Implementation notes
- `HGVS_UNCLEANED_PATTERN` (`snpdb/models/models_variant.py:46`) covers `[cnmg]\.`, so a new `@search_receiver` in `snpdb/signals/variant_search.py` with its own p. pattern keeps the two paths separate.
- Canonicalise both sides with the existing `PHGVS.parse()` (`genes/hgvs/hgvs.py:60`) — it maps 1-letter to 3-letter and strips the `p.(...)` parens, so `N760T` and `p.(Asn760Thr)` both work.
- Match the accession without its version and take all matching `TranscriptVersion` records, so `NP_000242.1` vs `.2` still resolves.
- Return a list — one p.HGVS legitimately maps to multiple variants via synonymous codon changes. Filter through `search_input.get_visible_variants(genome_build)` as the other receivers do.
- Search the latest annotation version, keeping it to a single partition.
## Open question
This finds variants already annotated in the database. The c./g. search can resolve a coordinate for a variant we have never seen and offer it for classification; p. search has no equivalent for frameshifts. For plain missense we could back-translate — a codon has at most 9 single-nucleotide alternatives, so the ones producing that amino acid change can be enumerated and offered as classifiable results. Worth deciding whether we want that parity.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the existing search receivers in snpdb/signals/variant_search.py, the HGVS_UNCLEANED_PATTERN in snpdb/models/models_variant.py, and PHGVS.parse() in genes/hgvs/hgvs.py. Inspect VariantTranscriptAnnotation.hgvs_p and the TranscriptVersion protein lookup, then validate the lookup against the dev database. Done means valid protein HGVS inputs resolve visible matching variants and the protein accession lookup is indexed appropriately.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, python
- Domain
- databases, search
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100