From 6c551b7d283c54ede6418e6d09e7ca0d70f6231a Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Mon, 10 Aug 2026 12:08:30 -0700 Subject: [PATCH 1/2] feat(variant): show gnomAD population frequency on the variant page Fills the annotations card's "Population Frequency" column, until now a "Data coming soon" placeholder. gnomAD is linked to a single mapped variant by ClinGen allele ID, so the frequency is a direct assertion about the measured allele rather than an aggregate over related variants. - Add MvGnomadSummary: AF with the AC/AN behind it, FAF95 with the genetic ancestry group attaining it, and a deep link to the gnomAD variant page, bottom-aligned so provenance lines up across the card's columns. - Read the record from the score set's variant data CSV via gnomadFromVariantRow, which normalises the namespace's NA sentinels. A variant absent from gnomAD reports as having no record, not zero frequency. - Derive GnomadFrequency from the generated OpenAPI schema, so renaming or retyping a field on the API model breaks compilation here. - Split HISTOGRAM_VARIANT_DATA_NAMESPACES into SCORE_SET_CHART_NAMESPACES and VARIANT_PAGE_NAMESPACES. One list fed two screens under a name describing neither, and only the variant page reads gnomAD, so the score set charts no longer fetch seven unused columns on every row. Requires the widened gnomad CSV namespace in the API. --- .../finding-data/external-integrations.md | 2 + docs/content/mavemd/variant-page.md | 11 ++ src/api/mavedb/score-sets.ts | 19 ++- src/api/mavedb/variants.ts | 7 +- src/components/screens/ScoreSetView.vue | 4 +- src/components/screens/VariantScreen.vue | 92 +++++++++---- src/components/variant/MvGnomadSummary.vue | 56 ++++++++ src/composables/use-csv-namespaces.test.ts | 4 +- src/composables/use-variant-lookup.ts | 8 +- src/lib/gnomad.test.ts | 125 +++++++++++++++++- src/lib/gnomad.ts | 87 ++++++++++++ src/lib/variants.ts | 11 ++ 12 files changed, 382 insertions(+), 44 deletions(-) create mode 100644 src/components/variant/MvGnomadSummary.vue diff --git a/docs/content/finding-data/external-integrations.md b/docs/content/finding-data/external-integrations.md index e6005c88..4c914681 100644 --- a/docs/content/finding-data/external-integrations.md +++ b/docs/content/finding-data/external-integrations.md @@ -35,6 +35,8 @@ MaveDB displays ClinVar significance classifications and **star** status alongsi Mapped variants in MaveDB are cross-referenced with the [gnomAD database](https://gnomad.broadinstitute.org/) to retrieve population allele frequency data. This integration provides context about the prevalence of variants in diverse human populations, which is an important factor in clinical variant interpretation alongside functional evidence. +A variant's frequency is displayed on its [variant page](../mavemd/variant-page.md), and is available in bulk through the `gnomad` namespace of the variant data download. Each frequency is matched by ClinGen allele ID, so it is a direct assertion about that variant rather than an aggregate over related variants. Variants absent from gnomAD show no frequency. + ## Ensembl VEP MaveDB uses the [Ensembl Variant Effect Predictor (VEP)](https://www.ensembl.org/info/docs/tools/vep/index.html) to annotate mapped variants with predicted functional consequences, including effects on protein coding sequences, splicing, and regulatory regions. These VEP annotations are displayed alongside variant effect scores on score set pages, providing additional context for interpreting the functional impact of each variant. diff --git a/docs/content/mavemd/variant-page.md b/docs/content/mavemd/variant-page.md index 2a1b20ce..5d52f1f1 100644 --- a/docs/content/mavemd/variant-page.md +++ b/docs/content/mavemd/variant-page.md @@ -36,6 +36,17 @@ Each dataset on the variant page includes an [assay fact](../reference/assay-fac For the full list of assay fact properties and their definitions, see the [assay facts reference](../reference/assay-facts.md). +## Annotations + +Beneath the assay details, an annotations card gathers evidence about the variant itself, independent of any one assay. **Classification** reports the selected measurement's functional score, ACMG code, and OddsPath ratio. + +**Population frequency** reports the variant's frequency in [gnomAD](../finding-data/external-integrations.md#gnomad), where it is present: + +- **AF** -- The allele frequency, followed by the allele count and allele number it was computed from. +- **FAF95** -- The filtering allele frequency at 95% confidence, a conservative sampling-adjusted estimate, with the genetic ancestry group that attains it. A variant whose FAF95 exceeds a disease's maximum credible allele frequency is too common to be pathogenic (ACMG BA1/BS1). + +The gnomAD release the frequencies were drawn from is shown alongside them, with a link to the variant's gnomAD page. Variants absent from gnomAD are reported as having no record rather than as having zero frequency. + ## Interactive histogram The variant page includes the same interactive score histogram shown on score set pages, but with the selected variant's position highlighted within the distribution. This visualization helps you see where the variant falls relative to all other measured variants in the assay. diff --git a/src/api/mavedb/score-sets.ts b/src/api/mavedb/score-sets.ts index 24ef5517..77cfe891 100644 --- a/src/api/mavedb/score-sets.ts +++ b/src/api/mavedb/score-sets.ts @@ -7,7 +7,14 @@ type ScoreSetSearch = components['schemas']['ScoreSetsSearch'] type ScoreSetsSearchResponse = components['schemas']['ScoreSetsSearchResponse'] export type ScoreSetsSearchFilterOptionsResponse = components['schemas']['ScoreSetsSearchFilterOptionsResponse'] -const HISTOGRAM_VARIANT_DATA_NAMESPACES = ['vep', 'scores', 'clingen', 'mavedb'] +// Both screens read a score set's whole variant table from the same endpoint, but they render different +// things from it, so each names the namespaces it actually consumes. Keeping these separate matters at +// scale: a saturation-mutagenesis score set is 100k+ rows, and an unused namespace is 100k+ wasted cells. +const SCORE_SET_CHART_NAMESPACES = ['vep', 'scores', 'clingen', 'mavedb'] + +// The variant page additionally reads the selected measurement's gnomAD frequency out of its row; this +// request is the only source of it. See `gnomadFromVariantRow`. +const VARIANT_PAGE_NAMESPACES = [...SCORE_SET_CHART_NAMESPACES, 'gnomad'] function scoreSetVariantDataParams(options: {namespaces?: string[]} = {}): URLSearchParams { const params = new URLSearchParams() @@ -21,8 +28,14 @@ function scoreSetVariantDataUrl(urn: string, params: URLSearchParams = new URLSe return query ? `${baseUrl}?${query}` : baseUrl } -export function histogramScoreSetVariantDataUrl(urn: string): string { - return scoreSetVariantDataUrl(urn, scoreSetVariantDataParams({namespaces: HISTOGRAM_VARIANT_DATA_NAMESPACES})) +/** Variant data for a score set page's histogram and heatmap. */ +export function scoreSetChartVariantDataUrl(urn: string): string { + return scoreSetVariantDataUrl(urn, scoreSetVariantDataParams({namespaces: SCORE_SET_CHART_NAMESPACES})) +} + +/** Variant data for the variant page: the score distribution chart plus the selected row's annotations. */ +export function variantPageVariantDataUrl(urn: string): string { + return scoreSetVariantDataUrl(urn, scoreSetVariantDataParams({namespaces: VARIANT_PAGE_NAMESPACES})) } // --------------------------------------------------------------------------- diff --git a/src/api/mavedb/variants.ts b/src/api/mavedb/variants.ts index 24f5a31c..185d131c 100644 --- a/src/api/mavedb/variants.ts +++ b/src/api/mavedb/variants.ts @@ -1,7 +1,7 @@ import axios from 'axios' import config from '@/config' -import {histogramScoreSetVariantDataUrl} from '@/api/mavedb/score-sets' +import {variantPageVariantDataUrl} from '@/api/mavedb/score-sets' import {components} from '@/schema/openapi' type ScoreSet = components['schemas']['ScoreSet'] @@ -31,8 +31,9 @@ export async function getVariantDetail(urn: string): Promise { - const response = await axios.get(histogramScoreSetVariantDataUrl(scoreSetUrn)) +/** The containing score set's variant table, as read by the variant page. */ +export async function getVariantPageScoreSetData(scoreSetUrn: string): Promise { + const response = await axios.get(variantPageVariantDataUrl(scoreSetUrn)) return response.data } diff --git a/src/components/screens/ScoreSetView.vue b/src/components/screens/ScoreSetView.vue index 9cd13e8a..0ff431d4 100644 --- a/src/components/screens/ScoreSetView.vue +++ b/src/components/screens/ScoreSetView.vue @@ -508,7 +508,7 @@ import { deleteScoreSet, publishScoreSet, getScoreSetClinicalControlOptions, - histogramScoreSetVariantDataUrl + scoreSetChartVariantDataUrl } from '@/api/mavedb' import {components} from '@/schema/openapi' import MvLoader from '@/components/common/MvLoader.vue' @@ -719,7 +719,7 @@ export default { this.setItemId(newValue) let scoresUrl = null if (this.itemType?.restCollectionName && this.itemId) { - scoresUrl = histogramScoreSetVariantDataUrl(this.itemId) + scoresUrl = scoreSetChartVariantDataUrl(this.itemId) } this.setScoresDataUrl(scoresUrl) this.ensureScoresDataLoaded() diff --git a/src/components/screens/VariantScreen.vue b/src/components/screens/VariantScreen.vue index 8cae1126..7abdfb21 100644 --- a/src/components/screens/VariantScreen.vue +++ b/src/components/screens/VariantScreen.vue @@ -9,7 +9,8 @@ :model="tableDownloadMenu" severity="secondary" size="small" - @click="primaryTableDownload.command()"> + @click="primaryTableDownload.command()" + >