Skip to content

fix(app,api): stop rendering ClinVar conflicting classifications as Pathogenic (#607) - #617

Merged
berntpopp merged 4 commits into
masterfrom
fix/clinvar-conflicting-classification-607
Jul 30, 2026
Merged

fix(app,api): stop rendering ClinVar conflicting classifications as Pathogenic (#607)#617
berntpopp merged 4 commits into
masterfrom
fix/clinvar-conflicting-classification-607

Conversation

@berntpopp

Copy link
Copy Markdown
Owner

Closes #607

The bug

"conflicting classifications of pathogenicity".includes('pathogenic') is truepathogenic is a substring of pathogenicity — and the string contains no likely, so every client-side gene-page visualization filed conflicting variants into the Pathogenic bucket.

On PCDH12 the Protein View legend read Pathogenic 46 (13 Pathogenic + 5 Pathogenic/Likely pathogenic + 28 Conflicting) directly below a server-computed ClinVar card reading P 18. Two contradicting numbers, same gene, same screen.

The repair

Four independent normalizers — three wrong, the fourth correct only by the accident of special-casing conflicting ahead of its substring tests — are replaced by one vocabulary matched by exact string equality:

  • app/src/types/clinvarSignificance.ts is the single client source of truth; protein.ts::normalizeClassification(), alphafold.ts::classifyClinicalSignificance() and GeneClinVarCard.vue are thin adapters over it.
  • normalize_clinvar_classification() (now in api/functions/external-proxy-gnomad-clinvar.R) mirrors the same table, with every grepl() fallback removed.
  • One shared fixtureapi/tests/testthat/fixtures/clinvar-significance-vocabulary.json — drives both suites, and both assert their table's key set equals the fixture's in both directions, so the two cannot drift.

ClinVar genuinely aggregates across submissions (Pathogenic/Likely pathogenic/Pathogenic, low penetrance/Established risk allele; risk factor is a live record), so its documented delimiter grammar is parsed and each token exact-matched, with one unresolvable token poisoning the whole value. Whole-string matching alone would have sent those to unknown and undercounted real pathogenic variants. Anything still unresolved is an explicit unknown, logged once per distinct term, and can never reach an ACMG tier.

Pathogenic/Likely pathogenic now resolves identically everywhere, which also fixes the 3D viewer and the lollipop reporting different P/LP splits for identical input.

Where conflicting variants go

Their own purple (#6f42c1, the $purple token value) legend category and filter chip on the lollipop, the gene-structure plot and the 3D variant panel, plus a CONF chip on the ClinVar card. Both plots keep their existing P/LP-only default, so Conflicting and Other are hidden there with their counts on the chip — explicit exclusion with the count surfaced, rather than a silent drop.

Three coupled defects fixed alongside

  1. geneStructureVariantPlotUtils.ts — unmapped classes bypassed the gene-structure plot's filters entirely (?? true), so conflicting variants rendered Pathogenic-red and ignored the P/LP-only default.
  2. PATHOGENICITY_SEVERITY ranked other as the most severe class, so one unrecognised variant hijacked the dominant colour of an aggregated position. Individual-mode rendering kept a second severity map that already disagreed; both now derive from pathogenicitySeverityRank().
  3. gene-structure-tooltip.ts truncated its breakdown to the top five of what are now seven possible classes.

Verification

  • npm run type-check clean; 2419 frontend unit tests pass (302 files), including 5 new spec files.
  • R: test-unit-gnomad-clinvar-summary.R FAIL 0 | PASS 87.
  • make lint-app, make lint-api, make code-quality-audit, make pre-commit all green. external-proxy-gnomad.R was split at the ClinVar section to stay under the 600-line ceiling.
  • Live check on a restarted dev stack, PCDH12: card reads P 18 | LP 13 | CONF 28 | VUS 261 | LB 145 | B 37; lollipop legend reads Pathogenic 18 | Likely pathogenic 13 | VUS 261 | Likely benign 145 | Benign 37 | Conflicting 28 | Other 1 — the two now reconcile, and both sum to the 503 variant_count. The plot draws 18 red + 13 orange markers (was 46 red); toggling the Conflicting chip adds exactly 28 #6f42c1 markers. No [clinvar] unknown-term warnings.

Process

Spec and plan were adversarially reviewed one round by Codex gpt-5.6-terra (high effort), which returned BLOCK with 1 P0 / 5 P1 / 4 P2. Every code claim was verified against the files before acceptance; all ten were accepted (one in part). Two invalidated the original spec: the aggregate-grammar undercount, and a factual error where the spec claimed the lollipop defaults to all-on when it is P/LP-only. Review text and resolutions are committed under .planning/.

🤖 Generated with Claude Code

https://claude.ai/code/session_01W7njywYE4bCjoJ6YedziVQ

berntpopp and others added 4 commits July 30, 2026 20:58
Codex gpt-5.6-terra (high) returned BLOCK: aggregate ClinVar grammar would be
undercounted, four exhaustive consumers missed, and the spec's claim that the
lollipop defaults to all-on was factually wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7njywYE4bCjoJ6YedziVQ
…thogenic (#607)

Replace four divergent substring-matching significance normalizers with one
table-driven vocabulary matched by exact string equality, plus a tokenized parse
of ClinVar's documented aggregate grammar. Conflicting classifications get their
own first-class category on all three visualizations and the ClinVar card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7njywYE4bCjoJ6YedziVQ
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7njywYE4bCjoJ6YedziVQ
@berntpopp
berntpopp merged commit 98e0e90 into master Jul 30, 2026
10 checks passed
@berntpopp
berntpopp deleted the fix/clinvar-conflicting-classification-607 branch July 30, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClinVar 'Conflicting classifications of pathogenicity' rendered as Pathogenic in gene page visualizations

1 participant