Annotating variants & normalizing HGVS
Turn raw genomic variants — VCF rows, rsIDs, or HGVS strings — into annotated,
consequence-predicted records, and link them to the clinical context OpenMed
extracts from text (genes, variants, oncology findings, phenotype). The workhorse
for a quick, no-install annotation is the Ensembl VEP REST API; for scale,
run VEP, SnpEff, or ANNOVAR offline.
These annotators are free and license-permissive. Restricted clinical
interpretation databases (e.g. licensed HGMD) are user-supplied — this skill
sticks to open resources (Ensembl, gnomAD, ClinVar).
When to use
- You have a VCF / HGVS / rsID and need consequence predictions (missense,
stop-gain, splice), affected transcripts, and protein change.
- You need to normalize HGVS to genomic coordinates (and back) on a known
build (GRCh38 by default; GRCh37 via the dedicated endpoint).
- You want gnomAD population allele frequencies to flag common vs rare.
- You are pairing molecular findings with the phenotype/oncology context that
OpenMed pulls from notes or literature.
Quick start (real Ensembl VEP REST call)
Base URL: https://rest.ensembl.org (GRCh38). For GRCh37 use
https://grch37.rest.ensembl.org. Default species is human/homo_sapiens.
import requests
REST = "https://rest.ensembl.org"
HEADERS = {"Content-Type": "application/json", "Accept": "application/json"}
def vep_hgvs(hgvs: str) -> list[dict]:
"""Annotate a single HGVS variant (GET)."""
r = requests.get(f"{REST}/vep/human/hgvs/{hgvs}", headers=HEADERS, timeout=30)
r.raise_for_status()
return r.json()
# Transcript-level HGVS (coding) — note the build-aware default transcript set
ann = vep_hgvs("ENST00000269305.9:c.215C>G") # TP53 example
v = ann[0]
print(v["most_severe_consequence"]) # e.g. "missense_variant"
for tc in v.get("transcript_consequences", []):
print(tc["gene_symbol"], tc.get("hgvsp"), tc.get("sift_prediction"),
tc.get("polyphen_prediction"))
Batch many variants with the POST endpoint (region "CHROM POS ID REF ALT . . ."
format, up to 200 per request):
def vep_region_batch(variants: list[str]) -> list[dict]:
body = {"variants": variants} # ["17 7676154 . C G . . .", ...] 1-based
r = requests.post(f"{REST}/vep/human/region", headers=HEADERS,
json=body, timeout=60)
r.raise_for_status()
return r.json()
Equivalent cURL:
curl 'https://rest.ensembl.org/vep/human/hgvs/ENST00000269305.9:c.215C>G' \
-H 'Content-Type:application/json'
Response highlights per variant: most_severe_consequence,
transcript_consequences[] (gene_symbol, hgvsc, hgvsp, sift_prediction,
polyphen_prediction, impact), and colocated_variants[] (rsIDs and
population frequencies). Request gnomAD frequencies and ClinVar via VEP options /
plugins.
Population frequencies via gnomAD (GraphQL)
For authoritative allele frequencies, query the gnomAD GraphQL API at
https://gnomad.broadinstitute.org/api. Use variant IDs in
chrom-pos-ref-alt form. Frequencies are derived from ac/an (allele count /
number) — request those, not a non-existent af on subpopulations.
GNOMAD = "https://gnomad.broadinstitute.org/api"
QUERY = """
query Variant($id: String!, $ds: DatasetId!) {
variant(variantId: $id, dataset: $ds) {
variant_id rsids
genome { ac an af homozygote_count }
exome { ac an af homozygote_count }
}
}"""
def gnomad_freq(variant_id: str, dataset: str = "gnomad_r4") -> dict:
r = requests.post(GNOMAD, json={"query": QUERY,
"variables": {"id": variant_id, "ds": dataset}}, timeout=30)
r.raise_for_status()
return r.json()["data"]["variant"]
# gnomad_freq("17-7676154-C-G") -> ac/an/af for exome and genome
Offline annotation at scale
For whole-VCF jobs, run a local annotator instead of per-variant REST calls:
| Tool |
Strengths |
Notes |
| Ensembl VEP (offline) |
richest, plugin ecosystem (gnomAD, CADD, SpliceAI), HGVS |
needs cache download per build |
| SnpEff |
fast, self-contained genome databases |
great for bulk consequence calling |
| ANNOVAR |
many annotation databases |
registration required; license terms apply |
All emit per-variant gene, consequence, and (with the right database) frequency
and clinical fields. Keep the reference build (GRCh38) consistent end to end.
Workflow
- Normalize input to a canonical form: left-align/trim VCF alleles; for HGVS,
confirm the reference transcript and build.
- Annotate — REST (
/vep/human/hgvs or /vep/human/region) for a handful,
offline VEP/SnpEff for a VCF.
- Attach frequencies from gnomAD; flag common variants (e.g. AF > 1%).
- Filter/prioritize by
most_severe_consequence, impact, and rarity.
- Join to clinical context from OpenMed (gene/variant mentions, oncology,
phenotype) to assemble an interpretable record.
Hand-off to / from OpenMed
- OpenMed → variant context.
openmed.analyze_text(report, model_name=<a Genomics or Oncology model>) extracts gene symbols, variant mentions (e.g.
"EGFR L858R"), and tumor/oncology findings from pathology or molecular reports.
Use those to (a) select which VCF variants matter and (b) attach phenotype
context to each annotation.
- Variant → OpenMed. Free-text variant descriptions in reports can be
normalized to HGVS here, then the surrounding clinical narrative is structured by
OpenMed — linking genotype to extracted phenotype/diagnosis.
- Keep genomic + clinical data local. The REST/GraphQL calls carry only the
variant coordinates (public allele data), never patient identifiers — and
any narrative is de-identified with
openmed.deidentify first.
Edge cases & gotchas
- Build mismatch is the #1 error. GRCh38 coordinates against a GRCh37 endpoint
(or cache) give wrong genes. Use
grch37.rest.ensembl.org only for GRCh37 data;
default REST is GRCh38.
- Transcript choice changes the HGVS.
c./p. notation depends on the
reference transcript (MANE Select vs others). Pin the transcript explicitly.
- Normalize before annotating. Un-left-aligned indels and multi-allelic VCF
rows produce inconsistent annotations — decompose and normalize first
(e.g.
bcftools norm).
- REST is rate-limited. ~15 req/s and 200 variants/POST on the Ensembl REST
server; switch to offline VEP for large VCFs. Honor
Retry-After on 429.
- gnomAD subpopulation fields. Query
ac/an (and compute AF) for
subpopulations; some schema paths reject af directly — track the current
schema version, which changes between gnomAD releases.
- No clinical interpretation here. Consequence ≠ pathogenicity. Pathogenicity
classification (ACMG/AMP) uses curated evidence and licensed databases the user
supplies; this skill produces annotations, not diagnoses.
Standards & references
1---2name: annotating-variants3description: Annotates VCF variants and normalizes HGVS nomenclature with public, license-free annotators (Ensembl VEP REST, VEP/SnpEff/ANNOVAR offline) and links variants to gnomAD population frequencies and the clinical context OpenMed extracts. Use when the user wants to predict variant consequences, map HGVS to genomic coordinates, annotate a VCF, attach allele frequencies, or pair variants with phenotype/oncology context. Trigger keywords: VCF, HGVS, variant annotation, VEP, SnpEff, ANNOVAR, consequence, missense, gnomAD, allele frequency, GRCh38, rsID, transcript. Pairs adjacent to OpenMed: combine annotated variants with Genomics/Oncology entities and phenotype from openmed.analyze_text. Tools used are free; restricted clinical databases are user-supplied.4license: Apache-2.05---67# Annotating variants & normalizing HGVS89Turn raw genomic variants — VCF rows, rsIDs, or **HGVS** strings — into annotated,10consequence-predicted records, and link them to the **clinical context** OpenMed11extracts from text (genes, variants, oncology findings, phenotype). The workhorse12for a quick, no-install annotation is the **Ensembl VEP REST API**; for scale,13run **VEP**, **SnpEff**, or **ANNOVAR** offline.1415These annotators are **free and license-permissive**. Restricted clinical16interpretation databases (e.g. licensed HGMD) are **user-supplied** — this skill17sticks to open resources (Ensembl, gnomAD, ClinVar).1819## When to use2021- You have a VCF / HGVS / rsID and need consequence predictions (missense,22 stop-gain, splice), affected transcripts, and protein change.23- You need to **normalize HGVS** to genomic coordinates (and back) on a known24 build (GRCh38 by default; GRCh37 via the dedicated endpoint).25- You want **gnomAD** population allele frequencies to flag common vs rare.26- You are pairing molecular findings with the phenotype/oncology context that27 OpenMed pulls from notes or literature.2829## Quick start (real Ensembl VEP REST call)3031Base URL: `https://rest.ensembl.org` (GRCh38). For GRCh37 use32`https://grch37.rest.ensembl.org`. Default species is `human`/`homo_sapiens`.3334```python35import requests3637REST = "https://rest.ensembl.org"38HEADERS = {"Content-Type": "application/json", "Accept": "application/json"}3940def vep_hgvs(hgvs: str) -> list[dict]:41 """Annotate a single HGVS variant (GET)."""42 r = requests.get(f"{REST}/vep/human/hgvs/{hgvs}", headers=HEADERS, timeout=30)43 r.raise_for_status()44 return r.json()4546# Transcript-level HGVS (coding) — note the build-aware default transcript set47ann = vep_hgvs("ENST00000269305.9:c.215C>G") # TP53 example48v = ann[0]49print(v["most_severe_consequence"]) # e.g. "missense_variant"50for tc in v.get("transcript_consequences", []):51 print(tc["gene_symbol"], tc.get("hgvsp"), tc.get("sift_prediction"),52 tc.get("polyphen_prediction"))53```5455Batch many variants with the POST endpoint (region "CHROM POS ID REF ALT . . ."56format, up to 200 per request):5758```python59def vep_region_batch(variants: list[str]) -> list[dict]:60 body = {"variants": variants} # ["17 7676154 . C G . . .", ...] 1-based61 r = requests.post(f"{REST}/vep/human/region", headers=HEADERS,62 json=body, timeout=60)63 r.raise_for_status()64 return r.json()65```6667Equivalent cURL:6869```bash70curl 'https://rest.ensembl.org/vep/human/hgvs/ENST00000269305.9:c.215C>G' \71 -H 'Content-Type:application/json'72```7374Response highlights per variant: `most_severe_consequence`,75`transcript_consequences[]` (`gene_symbol`, `hgvsc`, `hgvsp`, `sift_prediction`,76`polyphen_prediction`, `impact`), and `colocated_variants[]` (rsIDs and77population frequencies). Request gnomAD frequencies and ClinVar via VEP options /78plugins.7980## Population frequencies via gnomAD (GraphQL)8182For authoritative allele frequencies, query the **gnomAD GraphQL API** at83`https://gnomad.broadinstitute.org/api`. Use variant IDs in84`chrom-pos-ref-alt` form. Frequencies are derived from `ac`/`an` (allele count /85number) — request those, not a non-existent `af` on subpopulations.8687```python88GNOMAD = "https://gnomad.broadinstitute.org/api"8990QUERY = """91query Variant($id: String!, $ds: DatasetId!) {92 variant(variantId: $id, dataset: $ds) {93 variant_id rsids94 genome { ac an af homozygote_count }95 exome { ac an af homozygote_count }96 }97}"""9899def gnomad_freq(variant_id: str, dataset: str = "gnomad_r4") -> dict:100 r = requests.post(GNOMAD, json={"query": QUERY,101 "variables": {"id": variant_id, "ds": dataset}}, timeout=30)102 r.raise_for_status()103 return r.json()["data"]["variant"]104105# gnomad_freq("17-7676154-C-G") -> ac/an/af for exome and genome106```107108## Offline annotation at scale109110For whole-VCF jobs, run a local annotator instead of per-variant REST calls:111112| Tool | Strengths | Notes |113| --- | --- | --- |114| **Ensembl VEP** (offline) | richest, plugin ecosystem (gnomAD, CADD, SpliceAI), HGVS | needs cache download per build |115| **SnpEff** | fast, self-contained genome databases | great for bulk consequence calling |116| **ANNOVAR** | many annotation databases | registration required; license terms apply |117118All emit per-variant gene, consequence, and (with the right database) frequency119and clinical fields. Keep the **reference build (GRCh38)** consistent end to end.120121## Workflow1221231. **Normalize input** to a canonical form: left-align/trim VCF alleles; for HGVS,124 confirm the reference transcript and build.1252. **Annotate** — REST (`/vep/human/hgvs` or `/vep/human/region`) for a handful,126 offline VEP/SnpEff for a VCF.1273. **Attach frequencies** from gnomAD; flag common variants (e.g. AF > 1%).1284. **Filter/prioritize** by `most_severe_consequence`, impact, and rarity.1295. **Join to clinical context** from OpenMed (gene/variant mentions, oncology,130 phenotype) to assemble an interpretable record.131132## Hand-off to / from OpenMed133134- **OpenMed → variant context.** `openmed.analyze_text(report, model_name=<a135 Genomics or Oncology model>)` extracts gene symbols, variant mentions (e.g.136 "EGFR L858R"), and tumor/oncology findings from pathology or molecular reports.137 Use those to (a) select which VCF variants matter and (b) attach phenotype138 context to each annotation.139- **Variant → OpenMed.** Free-text variant descriptions in reports can be140 normalized to HGVS here, then the surrounding clinical narrative is structured by141 OpenMed — linking genotype to extracted phenotype/diagnosis.142- Keep genomic + clinical data **local**. The REST/GraphQL calls carry only the143 **variant coordinates** (public allele data), never patient identifiers — and144 any narrative is de-identified with `openmed.deidentify` first.145146## Edge cases & gotchas147148- **Build mismatch is the #1 error.** GRCh38 coordinates against a GRCh37 endpoint149 (or cache) give wrong genes. Use `grch37.rest.ensembl.org` only for GRCh37 data;150 default REST is GRCh38.151- **Transcript choice changes the HGVS.** `c.`/`p.` notation depends on the152 reference transcript (MANE Select vs others). Pin the transcript explicitly.153- **Normalize before annotating.** Un-left-aligned indels and multi-allelic VCF154 rows produce inconsistent annotations — decompose and normalize first155 (e.g. `bcftools norm`).156- **REST is rate-limited.** ~15 req/s and 200 variants/POST on the Ensembl REST157 server; switch to offline VEP for large VCFs. Honor `Retry-After` on 429.158- **gnomAD subpopulation fields.** Query `ac`/`an` (and compute AF) for159 subpopulations; some schema paths reject `af` directly — track the current160 schema version, which changes between gnomAD releases.161- **No clinical interpretation here.** Consequence ≠ pathogenicity. Pathogenicity162 classification (ACMG/AMP) uses curated evidence and licensed databases the user163 supplies; this skill produces annotations, not diagnoses.164165## Standards & references166167- VCF specification — https://samtools.github.io/hts-specs/VCFv4.4.pdf168- HGVS nomenclature — https://hgvs-nomenclature.org/169- Ensembl VEP REST (HGVS GET) — https://rest.ensembl.org/documentation/info/vep_hgvs_get170- Ensembl VEP REST (region POST) — https://rest.ensembl.org/documentation/info/vep_region_post171- Ensembl VEP (offline) — https://www.ensembl.org/info/docs/tools/vep/index.html172- SnpEff — https://pcingola.github.io/SnpEff/173- gnomAD API — https://gnomad.broadinstitute.org/api174- ClinVar — https://www.ncbi.nlm.nih.gov/clinvar/