From db2bb496ea4ee02bca3adff1afbedd4a8f8ca54a Mon Sep 17 00:00:00 2001 From: David Reinhart Date: Tue, 18 Aug 2026 10:17:59 -0700 Subject: [PATCH 1/3] fix(worker): clear stale uniprot_id_from_mapped_metadata on remap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UniProt polling job only writes target_gene.uniprot_id_from_mapped_metadata on a successful lookup. When a remap produced no UniProt ID (no results, ambiguous results, or the target gene was not found), the value from the previous mapping run survived and became mismatched against the freshly written pre/post mapped metadata — a state unreachable for a new target_gene record. Clear the field alongside the other mapped metadata writes in map_variants_for_score_set, so it is reset before the downstream UniProt mapping jobs run. Closes #794 --- .../worker/jobs/variant_processing/mapping.py | 2 + .../jobs/variant_processing/test_mapping.py | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/mavedb/worker/jobs/variant_processing/mapping.py b/src/mavedb/worker/jobs/variant_processing/mapping.py index efc8226b..7762a218 100644 --- a/src/mavedb/worker/jobs/variant_processing/mapping.py +++ b/src/mavedb/worker/jobs/variant_processing/mapping.py @@ -203,6 +203,8 @@ async def map_variants_for_score_set(ctx: dict, job_id: int, job_manager: JobMan target_gene.pre_mapped_metadata = cast(pre_mapped_metadata, JSONB) target_gene.post_mapped_metadata = cast(post_mapped_metadata, JSONB) + target_gene.uniprot_id_from_mapped_metadata = None + job_manager.db.add(target_gene) logger.debug("Added mapping metadata to target gene.", extra=job_manager.logging_context()) diff --git a/tests/worker/jobs/variant_processing/test_mapping.py b/tests/worker/jobs/variant_processing/test_mapping.py index dc2ac843..bbeafe3a 100644 --- a/tests/worker/jobs/variant_processing/test_mapping.py +++ b/tests/worker/jobs/variant_processing/test_mapping.py @@ -333,6 +333,62 @@ async def dummy_mapping_job(): assert annotation_statuses[0].annotation_type == "vrs_mapping" assert annotation_statuses[0].status == "success" + async def test_map_variants_for_score_set_clears_stale_uniprot_id( + self, + session, + with_independent_processing_runs, + mock_worker_ctx, + sample_independent_variant_mapping_run, + sample_score_set, + ): + """A remap must clear any UniProt ID left over from a prior mapping run. + + The downstream UniProt job only writes on success, so a stale value would otherwise + persist and be mismatched against the newly mapped metadata. + """ + + async def dummy_mapping_job(): + return await construct_mock_mapping_output( + session=session, + score_set=sample_score_set, + with_gene_info=True, + with_layers={"g", "c", "p"}, + with_pre_mapped=True, + with_post_mapped=True, + with_reference_metadata=True, + with_mapped_scores=True, + with_all_variants=True, + ) + + variant = Variant( + score_set_id=sample_score_set.id, hgvs_nt="NM_000000.1:c.1A>G", hgvs_pro="NP_000000.1:p.Met1Val", data={} + ) + session.add(variant) + + for target in sample_score_set.target_genes: + target.uniprot_id_from_mapped_metadata = "P00000" + session.add(target) + session.commit() + + with ( + patch.object( + _UnixSelectorEventLoop, + "run_in_executor", + return_value=dummy_mapping_job(), + ), + ): + result = await map_variants_for_score_set( + mock_worker_ctx, + sample_independent_variant_mapping_run.id, + JobManager(session, mock_worker_ctx["redis"], sample_independent_variant_mapping_run.id), + ) + + assert isinstance(result, JobExecutionOutcome) + assert result.status == JobStatus.SUCCEEDED + + for target in sample_score_set.target_genes: + assert target.uniprot_id_from_mapped_metadata is None + @pytest.mark.parametrize( "with_layers", [ From 79b3a6b6682747e10e0e1d93ed89198750d490be Mon Sep 17 00:00:00 2001 From: David Reinhart Date: Wed, 19 Aug 2026 11:50:21 -0700 Subject: [PATCH 2/3] Clear stale HGNC gene info on remap when mapping returns none The mapping job only wrote mapped_hgnc_name when gene info was present, so a target gene remapped without gene info kept values from a prior run. Explicitly reset to None in the else branch. --- src/mavedb/worker/jobs/variant_processing/mapping.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mavedb/worker/jobs/variant_processing/mapping.py b/src/mavedb/worker/jobs/variant_processing/mapping.py index 7762a218..be2fde45 100644 --- a/src/mavedb/worker/jobs/variant_processing/mapping.py +++ b/src/mavedb/worker/jobs/variant_processing/mapping.py @@ -173,6 +173,9 @@ async def map_variants_for_score_set(ctx: dict, job_id: int, job_manager: JobMan job_manager.save_to_context({"mapped_hgnc_name": target_gene.mapped_hgnc_name}) logger.debug("Added mapped HGNC name to target gene.", extra=job_manager.logging_context()) + else: + target_gene.mapped_hgnc_name = None + logger.debug("No gene-level info found for target gene.", extra=job_manager.logging_context()) # add annotation layer info for annotation_layer in reference_metadata[target_gene_identifier]["layers"]: From bef917a5cc7ae8d06d489e799cf27ead368a464e Mon Sep 17 00:00:00 2001 From: David Reinhart Date: Wed, 19 Aug 2026 20:35:45 -0700 Subject: [PATCH 3/3] test(worker): cover stale mapped_hgnc_name clearing on remap Add a unit test for the fix in 79b3a6b6. The test seeds mapped_hgnc_name on every target gene, runs map_variants_for_score_set against mapping output with no gene_info, and asserts the field is reset to None. The existing missing-gene-info test asserted the same final state but never seeded a prior value, so it passed before the fix. This one fails without the else branch. --- .../jobs/variant_processing/test_mapping.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/worker/jobs/variant_processing/test_mapping.py b/tests/worker/jobs/variant_processing/test_mapping.py index bbeafe3a..5c513d7e 100644 --- a/tests/worker/jobs/variant_processing/test_mapping.py +++ b/tests/worker/jobs/variant_processing/test_mapping.py @@ -389,6 +389,62 @@ async def dummy_mapping_job(): for target in sample_score_set.target_genes: assert target.uniprot_id_from_mapped_metadata is None + async def test_map_variants_for_score_set_clears_stale_mapped_hgnc_name( + self, + session, + with_independent_processing_runs, + mock_worker_ctx, + sample_independent_variant_mapping_run, + sample_score_set, + ): + """A remap must clear any mapped HGNC name left over from a prior mapping run. + + Gene-level info is only written when the mapping results include it, so a stale value + would otherwise persist and be mismatched against the newly mapped metadata. + """ + + async def dummy_mapping_job(): + return await construct_mock_mapping_output( + session=session, + score_set=sample_score_set, + with_gene_info=False, + with_layers={"g", "c", "p"}, + with_pre_mapped=True, + with_post_mapped=True, + with_reference_metadata=True, + with_mapped_scores=True, + with_all_variants=True, + ) + + variant = Variant( + score_set_id=sample_score_set.id, hgvs_nt="NM_000000.1:c.1A>G", hgvs_pro="NP_000000.1:p.Met1Val", data={} + ) + session.add(variant) + + for target in sample_score_set.target_genes: + target.mapped_hgnc_name = "STALE1" + session.add(target) + session.commit() + + with ( + patch.object( + _UnixSelectorEventLoop, + "run_in_executor", + return_value=dummy_mapping_job(), + ), + ): + result = await map_variants_for_score_set( + mock_worker_ctx, + sample_independent_variant_mapping_run.id, + JobManager(session, mock_worker_ctx["redis"], sample_independent_variant_mapping_run.id), + ) + + assert isinstance(result, JobExecutionOutcome) + assert result.status == JobStatus.SUCCEEDED + + for target in sample_score_set.target_genes: + assert target.mapped_hgnc_name is None + @pytest.mark.parametrize( "with_layers", [