fix(app,api): stop rendering ClinVar conflicting classifications as Pathogenic (#607) - #617
Merged
Merged
Conversation
#607) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7njywYE4bCjoJ6YedziVQ
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #607
The bug
"conflicting classifications of pathogenicity".includes('pathogenic')istrue—pathogenicis a substring of pathogenicity — and the string contains nolikely, 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 readingP 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
conflictingahead of its substring tests — are replaced by one vocabulary matched by exact string equality:app/src/types/clinvarSignificance.tsis the single client source of truth;protein.ts::normalizeClassification(),alphafold.ts::classifyClinicalSignificance()andGeneClinVarCard.vueare thin adapters over it.normalize_clinvar_classification()(now inapi/functions/external-proxy-gnomad-clinvar.R) mirrors the same table, with everygrepl()fallback removed.api/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 factoris 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 explicitunknown, logged once per distinct term, and can never reach an ACMG tier.Pathogenic/Likely pathogenicnow 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$purpletoken value) legend category and filter chip on the lollipop, the gene-structure plot and the 3D variant panel, plus aCONFchip 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
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.PATHOGENICITY_SEVERITYrankedotheras 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 frompathogenicitySeverityRank().gene-structure-tooltip.tstruncated its breakdown to the top five of what are now seven possible classes.Verification
npm run type-checkclean; 2419 frontend unit tests pass (302 files), including 5 new spec files.test-unit-gnomad-clinvar-summary.RFAIL 0 | PASS 87.make lint-app,make lint-api,make code-quality-audit,make pre-commitall green.external-proxy-gnomad.Rwas split at the ClinVar section to stay under the 600-line ceiling.P 18 | LP 13 | CONF 28 | VUS 261 | LB 145 | B 37; lollipop legend readsPathogenic 18 | Likely pathogenic 13 | VUS 261 | Likely benign 145 | Benign 37 | Conflicting 28 | Other 1— the two now reconcile, and both sum to the 503variant_count. The plot draws 18 red + 13 orange markers (was 46 red); toggling the Conflicting chip adds exactly 28#6f42c1markers. 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