+
@@ -758,7 +815,7 @@ import {
} from '@/lib/mavemd'
import {getTargetGeneName} from '@/lib/target-genes'
import {components} from '@/schema/openapi'
-import {getScoreSetShortName} from '@/lib/score-sets'
+import {getScoreSetShortName, getPublicationUrl} from '@/lib/score-sets'
import {type GenomeAssembly, GENOME_ASSEMBLY_NAMES, gnomadIdToHgvs, otherAssembly} from '@/lib/gnomad'
import {clinVarHgvsSearchStringRegex, hgvsSearchStringRegex} from '@/lib/mave-hgvs'
import {SEARCH_COLORS, SEARCH_PLACEHOLDERS} from '@/data/search'
@@ -786,6 +843,22 @@ import MvLoader from '@/components/common/MvLoader.vue'
const SCORE_SETS_TO_SHOW = 5
+/**
+ * Legend for the calibration-status column header, rendered as HTML so the states read as a scannable bullet list.
+ * Passed to the tooltip with `escape: false`; the markup is static, so there is no injection surface.
+ */
+const CALIBRATION_STATUS_LEGEND_HTML = `
+
+
ACMG/AMP calibration status
+
+
None — no calibrations
+
Uncalibrated — calibrations exist but assign no evidence strengths
+
Research use only — the only evidence-bearing calibrations are RUO
+
Calibrated — at least one calibration assigns evidence strengths for clinical use
+
+
+`
+
type ScoreSet = components['schemas']['ScoreSet']
type TargetGene = components['schemas']['TargetGene']
@@ -810,7 +883,17 @@ export default defineComponent({
const router = useRouter()
const toast = useToast()
const {getEntity} = useEntityCache()
- return {route, router, toast, getEntity, getScoreSetShortName, scoreSetUrnFromVariantUrn, AVE_CLINICAL_APPLICATION}
+ return {
+ route,
+ router,
+ toast,
+ getEntity,
+ getScoreSetShortName,
+ getPublicationUrl,
+ scoreSetUrnFromVariantUrn,
+ AVE_CLINICAL_APPLICATION,
+ CALIBRATION_STATUS_LEGEND_HTML
+ }
},
data: function () {
@@ -847,6 +930,7 @@ export default defineComponent({
associatedNucleotideScoreSetListIsExpanded: [] as Array,
defaultNumScoreSetsToShow: SCORE_SETS_TO_SHOW,
guideExpanded: false,
+ filterGene: '',
maveMdScoreSetUrns: [] as string[],
maveMdScoreSets: {} as {[urn: string]: ScoreSet | undefined},
maveMdScoreSetsError: false,
@@ -855,8 +939,9 @@ export default defineComponent({
},
computed: {
- maveMdScoreSetsGroupedByGene: function () {
- const groups = _(this.maveMdScoreSetUrns)
+ /** All score sets grouped by gene name, sorted alphabetically — the unfiltered, unsliced source list. */
+ allScoreSetsGroupedByGene: function (): Array<{gene: string; urns: string[]}> {
+ return _(this.maveMdScoreSetUrns)
.groupBy((urn) => {
const scoreSet = this.maveMdScoreSets[urn]
if (!scoreSet) return 'Unknown'
@@ -866,7 +951,14 @@ export default defineComponent({
.map(([gene, urns]) => ({gene, urns}))
.sortBy(({gene}) => gene.toLowerCase())
.value()
- return this.guideExpanded ? groups : groups.slice(0, 8)
+ },
+ maveMdScoreSetsGroupedByGene: function (): Array<{gene: string; urns: string[]}> {
+ // A gene filter searches the whole collection, so it bypasses the eight-gene preview and returns every match.
+ const filter = this.filterGene.trim().toLowerCase()
+ if (filter) {
+ return this.allScoreSetsGroupedByGene.filter(({gene}) => gene.toLowerCase().includes(filter))
+ }
+ return this.guideExpanded ? this.allScoreSetsGroupedByGene : this.allScoreSetsGroupedByGene.slice(0, 8)
},
searchIsClearable: function () {
return (
@@ -1616,24 +1708,100 @@ export default defineComponent({
}
},
- calibrationCountWithEvidence(urn: string): number {
- const scoreSet = this.maveMdScoreSets[urn]
- if (!scoreSet?.scoreCalibrations) return 0
- return scoreSet.scoreCalibrations.filter(
- (calibration: components['schemas']['ScoreCalibration']) =>
- Array.isArray(calibration.functionalClassifications) &&
- calibration.functionalClassifications.filter((range) => range.acmgClassification).length > 0
- ).length
+ /** A calibration carries evidence when at least one of its functional classifications assigns an ACMG strength. */
+ calibrationHasEvidence(calibration: components['schemas']['ScoreCalibration']): boolean {
+ return (
+ Array.isArray(calibration.functionalClassifications) &&
+ calibration.functionalClassifications.some((range) => range.acmgClassification)
+ )
+ },
+
+ /**
+ * Derive a labeled ACMG calibration status for a score set, replacing the opaque "with-evidence / total" badge.
+ *
+ * A single calibration that assigns ACMG evidence strengths is enough to call a score set calibrated — the status
+ * does not require every calibration to carry evidence. The one qualification is research-use-only (RUO): when the
+ * only evidence-bearing calibrations are RUO, the score set is flagged as such rather than shown as clinically
+ * calibrated, since RUO calibrations are not intended for clinical interpretation.
+ */
+ calibrationStatus(urn: string): {label: string; badgeClass: string; tooltip: string} {
+ const calibrations = this.maveMdScoreSets[urn]?.scoreCalibrations ?? []
+ const withEvidence = calibrations.filter((c) => this.calibrationHasEvidence(c))
+ const clinicalEvidence = withEvidence.filter((c) => !c.researchUseOnly)
+ const muted = 'border-gray-200 bg-gray-50 text-gray-500'
+
+ if (calibrations.length === 0) {
+ return {
+ label: 'None',
+ badgeClass: muted,
+ tooltip: 'This score set has no clinical evidence calibrations.'
+ }
+ }
+ if (withEvidence.length === 0) {
+ return {
+ label: 'Uncalibrated',
+ badgeClass: muted,
+ tooltip: 'Calibrations exist but none assign ACMG/AMP evidence strengths.'
+ }
+ }
+ if (clinicalEvidence.length === 0) {
+ return {
+ label: 'Research use only',
+ badgeClass: 'border-orange-border bg-orange-light text-orange-cta-dark',
+ tooltip:
+ 'The only calibrations assigning ACMG/AMP evidence strengths are marked research-use-only, so they are not intended for clinical variant interpretation.'
+ }
+ }
+ return {
+ label: 'Calibrated',
+ badgeClass: 'border-published-dot bg-published-light text-published',
+ tooltip: 'At least one calibration assigns ACMG/AMP evidence strengths for clinical variant interpretation.'
+ }
},
- calibrationCountTotal(urn: string): number {
- return this.maveMdScoreSets[urn]?.scoreCalibrations?.length || 0
+ /** Format a variant count with a thousands separator, or an em dash when the count is unavailable. */
+ formatVariantCount(scoreSet: ScoreSet | undefined): string {
+ return typeof scoreSet?.numVariants === 'number' ? scoreSet.numVariants.toLocaleString() : '—'
}
}
})