From 05175e162fb1c6007a2d3c8efdbb80272ee546de Mon Sep 17 00:00:00 2001 From: Brendan ODonnell Date: Thu, 15 Jun 2023 10:41:54 -0400 Subject: [PATCH 1/6] Fix variant region for ins and dup on intron-exon boundary --- src/hgvs/assemblymapper.py | 22 ++++++++ src/hgvs/utils/altseqbuilder.py | 20 ++++++-- src/hgvs/variantmapper.py | 14 ++--- tests/data/sanity_cp.tsv | 20 ++++---- tests/support/mock_input_source.py | 3 +- tests/test_hgvs_assemblymapper.py | 51 +++++++++++++++++++ tests/test_hgvs_variantmapper.py | 45 ++++++++++++++++ ...est_hgvs_variantmapper_cp_altseqbuilder.py | 1 + tests/test_hgvs_variantmapper_cp_sanity.py | 22 ++++++++ 9 files changed, 177 insertions(+), 21 deletions(-) diff --git a/src/hgvs/assemblymapper.py b/src/hgvs/assemblymapper.py index e14e07ba..d5785cee 100644 --- a/src/hgvs/assemblymapper.py +++ b/src/hgvs/assemblymapper.py @@ -2,6 +2,8 @@ import logging +from bioutils.sequences import reverse_complement + import hgvs import hgvs.normalizer from hgvs.exceptions import ( @@ -239,6 +241,26 @@ def _alt_ac_for_tx_ac(self, tx_ac): assert len(alt_acs) == 1, "Should have exactly one alignment at this point" return alt_acs[0] + def _replace_reference(self, var): + if (getattr(var.posedit.pos.start, 'offset', 0) != 0 or getattr(var.posedit.pos.end, 'offset', 0) != 0): + if var.type != 'g': + alt_ac = self._alt_ac_for_tx_ac(var.ac) + if var.type == 'c': + var_g = super(AssemblyMapper, self).c_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) + if var.type == 'n': + var_g = super(AssemblyMapper, self).n_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) + if var.type == 't': + var_g = super(AssemblyMapper, self).t_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) + super(AssemblyMapper, self)._replace_reference(var_g) + ref = var_g.posedit.edit.ref + if self._fetch_AlignmentMapper(tx_ac=var.ac).strand == -1: + ref = reverse_complement(ref) + var.posedit.edit.ref = ref + _logger.debug("Replaced reference sequence in {var} with {ref}".format(var=var, ref=var_g.posedit.edit.ref)) + return var + + return super(AssemblyMapper, self)._replace_reference(var) + def _fetch_AlignmentMapper(self, tx_ac, alt_ac=None, alt_aln_method=None): """convenience version of VariantMapper._fetch_AlignmentMapper that derives alt_ac from transcript, assembly, and alt_aln_method diff --git a/src/hgvs/utils/altseqbuilder.py b/src/hgvs/utils/altseqbuilder.py index 74e706c8..5c79910b 100644 --- a/src/hgvs/utils/altseqbuilder.py +++ b/src/hgvs/utils/altseqbuilder.py @@ -9,6 +9,8 @@ import logging import math +from hgvs.exceptions import HGVSError + import six from bioutils.sequences import reverse_complement, translate_cds @@ -195,8 +197,17 @@ def _get_variant_region(self): and self._var_c.posedit.pos.end.datum == Datum.CDS_END ): result = self.WHOLE_GENE + elif self._var_c.posedit.edit.type == "ins" and self._var_c.posedit.pos.start.offset == -1 and self._var_c.posedit.pos.end.offset == 0: + # ins at intron-exon boundary + result = self.EXON + elif self._var_c.posedit.edit.type == "ins" and self._var_c.posedit.pos.start.offset == 0 and self._var_c.posedit.pos.end.offset == 1: + # ins at exon-intron boundary + result = self.EXON + elif self._var_c.posedit.edit.type == "dup" and self._var_c.posedit.pos.start.offset <= -1 and self._var_c.posedit.pos.end.offset >= -1: + # dup at intron-exon boundary + result = self.EXON elif self._var_c.posedit.pos.start.offset != 0 or self._var_c.posedit.pos.end.offset != 0: - # leave out anything intronic for now + # leave out anything else intronic for now result = self.INTRON else: # anything else that contains an exon result = self.EXON @@ -266,7 +277,10 @@ def _incorporate_dup(self): """Incorporate dup into sequence""" seq, cds_start, cds_stop, start, end = self._setup_incorporate() - dup_seq = seq[start:end] + if not self._var_c.posedit.edit.ref: + raise HGVSError('Duplication variant is missing reference sequence') + + dup_seq = self._var_c.posedit.edit.ref seq[end:end] = dup_seq is_frameshift = len(dup_seq) % 3 != 0 @@ -329,7 +343,7 @@ def _setup_incorporate(self): result = cds_start - 1 else: # cds/intron if pos.offset <= 0: - result = (cds_start - 1) + pos.base - 1 + result = (cds_start - 1) + pos.base + pos.offset - 1 else: result = (cds_start - 1) + pos.base elif pos.datum == Datum.CDS_END: # 3' UTR diff --git a/src/hgvs/variantmapper.py b/src/hgvs/variantmapper.py index d73b98b7..5e2cb038 100644 --- a/src/hgvs/variantmapper.py +++ b/src/hgvs/variantmapper.py @@ -431,6 +431,7 @@ def c_to_p(self, var_c, pro_ac=None): raise HGVSInvalidVariantError("Expected a cDNA (c.) variant; got " + str(var_c)) if self._validator: self._validator.validate(var_c) + self._replace_reference(var_c) reference_data = RefTranscriptData(self.hdp, var_c.ac, pro_ac) builder = altseqbuilder.AltSeqBuilder(var_c, reference_data) @@ -466,11 +467,10 @@ def _replace_reference(self, var): return var pos = var.posedit.pos - if (isinstance(pos.start, hgvs.location.BaseOffsetPosition) and pos.start.offset != 0) or ( - isinstance(pos.end, hgvs.location.BaseOffsetPosition) and pos.end.offset != 0 - ): - _logger.info("Can't update reference sequence for intronic variant {}".format(var)) - return var + if (getattr(pos.start, 'offset', 0) != 0 or getattr(pos.end, 'offset', 0) != 0): + if var.type != "g": + _logger.info("Can't update reference sequence for intronic variant {}".format(var)) + return var # For c. variants, we need coords on underlying sequences if var.type == "c": @@ -481,8 +481,8 @@ def _replace_reference(self, var): else: pos = var.posedit.pos - seq_start = pos.start.base - 1 - seq_end = pos.end.base + seq_start = pos.start.base + getattr(pos.start, 'offset', 0) - 1 + seq_end = pos.end.base + getattr(pos.end, 'offset', 0) # When strict_bounds is False and an error occurs, return # variant as-is diff --git a/tests/data/sanity_cp.tsv b/tests/data/sanity_cp.tsv index 81fc9dee..3d23a3d7 100644 --- a/tests/data/sanity_cp.tsv +++ b/tests/data/sanity_cp.tsv @@ -1,10 +1,10 @@ -accession transcript_sequence cds_start_i cds_end_i -NM_999999.1 AAAATCAAAATGAAAGCGAAAGCGTTTCGCGCGAAATAGGGG 9 39 -NM_999998.1 AAAATCAAAATGAAAGCGAAAGCGTTTCGCGCGAAATAGAGGAGGTAGTTTCGC 9 39 -NM_999997.1 AAAATCAAAATGAAAGCGAAAGCGTTTCGCGTAGAATAGAGGAGGCAGTTTCGC 9 39 -NM_999996.1 AAAATCAAAATGAAATCGAAAGCGTTTCGCGCGAAATAGAGGAGGCAGTTTCGC 9 39 -NM_999995.1 AAAATCAAAATGAAAAAATCGAAAGCGTTTCGCGCGAAATAGAGGAGGCAGTTTCGC 9 42 -NM_999994.1 AAAATCAAAATGAAAAAAAAATCGAAAGCGTTTCGCGCGAAATAGAGGAGGCAGTTTCGC 9 45 -NM_999993.1 AAAATCAAAATGGGGAGGGCCCGGCAGCCAGCTTTATAGAGGAGGCAGTTTCGC 9 39 -NM_999992.1 AAAATCAAAATGGGGTAGGCCCGGCAGCCAGCTTTATAGAGGAGGCAGTTTCGC 9 39 -NM_999992.2 AAAATCAAAATGGGGTAGGCCCGGCAGCCAGCTTTATAGAGGAGGCAGTTTCGCC 9 40 +accession transcript_sequence cds_start_i cds_end_i lengths +NM_999999.1 AAAATCAAAATGAAAGCGAAAGCGTTTCGCGCGAAATAGGGG 9 39 [10,25,30] +NM_999998.1 AAAATCAAAATGAAAGCGAAAGCGTTTCGCGCGAAATAGAGGAGGTAGTTTCGC 9 39 [10,25,30] +NM_999997.1 AAAATCAAAATGAAAGCGAAAGCGTTTCGCGTAGAATAGAGGAGGCAGTTTCGC 9 39 [10,25,30] +NM_999996.1 AAAATCAAAATGAAATCGAAAGCGTTTCGCGCGAAATAGAGGAGGCAGTTTCGC 9 39 [10,25,30] +NM_999995.1 AAAATCAAAATGAAAAAATCGAAAGCGTTTCGCGCGAAATAGAGGAGGCAGTTTCGC 9 42 [10,25,30] +NM_999994.1 AAAATCAAAATGAAAAAAAAATCGAAAGCGTTTCGCGCGAAATAGAGGAGGCAGTTTCGC 9 45 [10,25,30] +NM_999993.1 AAAATCAAAATGGGGAGGGCCCGGCAGCCAGCTTTATAGAGGAGGCAGTTTCGC 9 39 [10,25,30] +NM_999992.1 AAAATCAAAATGGGGTAGGCCCGGCAGCCAGCTTTATAGAGGAGGCAGTTTCGC 9 39 [10,25,30] +NM_999992.2 AAAATCAAAATGGGGTAGGCCCGGCAGCCAGCTTTATAGAGGAGGCAGTTTCGCC 9 40 [10,25,30] diff --git a/tests/support/mock_input_source.py b/tests/support/mock_input_source.py index 90a2dd6a..b646f809 100644 --- a/tests/support/mock_input_source.py +++ b/tests/support/mock_input_source.py @@ -31,7 +31,7 @@ def fetch_transcript_info(self, ac): result = None data = self._mock_data.get(ac) if data: # interbase coordinates - result = {"cds_start_i": data["cds_start_i"], "cds_end_i": data["cds_end_i"]} + result = {"cds_start_i": data["cds_start_i"], "cds_end_i": data["cds_end_i"], "lengths": data["lengths"]} return result def get_tx_identity_info(self, ac): @@ -72,6 +72,7 @@ def _read_input(self, in_file): "transcript_sequence": row["transcript_sequence"], "cds_start_i": int(row["cds_start_i"]), "cds_end_i": int(row["cds_end_i"]), + "lengths": eval(row["lengths"]), } return result diff --git a/tests/test_hgvs_assemblymapper.py b/tests/test_hgvs_assemblymapper.py index 460ea299..76cc8291 100644 --- a/tests/test_hgvs_assemblymapper.py +++ b/tests/test_hgvs_assemblymapper.py @@ -198,6 +198,57 @@ def test_c_to_p_with_stop_gain(self): self.assertEqual(str(var_p), hgvs_p) + def test_map_of_dup_intron_exon_boundary(self): + hgvs_c = "NM_004380.2:c.3251-1dup" + hgvs_p = "NP_004371.2:p.(Ile1084SerfsTer3)" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + + hgvs_c = "NM_024529.4:c.132-2_132-1dup" + hgvs_p = "NP_078805.3:p.(Thr45GlyfsTer65)" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + + hgvs_c = "NM_004985.4:c.112-1_113dup" + hgvs_p = "NP_004976.2:p.(Glu37dup)" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + + hgvs_c = "NM_004380.2:c.3251dup" + hgvs_p = "NP_004371.2:p.(Phe1085LeufsTer2)" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + + def test_map_of_dup_exon_intron_boundary(self): + hgvs_c = "NM_021140.3:c.619dup" + hgvs_p = "NP_066963.2:p.(Ile207AsnfsTer10)" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + + def test_map_of_dup_intron_exon_boundary_rc(self): + hgvs_c = "NM_004985.4:c.112-1_112dup" + hgvs_p = "NP_004976.2:p.(Asp38GlyfsTer8)" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + class Test_RefReplacement(unittest.TestCase): test_cases = [ diff --git a/tests/test_hgvs_variantmapper.py b/tests/test_hgvs_variantmapper.py index df90a0a9..6c650760 100644 --- a/tests/test_hgvs_variantmapper.py +++ b/tests/test_hgvs_variantmapper.py @@ -128,6 +128,51 @@ def test_map_of_dup_three_prime_utr(self): var_p = self.vm.c_to_p(var_c) self.assertEqual(str(var_p), "NP_694955.2:p.?") + def test_map_of_ins_intron_exon_boundary(self): + hgvs_c = "NM_004380.2:c.3251-1_3251insA" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.vm.c_to_p(var_c) + self.assertEqual(str(var_p), "NP_004371.2:p.(Ile1084AsnfsTer3)") + + def test_map_of_ins_exon_intron_boundary(self): + hgvs_c = "NM_004380.2:c.3250_3250+1insT" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.vm.c_to_p(var_c) + self.assertEqual(str(var_p), "NP_004371.2:p.(Phe1085LeufsTer2)") + + def test_map_of_dup_intron_exon_boundary(self): + hgvs_c = "NM_004380.2:c.3251-1dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + with self.assertRaises(HGVSError): + var_p = self.vm.c_to_p(var_c) + + hgvs_c = "NM_024529.4:c.132-2_132-1dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + with self.assertRaises(HGVSError): + var_p = self.vm.c_to_p(var_c) + + hgvs_c = "NM_004985.4:c.112-1_113dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + with self.assertRaises(HGVSError): + var_p = self.vm.c_to_p(var_c) + + hgvs_c = "NM_004380.2:c.3251dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.vm.c_to_p(var_c) + self.assertEqual(str(var_p), "NP_004371.2:p.(Phe1085LeufsTer2)") + + def test_map_of_dup_exon_intron_boundary(self): + hgvs_c = "NM_021140.3:c.619dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.vm.c_to_p(var_c) + self.assertEqual(str(var_p), "NP_066963.2:p.(Ile207AsnfsTer10)") + + def test_map_of_dup_intron_exon_boundary_rc(self): + hgvs_c = "NM_004985.4:c.112-1_112dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + with self.assertRaises(HGVSError): + var_p = self.vm.c_to_p(var_c) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_hgvs_variantmapper_cp_altseqbuilder.py b/tests/test_hgvs_variantmapper_cp_altseqbuilder.py index 21f16cb0..f3657ea4 100644 --- a/tests/test_hgvs_variantmapper_cp_altseqbuilder.py +++ b/tests/test_hgvs_variantmapper_cp_altseqbuilder.py @@ -116,6 +116,7 @@ def test_sequence_with_length_that_is_not_divisible_by_3(self): def _run_comparison(self, hgvsc, expected_sequence): ac_p = "DUMMY" var = self._parser.parse_hgvs_variant(hgvsc) + var.fill_ref(self._datasource) transcript_data = RefTranscriptData(hdp=self._datasource, tx_ac=var.ac, pro_ac=ac_p) builder = altseqbuilder.AltSeqBuilder(var, transcript_data) diff --git a/tests/test_hgvs_variantmapper_cp_sanity.py b/tests/test_hgvs_variantmapper_cp_sanity.py index 23e085db..13df198c 100644 --- a/tests/test_hgvs_variantmapper_cp_sanity.py +++ b/tests/test_hgvs_variantmapper_cp_sanity.py @@ -9,6 +9,7 @@ import hgvs.parser import hgvs.variantmapper as variantmapper +from hgvs.exceptions import HGVSError class TestHgvsCToP(unittest.TestCase): @@ -152,6 +153,27 @@ def test_intron(self): hgvsp_expected = "MOCK:p.?" self._run_conversion(hgvsc, hgvsp_expected) + def test_ins_intron_exon_boundary(self): + hgvsc = "NM_999999.1:c.9-1_9insG" + hgvsp_expected = "MOCK:p.(Lys4GlufsTer?)" + self._run_conversion(hgvsc, hgvsp_expected) + + def test_ins_exon_intron_boundary(self): + hgvsc = "NM_999999.1:c.39_39+1insC" + hgvsp_expected = "MOCK:p.(=)" + self._run_conversion(hgvsc, hgvsp_expected) + + def test_dup_intron_exon_boundary(self): + hgvsc = "NM_999999.1:c.9-1dup" + hgvsp_expected = "MOCK:p.?" + with self.assertRaises(HGVSError): + self._run_conversion(hgvsc, hgvsp_expected) + + def test_dup_exon_intron_boundary(self): + hgvsc = "NM_999999.1:c.39+1dup" + hgvsp_expected = "MOCK:p.?" + self._run_conversion(hgvsc, hgvsp_expected) + def test_five_prime_utr(self): hgvsc = "NM_999999.1:c.-2A>G" hgvsp_expected = "MOCK:p.?" From 310645c6a72b562b5b299c6e75d6ad3e2aa25f73 Mon Sep 17 00:00:00 2001 From: Brendan ODonnell Date: Tue, 19 Dec 2023 10:23:44 -0500 Subject: [PATCH 2/6] Update boundary conditions --- src/hgvs/utils/altseqbuilder.py | 5 +++- tests/test_hgvs_assemblymapper.py | 27 ++++++++-------------- tests/test_hgvs_variantmapper.py | 22 ++++++++---------- tests/test_hgvs_variantmapper_cp_sanity.py | 3 ++- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/hgvs/utils/altseqbuilder.py b/src/hgvs/utils/altseqbuilder.py index 5c79910b..16133de4 100644 --- a/src/hgvs/utils/altseqbuilder.py +++ b/src/hgvs/utils/altseqbuilder.py @@ -203,9 +203,12 @@ def _get_variant_region(self): elif self._var_c.posedit.edit.type == "ins" and self._var_c.posedit.pos.start.offset == 0 and self._var_c.posedit.pos.end.offset == 1: # ins at exon-intron boundary result = self.EXON - elif self._var_c.posedit.edit.type == "dup" and self._var_c.posedit.pos.start.offset <= -1 and self._var_c.posedit.pos.end.offset >= -1: + elif self._var_c.posedit.edit.type == "dup" and self._var_c.posedit.pos.end.offset == -1: # dup at intron-exon boundary result = self.EXON + elif self._var_c.posedit.edit.type == "dup" and self._var_c.posedit.pos.start.offset == 1: + # dup at exon-intron boundary + result = self.EXON elif self._var_c.posedit.pos.start.offset != 0 or self._var_c.posedit.pos.end.offset != 0: # leave out anything else intronic for now result = self.INTRON diff --git a/tests/test_hgvs_assemblymapper.py b/tests/test_hgvs_assemblymapper.py index 76cc8291..f7361c67 100644 --- a/tests/test_hgvs_assemblymapper.py +++ b/tests/test_hgvs_assemblymapper.py @@ -199,14 +199,6 @@ def test_c_to_p_with_stop_gain(self): self.assertEqual(str(var_p), hgvs_p) def test_map_of_dup_intron_exon_boundary(self): - hgvs_c = "NM_004380.2:c.3251-1dup" - hgvs_p = "NP_004371.2:p.(Ile1084SerfsTer3)" - - var_c = self.hp.parse_hgvs_variant(hgvs_c) - var_p = self.am.c_to_p(var_c) - - self.assertEqual(str(var_p), hgvs_p) - hgvs_c = "NM_024529.4:c.132-2_132-1dup" hgvs_p = "NP_078805.3:p.(Thr45GlyfsTer65)" @@ -215,16 +207,17 @@ def test_map_of_dup_intron_exon_boundary(self): self.assertEqual(str(var_p), hgvs_p) - hgvs_c = "NM_004985.4:c.112-1_113dup" - hgvs_p = "NP_004976.2:p.(Glu37dup)" + hgvs_c = "NM_004985.4:c.112-8_112-1dup" + hgvs_p = "NP_004976.2:p.(Asp38LeufsTer10)" var_c = self.hp.parse_hgvs_variant(hgvs_c) var_p = self.am.c_to_p(var_c) self.assertEqual(str(var_p), hgvs_p) - hgvs_c = "NM_004380.2:c.3251dup" - hgvs_p = "NP_004371.2:p.(Phe1085LeufsTer2)" + def test_map_of_dup_intron_exon_boundary_rc(self): + hgvs_c = "NM_004380.2:c.3251-1dup" + hgvs_p = "NP_004371.2:p.(Ile1084SerfsTer3)" var_c = self.hp.parse_hgvs_variant(hgvs_c) var_p = self.am.c_to_p(var_c) @@ -232,17 +225,17 @@ def test_map_of_dup_intron_exon_boundary(self): self.assertEqual(str(var_p), hgvs_p) def test_map_of_dup_exon_intron_boundary(self): - hgvs_c = "NM_021140.3:c.619dup" - hgvs_p = "NP_066963.2:p.(Ile207AsnfsTer10)" + hgvs_c = "NM_024529.4:c.131+1_131+3dup" + hgvs_p = "NP_078805.3:p.(Gly44_Thr45insVal)" var_c = self.hp.parse_hgvs_variant(hgvs_c) var_p = self.am.c_to_p(var_c) self.assertEqual(str(var_p), hgvs_p) - def test_map_of_dup_intron_exon_boundary_rc(self): - hgvs_c = "NM_004985.4:c.112-1_112dup" - hgvs_p = "NP_004976.2:p.(Asp38GlyfsTer8)" + def test_map_of_dup_exon_intron_boundary_rc(self): + hgvs_c = "NM_004985.4:c.111+1_111+4dup" + hgvs_p = "NP_004976.2:p.(Asp38GlyfsTer11)" var_c = self.hp.parse_hgvs_variant(hgvs_c) var_p = self.am.c_to_p(var_c) diff --git a/tests/test_hgvs_variantmapper.py b/tests/test_hgvs_variantmapper.py index 6c650760..196270da 100644 --- a/tests/test_hgvs_variantmapper.py +++ b/tests/test_hgvs_variantmapper.py @@ -141,34 +141,30 @@ def test_map_of_ins_exon_intron_boundary(self): self.assertEqual(str(var_p), "NP_004371.2:p.(Phe1085LeufsTer2)") def test_map_of_dup_intron_exon_boundary(self): - hgvs_c = "NM_004380.2:c.3251-1dup" + hgvs_c = "NM_024529.4:c.132-2_132-1dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) with self.assertRaises(HGVSError): var_p = self.vm.c_to_p(var_c) - hgvs_c = "NM_024529.4:c.132-2_132-1dup" + hgvs_c = "NM_004985.4:c.112-8_112-1dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) with self.assertRaises(HGVSError): var_p = self.vm.c_to_p(var_c) - hgvs_c = "NM_004985.4:c.112-1_113dup" + def test_map_of_dup_intron_exon_boundary_rc(self): + hgvs_c = "NM_004380.2:c.3251-1dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) with self.assertRaises(HGVSError): var_p = self.vm.c_to_p(var_c) - hgvs_c = "NM_004380.2:c.3251dup" - var_c = self.hp.parse_hgvs_variant(hgvs_c) - var_p = self.vm.c_to_p(var_c) - self.assertEqual(str(var_p), "NP_004371.2:p.(Phe1085LeufsTer2)") - def test_map_of_dup_exon_intron_boundary(self): - hgvs_c = "NM_021140.3:c.619dup" + hgvs_c = "NM_024529.4:c.131+1_131+3dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) - var_p = self.vm.c_to_p(var_c) - self.assertEqual(str(var_p), "NP_066963.2:p.(Ile207AsnfsTer10)") + with self.assertRaises(HGVSError): + var_p = self.vm.c_to_p(var_c) - def test_map_of_dup_intron_exon_boundary_rc(self): - hgvs_c = "NM_004985.4:c.112-1_112dup" + def test_map_of_dup_exon_intron_boundary_rc(self): + hgvs_c = "NM_004985.4:c.111+1_111+4dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) with self.assertRaises(HGVSError): var_p = self.vm.c_to_p(var_c) diff --git a/tests/test_hgvs_variantmapper_cp_sanity.py b/tests/test_hgvs_variantmapper_cp_sanity.py index 13df198c..7a5cbbfb 100644 --- a/tests/test_hgvs_variantmapper_cp_sanity.py +++ b/tests/test_hgvs_variantmapper_cp_sanity.py @@ -172,7 +172,8 @@ def test_dup_intron_exon_boundary(self): def test_dup_exon_intron_boundary(self): hgvsc = "NM_999999.1:c.39+1dup" hgvsp_expected = "MOCK:p.?" - self._run_conversion(hgvsc, hgvsp_expected) + with self.assertRaises(HGVSError): + self._run_conversion(hgvsc, hgvsp_expected) def test_five_prime_utr(self): hgvsc = "NM_999999.1:c.-2A>G" From 6910a57a921bec78ed4fea482a60a8ee2ec75b67 Mon Sep 17 00:00:00 2001 From: Brendan ODonnell Date: Wed, 20 Dec 2023 11:29:19 -0500 Subject: [PATCH 3/6] Fix tests --- src/hgvs/utils/altseqbuilder.py | 6 +++--- tests/test_hgvs_assemblymapper.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hgvs/utils/altseqbuilder.py b/src/hgvs/utils/altseqbuilder.py index 16133de4..f1f012f5 100644 --- a/src/hgvs/utils/altseqbuilder.py +++ b/src/hgvs/utils/altseqbuilder.py @@ -345,10 +345,10 @@ def _setup_incorporate(self): if pos.base < 0: # 5' UTR result = cds_start - 1 else: # cds/intron - if pos.offset <= 0: - result = (cds_start - 1) + pos.base + pos.offset - 1 + if pos.offset < 0: + result = (cds_start - 1) + pos.base - 2 else: - result = (cds_start - 1) + pos.base + result = (cds_start - 1) + pos.base - 1 elif pos.datum == Datum.CDS_END: # 3' UTR result = cds_stop + pos.base - 1 else: diff --git a/tests/test_hgvs_assemblymapper.py b/tests/test_hgvs_assemblymapper.py index f7361c67..6f747acd 100644 --- a/tests/test_hgvs_assemblymapper.py +++ b/tests/test_hgvs_assemblymapper.py @@ -226,7 +226,7 @@ def test_map_of_dup_intron_exon_boundary_rc(self): def test_map_of_dup_exon_intron_boundary(self): hgvs_c = "NM_024529.4:c.131+1_131+3dup" - hgvs_p = "NP_078805.3:p.(Gly44_Thr45insVal)" + hgvs_p = "NP_078805.3:p.(Thr45Ter)" var_c = self.hp.parse_hgvs_variant(hgvs_c) var_p = self.am.c_to_p(var_c) @@ -235,7 +235,7 @@ def test_map_of_dup_exon_intron_boundary(self): def test_map_of_dup_exon_intron_boundary_rc(self): hgvs_c = "NM_004985.4:c.111+1_111+4dup" - hgvs_p = "NP_004976.2:p.(Asp38GlyfsTer11)" + hgvs_p = "NP_004976.2:p.(Asp38ValfsTer11)" var_c = self.hp.parse_hgvs_variant(hgvs_c) var_p = self.am.c_to_p(var_c) From be0d6304aef1813ec81561a3f336db96819ebfef Mon Sep 17 00:00:00 2001 From: Brendan ODonnell Date: Wed, 20 Dec 2023 15:19:01 -0500 Subject: [PATCH 4/6] Add tests for dups spanning boundary --- tests/test_hgvs_assemblymapper.py | 16 ++++++++++++++++ tests/test_hgvs_variantmapper.py | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/test_hgvs_assemblymapper.py b/tests/test_hgvs_assemblymapper.py index 6f747acd..5456eb75 100644 --- a/tests/test_hgvs_assemblymapper.py +++ b/tests/test_hgvs_assemblymapper.py @@ -199,6 +199,14 @@ def test_c_to_p_with_stop_gain(self): self.assertEqual(str(var_p), hgvs_p) def test_map_of_dup_intron_exon_boundary(self): + hgvs_c = "NM_024529.4:c.132-1_132dup" + hgvs_p = "NP_078805.3:p.?" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + hgvs_c = "NM_024529.4:c.132-2_132-1dup" hgvs_p = "NP_078805.3:p.(Thr45GlyfsTer65)" @@ -225,6 +233,14 @@ def test_map_of_dup_intron_exon_boundary_rc(self): self.assertEqual(str(var_p), hgvs_p) def test_map_of_dup_exon_intron_boundary(self): + hgvs_c = "NM_024529.4:c.130_131+1dup" + hgvs_p = "NP_078805.3:p.?" + + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.am.c_to_p(var_c) + + self.assertEqual(str(var_p), hgvs_p) + hgvs_c = "NM_024529.4:c.131+1_131+3dup" hgvs_p = "NP_078805.3:p.(Thr45Ter)" diff --git a/tests/test_hgvs_variantmapper.py b/tests/test_hgvs_variantmapper.py index 196270da..24cb3c47 100644 --- a/tests/test_hgvs_variantmapper.py +++ b/tests/test_hgvs_variantmapper.py @@ -141,6 +141,11 @@ def test_map_of_ins_exon_intron_boundary(self): self.assertEqual(str(var_p), "NP_004371.2:p.(Phe1085LeufsTer2)") def test_map_of_dup_intron_exon_boundary(self): + hgvs_c = "NM_024529.4:c.132-1_132dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.vm.c_to_p(var_c) + self.assertEqual(str(var_p), "NP_078805.3:p.?") + hgvs_c = "NM_024529.4:c.132-2_132-1dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) with self.assertRaises(HGVSError): @@ -158,6 +163,11 @@ def test_map_of_dup_intron_exon_boundary_rc(self): var_p = self.vm.c_to_p(var_c) def test_map_of_dup_exon_intron_boundary(self): + hgvs_c = "NM_024529.4:c.130_131+1dup" + var_c = self.hp.parse_hgvs_variant(hgvs_c) + var_p = self.vm.c_to_p(var_c) + self.assertEqual(str(var_p), "NP_078805.3:p.?") + hgvs_c = "NM_024529.4:c.131+1_131+3dup" var_c = self.hp.parse_hgvs_variant(hgvs_c) with self.assertRaises(HGVSError): From dd47394fd0e2314460615b7f215992383f459cb7 Mon Sep 17 00:00:00 2001 From: Brendan ODonnell Date: Tue, 2 Jan 2024 14:22:03 -0500 Subject: [PATCH 5/6] Remove new getattr references --- src/hgvs/assemblymapper.py | 33 +++++++++++++++++---------------- src/hgvs/variantmapper.py | 19 ++++++++++++------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/hgvs/assemblymapper.py b/src/hgvs/assemblymapper.py index d5785cee..28984c30 100644 --- a/src/hgvs/assemblymapper.py +++ b/src/hgvs/assemblymapper.py @@ -242,22 +242,23 @@ def _alt_ac_for_tx_ac(self, tx_ac): return alt_acs[0] def _replace_reference(self, var): - if (getattr(var.posedit.pos.start, 'offset', 0) != 0 or getattr(var.posedit.pos.end, 'offset', 0) != 0): - if var.type != 'g': - alt_ac = self._alt_ac_for_tx_ac(var.ac) - if var.type == 'c': - var_g = super(AssemblyMapper, self).c_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) - if var.type == 'n': - var_g = super(AssemblyMapper, self).n_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) - if var.type == 't': - var_g = super(AssemblyMapper, self).t_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) - super(AssemblyMapper, self)._replace_reference(var_g) - ref = var_g.posedit.edit.ref - if self._fetch_AlignmentMapper(tx_ac=var.ac).strand == -1: - ref = reverse_complement(ref) - var.posedit.edit.ref = ref - _logger.debug("Replaced reference sequence in {var} with {ref}".format(var=var, ref=var_g.posedit.edit.ref)) - return var + if ( + var.type in "cn" + and var.posedit.pos is not None + and (var.posedit.pos.start.offset != 0 or var.posedit.pos.end.offset != 0) + ): + alt_ac = self._alt_ac_for_tx_ac(var.ac) + if var.type == 'c': + var_g = super(AssemblyMapper, self).c_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) + if var.type == 'n': + var_g = super(AssemblyMapper, self).n_to_g(var, alt_ac, alt_aln_method=self.alt_aln_method) + super(AssemblyMapper, self)._replace_reference(var_g) + ref = var_g.posedit.edit.ref + if self._fetch_AlignmentMapper(tx_ac=var.ac).strand == -1: + ref = reverse_complement(ref) + var.posedit.edit.ref = ref + _logger.debug("Replaced reference sequence in {var} with {ref}".format(var=var, ref=var_g.posedit.edit.ref)) + return var return super(AssemblyMapper, self)._replace_reference(var) diff --git a/src/hgvs/variantmapper.py b/src/hgvs/variantmapper.py index 5e2cb038..08a11149 100644 --- a/src/hgvs/variantmapper.py +++ b/src/hgvs/variantmapper.py @@ -466,11 +466,13 @@ def _replace_reference(self, var): # these types have no reference sequence (zero-width), so return as-is return var - pos = var.posedit.pos - if (getattr(pos.start, 'offset', 0) != 0 or getattr(pos.end, 'offset', 0) != 0): - if var.type != "g": - _logger.info("Can't update reference sequence for intronic variant {}".format(var)) - return var + if ( + var.type in "cnr" + and var.posedit.pos is not None + and (var.posedit.pos.start.offset != 0 or var.posedit.pos.end.offset != 0) + ): + _logger.info("Can't update reference sequence for intronic variant {}".format(var)) + return var # For c. variants, we need coords on underlying sequences if var.type == "c": @@ -481,8 +483,11 @@ def _replace_reference(self, var): else: pos = var.posedit.pos - seq_start = pos.start.base + getattr(pos.start, 'offset', 0) - 1 - seq_end = pos.end.base + getattr(pos.end, 'offset', 0) + seq_start = pos.start.base - 1 + seq_end = pos.end.base + if var.type in "cnr": + seq_start += pos.start.offset + seq_end += pos.end.offset # When strict_bounds is False and an error occurs, return # variant as-is From 9c1539fc39a0b869916c80169696db1bd6a2ff7a Mon Sep 17 00:00:00 2001 From: Brendan ODonnell Date: Tue, 4 Jun 2024 14:10:25 -0400 Subject: [PATCH 6/6] Update test cache --- tests/data/cache-py3.hdp | Bin 935293 -> 975929 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/cache-py3.hdp b/tests/data/cache-py3.hdp index 1fbcd59c041e84765b66dd4fbb7838a56777cfac..454654af0a4eda6cbc1ed010f361136c5e098ec0 100644 GIT binary patch delta 36751 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