Skip to content

Gene list matching: single alias hop, and a resync that deletes #1669 - #1846

Open
davmlaw wants to merge 2 commits into
masterfrom
issue_1669_single_hop_alias_matching
Open

Gene list matching: single alias hop, and a resync that deletes #1669#1846
davmlaw wants to merge 2 commits into
masterfrom
issue_1669_single_hop_alias_matching

Conversation

@davmlaw

@davmlaw davmlaw commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Implements #1669.

Problem

ReleaseGeneMatcher traversed the GeneSymbolAlias graph transitively and in both directions, so an alias string shared by two unrelated approved symbols bridged their genes. Reported from prod (SACGF/variantgrid_sapath#426): a hypogonadotropic hypogonadism panel returned a PDCD2 variant, because HGNC records RP8 as an alias for both MT-TS2 (on the list) and PDCD2. The walk went MT-TS2 → RP8 → PDCD2 and wrote:

ReleaseGeneSymbolGene  release_gene_symbol=MT-TS2  gene=5134  match_info='RP8 is an alias for PDCD2 (HGNC)'

MT-TS2 is a mitochondrial tRNA with no RefSeq gene in the release, so PDCD2 was its only matched gene — a false positive and a false negative from the same row.

Fix

aliases_dict now takes exactly one GeneSymbolAlias hop, in either direction, for symbols the release doesn't have itself:

  • forward — the queried symbol is the alias, the target is in the release (KAL1 → ANOS1: gene list uses an old name)
  • backward — the queried symbol is the target, the alias is in the release (ANOS1 → KAL1: HGNC is refreshed far more often than gene annotation releases, so a current-symbol gene list against an older release hits this constantly — over 1,100 rows in the T2T Ensembl 2022 release alone)

One hop is enough for renames: HGNC's prev_symbol lists every previous symbol of a gene, so a gene renamed twice has direct alias rows from both old names. Chaining is what let a shared alias string bridge unrelated genes. _aliases (the recursive walk) is gone.

Precedence is unchanged: a symbol in the release matches its own genes with match_info=None and aliases are never consulted; otherwise gene-version matches (Gene v0/GRCh38) win over alias matches for the same gene.

Cleanup

Matching only ever inserts ReleaseGeneSymbolGene rows, so a rematch leaves the chained rows in place — and rows derived from alias rows since deleted by a re-import survive too. fix_rematch_release_symbols_to_genes becomes a full resync per release (insert / update match_info / delete what the current rules no longer make), with --dry-run, and genes/migrations/0089_one_off_resync_release_gene_symbol_genes.py registers it as a ManualOperation so deployments pick it up.

--dry-run against this box's 175 GB database:

grch38/RefSeq - vrefseq_grch38_110        - delete: 730, update: 177, insert: 56
grch37/RefSeq - vrefseq_grch37_105        - delete: 807, update: 870, insert: 32
grch38/RefSeq - vGRCh38_RS_2023           - delete: 739, update: 309, insert: 51
grch37/RefSeq - vGRCh37_105               - delete: 816, update: 475, insert: 34
t2t-chm13v20/Ensembl - vT2Tv2_Ensembl_..  - delete: 966, update: 123, insert: 24
grch38/RefSeq - vgrch38_2024_08           - delete: 739, update:  44, insert: 21
grch38/RefSeq - vRefSeq_RS_2024_08        - delete: 742, update:  37, insert: 23
grch38/RefSeq - vRS_2025_08_GRCh38        - delete: 360, update:  12, insert:  0

Against RS_2025_08_GRCh38, the known bridges are all gone and the legitimate hop is kept:

MT-TS2  -> []
ATHS    -> []
CELIAC2 -> []
RP8     -> [('5134', 'RP8 is an alias for PDCD2 (HGNC)')]

Testing

genes/tests/test_gene_matching.py gets TestReleaseGeneMatcher (direct hit, forward hop, backward hop, shared alias doesn't bridge, alias matches only the release target) and TestFixRematchReleaseSymbolsToGenes (plants the MT-TS2 → 5134 chained row, asserts the resync deletes it, keeps the direct match and inserts the missing one; and that --dry-run writes nothing).

Full suite: 3112 tests, OK. scripts/vg map --check and scripts/vg docs check pass.

Not in scope

Item 3 of the issue (surfacing an alias substitution on the gene list page) stays with #1668, which is where the shared resolver and its provenance live. Once that exists, the backward hop should accept prev_symbol rows only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XeLN2A486UE31zfdk9SrRG

ReleaseGeneMatcher walked the GeneSymbolAlias graph transitively and in both
directions, so an alias string shared by two unrelated approved symbols acted
as a bridge. A hypogonadotropic hypogonadism panel returned a PDCD2 variant via
MT-TS2 -> RP8 -> PDCD2, and since MT-TS2 has no RefSeq gene in the release,
PDCD2 was its only match - a false positive and a false negative from one row.

aliases_dict now takes exactly one GeneSymbolAlias hop, in either direction:
the queried symbol is the alias of a symbol the release has, or a symbol the
release has is an alias of it. Both directions earn their keep - HGNC is
refreshed far more often than gene annotation releases, so a current-symbol
gene list against an older release relies on the backward hop constantly. One
hop is enough for renames: HGNC lists every previous symbol of a gene.

Matching only ever inserted, so a rematch left the chained rows in place.
fix_rematch_release_symbols_to_genes becomes a full resync per release -
insert, update match_info, delete what the current rules no longer make -
with --dry-run, and a ManualOperation migration so deployments run it.
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.

1 participant