From e0a77091610f3a6327ca41a2ef13788577b6463d Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Wed, 6 Dec 2023 04:18:22 -0800 Subject: [PATCH] fix: hgvs/free text dup/del end positions for definite ranges (#525) standard residue -> interresidue conversion: (a_b)_(c_d)-> start: models.Range([a-1, b-1]), end: models.Range([c,d]) --- tests/conftest.py | 16 +++- tests/fixtures/translators.yml | 6 +- tests/test_hgvs_dup_del_mode.py | 56 ++++--------- .../test_hgvs_to_copy_number.py | 82 ++++++------------- .../test_parsed_to_copy_number.py | 8 +- variation/to_copy_number_variation.py | 2 +- .../translators/ambiguous_translator_base.py | 2 +- variation/version.py | 2 +- 8 files changed, 63 insertions(+), 111 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d560a2b2..50a2da80 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -291,13 +291,13 @@ def genomic_dup2_38_cn(genomic_dup2_seq_loc_normalized): def genomic_del3_dup3_loc_not_normalized(): """Create genomic del3 dup3 sequence location""" return { - "id": "ga4gh:SL.NbjuicGHnxcERd1t0hGkzlw93YX1p3Y7", + "id": "ga4gh:SL.-zCp7JBaKQ0niPDueJkuCgQhRIQ50hKw", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP", }, "start": [31060226, 31100350], - "end": [33274279, 33417152], + "end": [33274278, 33417151], "type": "SequenceLocation", } @@ -536,6 +536,18 @@ def prpf8_amplification(prpf8_ncbi_seq_loc): return models.CopyNumberChange(**params) +@pytest.fixture(scope="module") +def genomic_del3_dup3_cn_38(genomic_del3_dup3_loc_not_normalized): + """Create test fixture copy number variation for del/dup 3 on GRCh38""" + params = { + "type": "CopyNumberCount", + "id": "ga4gh:CN.rPsK0krAHgmXhDZEw4fqymR0iDQa3UCJ", + "location": genomic_del3_dup3_loc_not_normalized, + "copies": 2, + } + return models.CopyNumberCount(**params) + + def assertion_checks(normalize_response, test_variation): """Check that normalize_response and test_variation are equal.""" actual = normalize_response.variation.model_dump(exclude_none=True) diff --git a/tests/fixtures/translators.yml b/tests/fixtures/translators.yml index 98009906..802a2446 100644 --- a/tests/fixtures/translators.yml +++ b/tests/fixtures/translators.yml @@ -1400,11 +1400,11 @@ genomic_duplication_ambiguous: variations: [ { - "id": "ga4gh:CX.Gq5d-uH01bgf7m703dOSfSp_29wFqpsb", + "id": "ga4gh:CX.gsV4KrWvNQ_c0UT8M31mqa0HJ-IAHL8q", "type": "CopyNumberChange", "location": { - "id": "ga4gh:SL.NbjuicGHnxcERd1t0hGkzlw93YX1p3Y7", + "id": "ga4gh:SL.-zCp7JBaKQ0niPDueJkuCgQhRIQ50hKw", "type": "SequenceLocation", "sequenceReference": { @@ -1412,7 +1412,7 @@ genomic_duplication_ambiguous: "refgetAccession": "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP", }, "start": [31060226, 31100350], - "end": [33274279, 33417152], + "end": [33274278, 33417151], }, "copyChange": "efo:0030070", }, diff --git a/tests/test_hgvs_dup_del_mode.py b/tests/test_hgvs_dup_del_mode.py index e6f76319..576b9b88 100644 --- a/tests/test_hgvs_dup_del_mode.py +++ b/tests/test_hgvs_dup_del_mode.py @@ -204,24 +204,12 @@ def genomic_dup2_free_text_cn(genomic_dup2_free_text_seq_loc): return models.CopyNumberCount(**params) -@pytest.fixture(scope="module") -def genomic_dup3_cn(genomic_del3_dup3_loc_not_normalized): - """Create a test fixture for genomic dup copy number count.""" - params = { - "type": "CopyNumberCount", - "id": "ga4gh:CN.Ka-Wsibx4GHmHgurHCdk0W4deqZt26y4", - "location": genomic_del3_dup3_loc_not_normalized, - "copies": 2, - } - return models.CopyNumberCount(**params) - - @pytest.fixture(scope="module") def genomic_dup3_cx(genomic_del3_dup3_loc_not_normalized): """Create a test fixture for genomic dup copy number change.""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.Gq5d-uH01bgf7m703dOSfSp_29wFqpsb", + "id": "ga4gh:CX.gsV4KrWvNQ_c0UT8M31mqa0HJ-IAHL8q", "location": genomic_del3_dup3_loc_not_normalized, "copyChange": "efo:0030070", } @@ -232,13 +220,13 @@ def genomic_dup3_cx(genomic_del3_dup3_loc_not_normalized): def genomic_dup3_free_text_subject(): """Create test fixture for genomic dup3 free text location""" return { - "id": "ga4gh:SL.h-0akgFon48yzCHKVdRwU5ImXRux4huN", + "id": "ga4gh:SL.OFGMAP2dUKRbBk5Q3MroJzbvcjEJQfyZ", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP", }, "start": [31147273, 31147277], - "end": [31182738, 31182740], + "end": [31182737, 31182739], "type": "SequenceLocation", } @@ -248,7 +236,7 @@ def genomic_dup3_free_text_cx(genomic_dup3_free_text_subject): """Create a test fixture for genomic dup copy number change.""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.aJYe9oIrxrwI0VTkEnvC0fKIT5x-vIne", + "id": "ga4gh:CX.qf8-7kAverUttRlwQBXFPeuVq5o2-bVa", "location": genomic_dup3_free_text_subject, "copyChange": "efo:0030070", } @@ -260,7 +248,7 @@ def genomic_dup3_free_text_cn(genomic_dup3_free_text_subject): """Create a test fixture for genomic dup copy number count.""" params = { "type": "CopyNumberCount", - "id": "ga4gh:CN.KXh3T716GG9uGwq9ejGmflT_67fNN8n3", + "id": "ga4gh:CN.3yNGNFGVAO5DGc0sPTThdUwTfJLPyWfM", "location": genomic_dup3_free_text_subject, "copies": 4, } @@ -594,36 +582,24 @@ def genomic_del3_cx(genomic_del3_dup3_loc_not_normalized): """Create a test fixture for genomic del copy number change.""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.h9krodUtVK--XmizkspBdOrptRNqrDHm", + "id": "ga4gh:CX.BWTPMUku6nwuWhULJogKyxEk64XDIYGm", "location": genomic_del3_dup3_loc_not_normalized, "copyChange": "efo:0030067", } return models.CopyNumberChange(**params) -@pytest.fixture(scope="module") -def genomic_del3_cn(genomic_del3_dup3_loc_not_normalized): - """Create a test fixture for genomic del copy number count.""" - params = { - "type": "CopyNumberCount", - "id": "ga4gh:CN.Ka-Wsibx4GHmHgurHCdk0W4deqZt26y4", - "location": genomic_del3_dup3_loc_not_normalized, - "copies": 2, - } - return models.CopyNumberCount(**params) - - @pytest.fixture(scope="module") def genomic_del3_free_text_subject(): """Create test fixture for genomic del3 free text location""" return { - "id": "ga4gh:SL.HMjpquCLV9iYib972N0_3tn9TvnevIga", + "id": "ga4gh:SL.5_TZXeJhFejft3jmfkqdNutVO2tenSeB", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP", }, "start": [68839264, 68839267], - "end": [68841121, 68841126], + "end": [68841120, 68841125], "type": "SequenceLocation", } @@ -633,7 +609,7 @@ def genomic_del3_free_text_cx(genomic_del3_free_text_subject): """Create a test fixture for genomic del copy number change.""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.qHYL7nZaKXl-h9D7d4JvEmXDZtfV9M2A", + "id": "ga4gh:CX.SS7Ywi8yq2fb7acAdbs1a-H6ELw4QxLy", "location": genomic_del3_free_text_subject, "copyChange": "efo:0030067", } @@ -645,7 +621,7 @@ def genomic_del3_free_text_cn(genomic_del3_free_text_subject): """Create a test fixture for genomic del copy number count.""" params = { "type": "CopyNumberCount", - "id": "ga4gh:CN.ESfNw8Y3Vil7UpRiV2UY-zfMoZNRchA3", + "id": "ga4gh:CN.nYr-z9MXHGx8p3hP0Wht3WDw0gju9QDL", "location": genomic_del3_free_text_subject, "copies": 2, } @@ -970,7 +946,7 @@ async def test_genomic_dup2( async def test_genomic_dup3( test_handler, genomic_dup3_cx, - genomic_dup3_cn, + genomic_del3_dup3_cn_38, genomic_dup3_free_text_cn, genomic_dup3_free_text_cx, ): @@ -982,7 +958,7 @@ async def test_genomic_dup3( resp = await test_handler.normalize( q, HGVSDupDelModeOption.COPY_NUMBER_COUNT, baseline_copies=1 ) - assertion_checks(resp, genomic_dup3_cn) + assertion_checks(resp, genomic_del3_dup3_cn_38) resp = await test_handler.normalize( q, @@ -1001,7 +977,7 @@ async def test_genomic_dup3( resp = await test_handler.normalize( q, HGVSDupDelModeOption.COPY_NUMBER_COUNT, baseline_copies=1 ) - assertion_checks(resp, genomic_dup3_cn) + assertion_checks(resp, genomic_del3_dup3_cn_38) resp = await test_handler.normalize(q, HGVSDupDelModeOption.COPY_NUMBER_CHANGE) assertion_checks(resp, genomic_dup3_cx) @@ -1383,7 +1359,7 @@ async def test_genomic_del2( @pytest.mark.asyncio async def test_genomic_del3( test_handler, - genomic_del3_cn, + genomic_del3_dup3_cn_38, genomic_del3_cx, genomic_del3_free_text_cn, genomic_del3_free_text_cx, @@ -1396,7 +1372,7 @@ async def test_genomic_del3( resp = await test_handler.normalize( q, HGVSDupDelModeOption.COPY_NUMBER_COUNT, baseline_copies=3 ) - assertion_checks(resp, genomic_del3_cn) + assertion_checks(resp, genomic_del3_dup3_cn_38) resp = await test_handler.normalize(q, HGVSDupDelModeOption.COPY_NUMBER_CHANGE) assertion_checks(resp, genomic_del3_cx) @@ -1411,7 +1387,7 @@ async def test_genomic_del3( resp = await test_handler.normalize( q, HGVSDupDelModeOption.COPY_NUMBER_COUNT, baseline_copies=3 ) - assertion_checks(resp, genomic_del3_cn) + assertion_checks(resp, genomic_del3_dup3_cn_38) resp = await test_handler.normalize(q, HGVSDupDelModeOption.COPY_NUMBER_CHANGE) assertion_checks(resp, genomic_del3_cx) diff --git a/tests/to_copy_number_variation/test_hgvs_to_copy_number.py b/tests/to_copy_number_variation/test_hgvs_to_copy_number.py index f14f4cc1..3e950cbc 100644 --- a/tests/to_copy_number_variation/test_hgvs_to_copy_number.py +++ b/tests/to_copy_number_variation/test_hgvs_to_copy_number.py @@ -109,24 +109,12 @@ def genomic_dup2_cx_37(genomic_dup2_37_loc): return models.CopyNumberChange(**params) -@pytest.fixture(scope="module") -def genomic_dup3_cn_38(genomic_del3_dup3_loc_not_normalized): - """Create test fixture copy number count variation""" - params = { - "type": "CopyNumberCount", - "id": "ga4gh:CN.Ka-Wsibx4GHmHgurHCdk0W4deqZt26y4", - "location": genomic_del3_dup3_loc_not_normalized, - "copies": 2, - } - return models.CopyNumberCount(**params) - - @pytest.fixture(scope="module") def genomic_dup3_cx_38(genomic_del3_dup3_loc_not_normalized): """Create test fixture copy number change variation""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.VDYux3JxzXmKQU5Ll4Knt6Y4RW5UMUbJ", + "id": "ga4gh:CX.3CEaG1qP1k9AU_aae_iIUx4uaTb72N1R", "location": genomic_del3_dup3_loc_not_normalized, "copyChange": "efo:0030072", } @@ -137,23 +125,23 @@ def genomic_dup3_cx_38(genomic_del3_dup3_loc_not_normalized): def genomic_dup3_37_loc(): """Create test fixture GRCh37 duplication location""" return { - "id": "ga4gh:SL.8aHcfpqhpWQ0kigsdn9mY61Alod63tDP", + "id": "ga4gh:SL.xDN-t4g0hLgYTKyh3_88Drln1HdishyF", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.v7noePfnNpK8ghYXEqZ9NukMXW7YeNsm", }, "start": [31078343, 31118467], - "end": [33292396, 33435269], + "end": [33292395, 33435268], "type": "SequenceLocation", } @pytest.fixture(scope="module") -def genomic_dup3_cn_37(genomic_dup3_37_loc): - """Create test fixture copy number count variation""" +def genomic_del3_dup3_cn_37(genomic_dup3_37_loc): + """Create test fixture copy number variation for del/dup 3 on GRCh37""" params = { "type": "CopyNumberCount", - "id": "ga4gh:CN.gOq-IVcswisgIfsx9PpZ-BLxzcMVeJeR", + "id": "ga4gh:CN.TFwnv4Lv7f2ZvboyqSBygWbC57QzuQ8R", "location": genomic_dup3_37_loc, "copies": 2, } @@ -165,7 +153,7 @@ def genomic_dup3_cx_37(genomic_dup3_37_loc): """Create test fixture copy number change variation""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.8UZ5UZWGhsRU293nzuMfIpqG_6NBzHbs", + "id": "ga4gh:CX.A9Z_PAgta0MwukOSjrntOneG9N66_FED", "location": genomic_dup3_37_loc, "copyChange": "efo:0030072", } @@ -463,24 +451,12 @@ def genomic_del2_cx_37(genomic_del2_37_loc): return models.CopyNumberChange(**params) -@pytest.fixture(scope="module") -def genomic_del3_cn_38(genomic_del3_dup3_loc_not_normalized): - """Create test fixture copy number count variation""" - params = { - "type": "CopyNumberCount", - "id": "ga4gh:CN.Ka-Wsibx4GHmHgurHCdk0W4deqZt26y4", - "location": genomic_del3_dup3_loc_not_normalized, - "copies": 2, - } - return models.CopyNumberCount(**params) - - @pytest.fixture(scope="module") def genomic_del3_cx_38(genomic_del3_dup3_loc_not_normalized): """Create test fixture copy number change variation""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.JQvddf8xWhkCjdux8NLWNs4MdEMqDiLX", + "id": "ga4gh:CX.-9fCLzZprnBM-nl08MJUo5oqZ4ehduv7", "location": genomic_del3_dup3_loc_not_normalized, "copyChange": "efo:0030069", } @@ -491,35 +467,23 @@ def genomic_del3_cx_38(genomic_del3_dup3_loc_not_normalized): def genomic_del3_37_loc(): """Create test fixture GRCh37 deletion location""" return { - "id": "ga4gh:SL.8aHcfpqhpWQ0kigsdn9mY61Alod63tDP", + "id": "ga4gh:SL.xDN-t4g0hLgYTKyh3_88Drln1HdishyF", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.v7noePfnNpK8ghYXEqZ9NukMXW7YeNsm", }, "start": [31078343, 31118467], - "end": [33292396, 33435269], + "end": [33292395, 33435268], "type": "SequenceLocation", } -@pytest.fixture(scope="module") -def genomic_del3_cn_37(genomic_del3_37_loc): - """Create test fixture copy number count variation""" - params = { - "type": "CopyNumberCount", - "id": "ga4gh:CN.gOq-IVcswisgIfsx9PpZ-BLxzcMVeJeR", - "location": genomic_del3_37_loc, - "copies": 2, - } - return models.CopyNumberCount(**params) - - @pytest.fixture(scope="module") def genomic_del3_cx_37(genomic_del3_37_loc): """Create test fixture copy number change variation""" params = { "type": "CopyNumberChange", - "id": "ga4gh:CX.-s0MQI48uGQ97FAGOVDeFgGAAL5J4wak", + "id": "ga4gh:CX.uiJMtQrX_-w3-ehTTu7o2ByqatZYhYVu", "location": genomic_del3_37_loc, "copyChange": "efo:0030069", } @@ -827,32 +791,32 @@ async def test_genomic_dup2_copy_number_change( @pytest.mark.asyncio async def test_genomic_dup3_copy_number_count( - test_cnv_handler, genomic_dup3_cn_38, genomic_dup3_cn_37 + test_cnv_handler, genomic_del3_dup3_cn_38, genomic_del3_dup3_cn_37 ): """Test that genomic duplication works correctly""" q = "NC_000023.11:g.(31060227_31100351)_(33274278_33417151)dup" # 38 resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=1, do_liftover=False ) - cnv_assertion_checks(resp, genomic_dup3_cn_38) + cnv_assertion_checks(resp, genomic_del3_dup3_cn_38) q = "NC_000023.10:g.(31078344_31118468)_(33292395_33435268)dup" # 37 resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=1, do_liftover=False ) - cnv_assertion_checks(resp, genomic_dup3_cn_37) + cnv_assertion_checks(resp, genomic_del3_dup3_cn_37) resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=1, do_liftover=True ) - cnv_assertion_checks(resp, genomic_dup3_cn_38) + cnv_assertion_checks(resp, genomic_del3_dup3_cn_38) resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=2, do_liftover=True ) - expected = copy.deepcopy(genomic_dup3_cn_38) + expected = copy.deepcopy(genomic_del3_dup3_cn_38) expected.copies = 3 - expected.id = "ga4gh:CN.SiA0poCKmElUkXA-vTsNnuUiEBkzG6v_" + expected.id = "ga4gh:CN.k_3m5Hu3_J5Mb8Rx8zH0plZ12U0XD1Du" cnv_assertion_checks(resp, expected) @@ -1151,32 +1115,32 @@ async def test_genomic_del2_copy_number_change( @pytest.mark.asyncio async def test_genomic_del3_copy_number_count( - test_cnv_handler, genomic_del3_cn_38, genomic_del3_cn_37 + test_cnv_handler, genomic_del3_dup3_cn_38, genomic_del3_dup3_cn_37 ): """Test that genomic deletion works correctly""" q = "NC_000023.11:g.(31060227_31100351)_(33274278_33417151)del" # 38 resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=3, do_liftover=False ) - cnv_assertion_checks(resp, genomic_del3_cn_38) + cnv_assertion_checks(resp, genomic_del3_dup3_cn_38) q = "NC_000023.10:g.(31078344_31118468)_(33292395_33435268)del" # 37 resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=3, do_liftover=False ) - cnv_assertion_checks(resp, genomic_del3_cn_37) + cnv_assertion_checks(resp, genomic_del3_dup3_cn_37) resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=3, do_liftover=True ) - cnv_assertion_checks(resp, genomic_del3_cn_38) + cnv_assertion_checks(resp, genomic_del3_dup3_cn_38) resp = await test_cnv_handler.hgvs_to_copy_number_count( q, baseline_copies=2, do_liftover=True ) - expected = copy.deepcopy(genomic_del3_cn_38) + expected = copy.deepcopy(genomic_del3_dup3_cn_38) expected.copies = 1 - expected.id = "ga4gh:CN.JM71BLpaKrASgTbtoKaSXZ6GZ4FpeDsZ" + expected.id = "ga4gh:CN.vqcaYwbK8oewMEtKbP2pn5npQtkOAGdo" cnv_assertion_checks(resp, expected) diff --git a/tests/to_copy_number_variation/test_parsed_to_copy_number.py b/tests/to_copy_number_variation/test_parsed_to_copy_number.py index 16c83b41..d147b947 100644 --- a/tests/to_copy_number_variation/test_parsed_to_copy_number.py +++ b/tests/to_copy_number_variation/test_parsed_to_copy_number.py @@ -182,16 +182,16 @@ def cx_definite_ranges(): """ variation = { "type": "CopyNumberChange", - "id": "ga4gh:CX.pAvWqzj0yKdETBy_rjApb2j3A_5o213Z", + "id": "ga4gh:CX.gn7z-74PrlvMWAVK7jsP9oYnp0pCezee", "location": { "type": "SequenceLocation", - "id": "ga4gh:SL.1ZWJdPDJHP1XwDZcI1JHU2Obf9-3AYpz", + "id": "ga4gh:SL.jOyDc0XwpyvY-SqxowgWxb7N5ODEYc4I", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.8_liLu1aycC0tPQPFmUaGXJLDs5SbPZ5", }, "start": [10000, 10005], - "end": [1223131, 1223134], + "end": [1223130, 1223133], }, "copyChange": "efo:0030069", } @@ -355,7 +355,7 @@ def test_get_vrs_loc_start_or_end(test_cnv_handler): resp = test_cnv_handler._get_vrs_loc_start_or_end( ac, pos0, ParsedPosType.DEFINITE_RANGE, is_start=False, pos1=pos1 ) - assert resp == models.Range([140753337, 140753351]) + assert resp == models.Range([pos0, pos1]) # Indefinite Range start resp = test_cnv_handler._get_vrs_loc_start_or_end( diff --git a/variation/to_copy_number_variation.py b/variation/to_copy_number_variation.py index 46930f59..ac368642 100644 --- a/variation/to_copy_number_variation.py +++ b/variation/to_copy_number_variation.py @@ -394,7 +394,7 @@ def _get_vrs_loc_start_or_end( elif pos_type == ParsedPosType.DEFINITE_RANGE: self._validate_ac_pos(accession, pos1) vrs_val = models.Range( - [pos0 - 1 if is_start else pos0 + 1, pos1 - 1 if is_start else pos1 + 1] + [pos0 - 1 if is_start else pos0, pos1 - 1 if is_start else pos1] ) else: if comparator == Comparator.LT_OR_EQUAL: diff --git a/variation/translators/ambiguous_translator_base.py b/variation/translators/ambiguous_translator_base.py index e6618337..a796b665 100644 --- a/variation/translators/ambiguous_translator_base.py +++ b/variation/translators/ambiguous_translator_base.py @@ -130,7 +130,7 @@ def get_dup_del_ambiguous_seq_loc( """ if ambiguous_type == AmbiguousType.AMBIGUOUS_1: start = models.Range([pos0 - 1, pos1 - 1]) - end = models.Range([pos2 + 1, pos3 + 1]) + end = models.Range([pos2, pos3]) elif ambiguous_type == AmbiguousType.AMBIGUOUS_2: start = self.vrs.get_start_indef_range(pos1) end = self.vrs.get_end_indef_range(pos2) diff --git a/variation/version.py b/variation/version.py index bc4b3f4b..7b64f4f8 100644 --- a/variation/version.py +++ b/variation/version.py @@ -1,2 +1,2 @@ """Module for version of app""" -__version__ = "0.8.0-dev0" +__version__ = "0.8.0-dev1"