Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
1297468
feat(data-providers): add seqrepo-backed vrs data proxy
bencap Jun 4, 2026
7ea4504
fix(vrs): centralize allele id recomputation helpers
bencap Jun 4, 2026
825cf9d
feat(db): add ValidTime mixin for valid-time row versioning
bencap Jun 7, 2026
1603180
feat(hgvs): add HGVS accession and cis-phased expression helpers
bencap Jun 7, 2026
0e62464
feat(vrs): translate cis-phased multivariant HGVS into VRS blocks
bencap Jun 7, 2026
ce510c3
feat(variants): combine cis-phased members into one HGVS expression
bencap Jun 7, 2026
058ff3f
feat(db): add VRS allele closure tables for reverse translation
bencap Jun 7, 2026
fb0232f
chore(types): add mypy stubs for ga4gh and hgvs translation APIs
bencap Jun 7, 2026
bcddadd
build(deps): add variant-annotation as editable sibling dependency
bencap Jun 7, 2026
ec36115
feat(worker): write mapping job output to the allele closure tables
bencap Jun 7, 2026
b70611f
fixup into cisphased hgvs commit
bencap Jun 7, 2026
c6a06e6
feat(worker): provide a SeqRepo data proxy in the worker context
bencap Jun 7, 2026
a8e1636
fixup into mapper changes
bencap Jun 7, 2026
3bd9717
feat(worker): add reverse translation job for cross-level HGVS alleles
bencap Jun 7, 2026
7371d44
fix(reverse-translation): anchor cdna transcript lookup to mapping ru…
bencap Jun 8, 2026
181360f
feat(hgvs): add strip_protein_prediction_parens utility
bencap Jun 9, 2026
bd6f6a5
fix(vrs-utils): normalize and re-identify VRS alleles to prevent dige…
bencap Jun 9, 2026
c17caf7
refactor(reverse-translation): add p. alleles to equivalence set and …
bencap Jun 9, 2026
08634e4
feat(mapping): use typed MappingOutcome to distinguish benign skips f…
bencap Jun 9, 2026
0f1ea17
refactor(reverse-translation): replace NullTranscriptSource with live…
bencap Jun 10, 2026
6e45f56
fix(migration): correct down_revision reference in allele closure tab…
bencap Jul 9, 2026
d3f4da9
fix(vrs): stamp HGVS expressions on reverse-translated alleles and ha…
bencap Jun 18, 2026
225f824
feat(clingen): migrate CAR/LDH jobs from MappedVariant to Allele data…
bencap Jun 18, 2026
ac4cfa9
feat(variants): combine cis-phased members into one HGVS expression
bencap Jun 7, 2026
dba7bfe
feat(gnomad): migrate gnomAD linkage to the Allele model with version…
bencap Jun 22, 2026
c77c21b
feat: link VEP consequences to alleles with Ensembl-release versioning
bencap Jun 22, 2026
6247680
feat(clinvar): migrate ClinVar annotation onto the allele model
bencap Jun 22, 2026
3d9b5fa
refactor(annotation): retire the HGVS and variant-translation jobs
bencap Jun 23, 2026
c936dd4
feat(alleles): cross-layer allele equivalence query with as_of support
bencap Jun 23, 2026
d397398
feat(annotation): add append-only AnnotationEvent log and status voca…
bencap Jun 30, 2026
5773aa5
refactor(alleles): collapse to one work-unit per allele
bencap Jun 30, 2026
5d35ed0
refactor(annotation): rewrite AnnotationStatusManager to emit events
bencap Jun 30, 2026
aacc13a
feat(gnomad): derive annotation events from link verdicts
bencap Jun 30, 2026
6f0f47f
feat(vep): derive annotation events from resolution outcomes
bencap Jun 30, 2026
ed50102
feat(clingen): emit annotation events from CAR/LDH jobs
bencap Jun 30, 2026
73007fd
feat(clinvar): emit annotation events from ClinVar job
bencap Jun 30, 2026
e31363b
feat(mapping): emit annotation events
bencap Jun 30, 2026
6288f82
feat(reverse-translation): emit annotation events
bencap Jun 30, 2026
cbe0fd9
feat(scripts): add pipeline tracking and event-aware annotations script
bencap Jun 30, 2026
b05d3a3
fix(clingen): re-register existing CAIDs when force_reregister is set
bencap Jun 30, 2026
18ede44
fix(vep): route VEP HTTP rejections to Variant Recoder
bencap Jun 30, 2026
70036d8
fix(vrs): narrow RLE→LSE coercion types to satisfy mypy
bencap Jun 30, 2026
c19e78b
fix(clinvar): supersede links on the DB clock, not naive datetime.now()
bencap Jun 30, 2026
768d372
perf(annotation): index annotation_event.score_set_id
bencap Jun 30, 2026
a768039
chore(alembic): rename closure-tables migration to match its contents
bencap Jun 30, 2026
c1d26a0
test(annotation): restore status-manager query edge-case coverage
bencap Jun 30, 2026
e43f0ea
fix(hgvs): guard accession-less cis-phased split, order joined compon…
bencap Jun 30, 2026
3bf5cdd
fix(tests): update patch target for generate_clinvar_versions lost in…
bencap Jul 9, 2026
36f9389
feat(variants): combine cis-phased members into one HGVS expression
bencap Jun 7, 2026
522b694
feat(clinvar): migrate ClinVar annotation onto the allele model
bencap Jun 22, 2026
644e727
feat(cat-vrs): build categorical variant transit on the fly from live…
bencap Jun 30, 2026
bb62baa
refactor(variants): centralize canonical variant score access
bencap Jul 1, 2026
964a7d8
feat(score-sets): lean whole-set variant view (GET /score-sets/{urn}/…
bencap Jul 1, 2026
f563760
refactor(valid-time): add ValidTime.live_at for uniform temporal filt…
bencap Jul 1, 2026
a5f3da7
refactor(lean_variants): use live_at helper and join-based protein hgvs
bencap Jul 1, 2026
fc6c8ad
perf(lean-variants): materialize protein projection to avoid O(N^2) plan
bencap Jul 6, 2026
b6ea2dc
refactor(score-sets): rename lean_variants module to score_set_variants
bencap Jul 7, 2026
6a6112a
feat(variants): variant detail envelope for GET /variants/{urn}
bencap Jul 7, 2026
3016dc9
refactor(clinical-controls): serve from allele-link substrate with as_of
bencap Jul 7, 2026
a5918d1
refactor(clinical-controls): serve list and options from a shared lib…
bencap Jul 7, 2026
3717f5d
refactor(variants): rename supersededBy to supersededByScoreSet on va…
bencap Jul 7, 2026
ac8f405
fix(mapping): enforce one live authoritative allele link per record
bencap Jul 7, 2026
7b92d69
refactor(calibrations): extract shared calibration-preference and evi…
bencap Jul 8, 2026
e47791c
feat(clingen-alleles): add equivalence-class measurements endpoint
bencap Jul 8, 2026
41ada42
feat(variants): add per-allele identity sidecar to variant detail env…
bencap Jul 8, 2026
031eaab
perf(score-set-variants): split protein projection into separate quer…
bencap Jul 8, 2026
aafe524
feat(annotation): pair c/g projections via projection_group
bencap Jul 9, 2026
feb9c55
feat(allele-measurements): add include_nucleotide_siblings flag and r…
bencap Jul 9, 2026
0a365d0
refactor(annotations): rename AnnotationLayer to SequenceLevel and ty…
bencap Jul 10, 2026
ccf5b69
fix(docs): remove design references from endpoint documentation
bencap Jul 10, 2026
19072d6
feat(allele-measurements): rank current measurements before superseded
bencap Jul 12, 2026
0ff6cd0
feat(clinical-controls): tag control links with annotating allele digest
bencap Jul 13, 2026
bb369d9
refactor(allele-measurements): expose preferred classification, exclu…
bencap Jul 13, 2026
c1fb812
feat(clingen): batch CAR submissions and add HTTP timeouts
bencap Jul 15, 2026
3b9f7da
feat(worker): stop linking protein-level alleles to ClinVar and gnomAD
bencap Jul 15, 2026
5751e9c
feat(cat-vrs): add co_encodes relation and convergent derivation
bencap Jul 18, 2026
28f90ea
refactor(annotation): dissolve util.py into cohesive modules
bencap Jul 18, 2026
a8363a6
feat(annotation): serve variant annotation from the Allele substrate
bencap Jul 18, 2026
599c8e0
feat(variants): relocate VA-Spec and VRS-lookup endpoints; drop /mapp…
bencap Jul 18, 2026
275a75a
feat(alleles): add allele-detail endpoint (digest / CAID / PAID)
bencap Jul 18, 2026
193c55f
refactor(workflow): extract shared pipeline-kickoff helper from score…
bencap Jul 20, 2026
a712262
fixup ~2
bencap Jul 20, 2026
c87b72f
feat(scripts): add batch driver for score-set mapping/enrichment pipe…
bencap Jul 20, 2026
4b640e1
fix(variants): accept VariationDescriptor when extracting hgvs from p…
bencap Jul 20, 2026
575a2e2
feat(migration): add manual backfill of allele substrate from mapped_…
bencap Jul 21, 2026
b3fd1ba
feat(serving): rewrite annotation views and stats MV onto mapping_rec…
bencap Jul 21, 2026
9a3ded6
perf(gnomad): batch allele lookup in gnomAD linker to end per-row scans
bencap Jul 22, 2026
0f6ba0b
feat(scripts): gate unenriched selection on a usable transcript
bencap Jul 22, 2026
6f0a8e2
feat(variants): stream variant-details NDJSON, add gnomAD variant links
bencap Jul 23, 2026
cdbaeab
feat(score-sets): rewrite CSV export onto the allele substrate
bencap Jul 23, 2026
d53c273
feat(variants): add migration stubs for retired mapped-variant routes
bencap Jul 23, 2026
fbfd4b9
fix(measurements): reach a CA's protein consequence unconditionally
bencap Jul 24, 2026
a9c2f40
feat(cleanup): extend job timeouts
bencap Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ RUN curl -sSL https://install.python-poetry.org | python3 -
COPY poetry.lock pyproject.toml ./

# installs runtime dependencies to $VIRTUAL_ENV
RUN poetry install --no-root --extras server
RUN poetry install --no-root --extras server --no-directory
COPY alembic /code/alembic
COPY alembic.ini /code/alembic.ini
COPY src /code/src
Expand Down
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
VENV := .venv/bin

.DEFAULT_GOAL := help

.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-12s %s\n", $$1, $$2}'

.PHONY: dev
dev: ## Install deps including local editable variant-annotation
poetry install --extras server

.PHONY: test
test: ## Run the test suite
$(VENV)/pytest tests/

.PHONY: lint
lint: ## Check code with ruff
$(VENV)/ruff check src/ tests/

.PHONY: format
format: ## Format code with ruff
$(VENV)/ruff format src/ tests/
1,326 changes: 1,326 additions & 0 deletions alembic/manual_migrations/migrate_mapped_variants_to_allele_substrate.py

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions alembic/manual_migrations/migrate_target_gene_mapping_qc.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
from mavedb.models import * # noqa: F401,F403 pylint: disable=wildcard-import
from mavedb.db.session import SessionLocal
from mavedb.lib.variants import HGVS_G_REGEX, HGVS_P_REGEX, get_hgvs_from_post_mapped
from mavedb.models.enums.annotation_layer import AnnotationLayer
from mavedb.models.enums.sequence_level import SequenceLevel
from mavedb.models.mapped_variant import MappedVariant
from mavedb.models.score_set import ScoreSet
from mavedb.models.target_gene import TargetGene
Expand All @@ -102,18 +102,18 @@
_NUCLEOTIDE_ALPHABET = frozenset("ACGTUNRYSWKMBDHV-")


def _layer_from_hgvs(hgvs: str) -> Optional[AnnotationLayer]:
"""Classify an HGVS string into an :class:`AnnotationLayer`."""
def _layer_from_hgvs(hgvs: str) -> Optional[SequenceLevel]:
"""Classify an HGVS string into an :class:`SequenceLevel`."""
if HGVS_G_REGEX.search(hgvs):
return AnnotationLayer.genomic
return SequenceLevel.genomic
if HGVS_P_REGEX.search(hgvs):
return AnnotationLayer.protein
return SequenceLevel.protein
if _HGVS_C_REGEX.search(hgvs):
return AnnotationLayer.cdna
return SequenceLevel.cdna
return None


def _layer_from_vrs_sequence(post_mapped: dict) -> Optional[AnnotationLayer]:
def _layer_from_vrs_sequence(post_mapped: dict) -> Optional[SequenceLevel]:
"""
Infer layer from VRS sequence alphabet (protein vs nucleotide).
Returns protein if any allele/member sequence contains a non-nucleotide letter.
Expand Down Expand Up @@ -150,15 +150,15 @@ def extract_sequences(obj):
for seq in extract_sequences(post_mapped):
found_seq = True
if any(letter.upper() not in _NUCLEOTIDE_ALPHABET for letter in seq if letter.isalpha()):
return AnnotationLayer.protein
return SequenceLevel.protein

if found_seq:
return AnnotationLayer.genomic
return SequenceLevel.genomic

return None


def _alignment_level_for(target: TargetGene) -> Optional[AnnotationLayer]:
def _alignment_level_for(target: TargetGene) -> Optional[SequenceLevel]:
"""Recover the alignment layer for legacy mapped variants of ``target``.

Tries ``post_mapped_metadata`` first, then ``pre_mapped_metadata`` (same
Expand All @@ -177,16 +177,16 @@ def _alignment_level_for(target: TargetGene) -> Optional[AnnotationLayer]:
return None

if has_genomic:
return AnnotationLayer.genomic
return SequenceLevel.genomic
if has_protein:
return AnnotationLayer.protein
return SequenceLevel.protein
if has_cdna:
return AnnotationLayer.cdna
return SequenceLevel.cdna

return None


def _layer_from_sequence_type(target: TargetGene) -> Optional[AnnotationLayer]:
def _layer_from_sequence_type(target: TargetGene) -> Optional[SequenceLevel]:
"""Infer alignment layer from ``target_sequence.sequence_type`` and ``category``.

Last-resort fallback for targets where all variant-level mappings failed
Expand All @@ -201,16 +201,16 @@ def _layer_from_sequence_type(target: TargetGene) -> Optional[AnnotationLayer]:
return None
seq_type = target.target_sequence.sequence_type
if seq_type == "protein":
return AnnotationLayer.protein
return SequenceLevel.protein
if seq_type == "dna" and target.category is not None and target.category.value == "protein_coding":
return AnnotationLayer.genomic
return SequenceLevel.genomic
return None


def _populate_layer_by_target_id(
db: Session,
targets_by_score_set_id: dict[int, list[TargetGene]],
layer_by_target_id: dict[int, Optional[AnnotationLayer]],
layer_by_target_id: dict[int, Optional[SequenceLevel]],
layer_via_hgvs_target_ids: set[int],
layer_via_sequence_type_target_ids: set[int],
) -> None:
Expand Down Expand Up @@ -344,7 +344,7 @@ def do_migration(db: Session) -> None:
# Cache (target_gene_id, alignment_level, tool_version) -> persisted TargetGeneMapping
# so we create one row per distinct combination and reuse it across every
# mapped variant that maps into it.
cache: dict[tuple[int, AnnotationLayer, str], TargetGeneMapping] = {}
cache: dict[tuple[int, SequenceLevel, str], TargetGeneMapping] = {}

total = db.scalar(sa.select(sa.func.count(MappedVariant.id))) or 0
print(f" {total} mapped variants to consider.")
Expand Down Expand Up @@ -393,7 +393,7 @@ def do_migration(db: Session) -> None:
# only need to determine it once per target -- and any single attributable mapped
# variant for that target lets us attribute every sibling. The via_* sets track which
# fallback was used so the diagnostic report shows attribution breadth.
layer_by_target_id: dict[int, Optional[AnnotationLayer]] = {}
layer_by_target_id: dict[int, Optional[SequenceLevel]] = {}
layer_via_hgvs_target_ids: set[int] = set()
layer_via_sequence_type_target_ids: set[int] = set()
_populate_layer_by_target_id(
Expand All @@ -420,7 +420,7 @@ def do_migration(db: Session) -> None:

# Group ids to update by (tgm_id, level) so we can issue one bulk UPDATE
# per group instead of a per-row ORM flush.
updates_by_group: dict[tuple[int, AnnotationLayer], list[int]] = defaultdict(list)
updates_by_group: dict[tuple[int, SequenceLevel], list[int]] = defaultdict(list)

for row in chunk:
last_id = row.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@

from alembic import op

from mavedb.models.published_variant import signature


# revision identifiers, used by Alembic.
revision = "4726e4dddde8"
down_revision = "b85bc7b1bec7"
branch_labels = None
depends_on = None

# Frozen literal, not imported from the model: this migration replaces the historical
# mapped_variants-based MV indexes at its point in history. A later migration rebuilds the MV onto
# the mapping_records substrate and re-indexes it, so the column names below stay the legacy ones.
signature = "published_variants_materialized_view"


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""Add mapping_records, alleles, and mapping_record_alleles tables

Revision ID: a1b2c3d4e5f6
Revises: a7f3c2e9b104
Create Date: 2026-05-29

New parallel tables for the Better Reverse Translation epic (#746).
The existing mapped_variants table is left untouched (frozen serving).
"""

import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

from alembic import op

# revision identifiers, used by Alembic.
revision = "a1b2c3d4e5f6"
down_revision = "a7f3c2e9b104"
branch_labels = None
depends_on = None

VALID_ASSAY_LEVELS = "('genomic', 'cdna', 'protein')"
VALID_ALIGNMENT_LEVELS = "('protein', 'cdna', 'genomic')"


def upgrade() -> None:
op.create_table(
"alleles",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("vrs_digest", sa.String(), nullable=False),
sa.Column("level", sa.String(length=16), nullable=False),
sa.Column("transcript", sa.String(), nullable=False),
sa.Column("hgvs_g", sa.String(), nullable=True),
sa.Column("hgvs_c", sa.String(), nullable=True),
sa.Column("hgvs_p", sa.String(), nullable=True),
sa.Column("clingen_allele_id", sa.String(), nullable=True),
sa.Column("post_mapped", postgresql.JSONB(), nullable=True),
sa.Column("created_at", sa.Date(), nullable=False, server_default=sa.text("CURRENT_DATE")),
sa.Column(
"updated_at",
sa.Date(),
nullable=False,
server_default=sa.text("CURRENT_DATE"),
onupdate=sa.text("CURRENT_DATE"),
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("vrs_digest", name="uq_alleles_vrs_digest"),
)
op.create_index("ix_alleles_vrs_digest", "alleles", ["vrs_digest"])
op.create_index("ix_alleles_level", "alleles", ["level"])
op.create_index("ix_alleles_clingen_allele_id", "alleles", ["clingen_allele_id"])

op.create_table(
"mapping_records",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("variant_id", sa.Integer(), nullable=False),
sa.Column("vrs_digest", sa.String(), nullable=True),
sa.Column("pre_mapped", postgresql.JSONB(), nullable=True),
sa.Column("assay_level", sa.String(length=16), nullable=False),
sa.Column("hgvs_assay_level", sa.String(), nullable=True),
sa.Column("mapping_api_version", sa.String(), nullable=False),
sa.Column("mapped_date", sa.Date(), nullable=False),
sa.Column("vrs_version", sa.String(), nullable=True),
sa.Column("current", sa.Boolean(), nullable=False),
sa.Column("alignment_level", sa.String(length=16), nullable=True),
sa.Column("at_mismatched_locus", sa.Boolean(), nullable=True),
sa.Column("near_gap", sa.Boolean(), nullable=True),
sa.Column("target_gene_mapping_id", sa.Integer(), nullable=True),
sa.Column("created_at", sa.Date(), nullable=False, server_default=sa.text("CURRENT_DATE")),
sa.Column(
"updated_at",
sa.Date(),
nullable=False,
server_default=sa.text("CURRENT_DATE"),
onupdate=sa.text("CURRENT_DATE"),
),
sa.ForeignKeyConstraint(
["variant_id"],
["variants.id"],
name="fk_mapping_records_variant_id",
),
sa.ForeignKeyConstraint(
["target_gene_mapping_id"],
["target_gene_mappings.id"],
name="fk_mapping_records_target_gene_mapping_id",
),
sa.PrimaryKeyConstraint("id"),
sa.CheckConstraint(
f"assay_level IN {VALID_ASSAY_LEVELS}",
name="ck_mapping_records_assay_level_valid",
),
)
op.create_index("ix_mapping_records_variant_id", "mapping_records", ["variant_id"])
op.create_index("ix_mapping_records_vrs_digest", "mapping_records", ["vrs_digest"])
op.create_index(
"ix_mapping_records_target_gene_mapping_id",
"mapping_records",
["target_gene_mapping_id"],
)

op.create_table(
"mapping_record_alleles",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("mapping_record_id", sa.Integer(), nullable=False),
sa.Column("allele_id", sa.Integer(), nullable=False),
sa.Column(
"is_authoritative",
sa.Boolean(),
nullable=False,
server_default=sa.text("false"),
),
sa.ForeignKeyConstraint(
["mapping_record_id"],
["mapping_records.id"],
name="fk_mapping_record_alleles_mapping_record_id",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["allele_id"],
["alleles.id"],
name="fk_mapping_record_alleles_allele_id",
ondelete="RESTRICT",
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
"ix_mapping_record_alleles_mapping_record_id",
"mapping_record_alleles",
["mapping_record_id"],
)
op.create_index(
"ix_mapping_record_alleles_allele_id",
"mapping_record_alleles",
["allele_id"],
)


def downgrade() -> None:
op.drop_index("ix_mapping_record_alleles_allele_id", table_name="mapping_record_alleles")
op.drop_index("ix_mapping_record_alleles_mapping_record_id", table_name="mapping_record_alleles")
op.drop_table("mapping_record_alleles")

op.drop_index("ix_mapping_records_target_gene_mapping_id", table_name="mapping_records")
op.drop_index("ix_mapping_records_vrs_digest", table_name="mapping_records")
op.drop_index("ix_mapping_records_variant_id", table_name="mapping_records")
op.drop_table("mapping_records")

op.drop_index("ix_alleles_clingen_allele_id", table_name="alleles")
op.drop_index("ix_alleles_level", table_name="alleles")
op.drop_index("ix_alleles_vrs_digest", table_name="alleles")
op.drop_table("alleles")
Loading