From 81e0c3294f0965c6a80ec90c9c7a1c021c1f8a45 Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Wed, 13 May 2026 16:08:09 -0700 Subject: [PATCH] fix(VariantScreen): use mapped coordinate mode for histogram MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Variant view histogram fell back to raw-mode tooltip labels because three things lined up: the `coordinates` prop defaulted to `'raw'`, the variant cache only ran `parseScoresOrCounts` (so `parsedPostMappedHgvs*` and `translated_hgvs_p` were never populated), and the fetch omitted `include_post_mapped_hgvs` plus the `vep`/`clingen` namespaces — so even a mapped-mode tooltip would have had nothing to render. Pass `coordinates="mapped"` on VariantScreen and route the fetch + parse through the same pipeline ScoreSetView uses. Along the way, consolidate the variant-data plumbing that had drifted between the two screens: - Extract `histogramScoreSetVariantDataUrl` and a small params/URL builder in `api/mavedb/score-sets.ts`; ScoreSetView and the histogram fetch now share one definition of the endpoint and namespace set. - Add `parseScoreSetVariantData` in `lib/variants.ts` as the single entrypoint for the `parseScoresOrCounts` → `parseSimpleCodingVariants` → `translateSimpleCodingVariants` chain; the latter two are now internal. - Rename `getVariantScores` → `getHistogramVariantData` to reflect that it returns full variant rows, not just scores. - Type `useVariantLookup`'s `scores`/`variantScoreRow` as `Variant` instead of `ScoresOrCountsRow`, removing the `as any` cast at the histogram `:variants` binding. - Introduce `ParsedSimpleDnaVariation`/`ParsedSimpleProteinVariation` aliases so the `residueType`/`origin` fields added by `parseSimpleCodingVariants` are reflected in the type. --- src/api/mavedb/score-sets.ts | 28 ++++++++++++++-- src/api/mavedb/variants.ts | 12 +++---- src/components/screens/ScoreSetView.vue | 18 +++++----- src/components/screens/VariantScreen.vue | 3 +- src/composables/use-variant-lookup.ts | 14 ++++---- src/lib/variants.ts | 42 +++++++++++++++++------- 6 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/api/mavedb/score-sets.ts b/src/api/mavedb/score-sets.ts index 4cb9d120..f2211a84 100644 --- a/src/api/mavedb/score-sets.ts +++ b/src/api/mavedb/score-sets.ts @@ -7,6 +7,30 @@ type ScoreSetSearch = components['schemas']['ScoreSetsSearch'] type ScoreSetsSearchResponse = components['schemas']['ScoreSetsSearchResponse'] export type ScoreSetsSearchFilterOptionsResponse = components['schemas']['ScoreSetsSearchFilterOptionsResponse'] +const HISTOGRAM_VARIANT_DATA_NAMESPACES = ['vep', 'scores', 'clingen'] + +function scoreSetVariantDataParams( + options: {includePostMappedHgvs?: boolean; namespaces?: string[]} = {} +): URLSearchParams { + const params = new URLSearchParams() + if (options.includePostMappedHgvs) params.append('include_post_mapped_hgvs', 'true') + for (const namespace of options.namespaces ?? []) params.append('namespaces', namespace) + return params +} + +function scoreSetVariantDataUrl(urn: string, params: URLSearchParams = new URLSearchParams()): string { + const query = params.toString() + const baseUrl = `${config.apiBaseUrl}/score-sets/${encodeURIComponent(urn)}/variants/data` + return query ? `${baseUrl}?${query}` : baseUrl +} + +export function histogramScoreSetVariantDataUrl(urn: string): string { + return scoreSetVariantDataUrl( + urn, + scoreSetVariantDataParams({includePostMappedHgvs: true, namespaces: HISTOGRAM_VARIANT_DATA_NAMESPACES}) + ) +} + // --------------------------------------------------------------------------- // Search // --------------------------------------------------------------------------- @@ -91,9 +115,7 @@ export async function downloadScoreSetFile(urn: string, type: 'scores' | 'counts } export async function downloadScoreSetVariantData(urn: string, params: URLSearchParams): Promise { - const response = await axios.get( - `${config.apiBaseUrl}/score-sets/${encodeURIComponent(urn)}/variants/data?${params.toString()}` - ) + const response = await axios.get(scoreSetVariantDataUrl(urn, params)) return response.data } diff --git a/src/api/mavedb/variants.ts b/src/api/mavedb/variants.ts index 83a3157e..f0db95a4 100644 --- a/src/api/mavedb/variants.ts +++ b/src/api/mavedb/variants.ts @@ -1,6 +1,7 @@ import axios from 'axios' import config from '@/config' +import {histogramScoreSetVariantDataUrl} from '@/api/mavedb/score-sets' import {components} from '@/schema/openapi' type ScoreSet = components['schemas']['ScoreSet'] @@ -18,10 +19,9 @@ export async function lookupVariantsByClingenId( } export async function lookupVariantsByVrsDigest(identifier: string): Promise { - const response = await axios.get( - `${config.apiBaseUrl}/mapped-variants/vrs/${encodeURIComponent(identifier)}`, - {params: {only_current: true}} - ) + const response = await axios.get(`${config.apiBaseUrl}/mapped-variants/vrs/${encodeURIComponent(identifier)}`, { + params: {only_current: true} + }) return response.data } @@ -30,8 +30,8 @@ export async function getVariantDetail(urn: string): Promise { - const response = await axios.get(`${config.apiBaseUrl}/score-sets/${encodeURIComponent(scoreSetUrn)}/variants/data`) +export async function getHistogramVariantData(scoreSetUrn: string): Promise { + const response = await axios.get(histogramScoreSetVariantDataUrl(scoreSetUrn)) return response.data } diff --git a/src/components/screens/ScoreSetView.vue b/src/components/screens/ScoreSetView.vue index 1d34f52a..6d34b583 100644 --- a/src/components/screens/ScoreSetView.vue +++ b/src/components/screens/ScoreSetView.vue @@ -502,9 +502,13 @@ import config from '@/config' import {hasPathogenicityCalibrations, hasFunctionalCalibrations} from '@/lib/calibrations' import {variantNotNullOrNA} from '@/lib/mave-hgvs' import {getScoreSetShortName} from '@/lib/score-sets' -import {parseScoresOrCounts} from '@/lib/scores' -import {parseSimpleCodingVariants, translateSimpleCodingVariants, type Variant} from '@/lib/variants' -import {deleteScoreSet, publishScoreSet, getScoreSetClinicalControlOptions} from '@/api/mavedb' +import {parseScoreSetVariantData, type Variant} from '@/lib/variants' +import { + deleteScoreSet, + publishScoreSet, + getScoreSetClinicalControlOptions, + histogramScoreSetVariantDataUrl +} from '@/api/mavedb' import {components} from '@/schema/openapi' import MvLoader from '@/components/common/MvLoader.vue' import MvEmptyState from '@/components/common/MvEmptyState.vue' @@ -714,7 +718,7 @@ export default { this.setItemId(newValue) let scoresUrl = null if (this.itemType?.restCollectionName && this.itemId) { - scoresUrl = `${config.apiBaseUrl}/${this.itemType.restCollectionName}/${this.itemId}/variants/data?include_post_mapped_hgvs=true&namespaces=vep&namespaces=scores&namespaces=clingen` + scoresUrl = histogramScoreSetVariantDataUrl(this.itemId) } this.setScoresDataUrl(scoresUrl) this.ensureScoresDataLoaded() @@ -723,11 +727,7 @@ export default { immediate: true }, scoresData(newValue: unknown) { - const parsed = newValue ? parseScoresOrCounts(newValue as string, true) : null - if (parsed) { - parseSimpleCodingVariants(parsed as Variant[]) - translateSimpleCodingVariants(parsed as Variant[]) - } + const parsed = newValue ? parseScoreSetVariantData(newValue as string) : null this.variants = parsed ? (Object.freeze(parsed) as Variant[]) : null this.applyUrlState() }, diff --git a/src/components/screens/VariantScreen.vue b/src/components/screens/VariantScreen.vue index a469ff5b..7960af29 100644 --- a/src/components/screens/VariantScreen.vue +++ b/src/components/screens/VariantScreen.vue @@ -228,11 +228,12 @@ diff --git a/src/composables/use-variant-lookup.ts b/src/composables/use-variant-lookup.ts index 1e3ca33b..6d8ece67 100644 --- a/src/composables/use-variant-lookup.ts +++ b/src/composables/use-variant-lookup.ts @@ -4,7 +4,7 @@ import {computed, ref, shallowRef, watch, type ComputedRef, type Ref} from 'vue' import { getVariantAnnotation, getVariantDetail, - getVariantScores, + getHistogramVariantData, lookupVariantsByClingenId } from '@/api/mavedb/variants' import {useCalibrationResolution, type UseCalibrationResolutionReturn} from '@/composables/use-calibration-resolution' @@ -18,7 +18,7 @@ import { } from '@/lib/calibrations' import {triggerDownload} from '@/lib/downloads' import {getExperimentKeyword} from '@/lib/experiments' -import {parseScoresOrCounts, type ScoresOrCountsRow} from '@/lib/scores' +import {parseScoreSetVariantData, type Variant} from '@/lib/variants' import type {MeasurementType} from '@/lib/measurement-types' import type {components} from '@/schema/openapi' @@ -60,8 +60,8 @@ export interface UseVariantLookupReturn { // Scores selectedScoreSet: ComputedRef selectedScoreSetUrn: ComputedRef - scores: ComputedRef - variantScoreRow: ComputedRef + scores: ComputedRef + variantScoreRow: ComputedRef selectedVariantScore: ComputedRef // Calibration @@ -116,7 +116,7 @@ export function useVariantLookup( const showNucleotide = ref(true) const showProtein = ref(true) const variantDetailCache = ref>({}) - const scoresCache = shallowRef>({}) + const scoresCache = shallowRef>({}) const selectedCalibration = ref(null) // ── Filters ─────────────────────────────────────────────── @@ -212,10 +212,10 @@ export function useVariantLookup( async function fetchScores(scoreSetUrn: string) { if (scoresCache.value[scoreSetUrn]) return try { - const data = await getVariantScores(scoreSetUrn) + const data = await getHistogramVariantData(scoreSetUrn) scoresCache.value = { ...scoresCache.value, - [scoreSetUrn]: Object.freeze(parseScoresOrCounts(data, true)) + [scoreSetUrn]: parseScoreSetVariantData(data) } } catch (error) { console.error(`Error fetching scores for score set "${scoreSetUrn}"`, error) diff --git a/src/lib/variants.ts b/src/lib/variants.ts index 168e2bf0..1046fd77 100644 --- a/src/lib/variants.ts +++ b/src/lib/variants.ts @@ -4,10 +4,8 @@ import {AMINO_ACIDS, AMINO_ACIDS_WITH_TER, singleLetterAminoAcidOrHgvsCode} from import {DEFAULT_CLNREVSTAT_FIELD, DEFAULT_CLNSIG_FIELD} from '@/lib/clinical-controls' import geneticCodes from '@/lib/genetic-codes' import {parseSimpleNtVariant, parseSimpleProVariant} from '@/lib/mave-hgvs' +import {parseScoresOrCounts} from '@/lib/scores' import type {SimpleDnaVariation, SimpleProteinVariation} from '@/lib/mave-hgvs' -import {components} from '@/schema/openapi' - -type ScoreSet = components['schemas']['ScoreSet'] export type HgvsReferenceSequenceType = 'c' | 'p' // | 'n' @@ -21,6 +19,16 @@ export interface ClinicalControlVariant { [DEFAULT_CLNREVSTAT_FIELD]: string } +type ParsedSimpleDnaVariation = SimpleDnaVariation & { + residueType?: 'nt' + origin?: 'mapped' | 'unmapped' +} + +type ParsedSimpleProteinVariation = SimpleProteinVariation & { + residueType?: 'aa' + origin?: 'mapped' | 'unmapped' +} + export interface RawVariant { accession: string hgvs_nt?: string @@ -51,9 +59,9 @@ export interface RawVariant { } export interface VariantPropertiesAddedByPreparingCodingVariants { - // Added by prepareSimpleCodingVariants. - parsedPostMappedHgvsC?: SimpleDnaVariation - parsedPostMappedHgvsP?: SimpleProteinVariation + // Added by parseSimpleCodingVariants. + parsedPostMappedHgvsC?: ParsedSimpleDnaVariation + parsedPostMappedHgvsP?: ParsedSimpleProteinVariation } export interface Variant extends RawVariant, VariantPropertiesAddedByPreparingCodingVariants { @@ -61,9 +69,10 @@ export interface Variant extends RawVariant, VariantPropertiesAddedByPreparingCo translated_hgvs_p?: string } -export const HGVS_REFERENCE_SEQUENCE_TYPES: { - [type: HgvsReferenceSequenceType]: {parsedPostMappedHgvsField: keyof VariantPropertiesAddedByPreparingCodingVariants} -} = { +export const HGVS_REFERENCE_SEQUENCE_TYPES: Record< + HgvsReferenceSequenceType, + {parsedPostMappedHgvsField: keyof VariantPropertiesAddedByPreparingCodingVariants} +> = { c: { parsedPostMappedHgvsField: 'parsedPostMappedHgvsC' }, @@ -106,6 +115,17 @@ export const VARIANT_EFFECT_TYPE_OPTIONS = [ export const DEFAULT_VARIANT_EFFECT_TYPES = ['Missense', 'Nonsense', 'Synonymous', 'Other'] +export function parseScoreSetVariantData(csvData: string): Variant[] { + const variants = parseScoresOrCounts(csvData, true) as Variant[] + prepareScoreSetVariantData(variants) + return variants +} + +function prepareScoreSetVariantData(variants: Variant[]) { + parseSimpleCodingVariants(variants) + translateSimpleCodingVariants(variants) +} + export const PARSED_POST_MAPPED_VARIANT_PROPERTIES: ParsedPostMappedVariantProperties = { c: 'parsedPostMappedHgvsC', g: 'parsedPostMappedHgvsC', @@ -128,7 +148,7 @@ function getParsedPostMappedHgvs(variant: Variant, type: HgvsReferenceSequenceTy * * @param variants The variants to modify. */ -export function parseSimpleCodingVariants(variants: Variant[]) { +function parseSimpleCodingVariants(variants: Variant[]) { for (const v of variants) { // Create the mavedb namespace if it doesn't exist. if (!v.mavedb) v.mavedb = {} @@ -302,7 +322,7 @@ export function inferReferenceSequenceFromVariants(variants: Variant[], referenc * * @param variants The array of variants to translate. */ -export function translateSimpleCodingVariants(variants: Variant[]) { +function translateSimpleCodingVariants(variants: Variant[]) { const {referenceSequence: codingSequence, referenceSequenceRange: codingSequenceRange} = inferReferenceSequenceFromVariants(variants, 'c') if (codingSequence.length > 0) {