diff --git a/VariantValidator/modules/complex_descriptions.py b/VariantValidator/modules/complex_descriptions.py index 991618f7..89351f0e 100644 --- a/VariantValidator/modules/complex_descriptions.py +++ b/VariantValidator/modules/complex_descriptions.py @@ -2,8 +2,10 @@ import copy from vvhgvs.assemblymapper import AssemblyMapper from VariantValidator.modules import utils as vv_utils, format_converters -from VariantValidator.modules.hgvs_utils import hgvs_obj_from_existing_edit +from VariantValidator.modules.hgvs_utils import hgvs_obj_from_existing_edit,\ + _hgvs_offset_pos_from_str_in import vvhgvs.exceptions +from vvhgvs.enums import Datum, ValidationLevel from vvhgvs.location import Interval class UncertainConversionError(Exception): @@ -12,8 +14,10 @@ class UncertainConversionError(Exception): # fuzzy but specified ended intervals, take a normal interval for both ends # can take a pair of Intervals or BaseOffsetIntervals class FEInterval(Interval): - uncertain = True - pass + def __init__(self,start = None,end = None): + self.uncertain = True + self.start = start + self.end = end def validate(self): if self.start: (res, msg) = self.start.validate() @@ -258,12 +262,12 @@ def fuzzy_ends(my_variant, validator): raise FuzzyRangeError("Fuzzy/unknown variant end position in submitted variant description") else: - if "?" in str(my_variant.hgvs_formatted.posedit.pos): - if "?" in str(my_variant.hgvs_formatted.posedit.pos.end) and "?" not in str( - my_variant.hgvs_formatted.posedit.pos.start): + if "?" in str(my_variant.quibble.posedit.pos): + if "?" in str(my_variant.quibble.posedit.pos.end) and "?" not in str( + my_variant.quibble.posedit.pos.start): raise FuzzyPositionError("Fuzzy/unknown variant end position in submitted variant description") - elif "?" in str(my_variant.hgvs_formatted.posedit.pos.start) and "?" not in str( - my_variant.hgvs_formatted.posedit.pos.end): + elif "?" in str(my_variant.quibble.posedit.pos.start) and "?" not in str( + my_variant.quibble.posedit.pos.end): raise FuzzyPositionError("Fuzzy/unknown variant start position in submitted variant description") else: raise FuzzyPositionError("Fuzzy/unknown variant start and end positions " @@ -369,9 +373,28 @@ def uncertain_positions(my_variant, validator): # Genomic Variants if "NC_" in my_variant.quibble: - my_variant.hgvs_genomic = my_variant.quibble - start_pos = position_1.split("_")[0] - end_pos = position_2.split("_")[1] + start_pos, _sep, start_end = position_1.partition("_") + o_start_pos, start_end = _hgvs_offset_pos_from_str_in( + start_pos, + None, + ref_type=var_type, + end=start_end) + start = Interval(start=o_start_pos,end=start_end) + end_start, _sep, end_pos = position_2.partition("_") + end_start, o_end_pos = _hgvs_offset_pos_from_str_in( + end_start, + None, + ref_type=var_type, + end=end_pos) + end = Interval(start=end_start,end=o_end_pos) + full_obj = hgvs_obj_from_existing_edit( + accession, + var_type, + FEInterval( + start=start, + end=end), + edit) + my_variant.hgvs_genomic = full_obj parsed_v3 = hgvs_obj_from_existing_edit( accession, var_type, @@ -438,7 +461,6 @@ def uncertain_positions(my_variant, validator): end=t_position_2), edit) - my_variant.hgvs_coding = tx_variant my_variant.quibble = copy.copy(tx_variant) my_variant.hgvs_transcript_variant = tx_variant else: @@ -472,15 +494,15 @@ def uncertain_positions(my_variant, validator): end=g_position_2), edit) my_variant.hgvs_genomic = gen_variant - my_variant.hgvs_coding = hgvs_obj_from_existing_edit( + hgvs_coding = hgvs_obj_from_existing_edit( parsed_v1.ac, parsed_v2.type, FEInterval(start=parsed_v1.posedit.pos, end=parsed_v2.posedit.pos), edit ) - my_variant.hgvs_transcript_variant = copy.copy(my_variant.hgvs_coding) - my_variant.quibble = copy.copy(my_variant.hgvs_coding) + my_variant.hgvs_transcript_variant = copy.copy(hgvs_coding) + my_variant.quibble = copy.copy(hgvs_coding) my_variant.output_type_flag = "gene" elif ")_(" not in my_variant.quibble and not "?" in my_variant.quibble: @@ -489,7 +511,6 @@ def uncertain_positions(my_variant, validator): accession, _sep, var_type_and_posedit = my_variant.quibble.partition(':') var_type, _sep ,position_and_edit = var_type_and_posedit.partition(".(") position_1, variation = position_and_edit.split(")") - v1 = f"{accession}:{var_type}.{position_1}=" my_variant.reftype = f":{var_type}." try: if var_type == 'p': @@ -517,7 +538,7 @@ def uncertain_positions(my_variant, validator): raise IncompatibleTypeError(str(e)) elif "base start position must be <= end position" in str(e): raise InvalidRangeError(f"{str(e)} in position {str(parsed_v1.posedit.pos)}") - elif re.search("[+-]", str(parsed_v1.posedit.pos)) or re.search("[+-]", str(parsed_v1.posedit.pos)): + elif "+" in str(parsed_v1.posedit.pos) or "-" in str(parsed_v1.posedit.pos): pass else: raise InvalidRangeError(f"{position_1} is an invalid range for " @@ -529,8 +550,23 @@ def uncertain_positions(my_variant, validator): # Genomic Variants if "NC_" in my_variant.quibble: - my_variant.hgvs_genomic = my_variant.quibble - + start_pos, _sep, start_end = position_1.partition("_") + start_pos, end_pos = _hgvs_offset_pos_from_str_in( + start_pos, + None, + end=start_end, + ref_type=var_type) + pos = Interval(start=start_pos,end=end_pos) + try: + full_obj = hgvs_obj_from_existing_edit( + accession, + var_type, + pos, + edit) + except Exception as e: + raise e + full_obj.posedit.pos.uncertain = True + my_variant.hgvs_genomic = full_obj # Make select_transcriopts "select" unless specified if (validator.select_transcripts != "select" and ("NM_" in validator.select_transcripts or "ENST" in validator.select_transcripts) and @@ -567,8 +603,6 @@ def uncertain_positions(my_variant, validator): ptv1[0].posedit.pos, edit) tx_variant.posedit.pos.uncertain = True - - my_variant.hgvs_coding = tx_variant my_variant.quibble = copy.copy(tx_variant) my_variant.hgvs_transcript_variant = tx_variant else: @@ -587,14 +621,14 @@ def uncertain_positions(my_variant, validator): gen_variant.posedit.pos.uncertain = True my_variant.hgvs_genomic = gen_variant - my_variant.hgvs_coding = hgvs_obj_from_existing_edit( + hgvs_coding = hgvs_obj_from_existing_edit( parsed_v1.ac, parsed_v1.type, parsed_v1.posedit.pos, edit) - my_variant.hgvs_coding.posedit.pos.uncertain = True - my_variant.hgvs_transcript_variant = my_variant.hgvs_coding - my_variant.quibble = copy.copy(my_variant.hgvs_coding) + hgvs_coding.posedit.pos.uncertain = True + my_variant.hgvs_transcript_variant = hgvs_coding + my_variant.quibble = copy.copy(hgvs_coding) my_variant.output_type_flag = "gene" else: diff --git a/VariantValidator/modules/exon_numbering.py b/VariantValidator/modules/exon_numbering.py index ac455c76..15ce9205 100644 --- a/VariantValidator/modules/exon_numbering.py +++ b/VariantValidator/modules/exon_numbering.py @@ -37,7 +37,7 @@ def finds_exon_number(variant, validator): info_dict = {} for i in range(len(response_dictionary["transcripts"])): - if response_dictionary["transcripts"][i]["reference"] == variant.hgvs_coding.ac: + if response_dictionary["transcripts"][i]["reference"] == variant.quibble.ac: # Create record info_dict[(response_dictionary["transcripts"][i]["reference"])] = {} @@ -53,7 +53,7 @@ def finds_exon_number(variant, validator): # Step 2 - Get the necessary variant information # Find the variant position from the variant nomenclature - coordinates = str(variant.hgvs_coding.posedit.pos) + coordinates = str(variant.quibble.posedit.pos) # Identify start and end of variant from input coordinates if '_' in coordinates: @@ -72,9 +72,9 @@ def finds_exon_number(variant, validator): # Create c_to_n varint try: - to_n = validator.vm.c_to_n(variant.hgvs_coding) + to_n = validator.vm.c_to_n(variant.quibble) except vvhgvs.exceptions.HGVSInvalidVariantError: - to_n = variant.hgvs_coding + to_n = variant.quibble """ This for loop identifies the exon/intron number for the transcript @@ -85,8 +85,8 @@ def finds_exon_number(variant, validator): keys: start_exon and end_exon values: start and position of variant in the reference sequence """ - exon_structure_dict = info_dict[variant.hgvs_coding.ac]["exon_structure_dict"] - coding_start = info_dict[variant.hgvs_coding.ac]['coding_start'] + exon_structure_dict = info_dict[variant.quibble.ac]["exon_structure_dict"] + coding_start = info_dict[variant.quibble.ac]['coding_start'] if coding_start is None: coding_start = 1 diff --git a/VariantValidator/modules/format_converters.py b/VariantValidator/modules/format_converters.py index 42d352c7..3d3a4ba5 100644 --- a/VariantValidator/modules/format_converters.py +++ b/VariantValidator/modules/format_converters.py @@ -106,7 +106,9 @@ def initial_format_conversions(variant, validator, select_transcripts_dict_plus_ # fail if un-corrected errors persist (warning should already have been generated) if toskip: return True - + # make sure ref type and source are set if we intend to continue + variant.set_reftype() + variant.set_refsource() # Tackle compound variant descriptions NG or NC (NM_) i.e. correctly input NG/NC_(NM_):c. intronic_converter(variant, validator) return False @@ -1077,21 +1079,19 @@ def lrg_to_refseq(variant, validator): """ caution = '' if variant.refsource == 'LRG': - if variant.hgvs_formatted.ac.startswith('LRG') and variant.hgvs_formatted.ac[3:4].isdigit(): - reference = variant.hgvs_formatted.ac.replace('LRG', 'LRG_') - caution = variant.hgvs_formatted.ac + ' updated to ' + reference + ': ' - variant.hgvs_formatted.ac = reference - variant.set_quibble(variant.hgvs_formatted) + if variant.quibble.ac.startswith('LRG') and variant.quibble.ac[3:4].isdigit(): + reference = variant.quibble.ac.replace('LRG', 'LRG_') + caution = variant.quibble.ac + ' updated to ' + reference + ': ' + variant.quibble.ac = reference if re.match(r'^LRG_\d+t\d+$', variant.quibble.ac): lrg_reference = variant.quibble.ac refseqtrans_reference = validator.db.get_refseq_transcript_id_from_lrg_transcript_id(lrg_reference) if refseqtrans_reference != 'none': - old_var_str = str(variant.hgvs_formatted) - variant.hgvs_formatted.ac = refseqtrans_reference - variant.set_quibble(variant.hgvs_formatted) + old_var_str = str(variant.quibble) + variant.quibble.ac = refseqtrans_reference caution += old_var_str + ' automapped to equivalent RefSeq record ' \ - '' + str(variant.hgvs_formatted) + '' + str(variant.quibble) variant.warnings.append(caution) logger.info(caution) @@ -1099,11 +1099,10 @@ def lrg_to_refseq(variant, validator): lrg_reference = variant.quibble.ac refseqprot_reference = validator.db.get_refseq_protein_id_from_lrg_protein_id(lrg_reference) if refseqprot_reference != 'none': - old_var_str = str(variant.hgvs_formatted) - variant.hgvs_formatted.ac = refseqprot_reference - variant.set_quibble(variant.hgvs_formatted) + old_var_str = str(variant.quibble) + variant.quibble.ac = refseqprot_reference caution += old_var_str + ' automapped to equivalent RefSeq record ' \ - '' + str(variant.hgvs_formatted) + '' + str(variant.quibble) variant.warnings.append(caution) logger.info(caution) @@ -1111,11 +1110,10 @@ def lrg_to_refseq(variant, validator): lrg_reference = variant.quibble.ac refseqgene_reference = validator.db.get_refseq_id_from_lrg_id(lrg_reference) if refseqgene_reference != 'none': - old_var_str = str(variant.hgvs_formatted) - variant.hgvs_formatted.ac = refseqgene_reference - variant.set_quibble(variant.hgvs_formatted) + old_var_str = str(variant.quibble) + variant.quibble.ac = refseqgene_reference caution += old_var_str + ' automapped to equivalent RefSeq record ' \ - '' + str(variant.hgvs_formatted) + '' + str(variant.quibble) variant.warnings.append(caution) logger.info(caution) @@ -1124,14 +1122,14 @@ def mitochondrial(variant, validator): """Will check if variant is mitochondrial and if so it will reformat the type to 'm' and save a value to the variant hgvs_genomic attribute""" - if variant.reftype == ':m.' or variant.hgvs_formatted.ac == 'NC_012920.1' or \ - variant.hgvs_formatted.ac == 'NC_001807.4': + if variant.reftype == ':m.' or variant.quibble.ac == 'NC_012920.1' or \ + variant.quibble.ac == 'NC_001807.4': # set flag variant.output_type_flag = 'mitochondrial' # Ensure the correct reference sequence type is used, if not, warn the user - hgvs_mito = copy.deepcopy(variant.hgvs_formatted) + hgvs_mito = copy.deepcopy(variant.quibble) if hgvs_mito.type == 'g' and (hgvs_mito.ac == 'NC_012920.1' or hgvs_mito.ac == 'NC_001807.4'): hgvs_mito.type = 'm' if "NC_012920.1" in hgvs_mito.ac and "hg19" in variant.selected_assembly: @@ -1164,7 +1162,7 @@ def mitochondrial(variant, validator): # Check for movement during normalization try: - norm_check = variant.hn.normalize(variant.hgvs_formatted) + norm_check = variant.hn.normalize(variant.quibble) if hgvs_mito.posedit.pos != norm_check.posedit.pos: norm_check.type = "m" error = "%s updated to %s" % (fn.valstr(hgvs_mito), fn.valstr(norm_check)) @@ -1184,7 +1182,6 @@ def mitochondrial(variant, validator): # Add a description of the reference sequence type and continue variant.hgvs_genomic = hgvs_mito if len(rel_var) == 0: - variant.genomic_g = unset_hgvs_obj_ref(hgvs_mito) variant.description = 'Homo sapiens mitochondrion, complete genome' logger.info('Homo sapiens mitochondrion, complete genome') return True @@ -1198,7 +1195,7 @@ def proteins(variant, validator): error = None hgvs_object = None # Try to validate the variant - hgvs_object = variant.hgvs_formatted + hgvs_object = variant.quibble try: validator.vr.validate(hgvs_object) @@ -1375,17 +1372,17 @@ def rna(variant, validator): convert r, into c. """ if variant.reftype == ':r.' or ":r." in variant.original: - if ":r.(" in str(variant.hgvs_formatted): - if type(variant.hgvs_formatted) is str: - strip_prediction = str(variant.hgvs_formatted).replace("(", "") + if ":r.(" in str(variant.quibble): + if type(variant.quibble) is str: + strip_prediction = str(variant.quibble).replace("(", "") strip_prediction = strip_prediction[:-1] hgvs_input = validator.hp.parse_hgvs_variant(strip_prediction) else: - hgvs_input = variant.hgvs_formatted + hgvs_input = variant.quibble hgvs_input.posedit.pos.uncertain = False #hgvs_input.posedit.uncertain = False else: - hgvs_input = variant.hgvs_formatted + hgvs_input = variant.quibble tx_info = validator.hdp.get_tx_identity_info(hgvs_input.ac) if tx_info[3] is None: @@ -1403,7 +1400,7 @@ def rna(variant, validator): variant.warnings.append(error) logger.info(str(error)) return True - variant.hgvs_formatted = hgvs_c + variant.quibble = hgvs_c # Create variant.rna_data dictionary rnd = VariantValidator.modules.rna_formatter.RnaDescriptions(validator.alt_aln_method, @@ -1492,4 +1489,4 @@ def uncertain_pos(variant, validator): # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# \ No newline at end of file +# diff --git a/VariantValidator/modules/gapped_mapping.py b/VariantValidator/modules/gapped_mapping.py index 9a9b0572..bdd76690 100644 --- a/VariantValidator/modules/gapped_mapping.py +++ b/VariantValidator/modules/gapped_mapping.py @@ -409,11 +409,11 @@ def gapped_g_to_c(self, rel_var, select_transcripts_dict): # take a look at the input genomic variant for potential base salvage stash_ac = vcf_dict['chr'] - stash_input = self.variant.post_format_conversion + stash_input = self.variant.quibble if type(stash_input) is str: stash_input = self.validator.hp.parse_hgvs_variant(stash_input) # Re-Analyse genomic positions - if 'NG_' in str(self.variant.hgvs_formatted): + if 'NG_' in str(self.variant.quibble): c = rel_var[0] if hasattr(c.posedit.edit, 'ref') and c.posedit.edit.ref is not None: c.posedit.edit.ref = c.posedit.edit.ref.upper() diff --git a/VariantValidator/modules/gene2transcripts.py b/VariantValidator/modules/gene2transcripts.py index 62f93bb7..ff5257c4 100644 --- a/VariantValidator/modules/gene2transcripts.py +++ b/VariantValidator/modules/gene2transcripts.py @@ -124,7 +124,7 @@ def gene2transcripts(g2t, # Gather transcript information lists if bypass_web_searches is True: tx_for_gene = [] - tx_info = g2t.hdp.get_tx_identity_info(query.hgvs_coding.ac) + tx_info = g2t.hdp.get_tx_identity_info(query.quibble.ac) # Add primary assembly queries for builds in query.primary_assembly_loci.keys(): @@ -132,7 +132,7 @@ def gene2transcripts(g2t, tx_for_gene.append([query.gene_symbol, tx_info[3], 0, - query.hgvs_coding.ac, + query.quibble.ac, query.primary_assembly_loci[builds]['hgvs_genomic_description'].ac, validator.alt_aln_method]) @@ -141,7 +141,7 @@ def gene2transcripts(g2t, tx_for_gene.append([query.gene_symbol, tx_info[3], 0, - query.hgvs_coding.ac, + query.quibble.ac, query.hgvs_refseqgene_variant.ac, validator.alt_aln_method]) diff --git a/VariantValidator/modules/hgvs_utils.py b/VariantValidator/modules/hgvs_utils.py index 2f2a8801..4ea6ffe2 100644 --- a/VariantValidator/modules/hgvs_utils.py +++ b/VariantValidator/modules/hgvs_utils.py @@ -164,10 +164,16 @@ def unset_hgvs_obj_ref(hgvs): """ Remove/unset ref bases from hgvs object, in the manner needed for output, but without re-parsing from text. + + Skips known cases where ref should not be unset, including any unc + positions, or other N ref. """ + if hgvs.posedit.pos.uncertain: + return hgvs edit = hgvs.posedit.edit if edit.type in ['inv', 'dup']: - edit.ref = '' + if edit.ref and not 'N' in edit.ref: + edit.ref = '' elif edit.ref is not None and edit.alt is not None: #if #edit.alt != edit.ref and \ if len(edit.alt) == 1 and len(edit.ref) == 1: @@ -2695,16 +2701,16 @@ def hgvs_ref_alt(hgvs_variant, sf): return ref_alt_dict -def incomplete_alignment_mapping_t_to_g(validator, variant): +def incomplete_alignment_mapping_t_to_g(validator, variant,t_hgvs_var): output = None - mapping_options = variant.map_dat.mapping_options(variant.input_parses.ac,hdp=validator.hdp) + mapping_options = validator.hdp.get_tx_mapping_options(t_hgvs_var.ac,hdp=validator.hdp) for option in mapping_options: if option[2] == validator.alt_aln_method and "NC_" not in option[1]: in_assembly = seq_data.to_chr_num_refseq(option[1], variant.primary_assembly) if in_assembly is not None: try: - output = validator.vm.t_to_g(variant.input_parses, option[1]) - if variant.input_parses.posedit.edit.type == "identity": + output = validator.vm.t_to_g(t_hgvs_var, option[1]) + if t_hgvs_var.posedit.edit.type == "identity": output.posedit.edit.alt = output.posedit.edit.ref except vvhgvs.exceptions.HGVSError: pass diff --git a/VariantValidator/modules/mappers.py b/VariantValidator/modules/mappers.py index 44278613..6837c045 100644 --- a/VariantValidator/modules/mappers.py +++ b/VariantValidator/modules/mappers.py @@ -22,8 +22,8 @@ class TranscriptMappingError(Exception): pass def gene_to_transcripts(variant, validator, select_transcripts_dict, batch_list): - logger.info(f"Mapping {variant.hgvs_formatted} to transcripts") - g_query = variant.hgvs_formatted + logger.info(f"Mapping {variant.quibble} to transcripts") + g_query = variant.quibble # set hdp for exon mapping fetch before first use if not variant.map_dat.hdp: variant.map_dat.hdp = validator.hdp @@ -58,7 +58,7 @@ def gene_to_transcripts(variant, validator, select_transcripts_dict, batch_list) # use updated position if normalized to a different position if g_query.posedit.pos != g_test.posedit.pos: variant.hgvs_genomic = g_test - variant.hgvs_formatted = g_test + variant.quibble = g_test else: variant.hgvs_genomic = g_query @@ -135,14 +135,14 @@ def gene_to_transcripts(variant, validator, select_transcripts_dict, batch_list) except Exception as e: logger.info(f"nw_rel_var creation failed with exception {str(e)}") raise TranscriptMappingError(f"Encountered an issue when mapping genomic description " - f"{variant.hgvs_formatted} to available transcripts") + f"{variant.quibble} to available transcripts") rel_var = nw_rel_var if len(rel_var) == 0: # Check for NG_ - if variant.hgvs_formatted.ac.startswith('NG_'): - hgvs_refseqgene =variant.hgvs_formatted + if variant.quibble.ac.startswith('NG_'): + hgvs_refseqgene =variant.quibble # Convert to chromosomal position refseqgene_data = validator.rsg_to_chr(hgvs_refseqgene, variant.primary_assembly, variant.hn) # There should only ever be one description returned @@ -153,11 +153,11 @@ def gene_to_transcripts(variant, validator, select_transcripts_dict, batch_list) variant.output_type_flag = 'intergenic' # set genomic and where available RefSeqGene outputs variant.warnings.append(no_tx_found_error) - error = 'TranscriptIdentificationWarning: Mapping unavailable for RefSeqGene ' + str(variant.hgvs_formatted) + \ + error = 'TranscriptIdentificationWarning: Mapping unavailable for RefSeqGene ' + str(variant.quibble) + \ ' using alignment method = ' + validator.alt_aln_method variant.warnings.append(error) - variant.genomic_r = variant.hgvs_formatted - variant.refseqgene_variant = variant.hgvs_formatted + variant.hgvs_refseqgene_variant = variant.quibble + variant.refseqgene_variant = variant.quibble return True # Extract data @@ -165,15 +165,16 @@ def gene_to_transcripts(variant, validator, select_transcripts_dict, batch_list) genomic_input = refseqgene_data['hgvs_genomic'] # re_submit # Tag the line so that it is not written out - variant.warnings.append(str(variant.hgvs_formatted) + ' automapped to genome position ' + + variant.warnings.append(str(variant.quibble) + ' automapped to genome position ' + str(genomic_input)) + variant.write = False query = Variant(variant.original, quibble=genomic_input, warnings=variant.warnings, primary_assembly=variant.primary_assembly, order=variant.order, selected_assembly=variant.selected_assembly) batch_list.append(query) logger.info('Submitting new variant with format %s', genomic_input) else: - error = 'TranscriptIdentificationWarning: Mapping unavailable for RefSeqGene ' + str(variant.hgvs_formatted) + \ + error = 'TranscriptIdentificationWarning: Mapping unavailable for RefSeqGene ' + str(variant.quibble) + \ ' using alignment method = ' + validator.alt_aln_method variant.warnings.append(error) logger.info(str(error)) @@ -217,9 +218,8 @@ def gene_to_transcripts(variant, validator, select_transcripts_dict, batch_list) variant.output_type_flag = 'intergenic' # set genomic and where available RefSeqGene outputs variant.warnings.append(error) - variant.genomic_g = unset_hgvs_obj_ref(variant.hgvs_genomic) - variant.genomic_r = rsg_data[0] - logger.info(str(error)) + variant.hgvs_refseqgene_variant = rsg_data[0] + logger.warning(str(error)) return True else: error = 'Validation will fail if the selected chromosome reference sequence does not corresponds to ' \ @@ -267,20 +267,15 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version valid = False caution = '' error = '' - # Collect information for genomic level validation - obj = variant.hgvs_formatted - if type(obj) is str: #still happens for some error cases - variant.hgvs_formatted = validator.hp.parse_hgvs_variant( - variant.hgvs_formatted) - obj = variant.hgvs_formatted - tx_ac = obj.ac - - quibble_input = str(variant.quibble) - quibble_input_hgvs_obj = variant.quibble - if isinstance(quibble_input_hgvs_obj, str): - quibble_input_hgvs_obj = validator.hp.parse_hgvs_variant(variant.quibble) - formatted_variant = str(variant.hgvs_formatted) - out_hgvs_obj = variant.hgvs_formatted + if isinstance(variant.quibble, str): + variant.quibble = validator.hp.parse_hgvs_variant(variant.quibble) + # preserved input variables + input_formatted_variant= str(variant.quibble) + input_hgvs_obj = copy.copy(variant.quibble) + tx_ac = input_hgvs_obj.ac + # to be changed versions for output + formatted_variant = str(variant.quibble) + out_hgvs_obj = variant.quibble # Do we keep it? if (validator.select_transcripts != 'all' and validator.select_transcripts != 'raw') \ and "select" not in validator.select_transcripts and \ @@ -288,7 +283,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version if tx_ac not in list(select_transcripts_dict_plus_version.keys()): # By marking it as Do Not Write and continuing through the validation loop if ":g." not in variant.original: - error = (f"TranscriptSelectionError: Variant {variant.hgvs_formatted} is not in the list of " + error = (f"TranscriptSelectionError: Variant {variant.quibble} is not in the list of " f"transcripts selected for validation {validator.select_transcripts}") logger.info(error) variant.warnings.append(error) @@ -303,10 +298,10 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version # First task is to get the genomic equivalent, and output a useful error messages if it can't be found. try: reset_g_origin = False - if obj.rel_ac.startswith('NG_'): + if out_hgvs_obj.rel_ac.startswith('NG_'): reset_g_origin=True to_g = validator.myevm_t_to_g( - obj, + out_hgvs_obj, variant.no_norm_evm, variant.primary_assembly, variant.hn, @@ -352,7 +347,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version plus = re.compile(r"\d\+\d") # finds digit + digit minus = re.compile(r"\d-\d") # finds digit - digit - if plus.search(quibble_input) or minus.search(quibble_input): + if plus.search(input_formatted_variant) or minus.search(input_formatted_variant): if 'error' in str(to_g): if validator.alt_aln_method != 'genebuild': error = "If the following error message does not address the issue and the problem persists please " \ @@ -371,7 +366,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version else: # Incorrect genomic reference base try: - check_base = validator.nr_vm.t_to_g(obj, to_g.ac) + check_base = validator.nr_vm.t_to_g(out_hgvs_obj, to_g.ac) except vvhgvs.exceptions.HGVSError: pass else: @@ -381,26 +376,28 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version if "does not agree with reference sequence" in str(e): a, b = re.findall(r'\(([GATC]+)\)', str(e))[:2] if (len(a) == len(b) and - obj.posedit.edit.type == check_base.posedit.edit.type): - error = f"{str(obj)} mapped to {str(e)}" + out_hgvs_obj.posedit.edit.type == check_base.posedit.edit.type): + error = f"{str(out_hgvs_obj)} mapped to {str(e)}" variant.warnings.append(error) logger.info(error) # Insertions at exon boundaries are miss-handled by vm.g_to_t - if (obj.posedit.edit.type == 'ins' and - obj.posedit.pos.start.offset == 0 and - obj.posedit.pos.end.offset != 0) or (obj.posedit.edit.type == 'ins' and - obj.posedit.pos.start.offset != 0 and - obj.posedit.pos.end.offset == 0): - formatted_variant = str(obj) - out_hgvs_obj = obj + if ( + out_hgvs_obj.posedit.edit.type == 'ins' and + out_hgvs_obj.posedit.pos.start.offset == 0 and + out_hgvs_obj.posedit.pos.end.offset != 0 + ) or ( + out_hgvs_obj.posedit.edit.type == 'ins' and + out_hgvs_obj.posedit.pos.start.offset != 0 and + out_hgvs_obj.posedit.pos.end.offset == 0): + formatted_variant = str(out_hgvs_obj) else: try: out_hgvs_obj = validator.myevm_g_to_t(variant.evm, to_g, tx_ac) formatted_variant = str(out_hgvs_obj) except vvhgvs.exceptions.HGVSError as e: if "Alignment is incomplete" in str(e): - to_g = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant) + to_g = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant,variant.quibble) if to_g is None: error = str(e) variant.warnings.append(error) @@ -411,7 +408,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version out_hgvs_obj = validator.vm.g_to_t(to_g, tx_ac) formatted_variant = str(out_hgvs_obj) - elif ':g.' in quibble_input: + elif ':g.' in formatted_variant: if plus.search(formatted_variant) or minus.search(formatted_variant): to_g = validator.genomic(out_hgvs_obj, variant.no_norm_evm, variant.primary_assembly, variant) if 'error' in str(to_g): @@ -430,13 +427,14 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version return True else: # Insertions at exon boundaries are miss-handled by vm.g_to_t - if (obj.posedit.edit.type == 'ins' and - obj.posedit.pos.start.offset == 0 and - obj.posedit.pos.end.offset != 0) or (obj.posedit.edit.type == 'ins' and - obj.posedit.pos.start.offset != 0 and - obj.posedit.pos.end.offset == 0): - out_hgvs_obj = obj - formatted_variant = str(obj) + if ( + out_hgvs_obj.posedit.edit.type == 'ins' and + out_hgvs_obj.posedit.pos.start.offset == 0 and + out_hgvs_obj.posedit.pos.end.offset != 0) or ( + out_hgvs_obj.posedit.edit.type == 'ins' and + out_hgvs_obj.posedit.pos.start.offset != 0 and + out_hgvs_obj.posedit.pos.end.offset == 0): + formatted_variant = str(out_hgvs_obj) else: # Normalize was I believe to replace ref. Mapping does this anyway # to_g = hn.normalize(to_g) @@ -446,7 +444,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version else: # Normalize the variant try: - h_variant = variant.hn.normalize(obj) + h_variant = variant.hn.normalize(out_hgvs_obj) except vvhgvs.exceptions.HGVSUnsupportedOperationError as error: if 'Unsupported normalization of variants spanning the exon-intron boundary' in str(error): formatted_variant = formatted_variant @@ -471,17 +469,17 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version # Tackle the plus intronic offset cck = False - if plus.search(quibble_input): + if plus.search(input_formatted_variant): # Regular expression catches the start of the interval only based on .00+00 pattern inv_start = re.compile(r"\.\d+\+\d") inv_end = re.compile(r"_\d+\+\d") - if inv_start.search(quibble_input) or inv_end.search(quibble_input): + if inv_start.search(input_formatted_variant) or inv_end.search(input_formatted_variant): cck = True - if minus.search(quibble_input): + if minus.search(input_formatted_variant): # Regular expression catches the start of the interval only based on .00-00 pattern inv_start = re.compile(r"\.\d+-\d") inv_end = re.compile(r"_\d+-\d") - if inv_start.search(quibble_input) or inv_end.search(quibble_input): + if inv_start.search(input_formatted_variant) or inv_end.search(input_formatted_variant): cck = True # COORDINATE CHECKER @@ -493,7 +491,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version if 'del' in formatted_variant: if out_hgvs_obj.type == 'c': coding = out_hgvs_obj - elif quibble_input_hgvs_obj.type == 'c': + elif input_hgvs_obj.type == 'c': coding = validator.coding(out_hgvs_obj) else:# not actually coding coding = out_hgvs_obj @@ -520,7 +518,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version post_var = validator.myevm_g_to_t(variant.evm, pre_var, trans_acc) except vvhgvs.exceptions.HGVSError as error: if "Alignment is incomplete" in str(error): - output = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant) + output = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant,out_hgvs_obj) if output is None: error = str(error) variant.warnings.append(error) @@ -534,7 +532,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version logger.info(str(error)) return True - test = quibble_input_hgvs_obj + test = input_hgvs_obj if post_var.posedit.pos.start.base != test.posedit.pos.start.base or \ post_var.posedit.pos.end.base != test.posedit.pos.end.base: @@ -578,7 +576,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version if out_hgvs_obj.type == 'c': coding = out_hgvs_obj - elif quibble_input_hgvs_obj.type == 'c': + elif input_hgvs_obj.type == 'c': coding = validator.coding(out_hgvs_obj) else:# not actually coding coding = out_hgvs_obj @@ -594,7 +592,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version except vvhgvs.exceptions.HGVSError as e: logger.info(f"Error: {str(e)}. Variant: {pre_var}") if "Alignment is incomplete" in str(e): - pre_var = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant) + pre_var = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant,out_hgvs_obj) if to_g is None: error = str(e) variant.warnings.append(error) @@ -606,7 +604,7 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version else: logger.info(f"{pre_var} mapped to {post_var}") - test = quibble_input_hgvs_obj + test = input_hgvs_obj if post_var.posedit.pos.start.base != test.posedit.pos.start.base or \ post_var.posedit.pos.end.base != test.posedit.pos.end.base: @@ -646,9 +644,9 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version variant.warnings.extend([caution]) raise MappersError(caution) - elif ':g.' not in quibble_input: + elif ':g.' not in input_formatted_variant: query = out_hgvs_obj - test = quibble_input_hgvs_obj + test = input_hgvs_obj if str(query.posedit.pos) != str(test.posedit.pos): automap = str(test) + ' automapped to ' + str(query) @@ -656,12 +654,12 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version variant.quibble=out_hgvs_obj # VALIDATION of intronic variants - pre_valid = quibble_input_hgvs_obj + pre_valid = input_hgvs_obj post_valid = out_hgvs_obj # valid is false if the input contains a \d+\d, \d-\d or :g. if not valid: - genomic_validation = validator.genomic(quibble_input_hgvs_obj, variant.no_norm_evm, variant.primary_assembly, + genomic_validation = validator.genomic(input_hgvs_obj, variant.no_norm_evm, variant.primary_assembly, variant) if fn.valstr(pre_valid) != fn.valstr(post_valid): if variant.reftype != ':g.': @@ -980,9 +978,12 @@ def transcripts_to_gene(variant, validator, select_transcripts_dict_plus_version str(updated_transcript_variant) + ' MUST be fully validated prior to ' 'use in reports: ' 'select_variants=' + fn.valstr(updated_transcript_variant)) - variant.coding = hgvs_coding - variant.genomic_r = hgvs_refseq - variant.genomic_g = unset_hgvs_obj_ref(hgvs_genomic) + + if hgvs_coding: + print("coding:" + str(hgvs_coding)) + variant.hgvs_transcript_variant = hgvs_coding + variant.hgvs_genomic = hgvs_genomic + variant.hgvs_refseqgene_variant = hgvs_refseq variant.protein = hgvs_protein return False @@ -993,14 +994,14 @@ def final_tx_to_multiple_genomic(variant, validator, tx_variant, liftover_level= try: tx_variant.ac - variant.hgvs_coding = tx_variant + hgvs_coding = tx_variant except AttributeError: - variant.hgvs_coding = validator.hp.parse_hgvs_variant(str(tx_variant)) + hgvs_coding = validator.hp.parse_hgvs_variant(str(tx_variant)) # Look for variants spanning introns disable_gap_compensation = False try: - variant.hn.normalize(variant.hgvs_coding) + variant.hn.normalize(hgvs_coding) except vvhgvs.exceptions.HGVSUnsupportedOperationError as e: error = str(e) if 'boundary' in error or 'spanning' in error: @@ -1012,12 +1013,11 @@ def final_tx_to_multiple_genomic(variant, validator, tx_variant, liftover_level= multi_g = [] multi_list = [] - mapping_options = variant.map_dat.mapping_options(variant.hgvs_coding.ac,hdp=validator.hdp) + mapping_options = variant.map_dat.mapping_options(hgvs_coding.ac,hdp=validator.hdp) mapping_options = sorted(mapping_options, key=itemgetter(1)) - for alt_chr in mapping_options: if liftover_level is None: - multi_list.append(variant.genomic_g.ac) + multi_list.append(variant.hgvs_genomic.ac) elif liftover_level == 'primary': if ('NC_' in alt_chr[1]) and alt_chr[2] == validator.alt_aln_method: multi_list.append(alt_chr[1]) @@ -1036,22 +1036,21 @@ def final_tx_to_multiple_genomic(variant, validator, tx_variant, liftover_level= try: # Re set ori ori = variant.map_dat.tx_exons( - variant.hgvs_coding.ac, alt_chr, + hgvs_coding.ac, alt_chr, validator.alt_aln_method, hdp=validator.hdp) # Map the variant to the genome - hgvs_alt_genomic = validator.myvm_t_to_g(variant.hgvs_coding, alt_chr, variant.no_norm_evm, + hgvs_alt_genomic = validator.myvm_t_to_g(hgvs_coding, alt_chr, variant.no_norm_evm, variant.hn, variant.map_dat) # Loop out gap code under these circumstances! - if variant.map_dat.is_gapped_map(variant.hgvs_coding.ac,hgvs_alt_genomic.ac,validator): + if variant.map_dat.is_gapped_map(hgvs_coding.ac,hgvs_alt_genomic.ac,validator): # warn on gap_compensation for gap_mapper = gapped_mapping.GapMapper(variant, validator) hgvs_alt_genomic, hgvs_coding = gap_mapper.g_to_t_gap_compensation_version3( - hgvs_alt_genomic, variant.hgvs_coding, ori, alt_chr, rec_var) - logger.info(f"gap_compensation_3 done for {variant.hgvs_coding} mapped to {hgvs_alt_genomic}") - variant.hgvs_coding = hgvs_coding + hgvs_alt_genomic, hgvs_coding, ori, alt_chr, rec_var) + logger.info(f"gap_compensation_3 done for {hgvs_coding} mapped to {hgvs_alt_genomic}") logger.info(f"hgvs_coding updated to {hgvs_coding}") # Check for mismatched sequence in dup variants @@ -1071,10 +1070,10 @@ def final_tx_to_multiple_genomic(variant, validator, tx_variant, liftover_level= multi_g.append(hgvs_alt_genomic) else: - if hgvs_alt_genomic.posedit.edit.type == variant.hgvs_coding.posedit.edit.type and \ + if hgvs_alt_genomic.posedit.edit.type == hgvs_coding.posedit.edit.type and \ "ins" not in hgvs_alt_genomic.posedit.edit.type: try: - rev_tx = variant.reverse_normalizer.normalize(variant.hgvs_coding) + rev_tx = variant.reverse_normalizer.normalize(hgvs_coding) rev_g = validator.myvm_t_to_g(rev_tx, alt_chr, variant.no_norm_evm, variant.hn, variant.map_dat) rev_g = variant.hn.normalize(rev_g) @@ -1088,7 +1087,7 @@ def final_tx_to_multiple_genomic(variant, validator, tx_variant, liftover_level= # truly gapped alignment. except KeyError: warnings = warnings + ': Suspected incomplete alignment between transcript %s and ' \ - 'genomic reference sequence %s' % (variant.hgvs_coding.ac, alt_chr) + 'genomic reference sequence %s' % (hgvs_coding.ac, alt_chr) except vvhgvs.exceptions.HGVSError as e: logger.info(str(e)) diff --git a/VariantValidator/modules/use_checking.py b/VariantValidator/modules/use_checking.py index 868f953c..894c6f49 100644 --- a/VariantValidator/modules/use_checking.py +++ b/VariantValidator/modules/use_checking.py @@ -342,80 +342,77 @@ def structure_checks(variant, validator): Primarily, this code filters out variants that cannot realistically be auto corrected and will cause the downstream functions to return errors """ - if type(variant.quibble) is not str: - input_parses = copy.deepcopy(variant.quibble) - else: - input_parses = validator.hp.parse_hgvs_variant(variant.quibble) - variant.input_parses = input_parses - variant.gene_symbol = validator.db.get_gene_symbol_from_transcript_id(variant.input_parses.ac) + + input_parses = copy.deepcopy(variant.quibble) + variant.gene_symbol = validator.db.get_gene_symbol_from_transcript_id(input_parses.ac) if variant.gene_symbol == 'none': variant.gene_symbol = '' if input_parses.type == 'g' or input_parses.type == 'm': - check = structure_checks_g(variant, validator) + check = structure_checks_g(variant, validator, input_parses) if check: return True elif input_parses.type == 'c': - check = structure_checks_c(variant, validator) + check = structure_checks_c(variant, validator, input_parses) if check: # Also check intron boundaries to provide additional warnings - if "beyond the bounds" in str(variant.warnings) and (variant.input_parses.posedit.pos.start.offset != 0 or - variant.input_parses.posedit.pos.end.offset != 0): - if variant.input_parses.posedit.pos.start.offset != 0: - variant.input_parses.posedit.pos.start.offset = 1 - if variant.input_parses.posedit.pos.end.offset != 0: - variant.input_parses.posedit.pos.end.offset =1 - hgvs_genomic_vt = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + if "beyond the bounds" in str(variant.warnings) and (input_parses.posedit.pos.start.offset != 0 or + input_parses.posedit.pos.end.offset != 0): + if input_parses.posedit.pos.start.offset != 0: + input_parses.posedit.pos.start.offset = 1 + if input_parses.posedit.pos.end.offset != 0: + input_parses.posedit.pos.end.offset =1 + hgvs_genomic_vt = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn, variant) - format_converters.remap_intronic(variant.input_parses, hgvs_genomic_vt, variant, validator) + format_converters.remap_intronic(input_parses, hgvs_genomic_vt, variant, validator) return True elif input_parses.type == 'n': - check = structure_checks_n(variant, validator) + check = structure_checks_n(variant, validator, input_parses) if check: return True else: pass -def structure_checks_g(variant, validator): +def structure_checks_g(variant, validator, input_parses): """ Structure checks for when reftype is genomic """ - if variant.input_parses.ac[:3] not in ['NC_', 'NG_', 'NT_', 'NW_']: - error = 'Invalid reference sequence identifier (' + variant.input_parses.ac + ')' + if input_parses.ac[:3] not in ['NC_', 'NG_', 'NT_', 'NW_']: + error = 'Invalid reference sequence identifier (' + input_parses.ac + ')' variant.warnings.append(error) logger.info(error) return True try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except Exception as e: if "does not agree with reference sequence ()" in str(e): - e = "The specified coordinate is outside the boundaries of reference sequence %s " % variant.input_parses.ac + e = "The specified coordinate is outside the boundaries of reference sequence %s " % input_parses.ac if "base start position must be <= end position" in str(e) and ( - "NC_012920.1" in variant.hgvs_formatted.ac or - "NC_001807.4" in variant.hgvs_formatted.ac): + "NC_012920.1" in variant.quibble.ac or + "NC_001807.4" in variant.quibble.ac): - if variant.hgvs_formatted.ac not in variant.original: + if variant.quibble.ac not in variant.original: err = "This is not a valid HGVS variant description, because no reference sequence ID has " \ - "been provided, instead use %s" % str(variant.hgvs_formatted) + "been provided, instead use %s" % str(variant.quibble) variant.warnings.append(err) variant.warnings.append("The variant positions are valid but we cannot normalize variants spanning " "the origin of circular reference sequences") return True - elif "insertion length must be 1" in str(e) and "(" in str(variant.input_parses.posedit.pos) and ")" in \ - str(variant.input_parses.posedit.pos): + elif "insertion length must be 1" in str(e) and "(" in str(input_parses.posedit.pos) and ")" in \ + str(input_parses.posedit.pos): return True - elif "insertion length must be 1" in str(e) and "(" not in str(variant.input_parses.posedit.pos) and ")" not in\ - str(variant.input_parses.posedit.pos): + elif "insertion length must be 1" in str(e) and "(" not in str(input_parses.posedit.pos) and ")" not in\ + str(input_parses.posedit.pos): ins_warning = (f'Insertion length must be 1 e.g. ' - f'{str(int(variant.input_parses.posedit.pos.start.base))}' - f'_{str(int(variant.input_parses.posedit.pos.start.base)+1)}' - f'ins{variant.input_parses.posedit.edit.alt}') + f'{str(int(input_parses.posedit.pos.start.base))}' + f'_{str(int(input_parses.posedit.pos.start.base)+1)}' + f'ins{input_parses.posedit.edit.alt}') variant.warnings.append(ins_warning) for warning in variant.warnings: if warning == "insertion length must be 1": @@ -424,7 +421,7 @@ def structure_checks_g(variant, validator): return True elif "Length implied by coordinates must equal sequence deletion length" in str(e) and \ - "(" in str(variant.input_parses.posedit.pos) and ")" in str(variant.input_parses.posedit.pos): + "(" in str(input_parses.posedit.pos) and ")" in str(input_parses.posedit.pos): return True else: error = str(e) @@ -434,7 +431,7 @@ def structure_checks_g(variant, validator): # Additional test try: - np = variant.hn.normalize(variant.input_parses) + np = variant.hn.normalize(input_parses) except vvhgvs.exceptions.HGVSError as e: error = str(e) variant.warnings.append(error) @@ -443,8 +440,8 @@ def structure_checks_g(variant, validator): # Look for variants in runs of N bases try: - if "N" in variant.input_parses.posedit.edit.ref: - error = (f"UncertainSequenceError: The submitted variant description {variant.input_parses} refers to a " + if "N" in input_parses.posedit.edit.ref: + error = (f"UncertainSequenceError: The submitted variant description {input_parses} refers to a " f"genomic reference region with " f"an uncertain base composition (N)") variant.warnings.append(error) @@ -456,7 +453,7 @@ def structure_checks_g(variant, validator): return False -def structure_checks_c(variant, validator): +def structure_checks_c(variant, validator, input_parses): """ structure checks for when reftype is coding :param variant: @@ -464,18 +461,18 @@ def structure_checks_c(variant, validator): :return: """ - if '*' in str(variant.input_parses) or 'c.-' in str(variant.input_parses): + if '*' in str(input_parses) or 'c.-' in str(input_parses): # Catch variation in UTRs # These should be in the sequence so can be directly validated. Need to pass to n. try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except vvhgvs.exceptions.HGVSError as e: error = str(e) if 'datums is ill-defined' in error: - called_ref = variant.input_parses.posedit.edit.ref + called_ref = input_parses.posedit.edit.ref try: - to_n = variant.evm.c_to_n(variant.input_parses) + to_n = variant.evm.c_to_n(input_parses) except vvhgvs.exceptions.HGVSInvalidVariantError as e: error = str(e) variant.warnings.append(error) @@ -490,61 +487,61 @@ def structure_checks_c(variant, validator): logger.info(error) return True else: - if variant.input_parses.posedit.edit.type == "ins": - variant.input_parses.posedit.edit.ref = None + if input_parses.posedit.edit.type == "ins": + input_parses.posedit.edit.ref = None else: - variant.input_parses.posedit.edit.ref = '' - variant.hgvs_formatted = variant.input_parses + input_parses.posedit.edit.ref = '' + variant.quibble = input_parses else: if 'bounds' in error or 'intronic variant' in error: try: - variant.hn.normalize(variant.input_parses) + variant.hn.normalize(input_parses) except vvhgvs.exceptions.HGVSError as e: logger.debug("Except passed, %s", e) if 'bounds' in error: try: - identity_info = validator.hdp.get_tx_identity_info(variant.input_parses.ac) + identity_info = validator.hdp.get_tx_identity_info(input_parses.ac) ref_start = identity_info[3] ref_end = identity_info[4] - if '-' in str(variant.input_parses.posedit.pos.start) and \ - variant.input_parses.posedit.pos.start.offset == 0: + if '-' in str(input_parses.posedit.pos.start) and \ + input_parses.posedit.pos.start.offset == 0: # upstream positions boundary = -ref_start - remainder = variant.input_parses.posedit.pos.start.base - boundary - variant.input_parses.posedit.pos.start.base = boundary - variant.input_parses.posedit.pos.start.offset = remainder - if '-' in str(variant.input_parses.posedit.pos.end) and \ - variant.input_parses.posedit.pos.end.offset == 0: + remainder = input_parses.posedit.pos.start.base - boundary + input_parses.posedit.pos.start.base = boundary + input_parses.posedit.pos.start.offset = remainder + if '-' in str(input_parses.posedit.pos.end) and \ + input_parses.posedit.pos.end.offset == 0: boundary = -ref_start - remainder = variant.input_parses.posedit.pos.end.base - boundary - variant.input_parses.posedit.pos.end.base = boundary - variant.input_parses.posedit.pos.end.offset = remainder - if '*' in str(variant.input_parses.posedit.pos.start) and \ - variant.input_parses.posedit.pos.start.offset == 0: + remainder = input_parses.posedit.pos.end.base - boundary + input_parses.posedit.pos.end.base = boundary + input_parses.posedit.pos.end.offset = remainder + if '*' in str(input_parses.posedit.pos.start) and \ + input_parses.posedit.pos.start.offset == 0: # downstream positions - tot_end_pos = str(variant.input_parses.posedit.pos.start).replace('*', '') - ts_seq = validator.sf.fetch_seq(variant.input_parses.ac) + tot_end_pos = str(input_parses.posedit.pos.start).replace('*', '') + ts_seq = validator.sf.fetch_seq(input_parses.ac) boundary = len(ts_seq) - ref_end - variant.input_parses.posedit.pos.start.base = boundary + input_parses.posedit.pos.start.base = boundary offset = int(tot_end_pos) - boundary - variant.input_parses.posedit.pos.start.offset = offset - if '*' in str(variant.input_parses.posedit.pos.end) and \ - variant.input_parses.posedit.pos.end.offset == 0: - tot_end_pos = str(variant.input_parses.posedit.pos.end).replace('*', '') - ts_seq = validator.sf.fetch_seq(variant.input_parses.ac) + input_parses.posedit.pos.start.offset = offset + if '*' in str(input_parses.posedit.pos.end) and \ + input_parses.posedit.pos.end.offset == 0: + tot_end_pos = str(input_parses.posedit.pos.end).replace('*', '') + ts_seq = validator.sf.fetch_seq(input_parses.ac) boundary = len(ts_seq) - ref_end - variant.input_parses.posedit.pos.end.base = boundary + input_parses.posedit.pos.end.base = boundary offset = int(tot_end_pos) - boundary - variant.input_parses.posedit.pos.end.offset = offset + input_parses.posedit.pos.end.offset = offset # Create a lose vm instance variant.lose_vm = vvhgvs.variantmapper.VariantMapper(validator.hdp, replace_reference=True, prevalidation_level=None) - report_gen = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + report_gen = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn, variant) report_gen = variant.hn.normalize(report_gen) error = 'Using a transcript reference sequence to specify a variant position that lies ' \ @@ -557,15 +554,15 @@ def structure_checks_c(variant, validator): return True try: - variant.input_parses = variant.evm.c_to_n(variant.input_parses) + input_parses = variant.evm.c_to_n(input_parses) except vvhgvs.exceptions.HGVSError as e: error = str(e) variant.warnings.append(error) logger.info(e) return True - if 'n.1-' in str(variant.input_parses): - input_parses = variant.evm.n_to_c(variant.input_parses) + if 'n.1-' in str(input_parses): + input_parses = variant.evm.n_to_c(input_parses) error = 'Using a transcript reference sequence to specify a variant position that lies outside of the ' \ 'reference sequence is not HGVS-compliant. Instead re-submit ' genomic_position = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, @@ -577,54 +574,54 @@ def structure_checks_c(variant, validator): return True # Re-map input_parses back to c. variant - variant.input_parses = variant.evm.n_to_c(variant.input_parses) + input_parses = variant.evm.n_to_c(input_parses) # Intronic positions in UTRs - if re.search(r'\d-\d', str(variant.input_parses)) or re.search(r'\d\+\d', str(variant.input_parses)): + if re.search(r'\d-\d', str(input_parses)) or re.search(r'\d\+\d', str(input_parses)): # Can we go c-g-c try: - to_genome = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + to_genome = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn, variant) - to_tx = variant.evm.g_to_t(to_genome, variant.input_parses.ac) + to_tx = variant.evm.g_to_t(to_genome, input_parses.ac) except vvhgvs.exceptions.HGVSInvalidIntervalError as e: error = str(e) if 'bounds' in error: try: - identity_info = validator.hdp.get_tx_identity_info(variant.input_parses.ac) + identity_info = validator.hdp.get_tx_identity_info(input_parses.ac) ref_start = identity_info[3] ref_end = identity_info[4] - if '-' in str(variant.input_parses.posedit.pos.start): + if '-' in str(input_parses.posedit.pos.start): # upstream positions boundary = -ref_start - remainder = variant.input_parses.posedit.pos.start.base - boundary - variant.input_parses.posedit.pos.start.base = boundary - variant.input_parses.posedit.pos.start.offset = remainder - if '-' in str(variant.input_parses.posedit.pos.end): + remainder = input_parses.posedit.pos.start.base - boundary + input_parses.posedit.pos.start.base = boundary + input_parses.posedit.pos.start.offset = remainder + if '-' in str(input_parses.posedit.pos.end): boundary = -ref_start - remainder = variant.input_parses.posedit.pos.end.base - boundary - variant.input_parses.posedit.pos.end.base = boundary - variant.input_parses.posedit.pos.end.offset = remainder - if '*' in str(variant.input_parses.posedit.pos.start): + remainder = input_parses.posedit.pos.end.base - boundary + input_parses.posedit.pos.end.base = boundary + input_parses.posedit.pos.end.offset = remainder + if '*' in str(input_parses.posedit.pos.start): # downstream positions - tot_end_pos = str(variant.input_parses.posedit.pos.start).replace('*', '') - ts_seq = validator.sf.fetch_seq(variant.input_parses.ac) + tot_end_pos = str(input_parses.posedit.pos.start).replace('*', '') + ts_seq = validator.sf.fetch_seq(input_parses.ac) boundary = len(ts_seq) - ref_end - variant.input_parses.posedit.pos.start.base = boundary + input_parses.posedit.pos.start.base = boundary te1, te2 = tot_end_pos.split('+') tot_end_pos = int(te1) + int(te2) offset = tot_end_pos - boundary - variant.input_parses.posedit.pos.start.offset = offset - if '*' in str(variant.input_parses.posedit.pos.end): - tot_end_pos = str(variant.input_parses.posedit.pos.end).replace('*', '') - ts_seq = validator.sf.fetch_seq(variant.input_parses.ac) + input_parses.posedit.pos.start.offset = offset + if '*' in str(input_parses.posedit.pos.end): + tot_end_pos = str(input_parses.posedit.pos.end).replace('*', '') + ts_seq = validator.sf.fetch_seq(input_parses.ac) boundary = len(ts_seq) - ref_end - variant.input_parses.posedit.pos.end.base = boundary + input_parses.posedit.pos.end.base = boundary te1, te2 = tot_end_pos.split('+') tot_end_pos = int(te1) + int(te2) offset = tot_end_pos - boundary - variant.input_parses.posedit.pos.end.offset = offset + input_parses.posedit.pos.end.offset = offset - report_gen = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + report_gen = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn, variant) report_gen = variant.hn.normalize(report_gen) error = 'Using a transcript reference sequence to specify a variant position that lies ' \ @@ -647,14 +644,14 @@ def structure_checks_c(variant, validator): continue gens.append(el_l[-1]) acs = '; '.join(gens) - error = 'Cannot map ' + fn.valstr(variant.input_parses) + ' to a genomic position. '\ - + variant.input_parses.ac + ' can only be partially aligned to genomic reference ' \ + error = 'Cannot map ' + fn.valstr(input_parses) + ' to a genomic position. '\ + + input_parses.ac + ' can only be partially aligned to genomic reference ' \ 'sequences ' + acs variant.warnings.append(error) logger.info(error) return True - elif re.search(r'\d-', str(variant.input_parses)) or re.search(r'\d\+', str(variant.input_parses)): + elif re.search(r'\d-', str(input_parses)) or re.search(r'\d\+', str(input_parses)): # Create a no_replace vm instance variant.no_replace_vm = vvhgvs.variantmapper.VariantMapper(validator.hdp, @@ -663,12 +660,12 @@ def structure_checks_c(variant, validator): # Quick look at syntax validation try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except vvhgvs.exceptions.HGVSInvalidVariantError as e: error = str(e) if 'bounds' in error: try: - report_gen = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + report_gen = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn, variant) report_gen = variant.hn.normalize(report_gen) except vvhgvs.exceptions.HGVSError as e: @@ -680,18 +677,18 @@ def structure_checks_c(variant, validator): logger.info(error) return True elif 'insertion length must be 1' in error: - if "-" in str(variant.input_parses.posedit.pos.start.offset): - start_offset = str(variant.input_parses.posedit.pos.start.offset - 1) - end_offset = str(variant.input_parses.posedit.pos.start.offset) - elif ("-" not in str(variant.input_parses.posedit.pos.start.offset) and - variant.input_parses.posedit.pos.start.offset != 0): - start_offset = f"+{str(variant.input_parses.posedit.pos.start.offset)}" - end_offset = f"+{str(variant.input_parses.posedit.pos.start.offset + 1)}" - if "(" not in str(variant.input_parses.posedit.pos): + if "-" in str(input_parses.posedit.pos.start.offset): + start_offset = str(input_parses.posedit.pos.start.offset - 1) + end_offset = str(input_parses.posedit.pos.start.offset) + elif ("-" not in str(input_parses.posedit.pos.start.offset) and + input_parses.posedit.pos.start.offset != 0): + start_offset = f"+{str(input_parses.posedit.pos.start.offset)}" + end_offset = f"+{str(input_parses.posedit.pos.start.offset + 1)}" + if "(" not in str(input_parses.posedit.pos): ins_warning = (f'Insertion length must be 1 e.g. ' - f'{variant.input_parses.posedit.pos.start.base}{start_offset}' - f'_{str(int(variant.input_parses.posedit.pos.start.base))}{end_offset}' - f'ins{variant.input_parses.posedit.edit.alt}') + f'{input_parses.posedit.pos.start.base}{start_offset}' + f'_{str(int(input_parses.posedit.pos.start.base))}{end_offset}' + f'ins{input_parses.posedit.edit.alt}') variant.warnings.append(ins_warning) for warning in variant.warnings: if warning == "insertion length must be 1": @@ -699,9 +696,9 @@ def structure_checks_c(variant, validator): return True elif 'base start position must be <= end position' in error: - correction = copy.deepcopy(variant.input_parses) - st = variant.input_parses.posedit.pos.start - ed = variant.input_parses.posedit.pos.end + correction = copy.deepcopy(input_parses) + st = input_parses.posedit.pos.start + ed = input_parses.posedit.pos.end correction.posedit.pos.start = ed correction.posedit.pos.end = st error = error + ': Did you mean ' + str(correction) + '?' @@ -713,34 +710,34 @@ def structure_checks_c(variant, validator): # Have to use this method due to potential multi chromosome error, note normalizes but does not replace sequence output = None try: - output = validator.noreplace_myevm_t_to_g(variant.input_parses, variant) + output = validator.noreplace_myevm_t_to_g(input_parses, variant) except vvhgvs.exceptions.HGVSDataNotAvailableError: - tx_info = validator.hdp.get_tx_identity_info(variant.input_parses.ac) - if (variant.input_parses.posedit.pos.end.base > int(tx_info[4]) or variant.input_parses.posedit.pos.end.base - > int(tx_info[4])) and ("*" not in str(variant.input_parses.posedit.pos.end) or "*" not in - str(variant.input_parses.posedit.pos.start)): + tx_info = validator.hdp.get_tx_identity_info(input_parses.ac) + if (input_parses.posedit.pos.end.base > int(tx_info[4]) or input_parses.posedit.pos.end.base + > int(tx_info[4])) and ("*" not in str(input_parses.posedit.pos.end) or "*" not in + str(input_parses.posedit.pos.start)): errors = ["CDSError: Variant start position and/or end position are beyond the CDS end position " "and likely also beyond the end of the selected reference sequence"] else: - errors = ['Required information for ' + variant.input_parses.ac + ' is missing from the Universal ' + errors = ['Required information for ' + input_parses.ac + ' is missing from the Universal ' 'Transcript Archive', 'Query gene2transcripts with search term %s for ' - 'available transcripts' % variant.input_parses.ac.split('.')[0]] + 'available transcripts' % input_parses.ac.split('.')[0]] variant.warnings.extend(errors) logger.info(str(errors)) return True except ValueError as e: error = str(e) if '> end' in error: - error = 'Interval start position ' + str(variant.input_parses.posedit.pos.start) + ' > interval end '\ - 'position ' + str(variant.input_parses.posedit.pos.end) + error = 'Interval start position ' + str(input_parses.posedit.pos.start) + ' > interval end '\ + 'position ' + str(input_parses.posedit.pos.end) variant.warnings.append(error) logger.info(error) return True except vvhgvs.exceptions.HGVSInvalidVariantError as e: error = str(e) if 'base start position must be <= end position' in error: - error = 'Interval start position ' + str(variant.input_parses.posedit.pos.start) + ' > interval end' \ - ' position ' + str(variant.input_parses.posedit.pos.end) + error = 'Interval start position ' + str(input_parses.posedit.pos.start) + ' > interval end' \ + ' position ' + str(input_parses.posedit.pos.end) variant.warnings.append(error) logger.info(error) return True @@ -750,10 +747,10 @@ def structure_checks_c(variant, validator): return True try: - variant.evm.g_to_t(output, variant.input_parses.ac) + variant.evm.g_to_t(output, input_parses.ac) except vvhgvs.exceptions.HGVSError as e: if "Alignment is incomplete" in str(e): - output = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant) + output = hgvs_utils.incomplete_alignment_mapping_t_to_g(validator, variant,input_parses) if output is None: error = str(e) variant.warnings.append(error) @@ -766,14 +763,14 @@ def structure_checks_c(variant, validator): return True # Check that the reference is correct by direct mapping without replacing reference - check_ref_g = variant.no_replace_vm.t_to_g(variant.input_parses, output.ac, + check_ref_g = variant.no_replace_vm.t_to_g(input_parses, output.ac, alt_aln_method=validator.alt_aln_method) - check_ref_t = variant.no_replace_vm.g_to_t(check_ref_g, variant.input_parses.ac, + check_ref_t = variant.no_replace_vm.g_to_t(check_ref_g, input_parses.ac, alt_aln_method=validator.alt_aln_method) # Snapshot current variant error log - if "*" in str(check_ref_t) and "*" not in str(variant.input_parses): - convert = "%s auto-mapped to %s" % (variant.input_parses, check_ref_t) + if "*" in str(check_ref_t) and "*" not in str(input_parses): + convert = "%s auto-mapped to %s" % (input_parses, check_ref_t) variant.warnings.append(convert) snap = copy.copy(variant.warnings) @@ -800,24 +797,24 @@ def structure_checks_c(variant, validator): else: # All other variation try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except vvhgvs.exceptions.HGVSUnsupportedOperationError as e: logger.debug("Except passed, %s", e) except vvhgvs.exceptions.HGVSInvalidVariantError as e: error = str(e) # This catches errors in introns if 'base start position must be <= end position' in error: - error = 'Interval start position ' + str(variant.input_parses.posedit.pos.start) + ' > interval end '\ - 'position ' + str(variant.input_parses.posedit.pos.end) - if "(" not in str(variant.input_parses.posedit.pos): + error = 'Interval start position ' + str(input_parses.posedit.pos.start) + ' > interval end '\ + 'position ' + str(input_parses.posedit.pos.end) + if "(" not in str(input_parses.posedit.pos): variant.warnings.append(error) logger.info(error) if 'insertion length must be 1' in error: - if "(" not in str(variant.input_parses.posedit.pos): + if "(" not in str(input_parses.posedit.pos): ins_warning = (f'Insertion length must be 1 e.g. ' - f'{str(int(variant.input_parses.posedit.pos.start.base))}' - f'_{str(int(variant.input_parses.posedit.pos.start.base)+1)}' - f'ins{variant.input_parses.posedit.edit.alt}') + f'{str(int(input_parses.posedit.pos.start.base))}' + f'_{str(int(input_parses.posedit.pos.start.base)+1)}' + f'ins{input_parses.posedit.edit.alt}') variant.warnings.append(ins_warning) for warning in variant.warnings: if warning == "insertion length must be 1": @@ -832,7 +829,7 @@ def structure_checks_c(variant, validator): except vvhgvs.exceptions.HGVSError as e: error = str(e) if 'bounds' in error: - error += ' (' + variant.input_parses.ac + ')' + error += ' (' + input_parses.ac + ')' variant.warnings.append(error) logger.info(error) return True @@ -840,25 +837,25 @@ def structure_checks_c(variant, validator): return False -def structure_checks_n(variant, validator): +def structure_checks_n(variant, validator, input_parses): """ structure checks for reftype nucleotide :param variant: :param validator: :return: """ - if '+' in str(variant.input_parses) or '-' in str(variant.input_parses): + if '+' in str(input_parses) or '-' in str(input_parses): # Catch variation in UTRs # These should be in the sequence so can be directly validated. Need to pass to n. try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except vvhgvs.exceptions.HGVSError as e: error = str(e) if 'intronic variant' in error: pass elif 'datums is ill-defined' in error: - called_ref = variant.input_parses.posedit.edit.ref - to_n = variant.evm.c_to_n(variant.input_parses) + called_ref = input_parses.posedit.edit.ref + to_n = variant.evm.c_to_n(input_parses) actual_ref = to_n.posedit.edit.ref if called_ref != actual_ref: error = 'Variant reference (' + called_ref + ') does not agree with reference sequence (' + \ @@ -867,27 +864,27 @@ def structure_checks_n(variant, validator): logger.info(str(error)) return True else: - variant.input_parses.posedit.edit.ref = '' - variant.hgvs_formatted = variant.input_parses + input_parses.posedit.edit.ref = '' + variant.quibble = input_parses elif 'base must be >=1 for datum = SEQ_START or CDS_END' in error: error = 'The given coordinate is outside the bounds of the reference sequence.' try: - if '-' in str(variant.input_parses.posedit.pos.start): + if '-' in str(input_parses.posedit.pos.start): # upstream positions boundary = 1 - remainder = variant.input_parses.posedit.pos.start.base - boundary + remainder = input_parses.posedit.pos.start.base - boundary remainder = remainder + 1 - variant.input_parses.posedit.pos.start.base = boundary - variant.input_parses.posedit.pos.start.offset = remainder - if '-' in str(variant.input_parses.posedit.pos.end): + input_parses.posedit.pos.start.base = boundary + input_parses.posedit.pos.start.offset = remainder + if '-' in str(input_parses.posedit.pos.end): boundary = 1 - remainder = variant.input_parses.posedit.pos.end.base - boundary + remainder = input_parses.posedit.pos.end.base - boundary remainder = remainder + 1 - variant.input_parses.posedit.pos.end.base = boundary - variant.input_parses.posedit.pos.end.offset = remainder - report_gen = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + input_parses.posedit.pos.end.base = boundary + input_parses.posedit.pos.end.offset = remainder + report_gen = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn, variant) report_gen = variant.hn.normalize(report_gen) error = 'Using a transcript reference sequence to specify a variant position that lies outside of' \ @@ -902,10 +899,10 @@ def structure_checks_n(variant, validator): logger.info(error) return True - if 'n.1-' in str(variant.input_parses): + if 'n.1-' in str(input_parses): error = 'Using a transcript reference sequence to specify a variant position that lies outside of the ' \ 'reference sequence is not HGVS-compliant. Instead re-submit ' - genomic_position = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, variant.primary_assembly, + genomic_position = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn,variant) genomic_position = variant.hn.normalize(genomic_position) error = error + fn.valstr(genomic_position) @@ -913,15 +910,15 @@ def structure_checks_n(variant, validator): logger.info(error) return True - if re.search(r'\d-', str(variant.input_parses)) or re.search(r'\d\+', str(variant.input_parses)): + if re.search(r'\d-', str(input_parses)) or re.search(r'\d\+', str(input_parses)): # Quick look at syntax validation try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except vvhgvs.exceptions.HGVSInvalidVariantError as e: error = str(e) if 'bounds' in error: try: - report_gen = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + report_gen = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn,variant) report_gen = variant.hn.normalize(report_gen) except vvhgvs.exceptions.HGVSError as e: @@ -937,9 +934,9 @@ def structure_checks_n(variant, validator): logger.info(error) return True elif 'base start position must be <= end position' in error: - correction = copy.deepcopy(variant.input_parses) - st = variant.input_parses.posedit.pos.start - ed = variant.input_parses.posedit.pos.end + correction = copy.deepcopy(input_parses) + st = input_parses.posedit.pos.start + ed = input_parses.posedit.pos.end correction.posedit.pos.start = ed correction.posedit.pos.end = st error = error + ': Did you mean ' + str(correction) + '?' @@ -950,13 +947,13 @@ def structure_checks_n(variant, validator): return True elif 'Cannot validate sequence of an intronic variant' in error: try: - test_g = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, variant.primary_assembly, + test_g = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn,variant) - variant.evm.g_to_t(test_g, variant.input_parses.ac) + variant.evm.g_to_t(test_g, input_parses.ac) except vvhgvs.exceptions.HGVSError as e: error = str(e) if 'bounds' in error: - report_gen = validator.myevm_t_to_g(variant.input_parses, variant.no_norm_evm, + report_gen = validator.myevm_t_to_g(input_parses, variant.no_norm_evm, variant.primary_assembly, variant.hn,variant) report_gen = variant.hn.normalize(report_gen) error = 'Using a transcript reference sequence to specify a variant position that lies ' \ @@ -971,12 +968,12 @@ def structure_checks_n(variant, validator): # sequence output = None try: - output = validator.noreplace_myevm_t_to_g(variant.input_parses, variant) + output = validator.noreplace_myevm_t_to_g(input_parses, variant) except vvhgvs.exceptions.HGVSDataNotAvailableError: - errors = ['Required information for ' + variant.input_parses.ac + ' is missing from the Universal ' + errors = ['Required information for ' + input_parses.ac + ' is missing from the Universal ' 'Transcript Archive', 'Query gene2transcripts with search term %s for ' - 'available transcripts' % variant.input_parses.ac.split('.')[0]] + 'available transcripts' % input_parses.ac.split('.')[0]] variant.warnings.extend(errors) logger.info(str(errors)) return True @@ -984,23 +981,23 @@ def structure_checks_n(variant, validator): error = str(e) if '> end' in error: error = 'Interval start position ' + str( - variant.input_parses.posedit.pos.start) + ' > interval end position ' + str( - variant.input_parses.posedit.pos.end) + input_parses.posedit.pos.start) + ' > interval end position ' + str( + input_parses.posedit.pos.end) variant.warnings.append(error) logger.info(error) return True except vvhgvs.exceptions.HGVSInvalidVariantError as e: error = str(e) if 'base start position must be <= end position' in error: - correction = copy.deepcopy(variant.input_parses) - st = variant.input_parses.posedit.pos.start - ed = variant.input_parses.posedit.pos.end + correction = copy.deepcopy(input_parses) + st = input_parses.posedit.pos.start + ed = input_parses.posedit.pos.end correction.posedit.pos.start = ed correction.posedit.pos.end = st # error = error + ': Did you mean ' + str(correction) + '?' error = 'Interval start position ' + str( - variant.input_parses.posedit.pos.start) + ' > interval end position ' + str( - variant.input_parses.posedit.pos.end) + input_parses.posedit.pos.start) + ' > interval end position ' + str( + input_parses.posedit.pos.end) variant.warnings.append(error) logger.info(error) return True @@ -1015,7 +1012,7 @@ def structure_checks_n(variant, validator): else: # All other variation try: - validator.vr.validate(variant.input_parses) + validator.vr.validate(input_parses) except vvhgvs.exceptions.HGVSUnsupportedOperationError as e: logger.debug("Except passed, %s", e) except vvhgvs.exceptions.HGVSInvalidVariantError as e: @@ -1039,15 +1036,15 @@ def structure_checks_n(variant, validator): # continue # This catches errors in introns if 'base start position must be <= end position' in error: - # correction = copy.deepcopy(variant.input_parses) - # st = variant.input_parses.posedit.pos.start - # ed = variant.input_parses.posedit.pos.end + # correction = copy.deepcopy(input_parses) + # st = input_parses.posedit.pos.start + # ed = input_parses.posedit.pos.end # correction.posedit.pos.start = ed # correction.posedit.pos.end = st # error = error + ': Did you mean ' + str(correction) + '?' error = 'Interval start position ' + str( - variant.input_parses.posedit.pos.start) + ' > interval end position ' + str( - variant.input_parses.posedit.pos.end) + input_parses.posedit.pos.start) + ' > interval end position ' + str( + input_parses.posedit.pos.end) logger.info(error) variant.warnings.append(error) return True @@ -1062,7 +1059,7 @@ def structure_checks_n(variant, validator): except vvhgvs.exceptions.HGVSError as e: error = str(e) if 'bounds' in error: - error = error + ' (' + variant.input_parses.ac + ')' + error = error + ' (' + input_parses.ac + ')' variant.warnings.append(error) logger.info(error) return True diff --git a/VariantValidator/modules/valoutput.py b/VariantValidator/modules/valoutput.py index 3eb95063..2c2ba131 100644 --- a/VariantValidator/modules/valoutput.py +++ b/VariantValidator/modules/valoutput.py @@ -1,6 +1,7 @@ import logging import json from VariantValidator.modules import lovd_api +from VariantValidator.modules.vrs_utils import HGVS_to_VRS logger = logging.getLogger(__name__) @@ -35,8 +36,11 @@ def format_as_dict(self, with_meta=True, test=False): if variant.is_obsolete() and variant.hgvs_transcript_variant == '': validation_obsolete_counter += 1 identification_key = 'obsolete_record_%s' % validation_obsolete_counter + elif variant.hgvs_transcript_variant: + identification_key = variant.hgvs_transcript_variant.format({ + 'max_ref_length': 0}) else: - identification_key = str(variant.hgvs_transcript_variant) + identification_key = 'None' validation_output[identification_key] = variant.output_dict(test=test) @@ -121,19 +125,22 @@ def format_as_table(self, with_meta=True): prot = '' if variant.hgvs_predicted_protein_consequence is not None: - prot = variant.hgvs_predicted_protein_consequence['tlr'] + prot = variant.hgvs_predicted_protein_consequence['prot'].format({ + 'max_ref_length': 0}) if variant.rna_data is not None: prot = variant.rna_data["translation"] grch37 = '' grch37_vcf = {'chr': '', 'pos': '', 'ref': '', 'alt': '', 'id': ''} if variant.primary_assembly_loci and 'grch37' in variant.primary_assembly_loci: - grch37 = variant.primary_assembly_loci['grch37']['hgvs_genomic_description'] + grch37 = variant.primary_assembly_loci['grch37']['hgvs_genomic_description'].format({ + 'max_ref_length': 0}) grch37_vcf = variant.primary_assembly_loci['grch37']['vcf'] grch37_vcf['id'] = '.' grch38 = '' grch38_vcf = {'chr': '', 'pos': '', 'ref': '', 'alt': '', 'id': ''} if variant.primary_assembly_loci and 'grch38' in variant.primary_assembly_loci: - grch38 = variant.primary_assembly_loci['grch38']['hgvs_genomic_description'] + grch38 = variant.primary_assembly_loci['grch38']['hgvs_genomic_description'].format({ + 'max_ref_length': 0}) grch38_vcf = variant.primary_assembly_loci['grch38']['vcf'] grch38_vcf['id'] = '.' alt_genomic = [] @@ -141,7 +148,7 @@ def format_as_table(self, with_meta=True): for alt in variant.alt_genomic_loci: for k, v in alt.items(): if k == 'grch37' or k == 'grch38': - alt_genomic.append(v['hgvs_genomic_description']) + alt_genomic.append(v['hgvs_genomic_description'].format({'max_ref_length': 0})) gene_id = '' if variant.stable_gene_ids: if 'hgnc_id' in variant.stable_gene_ids: @@ -156,16 +163,36 @@ def format_as_table(self, with_meta=True): pass if variant.rna_data is None: + if variant.hgvs_transcript_variant is not None: + hgvs_transcript = variant.hgvs_transcript_variant.format({'max_ref_length': 0}) + else: + hgvs_transcript = None + + if variant.hgvs_refseqgene_variant: + hgvs_refseqgene = variant.hgvs_refseqgene_variant.format({'max_ref_length': 0}) + else: + hgvs_refseqgene = None + + if variant.hgvs_lrg_variant: + hgvs_lrg = variant.hgvs_lrg_variant.format({'max_ref_length': 0}) + else: + hgvs_lrg = None + + if variant.hgvs_lrg_transcript_variant: + hgvs_lrg_transcript = variant.hgvs_lrg_transcript_variant.format({'max_ref_length': 0}) + else: + hgvs_lrg_transcript = None + outputstrings.append([ variant.original, '|'.join(variant.process_warnings(string_all=True)), select_tx, - variant.hgvs_transcript_variant, + hgvs_transcript, variant.genome_context_intronic_sequence, variant.refseqgene_context_intronic_sequence, - variant.hgvs_refseqgene_variant, - variant.hgvs_lrg_variant, - variant.hgvs_lrg_transcript_variant, + hgvs_refseqgene, + hgvs_lrg, + hgvs_lrg_transcript, prot, grch37, grch37_vcf['chr'], @@ -286,8 +313,31 @@ def lovd_syntax_check(self, variant): variant.lovd_corrections = lovd_corrections - - + def format_as_vrs(self): + """ + Format output into a set of VRS style outputs, does normalisation due + to HGVS->VRS differences. + """ + vrs_convert = HGVS_to_VRS() + vrs_output = {} + error_count = 0 + for variant in self.output_list: + if not variant.lose_vm: + variant.lose_vm = self.validator.lose_vm + out = vrs_convert.variant_validator_output_set_to_vrs(variant) + key = '' + if 'vrs_transcript_variant' in out: + key = out['vrs_transcript_variant']['id'] + elif 'vrs_intronic_genomic_variant' in out: + key = out['vrs_intronic_genomic_variant']['id'] + elif 'vrs_intergenic_genomic_variant' in out: + key = out['vrs_intergenic_genomic_variant']['id'] + else: + error_count = error_count + 1 + key = 'error_' + str(error_count) + # error state no output + vrs_output[key] = out + return vrs_output # # Copyright (C) 2016-2026 VariantValidator Contributors diff --git a/VariantValidator/modules/variant.py b/VariantValidator/modules/variant.py index 5b882f25..ab44d772 100644 --- a/VariantValidator/modules/variant.py +++ b/VariantValidator/modules/variant.py @@ -11,22 +11,22 @@ class Variant(object): def __init__(self, original, quibble=None, warnings=None, write=True, primary_assembly=False, order=False, selected_assembly=False, reformat_output=False, expanded_repeat=None): self.original = original - if quibble is None: + + # Managed variant mappings (hgvs)objects that are updated over the course of input processing + if quibble is None:# working version of the input variant, hgvs object after initial input processing self.quibble = original else: self.quibble = quibble - self.hgvs_formatted = None - self.hgvs_genomic = None - self.hgvs_coding = None - self.post_format_conversion = None # Used for first gapped_mapping function - self.pre_RNA_conversion = None - self.input_parses = None # quibble as hgvs variant object + self.hgvs_transcript_variant = None # specifically main hgvs transcript mapping if available + self.hgvs_genomic = None # main hgvs genomic mapping if available + self.hgvs_refseqgene_variant = None # main hgvs RefSeqGene genomic mapping, used to set LRG output data + + # Variant annotations self.transcript_type = None self.lovd_syntax_check = None self.shorthand_vcf = None self.lovd_messages = None self.lovd_corrections = None - if warnings is None: self.warnings = [] else: @@ -36,10 +36,6 @@ def __init__(self, original, quibble=None, warnings=None, write=True, primary_as self.warnings = [warnings] self.description = '' # hgnc_gene_info variable self.annotations = '' - self.coding = '' - self.coding_g = '' - self.genomic_r = '' - self.genomic_g = '' # should be a hgvs obj or nothing self.protein = '' self.write = write self.primary_assembly = primary_assembly @@ -68,10 +64,8 @@ def __init__(self, original, quibble=None, warnings=None, write=True, primary_as # Required for output self.stable_gene_ids = None - self.hgvs_transcript_variant = None # variant.coding but edited self.genome_context_intronic_sequence = None self.refseqgene_context_intronic_sequence = None - self.hgvs_refseqgene_variant = None # genomic_r but edited self.hgvs_predicted_protein_consequence = None self.hgvs_lrg_transcript_variant = None self.hgvs_lrg_variant = None # Same as hgvs_refseqgene_variant but with LRG accession @@ -242,8 +236,7 @@ def output_dict(self, test=False): except KeyError: pass try: - del self.hgvs_predicted_protein_consequence['lrg_tlr'] - del self.hgvs_predicted_protein_consequence['lrg_slr'] + del self.hgvs_predicted_protein_consequence['lrg_prot'] except KeyError: pass @@ -254,22 +247,69 @@ def output_dict(self, test=False): 'gene_ids': self.stable_gene_ids, 'annotations': self.annotations, 'transcript_description': self.description, - 'hgvs_transcript_variant': self.hgvs_transcript_variant, + 'hgvs_transcript_variant': None if self.hgvs_transcript_variant is None else \ + self.hgvs_transcript_variant.format({'max_ref_length': 0}), 'rna_variant_descriptions': self.rna_data, 'genome_context_intronic_sequence': self.genome_context_intronic_sequence, 'refseqgene_context_intronic_sequence': self.refseqgene_context_intronic_sequence, - 'hgvs_refseqgene_variant': self.hgvs_refseqgene_variant, - 'hgvs_predicted_protein_consequence': self.hgvs_predicted_protein_consequence, + 'hgvs_refseqgene_variant': None if self.hgvs_refseqgene_variant is None else \ + self.hgvs_refseqgene_variant.format({'max_ref_length': 0}), 'validation_warnings': self.process_warnings(), 'lovd_messages': self.lovd_messages, 'lovd_corrections': self.lovd_corrections, - 'hgvs_lrg_transcript_variant': self.hgvs_lrg_transcript_variant, - 'hgvs_lrg_variant': self.hgvs_lrg_variant, - 'alt_genomic_loci': self.alt_genomic_loci, - 'primary_assembly_loci': self.primary_assembly_loci, + 'hgvs_lrg_transcript_variant': None if self.hgvs_lrg_transcript_variant is None else \ + self.hgvs_lrg_transcript_variant.format({'max_ref_length': 0}), + 'hgvs_lrg_variant': None if self.hgvs_lrg_variant is None else \ + self.hgvs_lrg_variant.format({'max_ref_length': 0}), 'variant_exonic_positions': self.exonic_positions, 'reference_sequence_records': self.reference_sequence_records } + + primary_loci = None + if self.primary_assembly_loci is not None: + primary_loci = {} + for gen, dat in self.primary_assembly_loci.items(): + primary_loci[gen] = {} + primary_loci[gen]['vcf'] = dat['vcf'] + primary_loci[gen]['hgvs_genomic_description'] = \ + dat['hgvs_genomic_description'].format({ + 'max_ref_length': 0}) + dict_out['primary_assembly_loci'] = primary_loci + + alt_loci = None + if self.alt_genomic_loci is not None: + alt_loci = [] + for loc in self.alt_genomic_loci: + rep_loc = {} + for gen, dat in loc.items(): + rep_loc[gen] = {} + rep_loc[gen]['vcf'] = dat['vcf'] + rep_loc[gen]['hgvs_genomic_description'] = \ + dat['hgvs_genomic_description'].format({ + 'max_ref_length': 0}) + alt_loci.append(rep_loc) + dict_out['alt_genomic_loci'] = alt_loci + + protein_dict = None + if self.hgvs_predicted_protein_consequence is not None: + if not test: + protein_dict = {'tlr':'','slr':'','lrg_tlr':'','lrg_slr':''} + else: + protein_dict = {'tlr':'','slr':''} + if 'prot' in self.hgvs_predicted_protein_consequence: + protein_dict['tlr'] = self.hgvs_predicted_protein_consequence['prot'].format({ + 'max_ref_length': 0}) + protein_dict['slr'] = self.hgvs_predicted_protein_consequence['prot'].format({ + 'max_ref_length': 0, + 'p_3_letter':False}) + if 'lrg_prot' in self.hgvs_predicted_protein_consequence: + protein_dict['lrg_tlr'] = self.hgvs_predicted_protein_consequence['lrg_prot'].format({ + 'max_ref_length': 0}) + protein_dict['lrg_slr'] =self.hgvs_predicted_protein_consequence['lrg_prot'].format({ + 'max_ref_length': 0, + 'p_3_letter':False}) + dict_out['hgvs_predicted_protein_consequence'] = protein_dict + return dict_out def is_obsolete(self): diff --git a/VariantValidator/modules/vrs_utils.py b/VariantValidator/modules/vrs_utils.py new file mode 100644 index 00000000..48761869 --- /dev/null +++ b/VariantValidator/modules/vrs_utils.py @@ -0,0 +1,872 @@ +""" +A set of functions for handling VRS style data, for now focusing on creating +VRS output from hgvs input. +The first function makes a complete VRS output for a given completed +variantValidator output variant, for not converting data into "aleles" +(hgvs variants) and Cis-Phased Blocks, protin is not yet handled (this probably +needs Terminus) Derivative Molecule and Adjacency are not yet handled (and would +probably be inputted and processed as hgvs for the latter, or not at all for the +former, anyway. + +Each of the following are then used to produce the VRS components. +""" +import base64 +import hashlib +import os +import copy +from configparser import ConfigParser +from biocommons.seqrepo import SeqRepo +from VariantValidator.modules.complex_descriptions import FEInterval +try: + from VariantValidator.settings import CONFIG_DIR +except ImportError: + CONFIG_DIR = None +#from vvhgvs.location import Interval + +class HGVS_to_VRS: + """ + A module for creating VRS output from HGVS input + To avoid overcomplicating the core VV object this code is kept independent + from VV, however it should normally only be used by VV instances. We also + provide facilities for setting alternative ID and seq data/length sources. + """ + def __init__(self,seq_repo = False,id_fetch = False, + ref_fetch_blocksize = 10, cache = False): + """ + Module init + set "seq_repo" to use a pre-existing seqrepo connection, for now this + is somewhat direct and is used for the id_fetch too by default, but + we are set up to change this in the future if needed + set id_fetch to use a substitute other than seqrepo as a fetch location + for the ID this could be the VVTA or VV database if needed, though + currently they only work for transcripts + set ref_fetch_blocksize to change the seqrepo fetch amount, given that + the source data is block compressed the time savings from smaller + than ten are probably not worth it even for data which nearly never + "rolls" the input seq + set cache to true or a dict to cache the results for this object + instance, (or use the existing dict as a cache). This cache is + intended to be used for a single user input set, which will usually + share lots of genomic & prot mappings. It does not currently limit + the result set size or anything else. If cache is set to false + variant_validator_output_set_to_vrs will partly overrule this to + create a per output-set cache to limit the re-normalisation of + already normalised genomic mappings. + """ + if seq_repo: + self.seq_repo = seq_repo + elif CONFIG_DIR: + config = ConfigParser() + config.read(CONFIG_DIR) + seq_repo_path = os.path.join(config["seqrepo"]["location"], + config["seqrepo"]["version"]) + self.seq_repo = SeqRepo(seq_repo_path) + else: + raise ValueError("HGVS_to_VRS conversion eitehr needs a VV " + "CONFIG_DIR or a already set up seq_repo") + if id_fetch: + self.id_fetch = id_fetch + else: + self.id_fetch = self.get_vrs_id_for_seq + self.ref_fetch_blocksize = ref_fetch_blocksize + if isinstance(cache,dict): + self.cache = cache + elif cache: + self.cache = {} + else: + self.cache = False + + self.sorted_vrs_digest_keys = { + "SequenceLocation":["end", "sequenceReference","start","type"], + "Allele":["location","state","type"], + "LiteralSequenceExpression":['sequence', 'type'], + "ReferenceLengthExpression":[ + 'length','repeatSubunitLength','type'], + "LengthExpression":['length','type'], + "SequenceReference":['refgetAccession', 'type'], + } + self.vrs_digest_prefixes = { + "SequenceLocation":'ga4gh:SL.', + "Allele":'ga4gh:VA.', + } + + def variant_validator_output_set_to_vrs(self,variant): + """" + Convert a set of VV equivalent results into a VRS format, dataset. + + VV has the following fields that may contain differing HGVS data: + hgvs_transcript_variant, + genome_context_intronic_sequence, + refseqgene_context_intronic_sequence, + hgvs_refseqgene_variant, + hgvs_lrg_transcript_variant, + hgvs_lrg_variant, + alt_genomic_loci, + primary_assembly_loci, + exonic_positions, + + rna_data gets used instead of hgvs_transcript_variant when mapping n/a + hgvs_predicted_protein_consequence is used for valid transcripts + To avoid un-needed churn when variant==variant and ID/VRS ID==ID/VRS ID + we skip/reuse. + The current output structure is: + { + "selected_assembly': variant.selected_assembly, + "submitted_variant': variant.original, + + "gene_ids":{} + "hgvs_transcript_variant":VRS (if available and not intronic) + "hgvs_refseqgene_variant": VRS likewise (also tagged with redundant + "hgvs_lrg_transcript_variant" ?) + "hgvs_lrg_variant": VRS# + "predicted prot consequence":VRS? + "primary_assembly_loci":{VRS set} + "alt_genomic_loci":[VRS list] + "exonic_positions": variant.exonic_positions, # if wrt transcript + "warnings":[warn list] # Set of VariantValidator warnings: + # 'lovd_messages' 'lovd_corrections' + # include bonus "VRSConversionWarning:" for: + # "intronic type variant descriptions not allowed + # in the VRS standard" + # also add "VRS variant data for ### between + # transcript locations so gene data has not been + # attached" for r type? + } + + Add annot_level to get annotation skipped or used for partial filtering? + """ + output = { + 'selected_assembly': variant.selected_assembly, + 'submitted_variant': variant.original, + 'warnings_and_messages': { + 'validation_warnings': variant.process_warnings(), + 'lovd_messages': variant.lovd_messages, + 'lovd_corrections': variant.lovd_corrections, + }, + # Do we want to skip these 2 even as empty fields for intergenic + # transcripts? + "gene_ids":variant.stable_gene_ids, + 'transcript_description': variant.description, + } + if variant.gene_symbol: + if not output["gene_ids"]: + output["gene_ids"] = {} + if isinstance(output["gene_ids"], dict): + output["gene_ids"]['current_symbol'] = variant.gene_symbol + # annotations is already JSON (eg below), and relatively redundant with + # the other data, should add here if we want it but skip for now. + # e.g. annotation set: '{"db_xref": {"select": false, "ncbigene": + # "7840", "ensemblgene": null, "hgnc": "HGNC:428", "CCDS": null}, + # "chromosome": "2", "map": "2p13.1", "note": + # "ALMS1 centrosome and basal body associated protein", "variant": "1", + # "refseq_select": false, "mane_select": false, "ensembl_select": false, + # "mane_plus_clinical": false} + + # abort on bad output_type_flag, or other failure flag can be one of: + # 'gene' 'warning' 'mitochondrial' or 'intergenic' + if output['warnings_and_messages']['validation_warnings'] == [""] or \ + variant.output_type_flag == 'warning': + return output + pos_warnings = \ + "VRSUncertainPositionWarning:VRS description conversion of hgvs "\ + "data with unknown locations is not fully supported, normalisation"\ + " is disabled and other data, e.g. ins vs delins data, may be lost." + + if variant.rna_data: + assert not variant.hgvs_transcript_variant + assert not variant.primary_assembly_loci + # :r. RNA versions are independent of :c. versions, supposed to be + # derived directly from RNA sequencing data, and as such skip + # genomic and standard cDNA variant based validation steps. + # We add a warning message about this. For now the default one, but + # fold the output into hgvs_transcript_variant. + # content :"usage_warnings", "rna_variant","translation" and + # 'translation_slr' + # tlr translation is skipped due to vrs code relying on single + # letter alphabet + output['vrs_transcript_variant'] = self.hgvs_single_var_to_vrs( + variant.rna_data['rna_variant'],variant) + if variant.rna_data['translation_slr'].posedit: + output['vrs_predicted_protein_consequence'] = \ + self.hgvs_single_var_to_vrs( + variant.rna_data['translation_slr']) + output['warnings_and_messages']['r_type_input'] = \ + variant.rna_data['usage_warnings'] + if isinstance(variant.rna_data['rna_variant'], FEInterval): + output['warnings_and_messages']['vrs_output_warnings'] = [ + pos_warnings] + return output + + # now check for intergenic and/or intronic + reset_cache = False + if self.cache is False: + self.cache = {} + reset_cache = True + # use output flag instead? + if variant.hgvs_transcript_variant and not ( + variant.hgvs_transcript_variant.type in ['c','n'] and + self._intronic(variant.hgvs_transcript_variant)): + output['vrs_transcript_variant'] = self.hgvs_single_var_to_vrs( + variant.hgvs_transcript_variant,variant) + if variant.hgvs_refseqgene_variant: + output['vrs_refseqgene_variant'] = self.hgvs_single_var_to_vrs( + variant.hgvs_refseqgene_variant,variant) + if variant.hgvs_predicted_protein_consequence and \ + variant.hgvs_predicted_protein_consequence['prot'] and \ + variant.hgvs_predicted_protein_consequence['prot'].posedit and \ + getattr( + variant.hgvs_predicted_protein_consequence['prot'].posedit, + 'edit',False): + output['vrs_predicted_protein_consequence'] = \ + self.hgvs_single_var_to_vrs( + variant.hgvs_predicted_protein_consequence['prot']) + # LRG is redundant due to VRS checksum based seq id, but do we want + # to add it as a alias in a comment for the output object? + # the RSG/LRG vrs_predicted_protein_consequence is also skipped, do + # we want to add this too? + if isinstance(variant.hgvs_transcript_variant, FEInterval): + output['warnings_and_messages']['vrs_output_warnings'] = [ + pos_warnings] + elif variant.hgvs_transcript_variant: + # add warning for intronic data + output['warnings_and_messages']['vrs_output_warnings'] = [ ( + "VRSIntronWarning: VRS does not handle mappings shared between" + " genomic and transcript reference sequences. As such only the " + "genomic mappings for this hgvs intronic transcript " + "variant are preserved.")] + if isinstance(variant.hgvs_transcript_variant, FEInterval): + output['warnings_and_messages']['vrs_output_warnings'].append( + pos_warnings) + + if variant.hgvs_genomic: + output['vrs_intronic_genomic_variant'] = \ + self.hgvs_single_var_to_vrs(variant.hgvs_genomic) + # RSG data + if variant.hgvs_refseqgene_variant: + output['vrs_refseqgene_variant'] = self.hgvs_single_var_to_vrs( + variant.hgvs_refseqgene_variant) + elif variant.hgvs_genomic or variant.hgvs_refseqgene_variant: + if variant.hgvs_genomic: + output['vrs_intergenic_genomic_variant'] = \ + self.hgvs_single_var_to_vrs(variant.hgvs_genomic) + # RSG data + if variant.hgvs_refseqgene_variant: + output['vrs_refseqgene_variant'] = self.hgvs_single_var_to_vrs( + variant.hgvs_refseqgene_variant) + if ( + variant.hgvs_genomic and + isinstance(variant.hgvs_genomic, FEInterval)) or ( + variant.hgvs_refseqgene_variant and + isinstance(variant.hgvs_refseqgene_variant, FEInterval)): + output['warnings_and_messages']['vrs_output_warnings'] = [ + pos_warnings] + + + # continue for universally (non :r. included data) + primary_assembly_loci = {} + if variant.primary_assembly_loci: + primary_assembly_loci = variant.primary_assembly_loci + output['VRS_mappings_for_primary_assemblies'] = {} + for gen in primary_assembly_loci: + if gen in ['hg19', 'hg38']: + continue + output['VRS_mappings_for_primary_assemblies'][gen] = \ + self.hgvs_single_var_to_vrs( + primary_assembly_loci[gen][ + 'hgvs_genomic_description']) + output['VRS_mappings_for_alt_genomic_loci'] = [] + alt_genomic_loci = [] + + if variant.alt_genomic_loci: + alt_genomic_loci = variant.alt_genomic_loci + output['VRS_mappings_for_alt_genomic_loci'] = [] + for loci in alt_genomic_loci: + gen_key = list(loci.keys())[0] + if gen_key in ['hg19', 'hg38']: + continue + output['VRS_mappings_for_alt_genomic_loci'].append( + self.hgvs_single_var_to_vrs( + loci[gen_key]['hgvs_genomic_description'] + )) + # Crudely re-set cache, relies on us not caching this object in odd + # ways later, but if we do that we probably need a full caching system + # not something this simple. + if reset_cache: + self.cache = False + + return output + + def _intronic(self,hgvs): + if isinstance(hgvs.posedit.pos,FEInterval): + if (hgvs.posedit.pos.start.start.offset or + hgvs.posedit.pos.start.end.offset or + hgvs.posedit.pos.end.start.offset or + hgvs.posedit.pos.end.end.offset): + return True + elif (hgvs.posedit.pos.start.offset or hgvs.posedit.pos.end.offset): + return True + return False + + def hgvs_single_var_to_vrs(self, hgvs_variant,variant = False): + "convert a hgvs with a single location to VRS, if possible" + # first detect intronic variants and abort for them + # VRS only does the genomic sequnce in this case + if not self.cache is False: + hgvs_key = str(hgvs_variant) + if hgvs_key in self.cache: + return self.cache[hgvs_key] + if hgvs_variant.type in ['c','n','t'] and self._intronic(hgvs_variant): + return None + if hgvs_variant.type == 'c': + # fix transcript coordinates to n + if not variant: + raise ValueError( + "Meta-variant with a variant mapper needed for c->n case") + if hgvs_variant.posedit.pos.uncertain: + ref_seq = hgvs_variant.posedit.edit.ref + # handle paired ambig ends + if isinstance(hgvs_variant.posedit.pos, FEInterval): + hgvs1 = copy.deepcopy(hgvs_variant) + hgvs1.posedit.pos = hgvs1.posedit.pos.start + hgvs1 = variant.lose_vm.c_to_n(hgvs1) + hgvs2 = copy.deepcopy(hgvs_variant) + hgvs2.posedit.pos = hgvs2.posedit.pos.end + hgvs2 = variant.lose_vm.c_to_n(hgvs2) + hgvs_variant.type = 'n' + hgvs_variant.posedit.pos.start = hgvs1.posedit.pos + hgvs_variant.posedit.pos.end = hgvs2.posedit.pos + else: + hgvs_variant = variant.lose_vm.c_to_n(hgvs_variant) + # prevent ref for unc pos from being reset (eg NNN>TTTGGGTTTGGG) + hgvs_variant.posedit.edit.ref = ref_seq + else: + hgvs_variant = variant.lose_vm.c_to_n(hgvs_variant) + + # From this point genomic and transcript VRS should handle the same + # For objects IDs using child objects as variables the "canonical" + # set of content is used for the ID + + # fetch vrs id for sequence + # Derive "VRS refrence" using ID e.g. + # { + # "type": "SequenceReference", + # "refgetAccession": "SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul", + # "label": "NC_000007.14" + #} + #GA4GH Digest Prefix:None Inherent:[‘refgetAccession’, ‘type’] + vrs_ref_seq = { + "type": "SequenceReference", + "refgetAccession":self.id_fetch(hgvs_variant.ac), + "label": str(hgvs_variant.ac)} + # Handle sequences with start-stop ranges, normalisation skipped + if hgvs_variant.posedit.pos.uncertain: + # could check for FEInterval but since this sets uncertain don't + # for now. Currently we do not attempt to handle VRS types more + # complex than LiteralSequenceExpression. + # Ranges are specified but in VRS 2.1, but not + # normalisation for range input so we don't for now. + if not isinstance(hgvs_variant.posedit.pos, FEInterval): + vrs_start = [ + int(hgvs_variant.posedit.pos.start.base)-1, + int(hgvs_variant.posedit.pos.end.base)] + vrs_end = [ + int(hgvs_variant.posedit.pos.start.base)-1, + int(hgvs_variant.posedit.pos.end.base)] + else: + vrs_start = [ + int(hgvs_variant.posedit.pos.start.start.base)-1, + int(hgvs_variant.posedit.pos.start.end.base)] + vrs_end = [ + int(hgvs_variant.posedit.pos.end.start.base)-1, + int(hgvs_variant.posedit.pos.end.end.base)] + vrs_loc = { + "type": "SequenceLocation", + "sequenceReference":vrs_ref_seq, + "start":vrs_start, + "end":vrs_end, + } + # if del set length expr, if dupe likewise, else handle as + # LiteralSequenceExpression + if hgvs_variant.posedit.edit.type in 'dup': + min_len = 2* max(vrs_end[0] - vrs_start[1],0) + max_len = 2*(vrs_end[1] - vrs_start[0]) + vrs_state = { + "type": "LengthExpression", + "length": [min_len,max_len]} + elif hgvs_variant.posedit.edit.alt: + # ins, sub, inv, or delins + vrs_state = { + "type": "LiteralSequenceExpression", + "sequence": hgvs_variant.posedit.edit.alt, + } + else: + # del bases represent the deleted ref length + # so length is length - ref length + min_len = max( + vrs_end[0] - vrs_start[1] - \ + len(hgvs_variant.posedit.edit.ref), + 0) + max_len = vrs_end[1] - vrs_start[0] - \ + len(hgvs_variant.posedit.edit.ref) + vrs_state = { + "type": "LengthExpression", + "length":[min_len,max_len]} + vrs_allele = { + "type": "Allele", + "location":vrs_loc, + "state":vrs_state,} + vrs_allele['id'] = self.vrs_flatten(vrs_allele,digest=True) + if not self.cache is False: + self.cache[hgvs_key] = vrs_allele + return vrs_allele + # Derive normalised start and stop + # Derive VRS sequence location from VRS sequence and normalised location + # start by converting hgvs (1 based ins special) to vrs (0 based) + # coordinates, also handle empty refs, this probably should not be an + # issue for us in normal use but still needs to be handled for the + # general case. + ref_seq = '' + alt_seq = '' + hgvs_type = hgvs_variant.posedit.edit.type + if hgvs_type == 'ins': + #may need to switch to {"type": "Number", "value": num} post VRS 2.1 + vrs_start = int(hgvs_variant.posedit.pos.start.base) + vrs_end = int(hgvs_variant.posedit.pos.end.base) -1 + ref_seq = '' + alt_seq = hgvs_variant.posedit.edit.alt + else: + vrs_start = int(hgvs_variant.posedit.pos.start.base) -1 + vrs_end = int(hgvs_variant.posedit.pos.end.base) + ref_seq = hgvs_variant.posedit.edit.ref + if not ref_seq: + ref_seq = self.seq_repo[hgvs_variant.ac][vrs_start:vrs_end] + if hgvs_type == 'dup': + alt_seq = ref_seq + ref_seq + elif hgvs_type == 'identity': + alt_seq = ref_seq + else: + alt_seq = hgvs_variant.posedit.edit.alt + + # To finish the creation of a VRS allele we now need to derive the + # state, which could be either a ReferenceLengthExpression or a + # LiteralSequenceExpression + + # If both the ref and alt disappears on down-normalisation (or variant + # type is otherwise is equal) ReferenceLengthExpression is used with + # repeatSubunitLength set to the total length + + # cheat shortcut for = variants + if ref_seq == alt_seq: + #{ "type": "Number", "value": 55} + vrs_loc = { + "type": "SequenceLocation", + "sequenceReference":vrs_ref_seq, + "start":vrs_start, + "end":vrs_end, + } + vrs_loc["id"] = self.vrs_flatten(vrs_loc,digest=True) + vrs_state = { + "type": "ReferenceLengthExpression", + "length": vrs_end - vrs_start, + "repeatSubunitLength": vrs_end - vrs_start + } + vrs_allele = { + "type": "Allele", + "location":vrs_loc, + "state":vrs_state,} + vrs_allele["id"] = self.vrs_flatten(vrs_allele,digest=True) + if not self.cache is False: + self.cache[hgvs_key] = vrs_allele + return vrs_allele + # need to pre zero base normalise start/end + full_ref, full_alt,min_ref,min_alt,vrs_start,vrs_end,derived = \ + self.normalise_outwards( + hgvs_variant.ac, + ref_seq,alt_seq, + vrs_start, + vrs_end) + vrs_loc = { + "type": "SequenceLocation", + "sequenceReference":vrs_ref_seq, + "start":vrs_start, + "end":vrs_end, + } + vrs_loc["id"] = self.vrs_flatten(vrs_loc,digest=True) + + #Do we want to "assert min_alt or min_ref"? This should only happen on + # == and be disallowed by the above code, so skip for now. + # If both ref and alt are full post down-normalisation + # LiteralSequenceExpression is the chosen type. + # if we have a indel even minimalised then this must be a + # LiteralSequenceExpression + # If we have a pure (un-ambiguous location) ins post up-normalisation + # LiteralSequenceExpression is the chosen output type, + # for the other extreme ReferenceLengthExpressions are used for del's + if full_alt and not full_ref:#ins not norm outwards + vrs_state = { + "type": "LiteralSequenceExpression", + "sequence": full_alt, + } + elif full_ref and not full_alt: # del not norm outwards + vrs_state = { + "type": "ReferenceLengthExpression", + "length": 0, + "repeatSubunitLength": vrs_end-vrs_start + } + # For other del or ins after down-normalisation, that expand to delins + # after up-normalisation, if the ref or alt can fully be recreated from + # the flank we use ReferenceLengthExpressions + elif derived:#full_ref and full_alt is implicit beyond this point + vrs_state = { + "type": "ReferenceLengthExpression", + "length": len(full_alt), + # one of these must be 0 length, so + works as well as an if + "repeatSubunitLength":len(min_ref+min_alt) + } + # otherwise we use a LiteralSequenceExpression + elif not derived: + vrs_state = { + "type": "LiteralSequenceExpression", + "sequence": full_alt, + } + vrs_allele = { + "type": "Allele", + "location":vrs_loc, + "state":vrs_state,} + vrs_allele['id'] = self.vrs_flatten(vrs_allele,digest=True) + if not self.cache is False: + self.cache[hgvs_key] = vrs_allele + return vrs_allele + + def vrs_flatten(self, vrs_dict, dict_keys = None, digest = False): + """ + Derive a canonical VRS object or it's digest ID from a dictionaried set + of VRS type inputs. + VRS demands not the contents of the fields concatenated somehow, but + instead a RFC 8785 Canonicalised JSON object containing just said fields + (with IDs), because we can't have simple things. + Keys must be sorted alphabetically, but we trust that any given + dict_keys are pre-sorted. + We also presume that the object type is accurate, and all numbers are + integers, this fits our current use case. + The official VRS code uses 'canoicaljson' as a dependency, but given the + limits on the allowed input, this is overkill. + """ + if dict_keys: + # force specific dict keys, used in development, + # may be removed later + keys = dict_keys + else: + keys = self.sorted_vrs_digest_keys[vrs_dict['type']] + json = '{' + for key in keys: + if isinstance(vrs_dict[key],int): + json = json + f'"{key}":{vrs_dict[key]},' + elif isinstance(vrs_dict[key],str): + json = json + f'"{key}":"{vrs_dict[key]}",' + elif isinstance(vrs_dict[key],list): + assert len(vrs_dict[key]) == 2 + # 1 must be int, other may be None, start id looks like + def ga4gh_digest(seq): + digest = hashlib.sha512(seq.encode('utf-8')).digest() + url_safe_di = base64.urlsafe_b64encode(digest[:24]).decode("utf-8") + return f"ga4gh:SQ.{url_safe_di}" + """ + # we could also use VVTA for non genomic if it is faster, but this may + # need a different translation for the url safe encoding + out = self.seq_repo.translate_alias(hgvs_seq_id) + for seq_id in out: + if seq_id.startswith('ga4gh:SQ.'): + return seq_id[6:] + return False + #Failure to find ID for sequence should trigger a KeyError in SeqRepo + + def _trim(self, ref_seq, alt_seq): + """ + Trim a VRS compliant ref and alt seq to match the VRS standard + Trim suffix (trim right) first then prefix (left), this is specified as + the correct trim order in the VRS standard + """ + # Early return if we can't trim (only ins or del) + if not ref_seq or not alt_seq: + return ref_seq, alt_seq, 0, 0 + # Early return for ==, is not classically normalised + if alt_seq == ref_seq: + # return empty to flag this as == + return '', '', 0, 0 + + loop_len = len(ref_seq) + # Early return shortcut for most common single base transition + if loop_len == 1 and len(alt_seq) == 1: + return ref_seq, alt_seq, 0, 0 + + # Finally start to trim possible, hgvs delins type + #Trim Right + loop_len = min(loop_len,len(alt_seq)) + alt_len = 0 + for i in range(-1,-loop_len-1,-1): + if ref_seq[i] == alt_seq[i]: + alt_len = i + else: + break + if alt_len: + ref_seq = ref_seq[:alt_len] + alt_seq = alt_seq[:alt_len] + sufix = alt_len + + # Early return if fully trimmed i.e. one seq is a subset of the other + loop_len = min(len(ref_seq),len(alt_seq)) + if not loop_len: + return ref_seq, alt_seq, 0, sufix + + #Trim Left + alt_len = 0 + for i in range(loop_len): + if ref_seq[i] == alt_seq[i]: + alt_len = i + 1 + else: + break + if alt_len is not None: + ref_seq = ref_seq[alt_len:] + alt_seq = alt_seq[alt_len:] + + return ref_seq, alt_seq, alt_len, sufix + + def _push_r(self,target_ac,start,ref_flank,roll_seq): + """ + Function for "pushing" a minimised differing sequence along a reference + this function acts by "rolling" the diff sequence right. Used to VRS + normalise sequence locations outwards to the full possible location + span. + """ + edge_found = False + curr_roll_pos = 0 + ref_chunk_start = 0 + roll_len = len(roll_seq) + fully_derived = False + flank_section = '' + nomalised_edge = start + curr_flank_idx = 0 + while ref_flank: + for curr_flank_idx, flank_char in enumerate(ref_flank): + if flank_char != roll_seq[curr_roll_pos]: + edge_found = True + break + curr_roll_pos = curr_roll_pos + 1 + if curr_roll_pos >= roll_len: + fully_derived = True + curr_roll_pos = 0 + if edge_found: + nomalised_edge = start + ref_chunk_start + curr_flank_idx + flank_section = flank_section + \ + ref_flank[:curr_flank_idx] + break + ref_chunk_start = ref_chunk_start + len(ref_flank) + flank_section = flank_section + ref_flank + ref_flank = self.seq_repo[target_ac][ + start + ref_chunk_start: + start + ref_chunk_start + self.ref_fetch_blocksize] + if fully_derived: + bases_non_derived = 0 + else: + bases_non_derived = roll_len-curr_roll_pos + if not edge_found: + if not ref_flank: + curr_flank_idx = 0 + nomalised_edge = start + ref_chunk_start + curr_flank_idx + + return flank_section,nomalised_edge,bases_non_derived + + def _push_l(self,target_ac,start,ref_flank,roll_seq): + """ + Function for "pushing" a minimised differing sequence along a reference + this function acts by "rolling" the diff sequence left. Used to VRS + normalise sequence locations outwards to the full possible location + span. + """ + edge_found = False + curr_roll_pos = 0 + ref_chunk_start = 0 + roll_len = len(roll_seq) + fully_derived = False + curr_flank_idx = 0 + roll_seq = roll_seq[::-1] + flank_section = '' + nomalised_edge = 0 # if we stop before an edge is found then we hit 0 + while ref_flank: + curr_flank_idx = 0 + for curr_flank_idx, flank_char in enumerate(reversed(ref_flank)): + if flank_char != roll_seq[curr_roll_pos]: + edge_found = True + break + curr_roll_pos = curr_roll_pos + 1 + if curr_roll_pos >= roll_len: + fully_derived = True + curr_roll_pos = 0 + if edge_found: + nomalised_edge = start - ref_chunk_start - curr_flank_idx + if curr_flank_idx : + flank_section = ref_flank[-curr_flank_idx:] + flank_section + break + ref_chunk_start = ref_chunk_start + len(ref_flank) + flank_section = ref_flank + flank_section + # existing end was truncated due to sequence length + if start - ref_chunk_start > 0: + ref_flank = self.seq_repo[target_ac][ + max(start-ref_chunk_start-self.ref_fetch_blocksize,0): + start - ref_chunk_start] + else: + ref_flank = '' + if fully_derived: + bases_non_derived = 0 + else: + bases_non_derived = roll_len-curr_roll_pos + return flank_section,nomalised_edge,bases_non_derived + + def normalise_outwards( + self, + target_ac, + ref_seq,alt_seq, + start,end): + """ + Normalise a variant, defined in 0 based coordinates, outwards to the + limits of its ambiguity e.g. a ins GC in GCGC becomes ref GCGC alt + GCGCGC. + + This is not 'gap/mapping normalisation' like what VV does inside the + hard left/right hgvs2vcf functions, and as such ignores things like + intron-exon boundaries, which are not mentioned in the VRS standard for + normalisation, thus if we want to handle these then they need to be + handled external to this normalisation step. + + This should otherwise be equivalent to fully pushing right and left, for + ambiguous locations, at the same time. + """ + if not ref_seq: + assert start == end + else: + assert len(ref_seq) == end - start, ref_seq+str(end)+' '+str(start) + if not alt_seq: + alt_seq = '' # for del alt seq may start as None + # We first minimise then re-normalise back to full, minimalisation is + # is needed to allow "rolling" for variants like del C ins CGC in a GC + # stretch. We may be able to skip this step with hgvs pre-normalise d + # input. + orig_ref = ref_seq + orig_alt = alt_seq + ref_seq, alt_seq, prefix, suffix = self._trim(ref_seq, alt_seq) + # VRS does not do any normalisation on == (which trim to '') + if not ref_seq and not alt_seq: + return orig_ref,orig_alt,orig_ref,orig_alt,start,end,0 + # suffix is negative so handle a such + start = start + prefix + end = end + suffix + # VRS also does not push/expand normalise indels (or base transitions) + if ref_seq and alt_seq: + return ref_seq, alt_seq, ref_seq, alt_seq,\ + start, end,0 + # we must now either have an ins or a del but not a delins + + # First grab seq + ref_left_flank = '' + ref_right_flank = '' + if len(ref_seq) < self.ref_fetch_blocksize*2: + # Even if we go past the end we only get an error if we exceed the + # limits of seqrepo's index, otherwise we silently truncate! + seq = self.seq_repo[target_ac][ + max(start - self.ref_fetch_blocksize,0): + end + self.ref_fetch_blocksize] + if start - self.ref_fetch_blocksize >= 0: + ref_left_flank = seq[:self.ref_fetch_blocksize] + ref_right_flank = seq[self.ref_fetch_blocksize+len(ref_seq):] + else: + # this is simpler since we can run relative to the start + ref_left_flank = seq[:start] + ref_right_flank = seq[end:] + else: + ref_left_flank = self.seq_repo[target_ac][ + max(start - self.ref_fetch_blocksize,0):start] + ref_right_flank = self.seq_repo[target_ac][ + end:end + self.ref_fetch_blocksize] + + # Roll in both directions, this uses a roll_length and roll_seq that is + # from either the seq and length of the ins, or the seq and length of + # the del. + roll_seq = ref_seq + if alt_seq: + roll_seq = alt_seq + ref_left_flank, start, bases_non_derived_l = self._push_l( + target_ac, + start, + ref_left_flank, + roll_seq) + ref_right_flank, end, bases_non_derived_r = self._push_r( + target_ac, + end, + ref_right_flank, + roll_seq) + fully_derived = True + if bases_non_derived_r + bases_non_derived_l > len(roll_seq): + fully_derived = False + + #rf = ref_left_flank + ref_seq + ref_right_flank + #lf = ref_left_flank + alt_seq + ref_right_flank + return ref_left_flank + ref_seq + ref_right_flank,\ + ref_left_flank + alt_seq + ref_right_flank,\ + ref_seq, alt_seq,\ + start, end, fully_derived + +# +# Copyright (C) 2016-2026 VariantValidator Contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# diff --git a/VariantValidator/modules/vvDBGet.py b/VariantValidator/modules/vvDBGet.py index 4c974dd6..88773881 100644 --- a/VariantValidator/modules/vvDBGet.py +++ b/VariantValidator/modules/vvDBGet.py @@ -223,10 +223,10 @@ def get_urls(self, dict_out): if 'NM_' in dict_out['hgvs_transcript_variant'] or 'NR_' in dict_out['hgvs_transcript_variant']: report_urls['transcript'] = 'https://www.ncbi.nlm.nih.gov' \ '/nuccore/%s' % dict_out['hgvs_transcript_variant'].split(':')[0] - if 'NP_' in str(dict_out['hgvs_predicted_protein_consequence']['slr']): + if 'NP_' in str(dict_out['hgvs_predicted_protein_consequence']['prot']): report_urls['protein'] = 'https://www.ncbi.nlm.nih.gov' \ '/nuccore/%s' % str( - dict_out['hgvs_predicted_protein_consequence']['slr']).split(':')[0] + dict_out['hgvs_predicted_protein_consequence']['prot']).split(':')[0] if 'NG_' in dict_out['hgvs_refseqgene_variant']: report_urls['refseqgene'] = 'https://www.ncbi.nlm.nih.gov' \ '/nuccore/%s' % dict_out['hgvs_refseqgene_variant'].split(':')[0] @@ -247,19 +247,19 @@ def get_urls(self, dict_out): if 'ENST' in dict_out['hgvs_transcript_variant'] and str(dict_out['selected_assembly']).lower() == 'grch37': report_urls['transcript'] = 'https://grch37.ensembl.org/Homo_sapiens/Transcript/Summary?' \ 'db=core;t=%s' % dict_out['hgvs_transcript_variant'].split(':')[0] - if 'ENSP' in str(dict_out['hgvs_predicted_protein_consequence']['slr']) and str(dict_out['selected_assembly']).lower() == 'grch37': + if 'ENSP' in str(dict_out['hgvs_predicted_protein_consequence']['prot']) and str(dict_out['selected_assembly']).lower() == 'grch37': report_urls['protein'] = 'https://grch37.ensembl.org/Homo_sapiens/Transcript/ProteinSummary?' \ 'db=core;p=%s' % str( - dict_out['hgvs_predicted_protein_consequence']['slr']).split(':')[0] + dict_out['hgvs_predicted_protein_consequence']['prot']).split(':')[0] # When selected_assembly is GRCh38 if 'ENST' in dict_out['hgvs_transcript_variant'] and str(dict_out['selected_assembly']).lower() == 'grch38': report_urls['transcript'] = 'https://www.ensembl.org/Homo_sapiens/Transcript/Summary?' \ 'db=core;t=%s' % dict_out['hgvs_transcript_variant'].split(':')[0] - if 'ENSP' in str(dict_out['hgvs_predicted_protein_consequence']['slr']) and str(dict_out['selected_assembly']).lower() == 'grch38': + if 'ENSP' in str(dict_out['hgvs_predicted_protein_consequence']['prot']) and str(dict_out['selected_assembly']).lower() == 'grch38': report_urls['protein'] = 'https://www.ensembl.org/Homo_sapiens/Transcript/ProteinSummary?' \ 'db=core;p=%s' % str( - dict_out['hgvs_predicted_protein_consequence']['slr']).split(':')[0] + dict_out['hgvs_predicted_protein_consequence']['prot']).split(':')[0] # "http://www.ensembl.org/id/" ? What about historic versions????? return report_urls diff --git a/VariantValidator/modules/vvMixinCore.py b/VariantValidator/modules/vvMixinCore.py index 746f09be..b212fe5f 100644 --- a/VariantValidator/modules/vvMixinCore.py +++ b/VariantValidator/modules/vvMixinCore.py @@ -30,6 +30,7 @@ convert_seq_state_to_expanded_repeat from VariantValidator.modules.hgvs_utils import hgvs_delins_parts_to_hgvs_obj,\ unset_hgvs_obj_ref, to_vv_hgvs +from VariantValidator.modules.complex_descriptions import FEInterval logger = logging.getLogger(__name__) @@ -180,10 +181,10 @@ def validate(self, if type(my_variant.quibble) is not str: # already tidied input mapped to transcripts so no need to re-validate for user input type issues - if not my_variant.hgvs_formatted: - my_variant.hgvs_formatted = my_variant.quibble if not my_variant.reftype: my_variant.reftype = f':{my_variant.quibble.type}.' + if not my_variant.refsource: + my_variant.set_refsource() if my_variant.reftype != ':g.': toskip = self._get_transcript_info(my_variant) if toskip: @@ -216,6 +217,8 @@ def validate(self, my_variant.gene_symbol = self.db.get_gene_symbol_from_transcript_id( my_variant.quibble.ac) try: + # creates mapping from quibble, also sets + # hgvs_transcript_variant with info from genome mapping toskip = mappers.transcripts_to_gene( my_variant, self, @@ -235,7 +238,6 @@ def validate(self, my_variant.gene_symbol = '' if toskip: continue - my_variant.hgvs_transcript_variant = my_variant.quibble # set output to variant type specific if my_variant.reftype in [':n.',':t.',':c.'] and my_variant.hgvs_transcript_variant != '': @@ -403,10 +405,9 @@ def validate(self, try: # Test intronic variants for incorrect boundaries (see issue #169) test_variant = copy.copy(my_variant) - test_variant.hgvs_formatted = my_variant.quibble - if type(test_variant.hgvs_formatted) is str: - test_variant.hgvs_formatted = self.hp.parse_hgvs_variant( - test_variant.hgvs_formatted) + if type(test_variant.quibble) is str: + test_variant.quibble = self.hp.parse_hgvs_variant( + test_variant.quibble) # Create easy variant mapper (over variant mapper) and splign locked evm test_variant.evm = AssemblyMapper(self.hdp, @@ -491,8 +492,6 @@ def validate(self, # Set some configurations formatted_variant = my_variant.quibble - stash_input = my_variant.quibble - my_variant.post_format_conversion = stash_input logger.debug("Variant input formatted, proceeding to validate.") @@ -515,41 +514,39 @@ def validate(self, logger.info(str(e)) - my_variant.hgvs_formatted = formatted_variant + my_variant.quibble = formatted_variant - if 'LRG' in my_variant.hgvs_formatted.ac: - my_variant.hgvs_formatted.ac.replace('T', 't') + if 'LRG' in my_variant.quibble.ac: + my_variant.quibble.ac.replace('T', 't') else: - my_variant.hgvs_formatted.ac = my_variant.hgvs_formatted.ac.upper() + my_variant.quibble.ac = my_variant.quibble.ac.upper() - if my_variant.hgvs_formatted.type == "p" and my_variant.hgvs_formatted.posedit is None \ - and ":p.?" in str(my_variant.hgvs_formatted): + if my_variant.quibble.type == "p" and my_variant.quibble.posedit is None \ + and ":p.?" in str(my_variant.quibble): # Protein variants needed early! toskip = format_converters.proteins(my_variant, self) if toskip: continue - if hasattr(my_variant.hgvs_formatted.posedit.edit, 'alt'): - if my_variant.hgvs_formatted.posedit.edit.alt is not None: - my_variant.hgvs_formatted.posedit.edit.alt = \ - my_variant.hgvs_formatted.posedit.edit.alt.upper() - if hasattr(my_variant.hgvs_formatted.posedit.edit, 'ref'): - if my_variant.hgvs_formatted.posedit.edit.ref is not None: - my_variant.hgvs_formatted.posedit.edit.ref = \ - my_variant.hgvs_formatted.posedit.edit.ref.upper() - + if hasattr(my_variant.quibble.posedit.edit, 'alt'): + if my_variant.quibble.posedit.edit.alt is not None: + my_variant.quibble.posedit.edit.alt = \ + my_variant.quibble.posedit.edit.alt.upper() + if hasattr(my_variant.quibble.posedit.edit, 'ref'): + if my_variant.quibble.posedit.edit.ref is not None: + my_variant.quibble.posedit.edit.ref = \ + my_variant.quibble.posedit.edit.ref.upper() try: - formatted_variant = str(my_variant.hgvs_formatted) + formatted_variant = str(my_variant.quibble) except KeyError as e: - if "p" in my_variant.hgvs_formatted.type: + if "p" in my_variant.quibble.type: error = "Invalid amino acid %s stated in description %s" % ( str(e), str(my_variant.quibble.format({'p_3_letter':False}))) my_variant.warnings.append(error) continue - my_variant.set_quibble(my_variant.hgvs_formatted) logger.debug("HVGS acceptance test passed") # Check whether supported genome build is requested for non g. descriptions @@ -603,12 +600,12 @@ def validate(self, # i.e. out of bounds # skip if we have fuzzy ends - if isinstance(my_variant.quibble.posedit.pos.start, Interval) or \ - isinstance(my_variant.quibble.posedit.pos.end, Interval): + if isinstance(my_variant.quibble.posedit.pos, FEInterval) or \ + my_variant.quibble.posedit.pos.uncertain: continue - if '*' in str(my_variant.hgvs_formatted.posedit): - input_parses_copy = copy.deepcopy(my_variant.hgvs_formatted) + if '*' in str(my_variant.quibble.posedit): + input_parses_copy = copy.deepcopy(my_variant.quibble) input_parses_copy.type = "c" # Map to n. position # Create easy variant mapper (over variant mapper) and splign locked evm @@ -626,13 +623,13 @@ def validate(self, logger.info(error) continue - elif my_variant.hgvs_formatted.posedit.pos.end.base < \ - my_variant.hgvs_formatted.posedit.pos.start.base: - if my_variant.hgvs_formatted.ac not in ["NC_012920.1", "NC_001807.4"]: + elif my_variant.quibble.posedit.pos.end.base < \ + my_variant.quibble.posedit.pos.start.base: + if my_variant.quibble.ac not in ["NC_012920.1", "NC_001807.4"]: error = 'Interval end position ' +\ - str(my_variant.hgvs_formatted.posedit.pos.end.base) + \ + str(my_variant.quibble.posedit.pos.end.base) + \ ' < interval start position ' + \ - str(my_variant.hgvs_formatted.posedit.pos.start.base) + str(my_variant.quibble.posedit.pos.start.base) my_variant.warnings.append(error) logger.info(error) continue @@ -640,7 +637,7 @@ def validate(self, # Catch missing version number in refseq/ens is_version = re.compile(r"\d\.\d") if ((my_variant.refsource == 'RefSeq' or my_variant.refsource == 'ENS') and - not is_version.search(my_variant.hgvs_formatted.ac)): + not is_version.search(my_variant.quibble.ac)): error = 'RefSeq variant accession numbers MUST include a version number' my_variant.warnings.append(error) continue @@ -676,8 +673,7 @@ def validate(self, continue # RNA variants - trapped_input = str(my_variant.hgvs_formatted) - my_variant.pre_RNA_conversion = trapped_input + trapped_input = str(my_variant.quibble) toskip = format_converters.rna(my_variant, self) if toskip: continue @@ -772,16 +768,16 @@ def validate(self, # Genomic sequence variation # Check for gapped delins - if (variant.genomic_g and variant.genomic_g.posedit.edit.type == 'delins' and - variant.genomic_g.posedit.edit.alt == ""): - logger.info(f"Delins minus an ALT sequence identified {variant.genomic_g}") - variant.genomic_g = hgvs_delins_parts_to_hgvs_obj( - variant.genomic_g.ac, - variant.genomic_g.type, - variant.genomic_g.posedit.pos, + if (variant.hgvs_genomic and variant.hgvs_genomic.posedit.edit.type == 'delins' and + variant.hgvs_genomic.posedit.edit.alt == ""): + logger.info(f"Delins minus an ALT sequence identified {variant.hgvs_genomic}") + variant.hgvs_genomic = hgvs_delins_parts_to_hgvs_obj( + variant.hgvs_genomic.ac, + variant.hgvs_genomic.type, + variant.hgvs_genomic.posedit.pos, '', '') - hgvs_genomic_variant = variant.genomic_g + hgvs_genomic_variant = variant.hgvs_genomic # genomic accession logger.debug("genomic accession") @@ -793,10 +789,11 @@ def validate(self, # RefSeqGene variation logger.debug("RefSeqGene variation") - refseqgene_variant = variant.genomic_r + refseqgene_variant = variant.hgvs_refseqgene_variant if not refseqgene_variant or type(refseqgene_variant) is str and 'RefSeqGene' in refseqgene_variant: - variant.warnings.append(refseqgene_variant) + if type(refseqgene_variant) is str and 'RefSeqGene' in refseqgene_variant: + variant.warnings.append(refseqgene_variant) lrg_variant = '' refseqgene_variant = '' else: @@ -814,13 +811,14 @@ def validate(self, # Transcript sequence variation logger.debug("Transcript sequence variation") hgvs_tx_variant = None - if variant.coding: - if '(' in str(hgvs_tx_variant) and ')' in str(hgvs_tx_variant): - assert False + # ambiguous pos variants with spans instead of start and/or stop locations can't be mapped, + # yet (unlike simpler ones), so we null the mapping variables for them + if variant.hgvs_transcript_variant and not isinstance( + variant.hgvs_transcript_variant.posedit.pos,FEInterval): # transcript accession logger.debug("transcript accession") - hgvs_tx_variant = unset_hgvs_obj_ref(variant.coding) + hgvs_tx_variant = unset_hgvs_obj_ref(variant.hgvs_transcript_variant) hgvs_transcript_variant = copy.deepcopy(hgvs_tx_variant) transcript_accession = hgvs_transcript_variant.ac @@ -937,6 +935,10 @@ def validate(self, if 'NC_000' not in alt_gen_var.ac and 'NC_012920.1' not in alt_gen_var.ac and \ 'NC_001807.4' not in alt_gen_var.ac: continue + # skip if we have fuzzy ends in a currently non mappable form + if isinstance(alt_gen_var.posedit.pos.start, Interval) or \ + isinstance(alt_gen_var.posedit.pos.end, Interval): + continue try: alt_gen_var = variant.hn.normalize(alt_gen_var) except vvhgvs.exceptions.HGVSInvalidVariantError: @@ -991,11 +993,14 @@ def validate(self, # Warn not directly mapped to specified genome build if genomic_accession: - if primary_assembly.lower() not in list(primary_genomic_dicts.keys()): - errors = [str(variant.hgvs_coding) + ' is not part of genome build ' + primary_assembly] + if genomic_accession.startswith("NG_"): + # Skip for NG_ which fail to find genomic mapping (done via transcript) without extra errors + pass + elif primary_assembly.lower() not in list(primary_genomic_dicts.keys()): + errors = [str(variant.quibble) + ' is not part of genome build ' + primary_assembly] if self.alt_aln_method == "splign": - errors.append(str(variant.hgvs_coding) + ' cannot be mapped directly to genome build ' + primary_assembly) + errors.append(str(variant.quibble) + ' cannot be mapped directly to genome build ' + primary_assembly) errors.append('See alternative genomic loci or alternative genome builds for aligned genomic positions') elif self.alt_aln_method == "genebuild": @@ -1005,7 +1010,7 @@ def validate(self, elif primary_assembly == "GRCh37" or primary_assembly == "hg19": alt_build = "GRCh38" # Shows the alternative genome build too - errors.append(str(variant.hgvs_coding) + ' cannot be mapped directly to genome build ' + primary_assembly + errors.append(str(variant.quibble) + ' cannot be mapped directly to genome build ' + primary_assembly + ', did you mean ' + alt_build + '?') variant.warnings.extend(errors) @@ -1022,13 +1027,11 @@ def validate(self, gene_symbol = self.db.get_gene_symbol_from_refseq_id(refseqgene_variant.ac) variant.gene_symbol = gene_symbol - # Add predicted protein variant dictionary this is the output form so str for final is OK + # Add predicted protein variant dictionary if predicted_protein_variant != '': predicted_protein_variant_dict = {} - predicted_protein_variant_dict["slr"] = '' - predicted_protein_variant_dict["tlr"] = '' - predicted_protein_variant_dict["lrg_tlr"] = '' - predicted_protein_variant_dict["lrg_slr"] = '' + predicted_protein_variant_dict["prot"] = '' + predicted_protein_variant_dict["lrg_prot"] = '' if type(predicted_protein_variant) is not str: # add protein descriptions if not N type edit add_p_descps = True @@ -1053,12 +1056,12 @@ def validate(self, # convert UTR variants from p.? to p.(?) try: if ( - variant.hgvs_coding.posedit.pos.end.base < 0 or - variant.hgvs_coding.posedit.pos.start.datum == + variant.hgvs_transcript_variant.posedit.pos.end.base < 0 or + variant.hgvs_transcript_variant.posedit.pos.start.datum == Datum.CDS_END ): logger.info( - f"UTR variant {variant.hgvs_coding} identified. " + f"UTR variant {variant.hgvs_transcript_variant} identified. " f"Updating from p.? to p.(=)" ) @@ -1073,9 +1076,8 @@ def validate(self, # Add single letter AA code to protein descriptions - predicted_protein_variant_dict = {"tlr": str( - predicted_protein_variant.format({'max_ref_length': 0}) - ), "slr": ''} + predicted_protein_variant_dict = { + "prot":predicted_protein_variant} if re.search("[A-Z][a-z][a-z]1[A-Z][a-z][a-z]", str( predicted_protein_variant.posedit)): @@ -1100,34 +1102,22 @@ def validate(self, posedit="({aa_1}1?)") variant.warnings = cp_warnings - # Set formatted tlr - predicted_protein_variant_dict['tlr'] = \ - predicted_protein_variant.format({ - 'max_ref_length': 0}) - predicted_protein_variant_dict['slr']= \ - predicted_protein_variant.format({ - 'max_ref_length': 0, - 'p_3_letter':False}) - # set LRG outputs + # Set prot for output + predicted_protein_variant_dict['prot'] = predicted_protein_variant + # set LRG output if format_lrg is not None: - predicted_protein_variant_dict["lrg_tlr"] = \ - format_lrg + ':' + \ - predicted_protein_variant_dict["tlr"].split(':')[1] - predicted_protein_variant_dict["lrg_slr"] = \ - format_lrg + ':' + \ - predicted_protein_variant_dict["slr"].split(':')[1] + predicted_protein_variant_dict["lrg_prot"] = copy.copy( + predicted_protein_variant) + predicted_protein_variant_dict["lrg_prot"].ac = format_lrg else: - predicted_protein_variant_dict["lrg_tlr"] = '' - predicted_protein_variant_dict["lrg_slr"] = '' + predicted_protein_variant_dict["lrg_prot"] = '' except vvhgvs.exceptions.HGVSParseError as e: logger.debug("Except passed, %s", e) else: predicted_protein_variant_dict = {} - predicted_protein_variant_dict["slr"] = '' - predicted_protein_variant_dict["tlr"] = '' - predicted_protein_variant_dict["lrg_tlr"] = '' - predicted_protein_variant_dict["lrg_slr"] = '' + predicted_protein_variant_dict["prot"] = '' + predicted_protein_variant_dict["lrg_prot"] = '' # Add missing gene info which should be there (May have come from uncertain positions for example) if variant.hgvs_transcript_variant and variant.gene_symbol == '': @@ -1222,8 +1212,11 @@ def validate(self, variant.stable_gene_ids = stable_gene_ids variant.annotations = reference_annotations + + # for now string versions of equivalent variants + rel_acc variant.genome_context_intronic_sequence = genome_context_transcript_variant variant.refseqgene_context_intronic_sequence = refseqgene_context_transcript_variant + variant.hgvs_refseqgene_variant = refseqgene_variant variant.hgvs_predicted_protein_consequence = predicted_protein_variant_dict variant.hgvs_lrg_transcript_variant = lrg_transcript_variant @@ -1249,7 +1242,7 @@ def validate(self, # Add links to reference_sequence_records pre_out = { 'hgvs_transcript_variant':'', - 'hgvs_predicted_protein_consequence':{'slr':''}, + 'hgvs_predicted_protein_consequence':{'prot':''}, 'hgvs_refseqgene_variant':'', 'hgvs_lrg_variant':'', 'selected_assembly':self.selected_assembly} @@ -1259,9 +1252,10 @@ def validate(self, pre_out['hgvs_refseqgene_variant'] = variant.hgvs_refseqgene_variant.ac if variant.hgvs_lrg_variant:# is str pre_out['hgvs_lrg_variant'] = variant.hgvs_lrg_variant - if variant.hgvs_predicted_protein_consequence: - pre_out['hgvs_predicted_protein_consequence']['slr'] = \ - variant.hgvs_predicted_protein_consequence['slr'] + if variant.hgvs_predicted_protein_consequence and \ + variant.hgvs_predicted_protein_consequence['prot']: + pre_out['hgvs_predicted_protein_consequence']['prot'] = \ + variant.hgvs_predicted_protein_consequence['prot'].ac ref_records = self.db.get_urls(pre_out) if ref_records != {}: variant.reference_sequence_records = ref_records @@ -1375,7 +1369,8 @@ def validate(self, variant.alt_genomic_loci = alt_genomic_loci # Add exon numbering information see issue # - if variant.coding != "": + if variant.hgvs_transcript_variant and not isinstance( + variant.hgvs_transcript_variant.posedit.pos,FEInterval): try: exs = exon_numbering.finds_exon_number(variant, self) variant.exonic_positions = exs @@ -1607,17 +1602,12 @@ def _apply_met_variation(data): f"{variant.hgvs_lrg_transcript_variant.split(':c.')[0]}:c."\ + variant.hgvs_transcript_variant.posedit.format( {'max_ref_length': 0}) - - # Some variant objects must be strings for back-compatibility with old output - if variant.hgvs_transcript_variant: - variant.hgvs_transcript_variant = \ - variant.hgvs_transcript_variant.format({'max_ref_length': 0}) - else: + # Some variant objects must be set on successful completion for back-compatibility + # with old output (we currently assert that the object starts as None in tests but + # also assert that it has ''/{} as output, VF needs this for primary_assembly_loci) + if not variant.hgvs_transcript_variant: variant.hgvs_transcript_variant = '' - if variant.hgvs_refseqgene_variant: - variant.hgvs_refseqgene_variant = \ - variant.hgvs_refseqgene_variant.format({'max_ref_length': 0}) - else: + if not variant.hgvs_refseqgene_variant: variant.hgvs_refseqgene_variant = '' hgd = "hgvs_genomic_description" def _vcf_abrv(hgvs,vcf,max_non_abrv_len=500): @@ -1643,19 +1633,18 @@ def _vcf_abrv(hgvs,vcf,max_non_abrv_len=500): vcf["alt"] = hgvs.posedit.edit.type.upper() return vcf + if not variant.primary_assembly_loci: + variant.primary_assembly_loci = {} for gen in variant.primary_assembly_loci.keys(): variant.primary_assembly_loci[gen]['vcf'] = _vcf_abrv( variant.primary_assembly_loci[gen][hgd], variant.primary_assembly_loci[gen]['vcf']) - variant.primary_assembly_loci[gen][hgd] = \ - variant.primary_assembly_loci[gen][hgd].format({'max_ref_length': 0}) for loc in variant.alt_genomic_loci: for gen in loc.keys(): loc[gen]['vcf'] = _vcf_abrv( loc[gen][hgd], loc[gen]['vcf']) - loc[gen][hgd] = loc[gen][hgd].format({'max_ref_length': 0}) # Append to a list for return batch_out.append(variant) @@ -1777,7 +1766,7 @@ def _get_transcript_info(self, variant): Should only be called during the validator process """ logger.debug("Looking for transcript info") - hgvs_vt = variant.hgvs_formatted + hgvs_vt = variant.quibble try: self.hdp.get_tx_identity_info(str(hgvs_vt.ac)) except vvhgvs.exceptions.HGVSError as e: @@ -1793,7 +1782,7 @@ def _get_transcript_info(self, variant): if self.alt_aln_method != 'genebuild': # Gene description - requires GenBank search to get all the required info, i.e. transcript variant ID # accession number - hgvs_object = variant.hgvs_formatted + hgvs_object = variant.quibble accession = hgvs_object.ac # Look for the accession in our database # Connect to database and send request @@ -1874,7 +1863,7 @@ def _get_transcript_info(self, variant): # Ensembl databases else: # accession number - hgvs_object = variant.hgvs_formatted + hgvs_object = variant.quibble accession = hgvs_object.ac # Look for the accession in our database # Connect to database and send request diff --git a/tests/test_variant.py b/tests/test_variant.py index 09be1379..673f7cc7 100644 --- a/tests/test_variant.py +++ b/tests/test_variant.py @@ -64,18 +64,9 @@ def test_order_not_set(self): def test_all_defaults(self): var = Variant('NM_015120.4:c.34=') - self.assertEqual(var.hgvs_formatted, None) self.assertEqual(var.hgvs_genomic, None) - self.assertEqual(var.hgvs_coding, None) - self.assertEqual(var.post_format_conversion, None) - self.assertEqual(var.pre_RNA_conversion, None) - self.assertEqual(var.input_parses, None) self.assertEqual(var.description, '') - self.assertEqual(var.coding, '') - self.assertEqual(var.coding_g, '') - self.assertEqual(var.genomic_r, '') - self.assertEqual(var.genomic_g, '') self.assertEqual(var.protein, '') self.assertEqual(var.output_type_flag, 'warning') self.assertEqual(var.gene_symbol, '') diff --git a/tests/test_vrs.py b/tests/test_vrs.py new file mode 100644 index 00000000..34fd9abd --- /dev/null +++ b/tests/test_vrs.py @@ -0,0 +1,3393 @@ +""" +This test set tests on HGVS to VRS output conversion module to check first the +internal functions and then the overall outputs. + +The extra test data, particularly test checksums, is taken from the known good +output given in the VRS documentation. +""" +import copy +import os +import unittest +import json +from configparser import ConfigParser + +import vvhgvs +from vvhgvs.location import Interval +from biocommons.seqrepo import SeqRepo + +from VariantValidator.modules.vrs_utils import HGVS_to_VRS +from VariantValidator.modules.complex_descriptions import FEInterval +from VariantValidator import Validator +from VariantValidator.modules.hgvs_utils import hgvs_obj_from_existing_edit,\ + _hgvs_offset_pos_from_str_in +from VariantValidator.settings import CONFIG_DIR + +vv = Validator() +vv.alt_aln_method = "splign" + + +class MockVVData(): + "Mock class should be == to VV wrt to VRS output production" + def __init__( + self, + selected_assembly = 'GRCh38', original = 'TestOrigVarText', + warnings = None, lovd_messages = None, lovd_corrections = None, + stable_gene_ids = None, description = '', gene_symbol = '', + output_type_flag = None, rna_data = None, + hgvs_transcript_variant = '', primary_assembly_loci = None, + alt_genomic_loci = None, hgvs_predicted_protein_consequence = None, + hgvs_refseqgene_variant = None, hgvs_genomic = None): + self.selected_assembly = selected_assembly + self.original = original # submitted_variant + self.warnings = [] + if self.warnings: + self.warnings = warnings + self.lovd_messages = [] + if lovd_messages: + self.lovd_messages = lovd_messages + self.lovd_corrections = [] + if lovd_corrections: + self.lovd_corrections = lovd_corrections + self.hgvs_genomic = hgvs_genomic + + ## should be set to null if intergenic + self.stable_gene_ids = [] + if stable_gene_ids: + self.stable_gene_ids = stable_gene_ids + + self.description = description # transcript_description + self.gene_symbol = gene_symbol + + # one of 'gene' 'warning' 'mitochondrial' or 'intergenic' + self.output_type_flag = output_type_flag + + + # now set individual hgvs datasets + + # rna_data should be set to null if not r type variant, or else a dict + # containing: 'rna_variant', 'translation_slr', and 'usage_warnings' + self.rna_data = rna_data + + # non r type transcript data + self.hgvs_transcript_variant = hgvs_transcript_variant + + # mappings for transcript variant or main genomic variant given + self.primary_assembly_loci = primary_assembly_loci + self.alt_genomic_loci = alt_genomic_loci + + # prot variation + self.hgvs_predicted_protein_consequence = \ + hgvs_predicted_protein_consequence + # RSG variation + self.hgvs_refseqgene_variant = hgvs_refseqgene_variant + # VM / data provider + self.hdp = vvhgvs.dataproviders.uta.connect(pooling=True) + self.lose_vm = vvhgvs.variantmapper.VariantMapper( + self.hdp, + replace_reference=True, + prevalidation_level=None + ) # Variant mapper + def process_warnings(self): + return self.warnings + +class TestVRSOutputTrim(unittest.TestCase): + """ + Tests for the internal vrs_util.py module, particularly the HGVS_to_VRS + object's trimming function which reduces a variant to its most minimal + sequence length of differing bases. + + Tests are for: + minimise totally, + no change, + minimise from partial, + minimise from whole region, + minimise from left, and + minimise from right + + """ + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + + def test_vrs_trim_total(self): + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AACAA','AACAA') + assert ref_seq == '' + assert alt_seq == '' + assert not prefix_bases_removed and not sufix_bases_removed + + def test_vrs_trim_no_change(self): + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('ACGT','CTG') + assert ref_seq == 'ACGT' + assert alt_seq == 'CTG' + assert not prefix_bases_removed and not sufix_bases_removed + + def test_vrs_trim_both_edge_partial(self): + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AAAA','ACA') + assert ref_seq == 'AA' + assert alt_seq == 'C' + assert prefix_bases_removed == 1 + assert sufix_bases_removed == -1 + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('ACA','AAAA') + assert alt_seq == 'AA' + assert ref_seq == 'C' + assert prefix_bases_removed == 1 + assert sufix_bases_removed == -1 + + def test_vrs_trim_both_edge_total(self): + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AAAA','AACAA') + assert ref_seq == '' + assert alt_seq == 'C' + assert prefix_bases_removed == 2 + assert sufix_bases_removed == -2 + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AACAA','AAAA') + assert alt_seq == '' + assert ref_seq == 'C' + assert prefix_bases_removed == 2 + assert sufix_bases_removed == -2 + + def test_vrs_trim_from_left(self): + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AAAA','CAA') + assert ref_seq == 'AA' + assert alt_seq == 'C' + assert prefix_bases_removed == 0 + assert sufix_bases_removed == -2 + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('CAA','AAAA') + assert ref_seq == 'C' + assert alt_seq == 'AA' + assert prefix_bases_removed == 0 + assert sufix_bases_removed == -2 + + def test_vrs_trim_from_right(self): + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AAAA','AAC') + assert ref_seq == 'AA' + assert alt_seq == 'C' + assert prefix_bases_removed == 2 + assert sufix_bases_removed == 0 + ref_seq, alt_seq, prefix_bases_removed, sufix_bases_removed = \ + self.vrs._trim('AAC','AAAA') + assert ref_seq == 'C' + assert alt_seq == 'AA' + assert prefix_bases_removed == 2 + assert sufix_bases_removed == 0 + +class TestVRSOutputPush(unittest.TestCase): + """ + Tests for the internal vrs_util.py modules push left & right used in + normalisation of VRS input, the testes are: + normal push, + push already done, + push from full opposed end, + push with flank amount less than repeat unit, + push where flank fetch amount == repeat unit length, + push with flank fetch amount large enough to overlap transcript end, and + push with fetch end == end coordinate + known quirks: + ref_left_flank/ref_right_flank input needs to be set to something (or + we return as if we hit the end), for now, due to loop logic + push does not care (or know) about the difference between del an ins + push function data/key + returns: ref_(left/right)_flank, start, bases_non_derived + ref_(left/right)_flank, bases to add to the flank after roll + start, new start location + bases_non_derived are the no. of bases in the roll seq not in flank + input: target_ac, start,ref_flank, roll_seq + roll_seq is the minimised ins or del in question, + ref_(left/right)_flank is the current left/right flank, + roll_seq minimised (i.e. trimmed) version of input variant + """ + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + self.longer_vrs = copy.copy(self.vrs) + self.longer_vrs.ref_fetch_blocksize = 10 + self.mid_vrs = copy.copy(self.vrs) + self.mid_vrs.ref_fetch_blocksize = 5 + self.short_vrs = copy.copy(self.vrs) + self.short_vrs.ref_fetch_blocksize = 2 + self.ex_short_vrs = copy.copy(self.vrs) + self.ex_short_vrs.ref_fetch_blocksize = 1 + # push left data: + # NM_003073.5 + # cds start 204, seq AGAAGAAGA start 1288:1297 + # flank 'TGAGATGG' 'AGAAGAAGA' 'TCCGCGAC' + # this has a hanging part repeat of either CA or GC depending VRS + # normalisation should get the whole +partial when hgvs does not + # NM_002111.8 + # cds start 145 seq: + # CA GCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA + # start 198:261 (or rather 196:261) + # with flank is: + # CAAGTCCTTC + # CA GCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA + # ACAGCCGCCA + # longer mid seq (not used atm) + # NM_001350922.2:c.240+14TGGG[2] NC_000010.10:g.128358789_128358796= + # start seq + # NM_001330096.1 + # rep CT stop 26 + # seq CTCTCTCTCTCTCTCTCTCTCTCTCT TTCTTTCTTTCCGATGGGGAAGAGAGGGT + # NM_001330112.1 + # rep GGGGCGGGTC stop 28 (or 20 for complete segments) + # seq (with flank) GGGGCGGGTCGGGGCGGGTC GGGGCGGG CCCACGGGCGGCCGGATTTG + # end seq + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (w flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 + # seq ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATACATATATATATATATATATAT + def test_vrs_push_left_from_mid(self): + # push left from middle + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NR_110010.2',27,'C','GC') + assert start == 25 + assert bases_non_derived == 0 + assert ref_left_flank == 'GC' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_003073.5',1291,'AGA','AGA') + assert start == 1288 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGA' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_002111.8',202,'AG','CAG') + assert start == 196 + assert bases_non_derived == 0 + assert ref_left_flank == 'CAGCAG' + + def test_vrs_push_left_already_done(self): + # push left already done (from far push base) + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NR_110010.2',25,'A','GC') + assert start == 25 + assert bases_non_derived == 2 + assert ref_left_flank == '' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_003073.5',1288,'GG','AGA') + assert start == 1288 + assert bases_non_derived == 3 + assert ref_left_flank == '' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_002111.8',196,'CAAGTCCTTC','CAG') + assert start == 196 + assert bases_non_derived == 3 + assert ref_left_flank == '' + + def test_vrs_push_left_from_full_right(self): + # push left from full right + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NR_110010.2',32,'G','CG') + assert start == 25 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_003073.5',1297,'A','AGA') + assert start == 1288 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGAAGA' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_002111.8',261,'CA','GCA') + assert start == 196 + assert bases_non_derived == 0 + assert ref_left_flank == \ + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA' + + def test_vrs_push_left_from_full_right_w_fetch(self): + # push left from full right (pre fetched flank less than needed, to force internal fetch) + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_l('NR_110010.2',32,'G','CG') + assert start == 25 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_l('NM_003073.5',1297,'AGA','AGA') + assert start == 1288 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGAAGA' + + def test_vrs_push_left_fetch_lt_repeat_size(self): + # push left given flank to fetch amount less than the size repeat unit (roll_seq) + ref_left_flank, start, bases_non_derived = \ + self.ex_short_vrs._push_l('NR_110010.2',32,'G','CG') + assert start == 25 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_l('NM_003073.5',1297,'GA','AGA') + assert start == 1288 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGAAGA' + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_l('NM_001330112.1',10,'GTC', 'GGGGCGGGTC') + assert start == 0 + assert bases_non_derived == 0 + assert ref_left_flank == 'GGGGCGGGTC' + + def test_vrs_push_left_fetch_eq_repeat_size(self): + # push left, flank to fetch amount == repeat unit length (roll_seq) + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_l('NR_110010.2',32,'CG','CG') + assert start == 25 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + + def test_vrs_push_left_partial_roll(self): + # test partial roll + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NR_110010.2',27,'C','GCTGC') + assert start == 25 + assert bases_non_derived == 3 + assert ref_left_flank == 'GC' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_003073.5',1291,'AGA','AGACAGA') + assert start == 1288 + assert bases_non_derived == 4 + assert ref_left_flank == 'AGA' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_l('NM_002111.8',199,'CAG','CAGTCAG') + assert start == 196 + assert bases_non_derived == 4 + assert ref_left_flank == 'CAG' + + def test_vrs_push_left_hits_seq_start(self): + # test hits start + # NM_001330096.1 + # rep CT stop 26 + # seq CTCTCTCTCTCTCTCTCTCTCTCTCT TTCTTTCTTTCCGATGGGGAAGAGAGGGT + # NM_001330112.1 + # rep GGGGCGGGTC stop 28 (or 20 for complete segments) + # seq (with flank) GGGGCGGGTCGGGGCGGGTC GGGGCGGG CCCACGGGCGGCCGGATTTG + # test corner case roll fetch hits 0 and rolls to 0 + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_l('NM_001330096.1',4,'CT','CT') + assert start == 0 + assert bases_non_derived == 0 + assert ref_left_flank == 'CTCT' + ref_left_flank, start, bases_non_derived = self.longer_vrs._push_l( + 'NM_001330112.1',20,'GGGGCGGGTC','GGGGCGGGTC') + assert start == 0 + assert bases_non_derived == 0 + assert ref_left_flank == 'GGGGCGGGTCGGGGCGGGTC' + # test corner cases: roll fetch crosses 0 (truncated final fetch) + # and rolls to 0 + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_l('NM_001330096.1',4,'CT','CT') + assert start == 0 + assert bases_non_derived == 0 + assert ref_left_flank == 'CTCT' + ref_left_flank, start, bases_non_derived = self.longer_vrs._push_l( + 'NM_001330112.1',20,'GGGTCGGGGCGGGTC','GGGGCGGGTC') + assert start == 0 + assert bases_non_derived == 0 + assert ref_left_flank == 'GGGGCGGGTCGGGGCGGGTC' + + # Push right data + # end seq + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (w flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 + # seq ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATACATATATATATATATATATAT + # NR_110010.2 + # seq start 25:32 + # seq w flank ATGGTGGCGA GCGCGCG AGTGCAGAAG + # NM_003073.5 + # cds start 204 start 1288:1297 + # seq w flank TGAGATGG AGAAGAAGA TCCGCGAC + # this (NM_003073.5 rep) has a hanging partial repeat, either CA or GC + # depending, VRS normalisation should get whole +partial, hgvs does not + # NM_002111.8 + # cds start 145 start 198:261 or rather 196:261 + # seq with flank: + # CAAGTCCTTC + # CA GCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA + # ACAGCCGCCA + # longer mid seq (not used atm) + # NM_001350922.2:c.240+14TGGG[2] NC_000010.10:g.128358789_128358796= + # start seq + # NM_001330096.1 + # rep CT stop 26 + # seq CTCTCTCTCTCTCTCTCTCTCTCTCT TTCTTTCTTTCCGATGGGGAAGAGAGGGT + # NM_001330112.1 + # rep GGGGCGGGTC stop 28 (or 20 for complete segments) + # seq (with flank) GGGGCGGGTCGGGGCGGGTC GGGGCGGG CCCACGGGCGGCCGGATTTG + # end seq + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (w flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 + # seq ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATAC ATATATATATATATATATAT + + def test_vrs_push_right_from_middle(self): + # push right from middle + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NR_110010.2',27,'G','GC') + assert start == 32 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCG' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_003073.5',1291,'AGA','AGA') + assert start == 1297 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGA' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_002111.8',202,'CA','CAG') + assert start == 261 + assert bases_non_derived == 0 + assert ref_left_flank == \ + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA' + + def test_vrs_push_right_already_done(self): + # push right already done (from far push base) + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NR_110010.2',32,'A','GC') + assert start == 32 + assert bases_non_derived == 2 + assert ref_left_flank == '' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_003073.5',1297,'TCCGCGAC','AGA') + assert start == 1297 + assert bases_non_derived == 3 + assert ref_left_flank == '' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_002111.8',261,'ACAGCCGCCA','CAG') + assert start == 261 + assert bases_non_derived == 3 + assert ref_left_flank == '' + + def test_vrs_push_right_from_full_left(self): + # push right from full left + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NR_110010.2',25,'G','GC') + assert start == 32 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_003073.5',1288,'A','AGA') + assert start == 1297 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGAAGA' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_002111.8',196,'CA','CAG') + assert start == 261 + assert bases_non_derived == 0 + assert ref_left_flank == \ + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA' + + def test_vrs_push_right_from_full_left_w_forced_fetch(self): + # push right from full left (pre fetched flank less than needed, to force internal fetch) + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_r('NR_110010.2',25,'G','GC') + assert start == 32 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_r('NM_003073.5',1288,'AGA','AGA') + assert start == 1297 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGAAGA' + + def test_vrs_push_right_fetch_lt_repeat_unit(self): + # push right given flank to fetch amount less than the size repeat unit (roll_seq) + ref_left_flank, start, bases_non_derived = \ + self.ex_short_vrs._push_r('NR_110010.2',25,'G','GC') + assert start == 32 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_r('NM_003073.5',1288,'AG','AGA') + assert start == 1297 + assert bases_non_derived == 0 + assert ref_left_flank == 'AGAAGAAGA' + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_r('NM_001330112.1',0,'GG', 'GGGGCGGGTC') + assert start == 28 + assert bases_non_derived == 0 + assert ref_left_flank == 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG' + + def test_vrs_push_right_fetch_eq_repeat_unit(self): + # push right, flank to fetch amount == repeat unit length (roll_seq) + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_r('NR_110010.2',25,'GC','GC') + assert start == 32 + assert bases_non_derived == 0 + assert ref_left_flank == 'GCGCGCG' + + def test_vrs_push_right_partial_roll(self): + # test partial roll + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NR_110010.2',27,'G','GCTGC') + assert start == 29 + assert bases_non_derived == 3 + assert ref_left_flank == 'GC' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_003073.5',1288,'AGA','AGACAGA') + assert start == 1291 + assert bases_non_derived == 4 + assert ref_left_flank == 'AGA' + ref_left_flank, start, bases_non_derived = \ + self.vrs._push_r('NM_002111.8',196,'CAG','CAGTCAG') + assert start == 199 + assert bases_non_derived == 4 + assert ref_left_flank == 'CAG' + + def test_vrs_push_right_hits_end(self): + # test hits end + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (w flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 + # seq ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATAC ATATATATATATATATATAT' + # test corner case roll fetch hits last base and rolls to last base + ref_left_flank, start, bases_non_derived = \ + self.mid_vrs._push_r('NM_052928.3',4375,'GTT','GTTTT') + assert start == 4405 + assert bases_non_derived == 0 + assert ref_left_flank == 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT' + ref_left_flank, start, bases_non_derived = \ + self.short_vrs._push_r('NM_002129.4',1421,'AT','AT') + assert start == 1441 + assert bases_non_derived == 0 + assert ref_left_flank == 'ATATATATATATATATATAT' + def test_vrs_push_right_hits_end_handle_last_fetch_gt_final_base(self): + # test corner case roll fetch crosses last base (truncated final fetch) + # and rolls to last base + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_r('NM_052928.3',4375,'GTT','GTTTT') + assert start == 4405 + assert bases_non_derived == 0 + assert ref_left_flank == 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT' + ref_left_flank, start, bases_non_derived = \ + self.longer_vrs._push_r('NM_002129.4',1421,'AT','AT') + assert start == 1441 + assert bases_non_derived == 0 + assert ref_left_flank == 'ATATATATATATATATATAT' + +class TestVRSOutputNorm(unittest.TestCase): + """ + Tests for the internal vrs_util.py module's HGVS_to_VRS object's + normalise_outwards function, which should minimise and then push outwards + (in both directions) test structure as for the push left/right above. + known quirks: + Because the first step is minimise, followed by rolling the seq in both + directions the actual minimised seq can differ in roll position + e.g. CG vs GC. But otherwise for the same underlying variant the + results should always be the same, + push function data/key: + returns: full_ref, full_alt,min_ref,min_alt,vrs_start,vrs_end,derived + full_ref: normalised (maximised) version of the ref + full_alt: normalised (maximised) version of the alt + min_ref & min_alt: minimised version of the same, (used along with + derived_state for variant type detection) + derived_state: True/False, is the variant derived from the + immediate flank (eg cnv/tandem repeat) + input: ref_ac, ref_seq,alt_seq, vrs_start, vrs_end + ref_ac is the accession that the change is relevant for + ref_seq is the original sequence of the region + alt_seq is the sequence of the variant in question + vrs_start & vrs_end are the 0 based VRS type coordinates of the ref + location + tests done: + Done for both del and ins: + expand left, expand right, expand both directions, expand already done, + expand flank fetch amount less than repeat unit, expand flank amount + same as repeat unit, expand flank fetch amount large enough to overlap + transcript start/end, expand flank amount == start/end coordinate from + current + + Also test early abort on == data. + """ + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + self.ex_short_vrs = copy.copy(self.vrs) + self.ex_short_vrs.ref_fetch_blocksize = 1 + # NR_110010.2 + # seq start 25:32 + # seq with flank ATGGTGGCGA GCGCGCG AGTGCAGAAG + # NM_003073.5 + # cds start 204 start 1288:1297 + # seq with flank TGAGATGG AGAAGAAGA TCCGCGAC + # this has a hanging part repeat of either CA or GC depending + # VRS normalisation should get the whole +partial when hgvs does not + # NM_002111.8 + # cds start 145 start 198:261 or rather 196:261 + # seq with flank: + # CAAGTCCTTC + # CA GCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA + # ACAGCCGCCA + # longer mid seq (not used atm) + # NM_001350922.2:c.240+14TGGG[2] NC_000010.10:g.128358789_128358796= + # start seq + # NM_001330096.1 + # rep CT stop 26 + # seq CTCTCTCTCTCTCTCTCTCTCTCTCT TTCTTTCTTTCCGATGGGGAAGAGAGGGT + # NM_001330112.1 + # rep GGGGCGGGTC stop 28 (or 20 for complete segments) + # seq (with flank) GGGGCGGGTCGGGGCGGGTC GGGGCGGG CCCACGGGCGGCCGGATTTG + # end seq + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (with flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 + # seq ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATACATATATATATATATATATAT + + # result set key: + # full_ref, full_alt, min_ref, min_alt, vrs_start, vrs_end, derived + self.NR_110010_2_ins_result_set = ( + 'GCGCGCG','GCGCGCGCG','','CG',25,32,True) + self.NR_110010_2_ins_result_set_end = ( + 'GCGCGCG','GCGCGCGCG','','GC',25,32,True) + self.NM_003073_5_ins_result_set = ( + 'AGAAGAAGA','AGAAGAAGAAGA','','AGA',1288,1297,True) + self.NM_002111_8_ins_result_set = ( + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + '','CAG',196,261,True) + self.NM_002111_8_ins_result_set_end = ( + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + '','GCA',196,261,True) + # start seq + # NM_001330096.1 + # rep CT stop 26 + # seq CTCTCTCTCTCTCTCTCTCTCTCTCT TTCTTTCTTTCCGATGGGGAAGAGAGGGT + # NM_001330112.1 + # rep GGGGCGGGTC stop 28 (or 20 for complete segments) + # seq (with flank) GGGGCGGGTCGGGGCGGGTC GGGGCGGG CCCACGGGCGGCCGGATTTG + # end seq + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (w flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 + # seq ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATACATATATATATATATATATAT + self.res_set_start_NM_001330096_1 = ( + 'CTCTCTCTCTCTCTCTCTCTCTCTCT', + 'CTCTCTCTCTCTCTCTCTCTCTCTCTCT', + '','TC',0,26,True) + self.res_set_start_NM_001330096_1_end = ( + 'CTCTCTCTCTCTCTCTCTCTCTCTCT', + 'CTCTCTCTCTCTCTCTCTCTCTCTCTCT', + '','CT',0,26,True) + self.res_set_start_NM_001330112_1 = ( + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGGTCGGGGCGGG', + '','GGGGCGGGTC',0,28,True) + self.res_set_start_NM_001330112_1_end = ( + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGGTCGGGGCGGG', + '','TCGGGGCGGG',0,28,True) + + self.res_set_end_NM_052928_3 = ( + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + '','GTTTT',4375,4405,True) + self.res_set_end_NM_052928_3_end = ( + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + '','TTGTT' ,4375,4405,True) + self.res_set_end_NM_002129_4 = ( + 'ATATATATATATATATATAT', + 'ATATATATATATATATATATAT', + '','AT',1421,1441,True) + self.res_set_end_NM_002129_4_end = ( + 'ATATATATATATATATATAT', + 'ATATATATATATATATATATAT', + '','TA',1421,1441,True) + + # del + self.NR_110010_2_del_result_set = ( + 'GCGCGCG','GCGCG','CG','',25,32,True) + self.NR_110010_2_del_result_set_end = ( + 'GCGCGCG','GCGCG','GC','',25,32,True) + self.NM_003073_5_del_result_set = ( + 'AGAAGAAGA','AGAAGA','AGA','',1288,1297,True) + self.NM_002111_8_del_result_set = ( + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAG','',196,261,True) + self.NM_002111_8_del_result_set_end = ( + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'GCA','',196,261,True) + # start seq + # NM_001330096.1 + # rep CT stop 26 + # seq CTCTCTCTCTCTCTCTCTCTCTCTCT TTCTTTCTTTCCGATGGGGAAGAGAGGGT + # NM_001330112.1 + # rep GGGGCGGGTC stop 28 (or 20 for complete segments) + # seq (with flank) GGGGCGGGTCGGGGCGGGTC GGGGCGGG CCCACGGGCGGCCGGATTTG + # end seq + # NM_052928.3 + # rep GTTTT start 4375 len 4405 + # seq (with flank): + # ATAACACATATGCCTCCTTCTGAGTTGTTG GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT + # NM_002129.4 + # rep AT start 1408 len 1441 seq + # ATTAGCTAAATTGTTCCTCAGGTGTGTG T ATATATATATACATATATATATATATATATAT + self.res_set_d_start_NM_001330096_1 = ( + 'CTCTCTCTCTCTCTCTCTCTCTCTCT', + 'CTCTCTCTCTCTCTCTCTCTCTCT', + 'TC','',0,26,True) + self.res_set_d_start_NM_001330096_1_end = ( + 'CTCTCTCTCTCTCTCTCTCTCTCTCT', + 'CTCTCTCTCTCTCTCTCTCTCTCT', + 'CT','',0,26,True) + self.res_set_d_start_NM_001330112_1 = ( + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTC','',0,28,True) + self.res_set_d_start_NM_001330112_1_end = ( + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTCGGGGCGGG', + 'TCGGGGCGGG','',0,28,True) + + self.res_set_d_end_NM_052928_3 = ( + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTT', '', 4375,4405,True) + self.res_set_d_end_NM_052928_3_end = ( + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTTGTTTTGTTTTGTTTTGTTTT', + 'TTGTT', '', 4375,4405,True) + self.res_set_d_end_NM_002129_4 = ( + 'ATATATATATATATATATAT', + 'ATATATATATATATATAT', + 'AT','',1421,1441,True) + self.res_set_d_end_NM_002129_4_end = ( + 'ATATATATATATATATATAT', + 'ATATATATATATATATAT', + 'TA','',1421,1441,True) + + def test_vrs_normalise_outwards(self): + # == return un-trimmed + res = self.vrs.normalise_outwards('NR_110010.2', 'CG','CG', 31,33) + NR_110010_2_eq_set = ('CG','CG','CG','CG',31,33,0) + assert res == NR_110010_2_eq_set + + def test_vrs_normalise_outwards_ex_left_ins(self): + # expand left ins + res = self.vrs.normalise_outwards('NR_110010.2', '','CG', 32,32) + assert res == self.NR_110010_2_ins_result_set + res = self.vrs.normalise_outwards('NR_110010.2', 'G','GCG', 31,32) + assert res == self.NR_110010_2_ins_result_set_end + res = self.vrs.normalise_outwards('NM_003073.5', '','AGA', 1297,1297) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGA','AGAAGA', 1294,1297) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards('NM_002111.8', '','GCA', 261,261) + assert res == self.NM_002111_8_ins_result_set_end + res = self.vrs.normalise_outwards('NM_002111.8', 'CA','CAGCA', 259,261) + assert res == self.NM_002111_8_ins_result_set + + def test_vrs_normalise_outwards_ex_right_ins(self): + # expand right ins + res = self.vrs.normalise_outwards('NR_110010.2', '','GC', 25,25) + assert res == self.NR_110010_2_ins_result_set_end + res = self.vrs.normalise_outwards('NR_110010.2', 'G','GCG', 25,26) + assert res == self.NR_110010_2_ins_result_set_end + res = self.vrs.normalise_outwards('NM_003073.5', '','AGA', 1288,1288) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGA','AGAAGA', 1288,1291) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards('NM_002111.8', '','CAG', 196,196) + assert res == self.NM_002111_8_ins_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'CA','CAGCA', 196,198) + assert res == self.NM_002111_8_ins_result_set + + def test_vrs_normalise_outwards_ex_both_ins(self): + # expand both directions ins + res = self.vrs.normalise_outwards('NR_110010.2', '','GC', 27,27) + assert res == self.NR_110010_2_ins_result_set_end + res = self.vrs.normalise_outwards('NR_110010.2', 'G','GCG', 27,28) + assert res == self.NR_110010_2_ins_result_set_end + res = self.vrs.normalise_outwards('NM_003073.5', '','AGA', 1291,1291) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGA','AGAAGA', 1291,1294) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards('NM_002111.8', '','CAG', 202,202) + assert res == self.NM_002111_8_ins_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'CA','CAGCA', 202,204) + assert res == self.NM_002111_8_ins_result_set + + def test_vrs_normalise_outwards_ex_done_ins(self): + # expand already done ins + res = self.vrs.normalise_outwards( + 'NR_110010.2', 'GCGCGCG','GCGCGCGCG', 25,32) + assert res == self.NR_110010_2_ins_result_set_end + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGAAGAAGA','AGAAGAAGAAGA', 1288,1297) + assert res == self.NM_003073_5_ins_result_set + res = self.vrs.normalise_outwards( + 'NM_002111.8', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 196,261) + assert res == self.NM_002111_8_ins_result_set + + def test_vrs_normalise_outwards_ex_into_start_ins(self): + # expand into start ins + res = self.vrs.normalise_outwards('NM_001330096.1', '','CT', 24,24) + assert res == self.res_set_start_NM_001330096_1_end + res = self.vrs.normalise_outwards('NM_001330096.1', 'T','TCT', 23,24) + assert res == self.res_set_start_NM_001330096_1 + res = self.vrs.normalise_outwards( + 'NM_001330112.1', '','GGGGCGGGTC', 10,10) + assert res == self.res_set_start_NM_001330112_1 + res = self.vrs.normalise_outwards( + 'NM_001330112.1', 'TC','TCGGGGCGGGTC', 8,10) + assert res == self.res_set_start_NM_001330112_1_end + + def test_vrs_normalise_outwards_ex_into_start_done_ins(self): + # expand start already done ins + res = self.vrs.normalise_outwards( + 'NM_001330096.1', + 'CTCTCTCTCTCTCTCTCTCTCTCTCT', + 'CTCTCTCTCTCTCTCTCTCTCTCTCTCT', + 0,26) + assert res == self.res_set_start_NM_001330096_1_end + res = self.vrs.normalise_outwards( + 'NM_001330112.1', + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 0,28) + assert res == self.res_set_start_NM_001330112_1 + + + def test_vrs_normalise_outwards_ex_into_end_ins(self): + # expand into end ins + res = self.vrs.normalise_outwards('NM_052928.3', '','GTTTT', 4380,4380) + assert res == self.res_set_end_NM_052928_3 + res = self.vrs.normalise_outwards( + 'NM_052928.3', 'TT','TTGTTTT', 4378,4380) + assert res == self.res_set_end_NM_052928_3_end + res = self.vrs.normalise_outwards('NM_002129.4', '','TA', 1422,1422) + assert res == self.res_set_end_NM_002129_4_end + res = self.vrs.normalise_outwards('NM_002129.4', 'TA','TATA', 1422,1424) + assert res == self.res_set_end_NM_002129_4_end + + def test_vrs_normalise_outwards_ex_into_end_done_ins(self): + # expand end already done ins + res = self.vrs.normalise_outwards( + 'NM_052928.3', + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 4375,4405) + assert res == self.res_set_end_NM_052928_3 + res = self.vrs.normalise_outwards( + 'NM_002129.4', + 'ATATATATATATATATATAT', + 'ATATATATATATATATATATAT', + 1421,1441) + assert res == self.res_set_end_NM_002129_4 + + + ##DEL SET## + def test_vrs_normalise_outwards_ex_left_del(self): + # expand left del + res = self.vrs.normalise_outwards('NR_110010.2', 'CG','', 30,32) + assert res == self.NR_110010_2_del_result_set + res = self.vrs.normalise_outwards('NR_110010.2', 'GCG','G', 29,32) + assert res == self.NR_110010_2_del_result_set_end + res = self.vrs.normalise_outwards('NM_003073.5', 'AGA','', 1294,1297) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGAAGA','AGA', 1291,1297) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'GCA','', 258,261) + assert res == self.NM_002111_8_del_result_set_end + res = self.vrs.normalise_outwards('NM_002111.8', 'CAGCA','CA', 256,261) + assert res == self.NM_002111_8_del_result_set + + def test_vrs_normalise_outwards_ex_left_del_flank_sepfetch(self): + # repeat left shift with self.ex_short_vrs to test flank fetch with: + # minimised ref length > 2*flank fetch + # this is used as the cut-off for fetching flanks separately + res = self.ex_short_vrs.normalise_outwards( + 'NM_003073.5', 'AGA','', 1294,1297) + assert res == self.NM_003073_5_del_result_set + res = self.ex_short_vrs.normalise_outwards( + 'NM_003073.5', 'AGAAGA','AGA', 1291,1297) + assert res == self.NM_003073_5_del_result_set + res = self.ex_short_vrs.normalise_outwards( + 'NM_002111.8', 'GCA','', 258,261) + assert res == self.NM_002111_8_del_result_set_end + res = self.ex_short_vrs.normalise_outwards( + 'NM_002111.8', 'CAGCA','CA', 256,261) + assert res == self.NM_002111_8_del_result_set + + def test_vrs_normalise_outwards_ex_right_del(self): + # expand right del + res = self.vrs.normalise_outwards('NR_110010.2', 'GC','', 25,27) + assert res == self.NR_110010_2_del_result_set_end + res = self.vrs.normalise_outwards('NR_110010.2', 'GCG','G', 25,28) + assert res == self.NR_110010_2_del_result_set_end + res = self.vrs.normalise_outwards('NM_003073.5', 'AGA','', 1288,1291) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGAAGA','AGA', 1288,1294) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'CAG','', 196,199) + assert res == self.NM_002111_8_del_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'CAGCA','CA', 196,201) + assert res == self.NM_002111_8_del_result_set + + def test_vrs_normalise_outwards_ex_right_del_flank_sepfetch(self): + # repeat left shift with self.ex_short_vrs to test flank fetch with: + # minimised ref length > 2*flank fetch + # this is used as the cut-off for fetching flanks separately + res = self.ex_short_vrs.normalise_outwards( + 'NM_003073.5', 'AGA','', 1288,1291) + assert res == self.NM_003073_5_del_result_set + res = self.ex_short_vrs.normalise_outwards( + 'NM_003073.5', 'AGAAGA','AGA', 1288,1294) + assert res == self.NM_003073_5_del_result_set + res = self.ex_short_vrs.normalise_outwards( + 'NM_002111.8', 'CAG','', 196,199) + assert res == self.NM_002111_8_del_result_set + res = self.ex_short_vrs.normalise_outwards( + 'NM_002111.8', 'CAGCA','CA', 196,201) + assert res == self.NM_002111_8_del_result_set + + def test_vrs_normalise_outwards_ex_both_del(self): + # expand both directions del + res = self.vrs.normalise_outwards('NR_110010.2', 'GC','', 27,29) + assert res == self.NR_110010_2_del_result_set_end + res = self.vrs.normalise_outwards('NR_110010.2', 'GCG','G', 27,30) + assert res == self.NR_110010_2_del_result_set_end + res = self.vrs.normalise_outwards('NM_003073.5', 'AGA','', 1291,1294) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards('NM_003073.5', 'AGAAGA','AGA', 1291,1297) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'CAG','', 202,205) + assert res == self.NM_002111_8_del_result_set + res = self.vrs.normalise_outwards('NM_002111.8', 'CAGCA','CA', 202,207) + assert res == self.NM_002111_8_del_result_set + + def test_vrs_normalise_outwards_ex_done_del(self): + # expand already done del + res = self.vrs.normalise_outwards( + 'NR_110010.2', 'GCGCGCG','GCGCG', 25,32) + assert res == self.NR_110010_2_del_result_set_end + res = self.vrs.normalise_outwards( + 'NM_003073.5', 'AGAAGAAGA','AGAAGA', 1288,1297) + assert res == self.NM_003073_5_del_result_set + res = self.vrs.normalise_outwards( + 'NM_002111.8', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 'CAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCA', + 196,261) + assert res == self.NM_002111_8_del_result_set + + def test_vrs_normalise_outwards_ex_into_start_del(self): + # expand into start del + res = self.vrs.normalise_outwards('NM_001330096.1', 'CT','', 22,24) + assert res == self.res_set_d_start_NM_001330096_1_end + res = self.vrs.normalise_outwards('NM_001330096.1', 'TCT','T', 21,24) + assert res == self.res_set_d_start_NM_001330096_1 + res = self.vrs.normalise_outwards( + 'NM_001330112.1', 'GGGGCGGGTC','', 10,20) + assert res == self.res_set_d_start_NM_001330112_1 + res = self.vrs.normalise_outwards( + 'NM_001330112.1', 'TCGGGGCGGGTC', 'TC', 8,20) + assert res == self.res_set_d_start_NM_001330112_1_end + + def test_vrs_normalise_outwards_ex_into_start_done_del(self): + # expand start already done del + res = self.vrs.normalise_outwards( + 'NM_001330096.1', + 'CTCTCTCTCTCTCTCTCTCTCTCTCT', + 'CTCTCTCTCTCTCTCTCTCTCTCT', + 0,26) + assert res == self.res_set_d_start_NM_001330096_1_end + res = self.vrs.normalise_outwards( + 'NM_001330112.1', + 'GGGGCGGGTCGGGGCGGGTCGGGGCGGG', + 'GGGGCGGGTCGGGGCGGG', + 0,28) + assert res == self.res_set_d_start_NM_001330112_1 + + def test_vrs_normalise_outwards_ex_into_end_del(self): + # expand into end del + res = self.vrs.normalise_outwards('NM_052928.3', 'GTTTT','', 4380,4385) + assert res == self.res_set_d_end_NM_052928_3 + res = self.vrs.normalise_outwards( + 'NM_052928.3', 'TTGTTTT','TT', 4378,4385) + assert res == self.res_set_d_end_NM_052928_3_end + res = self.vrs.normalise_outwards('NM_002129.4', 'TA','', 1422,1424) + assert res == self.res_set_d_end_NM_002129_4_end + res = self.vrs.normalise_outwards('NM_002129.4', 'TATA','TA', 1422,1426) + assert res == self.res_set_d_end_NM_002129_4_end + + def test_vrs_normalise_outwards_ex_into_end_done_del(self): + # expand end already done del + res = self.vrs.normalise_outwards( + 'NM_052928.3', + 'GTTTTGTTTTGTTTTGTTTTGTTTTGTTTT', + 'GTTTTGTTTTGTTTTGTTTTGTTTT', + 4375,4405) + assert res == self.res_set_d_end_NM_052928_3 + res = self.vrs.normalise_outwards( + 'NM_002129.4', + 'ATATATATATATATATATAT', + 'ATATATATATATATATAT', + 1421,1441) + assert res == self.res_set_d_end_NM_002129_4 + +class TestVRSOutputVRSObjectIDs(unittest.TestCase): + """ + Tests for the internal vrs_util.py module, particularly the HGVS_to_VRS + object functions required for fetching and making VRS IDs. + + A good number of these tests take their required output from the VRS docs + (current as of VRS 2.1). + tests: + Test VRS IDs for all data types with specified id's in the VRS docs, + using stored VRS data as input + Test that the VRS id generation code produces the expected output for + the given input. + """ + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + + def test_vrs_seq_ids_bad_id(self): + # test 1 bad ID to check that it asserts in some way + with self.assertRaises(KeyError): + self.vrs.id_fetch('LIVE_PARROT') + + def test_vrs_seq_ids(self): + """ + Test the basic fetching of VRS sequence IDs using given hgvs ID. + The VRS id_fetch() calls get_vrs_id_for_seq() to get the base id + via SeqRepo, this might change in the future, but we want to test the + id_fetch in use for this, not the base function, as the id_fetch + function in use is the target of these tests. + """ + # the internal set of sequences used: + # NR_110010.2, NM_003073.5, NM_002111.8, NM_001350922.2, + # NM_001330096.1, NM_001330112.1, NM_052928.3, and NM_002129.4 + vrs_seq_id = self.vrs.id_fetch('NR_110010.2') + assert vrs_seq_id == 'SQ.Ksaa229gzGC4Uc3ytCixai4vziud-MKi' + vrs_seq_id = self.vrs.id_fetch('NM_003073.5') + assert vrs_seq_id == 'SQ.pL2lXe9Qjg9ME_5vP0o85MgTUIqTAklc' + vrs_seq_id = self.vrs.id_fetch('NM_002111.8') + assert vrs_seq_id == 'SQ.4qBhnA470l_6xfLWJyi8WkOKeW6u5KJJ' + vrs_seq_id = self.vrs.id_fetch('NM_001350922.2') + assert vrs_seq_id == 'SQ.OgH_wLQV1lYs_Z107fts3xI9CuarHG7_' + vrs_seq_id = self.vrs.id_fetch('NM_001330096.1') + assert vrs_seq_id == 'SQ.Xzqbjy7R3hDF81IInpgNugs7N3c0qk2M' + vrs_seq_id = self.vrs.id_fetch('NM_001330112.1') + assert vrs_seq_id == 'SQ.L_1dwsmKTbVRGrbiF4p6FrgaWkWKWycu' + vrs_seq_id = self.vrs.id_fetch('NM_052928.3') + assert vrs_seq_id == 'SQ.pGqCl6h3ASKqxNA6o0aLmx_6NIoQio9l' + vrs_seq_id = self.vrs.id_fetch('NM_002129.4') + assert vrs_seq_id == 'SQ.8rOzThqGEimTT1vXu8KEb73YqgPBMfWJ' + # sequence IDs found in the vrs documentation + # allele docs + vrs_seq_id = self.vrs.id_fetch('NC_000001.11') + assert vrs_seq_id == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + # sequence docs and from: + # https://github.com/ga4gh/vrs/blob/2.0/validation/models.yaml + vrs_seq_id = self.vrs.id_fetch('NC_000007.14') + assert vrs_seq_id == "SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul" + + def test_vrs_flatten(self): + """ Test the VRS flattening logic for basic functionality and currently + unused but, in the future needed, behaviour """ + # Test flattening start stop spans + flat = self.vrs.vrs_flatten({ + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": "SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul" + }, + "start": [44908820,44908821], + "end": [44908822,44908823] + }) + assert flat == \ + '{"end":"[44908822,44908823]","sequenceReference":{'\ + '"refgetAccession":"SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","type":"'\ + 'SequenceReference"},"start":"[44908820,44908821]","type":'\ + '"SequenceLocation"}' + flat = self.vrs.vrs_flatten({ + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": "SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul" + }, + "start": [None,44908821], + "end": [44908822,None] + }) + assert flat == \ + '{"end":"[44908822,null]","sequenceReference":{"refgetAccession":'\ + '"SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","type":"SequenceReference"}'\ + ',"start":"[null,44908821]","type":"SequenceLocation"}' + # Test specified digest_keys + flat = self.vrs.vrs_flatten({ + "type": "SequenceReference", + "refgetAccession": "SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul", + "test":"bla" + }, dict_keys=["refgetAccession","test","type"]) + assert flat == \ + '{"refgetAccession":"SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","test":'\ + '"bla","type":"SequenceReference"}' + + + def test_vrs_object_ids_assert_invalid_type(self): + # test type assert on flatten & hash on non valid data type + with self.assertRaises(TypeError): + vrs_eg_seq_loc = { + "type": "SequenceReference", + "refgetAccession": self.vrs + } + self.vrs.vrs_flatten(vrs_eg_seq_loc,digest=True) + + def test_vrs_object_ids_assert_fail_no_vrs_id_prefix(self): + # test value error on VRS type without VRS id prefix + with self.assertRaises(ValueError): + vrs_state = { + "type": "ReferenceLengthExpression", + "length": 11, + "repeatSubunitLength": 3 + } + self.vrs.vrs_flatten(vrs_state,digest=True) + + # now test working ids + def test_vrs_object_ids_seq_loc(self): + vrs_eg_seq_loc = { + "id": "ga4gh:SL.4t6JnYWqHwYw9WzBT_lmWBb3tLQNalkT", + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": "SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul" + }, + "start": 44908821, + "end": 44908822 + } + vrs_id = self.vrs.vrs_flatten(vrs_eg_seq_loc,digest=True) + assert vrs_id == vrs_eg_seq_loc['id'] + + def test_vrs_object_ids_full_allele(self): + vrs_eg_allele = { + "id": "ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT", + "type": "Allele", + "expressions": [ + { + "syntax": "spdi", + "value": "NC_000001.11:40819438:CTCCTCCT:CTCCTCCTCCT" + } + ], + "location": { + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO", + "residueAlphabet": "na", + "id": "NC_000001.11" + }, + "start": 40819438, + "end": 40819446 + }, + "state": { + "type": "ReferenceLengthExpression", + "length": 11, + "repeatSubunitLength": 3 + } + } + vrs_id = self.vrs.vrs_flatten(vrs_eg_allele,digest=True) + assert vrs_id == vrs_eg_allele['id'] + + def test_vrs_object_ids_allele_from_eg_page(self): + vrs_eg_allele_from_id_eg_page = { + "location": { + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": "SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl" + }, + "start": 44908821, + "end": 44908822 + }, + "state": { + "type": "LiteralSequenceExpression", + "sequence": "T" + }, + "type": "Allele" + } + #{"location":"wIlaGykfwHIpPY2Fcxtbx4TINbbODFVz","state": + # {"sequence":"T","type":"LiteralSequenceExpression"},"type":"Allele"} + # ga4gh:VA.0AePZIWZUNsUlQTamyLrjm2HWUw2opLt_ + # note that the _ on the end is not genarated even when running the + # sample code given by + # https://vrs.ga4gh.org/en/latest/conventions/computed_identifiers.html#digest-serialization + # *exactly* as-is, the rest of the ID is identical + vrs_id = self.vrs.vrs_flatten(vrs_eg_allele_from_id_eg_page,digest=True) + assert vrs_id == 'ga4gh:VA.0AePZIWZUNsUlQTamyLrjm2HWUw2opLt' + + def test_vrs_object_ids_b_moddels_notebook(self): + # test the ids from the basic models notebook + # https://github.com/ga4gh/vrs-python/blob/main/notebooks/getting_started/3_Basic_Models.ipynb + #'NM_002439.5'# SQ.Pw3Ch0x3XWD6ljsnIfmk_NERcZCI9sNM + vrs_eg_allele_from_basic_mod_nb = { + 'id': 'ga4gh:VA.5C67OBmCLuHPgDkCQj7EOMih58BS2Eor', + 'type': 'Allele', + 'location': {'type': 'SequenceLocation', + 'sequenceReference': {'type': 'SequenceReference', + 'refgetAccession': 'SQ.Pw3Ch0x3XWD6ljsnIfmk_NERcZCI9sNM'}, + 'start': 80656509, + 'end': 80656510}, + 'state': {'type': 'LiteralSequenceExpression', 'sequence': 'TT'}} + vrs_id_loc = self.vrs.vrs_flatten( + vrs_eg_allele_from_basic_mod_nb['location'],digest=True) + assert vrs_id_loc == 'ga4gh:SL.lGxOP1JRd4dysmrOVaskO5P_35DyCLnx' + vrs_id = self.vrs.vrs_flatten( + vrs_eg_allele_from_basic_mod_nb,digest=True) + assert vrs_id == 'ga4gh:VA.5C67OBmCLuHPgDkCQj7EOMih58BS2Eor' + + def test_vrs_object_ids_2_0_validation_models1(self): + # test id creation for Alleles and sequence loacations found in + # https://github.com/ga4gh/vrs/blob/2.0/validation/models.yaml + vrs_allele_valid_model_test_set_lit= {'type': 'Allele', + 'location': {'type': 'SequenceLocation', + 'sequenceReference': {'type': 'SequenceReference', + 'refgetAccession': 'SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl'}, + 'start': 44908821, + 'end': 44908822}, + 'state': {'type': 'LiteralSequenceExpression', 'sequence': 'T'}} + vrs_id = self.vrs.vrs_flatten( + vrs_allele_valid_model_test_set_lit['location'],digest=True) + assert vrs_id == 'ga4gh:SL.wIlaGykfwHIpPY2Fcxtbx4TINbbODFVz' + vrs_id = self.vrs.vrs_flatten( + vrs_allele_valid_model_test_set_lit,digest=True) + assert vrs_id == 'ga4gh:VA.0AePZIWZUNsUlQTamyLrjm2HWUw2opLt' + + def test_vrs_object_ids_2_0_validation_models2(self): + # test id creation for Alleles and sequence loacations found in + # https://github.com/ga4gh/vrs/blob/2.0/validation/models.yaml + vrs_allele_valid_model_test_set_len= {'type': 'Allele', + 'location': {'type': 'SequenceLocation', + 'sequenceReference': {'type': 'SequenceReference', + 'refgetAccession': 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO'}, + 'start': 40819438, + 'end': 40819446}, + 'state': { + 'type': 'ReferenceLengthExpression', + 'length': 11, + 'repeatSubunitLength': 3}} + vrs_id = self.vrs.vrs_flatten( + vrs_allele_valid_model_test_set_len['location'],digest=True) + assert vrs_id == 'ga4gh:SL.nQGBuvRQOLEboA5TYtcz975fp_GulxbZ' + vrs_id = self.vrs.vrs_flatten( + vrs_allele_valid_model_test_set_len,digest=True) + assert vrs_id == 'ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT' + + def test_vrs_object_ids_notebook_ex_allele_translator1(self): + # https://github.com/ga4gh/vrs-python/blob/main/notebooks/getting_started/4_Exploring_the_AlleleTranslator.ipynb + hgvs_out_lit = {'type': 'Allele', + 'location': {'type': 'SequenceLocation', + 'sequenceReference': {'type': 'SequenceReference', + 'refgetAccession': 'SQ.aUiQCzCPZ2d0csHbMSbh2NzInhonSXwI'}, + 'start': 80656488, + 'end': 80656489}, + 'state': {'type': 'LiteralSequenceExpression', 'sequence': 'T'}} + vrs_id = self.vrs.vrs_flatten(hgvs_out_lit['location'],digest=True) + assert vrs_id == 'ga4gh:SL.JiLRuuyS5wefF_6-Vw7m3Yoqqb2YFkss' + vrs_id = self.vrs.vrs_flatten(hgvs_out_lit,digest=True) + assert vrs_id == 'ga4gh:VA.ebezGL6HoAhtGJyVnB_mE5BH18ntKev4' + + def test_vrs_object_ids_notebook_ex_allele_translator2(self): + # https://github.com/ga4gh/vrs-python/blob/main/notebooks/getting_started/4_Exploring_the_AlleleTranslator.ipynb + hgvs_out_lit2 = {'type': 'Allele', + 'location': {'type': 'SequenceLocation', + 'sequenceReference': {'type': 'SequenceReference', + 'refgetAccession': 'SQ.vbjOdMfHJvTjK_nqvFvpaSKhZillW0SX'}, + 'start': 79952307, + 'end': 79952308}, + 'state': {'type': 'LiteralSequenceExpression', 'sequence': 'T'}} + vrs_id = self.vrs.vrs_flatten(hgvs_out_lit2['location'],digest=True) + assert vrs_id == 'ga4gh:SL.Y-itBtqe9IwbxyL4EVZ4T_X9TUsdbJ22' + vrs_id = self.vrs.vrs_flatten(hgvs_out_lit2,digest=True) + assert vrs_id == 'ga4gh:VA.hEyB1sGiQrdrPFIq4u4CF17uAuUs2Wvx' + +class TestVRSOutputVRSObjects(unittest.TestCase): + """ + Tests for the internal vrs_util.py module, particularly the HGVS_to_VRS + object function hgvs_single_var_to_vrs, which is the the basic core + hgvs->vrs function. + + Starts with standard input as from + https://github.com/ga4gh/vrs/blob/2.0/validation/models.yaml + then moves to extra tests on shortcuts and error/null return states. + + """ + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + + def test_vrs_object_output_NC_000019_10_g_44908822C_T_set(self): + + # allele test + # hgvs in NC_000019.10:g44908822_44908821C>T + hgvs = vv.hp.parse('NC_000019.10:g.44908822C>T') + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 44908822 + assert hgvs_allele['location']['start'] == 44908821 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.wIlaGykfwHIpPY2Fcxtbx4TINbbODFVz' + assert hgvs_allele['state']['sequence'] == 'T' + assert hgvs_allele['id'] == 'ga4gh:VA.0AePZIWZUNsUlQTamyLrjm2HWUw2opLt' + + def test_vrs_object_output_NC_000001_11_g_40819439_40819446delCTCCTCCTinsCTCCTCCTCCT(self): + # No need to test the 'canonical' JSON serilised form as this, is not available as output, + # and should be redundant with the checksum ID test + # hgvs in "NC_000001.11:40819439_40819446delCTCCTCCTinsCTCCTCCTCCT + hgvs = vv.hp.parse( + "NC_000001.11:g.40819439_40819446delCTCCTCCTinsCTCCTCCTCCT") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819446 + assert hgvs_allele['location']['start'] == 40819438 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.nQGBuvRQOLEboA5TYtcz975fp_GulxbZ' + assert hgvs_allele['state']['length'] == 11 + assert hgvs_allele['state']['repeatSubunitLength'] == 3 + assert hgvs_allele['id'] == 'ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT' + + def test_vrs_object_output_NC_000005_10_g_80656489C_T(self): + # test for good output from hgvs input matching + # https://github.com/ga4gh/vrs-python/blob/main/notebooks/getting_started/4_Exploring_the_AlleleTranslator.ipynb + #From HGVS + hgvs = vv.hp.parse("NC_000005.10:g.80656489C>T") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 80656489 + assert hgvs_allele['location']['start'] == 80656488 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.aUiQCzCPZ2d0csHbMSbh2NzInhonSXwI' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.JiLRuuyS5wefF_6-Vw7m3Yoqqb2YFkss' + assert hgvs_allele['state']['sequence'] == 'T' + assert hgvs_allele['id'] == 'ga4gh:VA.ebezGL6HoAhtGJyVnB_mE5BH18ntKev4' + + def test_vrs_object_output_NC_000005_9_g_79952308C_T(self): + hgvs = vv.hp.parse("NC_000005.9:g.79952308C>T") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 79952308 + assert hgvs_allele['location']['start'] == 79952307 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.vbjOdMfHJvTjK_nqvFvpaSKhZillW0SX' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.Y-itBtqe9IwbxyL4EVZ4T_X9TUsdbJ22' + assert hgvs_allele['state']['sequence'] == 'T' + assert hgvs_allele['id'] == 'ga4gh:VA.hEyB1sGiQrdrPFIq4u4CF17uAuUs2Wvx' + + + def test_vrs_obj_out_norm_identity_diff_input_start(self): + """ + test VRS IDs are the same for multiple hgvs inputs that should + normalise to the same output VRS start hgvs input. + """ + # hgvs in "NC_000001.11:40819439_40819446delCTCCTCCTinsCTCCTCCTCCT + hgvs = vv.hp.parse("NC_000001.11:g.40819438_40819439insCTC") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819446 + assert hgvs_allele['location']['start'] == 40819438 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.nQGBuvRQOLEboA5TYtcz975fp_GulxbZ' + assert hgvs_allele['state']['length'] == 11 + assert hgvs_allele['state']['repeatSubunitLength'] == 3 + assert hgvs_allele['id'] == 'ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT' + + def test_vrs_obj_out_norm_identity_diff_input_end(self): + """ + test VRS IDs are the same for multiple hgvs inputs that should + normalise to the same output VRS end hgvs input. + """ + # hgvs in "NC_000001.11:40819439_40819446delCTCCTCCTinsCTCCTCCTCCT + hgvs = vv.hp.parse("NC_000001.11:g.40819446_40819447insCCT") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819446 + assert hgvs_allele['location']['start'] == 40819438 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.nQGBuvRQOLEboA5TYtcz975fp_GulxbZ' + assert hgvs_allele['state']['length'] == 11 + assert hgvs_allele['state']['repeatSubunitLength'] == 3 + assert hgvs_allele['id'] == 'ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT' + + def test_vrs_obj_out_norm_identity_diff_input_mid(self): + """ + test VRS IDs are the same for multiple hgvs inputs that should + normalise to the same output VRS middle hgvs input. + """ + # hgvs in "NC_000001.11:40819439_40819446delCTCCTCCTinsCTCCTCCTCCT + hgvs = vv.hp.parse("NC_000001.11:g.40819443_40819444insCCT") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819446 + assert hgvs_allele['location']['start'] == 40819438 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.nQGBuvRQOLEboA5TYtcz975fp_GulxbZ' + assert hgvs_allele['state']['length'] == 11 + assert hgvs_allele['state']['repeatSubunitLength'] == 3 + assert hgvs_allele['id'] == 'ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT' + + def test_vrs_obj_out_norm_identity_diff_set_dup(self): + """ + test VRS IDs are the same for multiple hgvs inputs that should + normalise to the same output VRS middle hgvs input. + Dup, like ins also has a separate code path from del/delins/subtitue + test this as well. + """ + # hgvs in "NC_000001.11:40819439_40819446delCTCCTCCTinsCTCCTCCTCCT + hgvs = vv.hp.parse("NC_000001.11:g.40819442_40819444dup") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819446 + assert hgvs_allele['location']['start'] == 40819438 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.nQGBuvRQOLEboA5TYtcz975fp_GulxbZ' + assert hgvs_allele['state']['length'] == 11 + assert hgvs_allele['state']['repeatSubunitLength'] == 3 + assert hgvs_allele['id'] == 'ga4gh:VA.Oop4kjdTtKcg1kiZjIJAAR3bp7qi4aNT' + + def test_vrs_object_output_part_ambig_pos(self): + """ + test variant with partially ambiguous positions + eg ACCA to ACGCCA could be an ins either before (of CG) or after + (of GC) the fist C + so VRS expands to cover the first C + hgvs inputs should be normalised 3' in this case, but this also should + work either way + done for TCCT >TCGCCT and TCCT >TCCGCT in fully expanded delins + both + directions + """ + hgvs = vv.hp.parse("NC_000001.11:g.40819444_40819445delCCinsCCGC") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819445 + assert hgvs_allele['location']['start'] == 40819444 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.UhfMqgh_jnDrVNWaFx-_ksYssV1-9Hr3' + assert hgvs_allele['state']['sequence'] == 'CGC' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['id'] == 'ga4gh:VA.r-IMwTjeSjYhwqkaIX7tloTeeMnuEGYy' + + hgvs = vv.hp.parse("NC_000001.11:g.40819445delCinsCGC") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819445 + assert hgvs_allele['location']['start'] == 40819444 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.UhfMqgh_jnDrVNWaFx-_ksYssV1-9Hr3' + assert hgvs_allele['state']['sequence'] == 'CGC' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['id'] == 'ga4gh:VA.r-IMwTjeSjYhwqkaIX7tloTeeMnuEGYy' + + + # extra tests VRS varified, outside of the examples? + + def test_vrs_object_output_eq_shortcut(self): + "hgvs_single_var_to_vrs on == variant to test internal shortcut" + hgvs = vv.hp.parse("NC_000001.11:g.40819444_40819445=") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819445 + assert hgvs_allele['location']['start'] == 40819443 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.zxbyl3-hn1MHmcZYF0N8WWaAzNZcryGk' + assert hgvs_allele['state']['length'] == 2 + assert hgvs_allele['state']['repeatSubunitLength'] == 2 + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + + def test_vrs_object_output_fail_with_no_mapper_for_c_input(self): + # Test C type works, and failes propperly with mapper missing + with self.assertRaises(ValueError): + self.vrs.hgvs_single_var_to_vrs( + vv.hp.parse('NM_015120.4:c.35T>C')) + + def test_vrs_object_output_for_c_input(self): + hgvs_allele = self.vrs.hgvs_single_var_to_vrs( + vv.hp.parse('NM_015120.4:c.35T>C'), + variant = MockVVData()) + assert hgvs_allele == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.mq1Saz29534GzZ6T_jCfUjsqNlifC7pZ', + 'label': 'NM_015120.4' + }, + 'start': 145, + 'end': 146, + 'id': 'ga4gh:SL.x_rdZXUcg7V8Ae5pPVv0OS-faxKyzNQb' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C' + }, + 'id': 'ga4gh:VA.x8gKEBS-s638D8uuuQXVJKIgCDcuQJCW' + } + + def test_vrs_object_output_no_norm_shortcut_ins(self): + # test that ins and del that don't at all normalise outwards + # use the shortcut correctly + hgvs = vv.hp.parse("NC_000001.11:g.40819444_40819445insG") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819444 + assert hgvs_allele['location']['start'] == 40819444 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.aBJ1_py6od8pI4OWHQegaTxdAsszkbBy' + assert hgvs_allele['state']['sequence'] == 'G' + assert hgvs_allele['state']['type'] == 'LiteralSequenceExpression' + assert hgvs_allele['id'] == 'ga4gh:VA.i_1aVFr-IQvl8bXoIX_UjwAU6_IwAmCD' + + def test_vrs_object_output_no_norm_shortcut_del(self): + hgvs = vv.hp.parse("NC_000001.11:g.40819444_40819445delCC") + hgvs_allele = self.vrs.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819445 + assert hgvs_allele['location']['start'] == 40819443 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.zxbyl3-hn1MHmcZYF0N8WWaAzNZcryGk' + assert hgvs_allele['state']['repeatSubunitLength'] == 2 + assert hgvs_allele['state']['length'] == 0 + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['id'] == 'ga4gh:VA.gpJtRDCma3r-7snQ90wFU7i-hRPuJKGh' + + def test_vrs_object_output_intronic_null_result(self): + # test null result on intronic input + hgvs_allele = self.vrs.hgvs_single_var_to_vrs( + # not valid but works for test + vv.hp.parse('NM_015120.4:c.35-6T>C'), + variant = MockVVData()) + assert hgvs_allele is None + + def test_vrs_object_output_cache(self): + # test cached = type + vrs_cached = copy.copy(self.vrs) + vrs_cached.cache = {} + hgvs = vv.hp.parse("NC_000001.11:g.40819444_40819445=") + hgvs_allele = vrs_cached.hgvs_single_var_to_vrs(hgvs) + assert hgvs_allele['type'] == 'Allele' + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert hgvs_allele['location']['type'] == 'SequenceLocation' + assert hgvs_allele['location']['sequenceReference'][ + 'type'] == 'SequenceReference' + assert hgvs_allele['location']['end'] == 40819445 + assert hgvs_allele['location']['start'] == 40819443 + assert hgvs_allele['location']['sequenceReference'][ + 'refgetAccession'] == 'SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO' + assert hgvs_allele['location'][ + 'id'] == 'ga4gh:SL.zxbyl3-hn1MHmcZYF0N8WWaAzNZcryGk' + assert hgvs_allele['state']['length'] == 2 + assert hgvs_allele['state']['repeatSubunitLength'] == 2 + assert hgvs_allele['state']['type'] == 'ReferenceLengthExpression' + assert vrs_cached.cache["NC_000001.11:g.40819444_40819445="] == \ + hgvs_allele + +class TestVRSOutputFromDummyVV(unittest.TestCase): + """ + Test the generation of VRS output from dummy VV result objects, unlike raw + VV this relies objects this does not rely on VV working consistently with + previous results. Unfortunately this also means that this test set may need + to be changed in the case of VV alterations/improvements. + + See also the direct VV using tests below in the case of failures. + """ + + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + # core set of dummy input + self.dummy_vv_output = MockVVData( + selected_assembly='GRCh38', + original = 'test_input', + warnings=[], + lovd_messages = [], + lovd_corrections = [], + stable_gene_ids = { + 'hgnc_id': 'HGNC:TST', + 'entrez_gene_id': '0000', + 'ucsc_id': 'uc0tst.1', + 'omim_id': ['0000000']}, + gene_symbol = 'Dummy_S', + description = "Text transcript description",) + + def test_vv_output_to_vrs_output_val_err(self): + # test set 1: test that we trap and return variants with corrections + # without VRS data + # 1a test variant.warnings == ['Validation error'] response + err_fail = copy.copy(self.dummy_vv_output) + err_fail.warnings=['Validation error'] + output = self.vrs.variant_validator_output_set_to_vrs(err_fail) + assert output['selected_assembly'] == err_fail.selected_assembly + assert output['submitted_variant'] == err_fail.original + assert output['warnings_and_messages'] == { + 'validation_warnings': ['Validation error'], + 'lovd_messages': [], + 'lovd_corrections': [] + } + assert output['gene_ids']['hgnc_id'] == 'HGNC:TST' + assert output['gene_ids']['entrez_gene_id'] == '0000' + assert output['gene_ids']['ucsc_id'] == 'uc0tst.1' + assert output['gene_ids']['omim_id'] == ['0000000'] + assert output['gene_ids']['current_symbol'] == 'Dummy_S' + assert output['transcript_description'] == err_fail.description + + def test_vv_output_to_vrs_output_warn_type(self): + # 1b test variant.output_type_flag = 'warning' + err_warn = copy.copy(self.dummy_vv_output) + err_warn.output_type_flag = 'warning' + output = self.vrs.variant_validator_output_set_to_vrs(err_warn) + assert output['selected_assembly'] == err_warn.selected_assembly + assert output['submitted_variant'] == err_warn.original + assert output['warnings_and_messages'] == { + 'validation_warnings': [], + 'lovd_messages': [], + 'lovd_corrections': [] + } + assert output['gene_ids']['hgnc_id'] == 'HGNC:TST' + assert output['gene_ids']['entrez_gene_id'] == '0000' + assert output['gene_ids']['ucsc_id'] == 'uc0tst.1' + assert output['gene_ids']['omim_id'] == ['0000000'] + assert output['gene_ids']['current_symbol'] == 'Dummy_S' + assert output['transcript_description'] == err_warn.description + + def test_vv_output_to_vrs_output_warn_for_working_type(self): + # test warnings in output for variants with output_type_flag and w/o + err_warn = copy.copy(self.dummy_vv_output) + err_warn.output_type_flag = 'warning' + err_warn.warnings=['VV_Warn'] + err_warn.lovd_messages = ['LO_Warn'] + err_warn.lovd_corrections = ['LO_corr'] + output = self.vrs.variant_validator_output_set_to_vrs(err_warn) + assert output['selected_assembly'] == err_warn.selected_assembly + assert output['submitted_variant'] == err_warn.original + assert output['warnings_and_messages'] == { + 'validation_warnings': ['VV_Warn'], + 'lovd_messages': ['LO_Warn'], + 'lovd_corrections': ['LO_corr'] + } + assert output['gene_ids']['hgnc_id'] == 'HGNC:TST' + assert output['gene_ids']['entrez_gene_id'] == '0000' + assert output['gene_ids']['ucsc_id'] == 'uc0tst.1' + assert output['gene_ids']['omim_id'] == ['0000000'] + assert output['gene_ids']['current_symbol'] == 'Dummy_S' + assert output['transcript_description'] == err_warn.description + + def test_vv_output_to_vrs_output_gene_symbol(self): + # test that gene symbols get handled regardless of stable gene id state + no_gene_id = copy.copy(self.dummy_vv_output) + no_gene_id.stable_gene_ids = None + output = self.vrs.variant_validator_output_set_to_vrs(no_gene_id) + assert output['gene_ids'] == {'current_symbol': 'Dummy_S'} + no_gene_id.gene_symbol = None + output = self.vrs.variant_validator_output_set_to_vrs(no_gene_id) + assert output['gene_ids'] is None + + # seq tests done using data from input test 1 + def test_vv_output_to_vrs_output_r_type(self): + # test 2: test that we catch and return the simpler r type input + # 2a test for working output to valid input + rna_test = copy.copy(self.dummy_vv_output) + rna_test.rna_data = { + 'rna_variant':vv.hp.parse('NM_015120.4:c.35T>C'), + 'translation_slr':vv.hp.parse('NP_055935.4:p.(L12P)'), + 'usage_warnings':"RNA warning"} + vrs_out = self.vrs.variant_validator_output_set_to_vrs(rna_test) + assert vrs_out['vrs_transcript_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.mq1Saz29534GzZ6T_jCfUjsqNlifC7pZ', + 'label': 'NM_015120.4' + }, + 'start': 145, + 'end': 146, + 'id': 'ga4gh:SL.x_rdZXUcg7V8Ae5pPVv0OS-faxKyzNQb' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C' + }, + 'id': 'ga4gh:VA.x8gKEBS-s638D8uuuQXVJKIgCDcuQJCW' + } + assert vrs_out['vrs_predicted_protein_consequence'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.ggxH8oENu3WDprE6Z_Qg1HvOiT0d8UNa', + 'label': 'NP_055935.4' + }, + 'start': 11, + 'end': 12, + 'id': 'ga4gh:SL.qOpPs9dwgqK6MuH3CTQUDoRYbi9IWwSh' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'P' + }, + 'id': 'ga4gh:VA.cJPulDXE1G73sZS4dHaZX_27-c4pNjYo' + } + + def test_vv_output_to_vrs_output_r_type_missmatch1(self): + # 2b test that r type variant causes assertions on + # hgvs_transcript_variant or primary_assembly_loci being set + # (we don't check object type) + rna_test_bad_1 = copy.copy(self.dummy_vv_output) + rna_test_bad_1.rna_data = { + 'rna_variant':vv.hp.parse('NM_015120.4:c.35T>C'), + 'translation_slr':vv.hp.parse('NP_055935.4:p.(L12P)'), + 'usage_warnings':"RNA warning"} + rna_test_bad_1.hgvs_transcript_variant = 1 + with self.assertRaises(Exception): + self.vrs.variant_validator_output_set_to_vrs(rna_test_bad_1) + + def test_vv_output_to_vrs_output_r_type_missmatch2(self): + rna_test_bad_2 = copy.copy(self.dummy_vv_output) + rna_test_bad_2.rna_data = { + 'rna_variant':vv.hp.parse('NM_015120.4:c.35T>C'), + 'translation_slr':vv.hp.parse('NP_055935.4:p.(L12P)'), + 'usage_warnings':"RNA warning"} + rna_test_bad_2.primary_assembly_loci = 2 + with self.assertRaises(Exception): + self.vrs.variant_validator_output_set_to_vrs(rna_test_bad_2) + + def test_vv_output_to_vrs_output_working_exonic(self): + # test 3: test for working responses to c/n type input that is exonic + c_test = copy.copy(self.dummy_vv_output) + c_test.hgvs_transcript_variant = vv.hp.parse('NM_015120.4:c.35T>C') + hgvs_hg37 = vv.hp.parse('NC_000002.11:g.73613031delinsCGGA') + hgvs_hg38 = vv.hp.parse('NC_000002.12:g.73385903delinsCGGA') + c_test.primary_assembly_loci = { + 'hg19': { + 'hgvs_genomic_description': hgvs_hg37, + 'vcf': { + 'chr': 'chr2', 'pos': '73613031', 'ref': 'T', 'alt': 'CGGA'} + }, + 'hg38': { + 'hgvs_genomic_description': hgvs_hg37, + 'vcf': { + 'chr': 'chr2', 'pos': '73385903', 'ref': 'T', 'alt': 'CGGA'} + }, + 'grch37' : { + 'hgvs_genomic_description': hgvs_hg37, + 'vcf': { + 'chr': '2', 'pos': '73613031', 'ref': 'T', 'alt': 'CGGA'} + }, + 'grch38': { + 'hgvs_genomic_description': hgvs_hg38, + 'vcf': { + 'chr': '2', 'pos': '73385903', 'ref': 'T', 'alt': 'CGGA'} + } + } + c_test.hgvs_predicted_protein_consequence = { + 'prot':vv.hp.parse('NP_055935.4:p.(L12P)')} + vrs_out = self.vrs.variant_validator_output_set_to_vrs(c_test) + assert vrs_out['vrs_transcript_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.mq1Saz29534GzZ6T_jCfUjsqNlifC7pZ', + 'label': 'NM_015120.4' + }, + 'start': 145, + 'end': 146, + 'id': 'ga4gh:SL.x_rdZXUcg7V8Ae5pPVv0OS-faxKyzNQb' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C' + }, + 'id': 'ga4gh:VA.x8gKEBS-s638D8uuuQXVJKIgCDcuQJCW' + } + assert vrs_out['vrs_predicted_protein_consequence'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.ggxH8oENu3WDprE6Z_Qg1HvOiT0d8UNa', + 'label': 'NP_055935.4' + }, + 'start': 11, + 'end': 12, + 'id': 'ga4gh:SL.qOpPs9dwgqK6MuH3CTQUDoRYbi9IWwSh' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'P' + }, + 'id': 'ga4gh:VA.cJPulDXE1G73sZS4dHaZX_27-c4pNjYo' + } + assert vrs_out['VRS_mappings_for_primary_assemblies'] == { + 'grch37': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.9KdcA9ZpY1Cpvxvg8bMSLYDUpsX6GDLO', + 'label': 'NC_000002.11' + }, + 'start': 73613030, + 'end': 73613031, + 'id': 'ga4gh:SL.IzA6GY6mvDiJ1EzMbe1Cxv6jyhg4u8t7' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CGGA' + }, + 'id': 'ga4gh:VA.rKc_HxeylEKYQzYo_e_kY1lqYLF6Ch5m' + }, + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.pnAqCRBrTsUoBghSD1yp_jXWSmlbdh4g', + 'label': 'NC_000002.12' + }, + 'start': 73385902, + 'end': 73385903, + 'id': 'ga4gh:SL.ndPBndyntJTTRHuU6tYpgbN6TUsRv94B' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CGGA' + }, + 'id': 'ga4gh:VA.eDiJCZTUpLf1UQHaUFFP1UaPsHfbOjyC' + } + } + def test_vv_output_to_vrs_output_working_exonic_w_rsg(self): + c_test = copy.copy(self.dummy_vv_output) + c_test.hgvs_transcript_variant = vv.hp.parse('NM_015120.4:c.35T>C') + c_test.hgvs_refseqgene_variant = vv.hp.parse('NG_011690.1:g.5146T>C') + hgvs_hg37 = vv.hp.parse('NC_000002.11:g.73613031delinsCGGA') + hgvs_hg38 = vv.hp.parse('NC_000002.12:g.73385903delinsCGGA') + c_test.primary_assembly_loci = { + 'hg19': {'hgvs_genomic_description': hgvs_hg37}, + 'hg38': {'hgvs_genomic_description': hgvs_hg37}, + 'grch37' : {'hgvs_genomic_description': hgvs_hg37}, + 'grch38': {'hgvs_genomic_description': hgvs_hg38} + } + c_test.hgvs_predicted_protein_consequence = { + 'prot':vv.hp.parse('NP_055935.4:p.(L12P)')} + vrs_out = self.vrs.variant_validator_output_set_to_vrs(c_test) + assert vrs_out['vrs_transcript_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.mq1Saz29534GzZ6T_jCfUjsqNlifC7pZ', + 'label': 'NM_015120.4' + }, + 'start': 145, + 'end': 146, + 'id': 'ga4gh:SL.x_rdZXUcg7V8Ae5pPVv0OS-faxKyzNQb' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C' + }, + 'id': 'ga4gh:VA.x8gKEBS-s638D8uuuQXVJKIgCDcuQJCW' + } + assert vrs_out['vrs_predicted_protein_consequence'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.ggxH8oENu3WDprE6Z_Qg1HvOiT0d8UNa', + 'label': 'NP_055935.4' + }, + 'start': 11, + 'end': 12, + 'id': 'ga4gh:SL.qOpPs9dwgqK6MuH3CTQUDoRYbi9IWwSh' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'P' + }, + 'id': 'ga4gh:VA.cJPulDXE1G73sZS4dHaZX_27-c4pNjYo' + } + assert vrs_out['VRS_mappings_for_primary_assemblies'] == { + 'grch37': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.9KdcA9ZpY1Cpvxvg8bMSLYDUpsX6GDLO', + 'label': 'NC_000002.11' + }, + 'start': 73613030, + 'end': 73613031, + 'id': 'ga4gh:SL.IzA6GY6mvDiJ1EzMbe1Cxv6jyhg4u8t7' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CGGA' + }, + 'id': 'ga4gh:VA.rKc_HxeylEKYQzYo_e_kY1lqYLF6Ch5m' + }, + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.pnAqCRBrTsUoBghSD1yp_jXWSmlbdh4g', + 'label': 'NC_000002.12' + }, + 'start': 73385902, + 'end': 73385903, + 'id': 'ga4gh:SL.ndPBndyntJTTRHuU6tYpgbN6TUsRv94B' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CGGA' + }, + 'id': 'ga4gh:VA.eDiJCZTUpLf1UQHaUFFP1UaPsHfbOjyC' + } + } + assert vrs_out['vrs_refseqgene_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.r47Jw6neAxxIQgeyZLmdPrqFN4S6h7hy', + 'label': 'NG_011690.1'}, + 'start': 5145, + 'end': 5146, + 'id': 'ga4gh:SL.3CEnQ3JJo2jQbraJ0UHIO-lqBbR4xs15'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C'}, + 'id': 'ga4gh:VA.TjhuWhUG-PJapHphK3r0HWyU5b9CgGG_'} + + def test_vv_output_to_vrs_output_intronic_main_chr(self): + # test 4: test for working responses to c/n type input that is intronic + # 4a intronic matches main genomic ref, taken from input test 7 + c_int_test = copy.copy(self.dummy_vv_output) + c_int_test.hgvs_transcript_variant = vv.hp.parse( + 'NM_000548.4:c.138+821del') + c_int_test.genome_context_intronic_sequence = copy.copy( + c_int_test.hgvs_transcript_variant) + c_int_test.genome_context_intronic_sequence.rel_ac = 'NC_000016.9' + c_int_test.hgvs_predicted_protein_consequence = { + 'prot': vv.hp.parse('NP_000539.2:p.?')} + + hgvs_hg37 = vv.hp.parse('NC_000016.9:g.2099575del') + hgvs_hg38 = vv.hp.parse('NC_000016.10:g.2049574del') + c_int_test.primary_assembly_loci = { + 'hg19':{'hgvs_genomic_description': hgvs_hg37}, + 'hg38':{'hgvs_genomic_description': hgvs_hg38}, + 'grch37':{'hgvs_genomic_description': hgvs_hg37}, + 'grch38':{'hgvs_genomic_description': hgvs_hg38}} + c_int_test.hgvs_genomic = copy.copy(hgvs_hg37) + + vrs_out = self.vrs.variant_validator_output_set_to_vrs(c_int_test) + warnings_and_messages = { + 'validation_warnings': [], + 'lovd_messages': [], + 'lovd_corrections': [], + 'vrs_output_warnings': [ + 'VRSIntronWarning: VRS does not handle mappings shared between ' + 'genomic and transcript reference sequences. As such only the ' + 'genomic mappings for this hgvs intronic transcript variant are' + ' preserved.']} + vrs_intronic_genomic_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.W6wLoIFOn4G7cjopxPxYNk2lcEqhLQFb', + 'label': 'NC_000016.9'}, + 'start': 2099572, + 'end': 2099575, + 'id': 'ga4gh:SL.OSRCAmPHLKeVCIccBJw2PE2fjBBZVO5g'}, + 'state': { + 'type': 'ReferenceLengthExpression', + 'length': 2, 'repeatSubunitLength': 1}, + 'id': 'ga4gh:VA.R4sVDvRvhoB_h0TTNcxbRkEkqL8Iwzww'} + VRS_mappings_for_primary_assemblies = { + 'grch37': vrs_intronic_genomic_variant, + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.yC_0RBj3fgBlvgyAuycbzdubtLxq-rE0', + 'label': 'NC_000016.10'}, + 'start': 2049571, + 'end': 2049574, + 'id': 'ga4gh:SL.LBYz9IIcNSR00VhmUb06RRpf9WQKh_xN'}, + 'state': { + 'type': 'ReferenceLengthExpression', + 'length': 2, + 'repeatSubunitLength': 1}, + 'id': 'ga4gh:VA.swtaZ9h8m_gc6bDXPtNAALWSRBquhDcs'}} + assert vrs_out['warnings_and_messages']['vrs_output_warnings'] == \ + warnings_and_messages['vrs_output_warnings'] + assert vrs_out['warnings_and_messages'] == warnings_and_messages + assert vrs_out['vrs_intronic_genomic_variant'] == \ + vrs_intronic_genomic_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + + def test_vv_output_to_vrs_output_intronic_alt_chr_match(self): + # 4b intronic matches alt genomic ref, adapted from input test 189 + # also tests RSG+ intronic RSG + c_int_alt = copy.copy(self.dummy_vv_output) + c_int_alt.hgvs_transcript_variant = vv.hp.parse( + 'NM_012309.4:c.913-5058G>A') + # orig 'genome_context_intronic_sequence' = \ + # 'NC_000011.10(NM_012309.4):c.913-5058G>A' + # we switch to NW_004070871.1 which would normally only turn up in + # output if used in input to test such input handling + c_int_alt.hgvs_transcript_variant.rel_ac = 'NW_004070871.1' + c_int_alt.refseqgene_context_intronic_sequence = vv.hp.parse( + 'NM_012309.4:c.913-5058G>A') + c_int_alt.refseqgene_context_intronic_sequence.rel_ac = 'NG_042866.1' + c_int_alt.hgvs_refseqgene_variant = vv.hp.parse( + 'NG_042866.1:g.149464G>A') + c_int_alt.hgvs_predicted_protein_consequence = { + 'prot': vv.hp.parse('NP_036441.2:p.?')} + hgvs_hg37_alt = vv.hp.parse('NW_004070871.1:g.574546C>T') + c_int_alt.alt_genomic_loci = [{ + 'grch37': { + 'hgvs_genomic_description': hgvs_hg37_alt, + }}, { + 'hg19': { + 'hgvs_genomic_description': hgvs_hg37_alt, + }}] + hgvs_hg37 = vv.hp.parse('NC_000011.10:g.71080333C>T') + c_int_alt.primary_assembly_loci = { + 'hg38': {'hgvs_genomic_description': hgvs_hg37}, + 'grch38': {'hgvs_genomic_description': hgvs_hg37}} + c_int_alt.hgvs_genomic = hgvs_hg37_alt + vrs_out = self.vrs.variant_validator_output_set_to_vrs(c_int_alt) + warnings_and_messages = { + 'validation_warnings': [], + 'lovd_messages': [], + 'lovd_corrections': [], + 'vrs_output_warnings': [ + 'VRSIntronWarning: VRS does not handle mappings shared ' + 'between genomic and transcript reference sequences. As ' + 'such only the genomic mappings for this hgvs intronic ' + 'transcript variant are preserved.']} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.2NkFm8HK88MqeNkCgj78KidCAXgnsfV1', + 'label': 'NC_000011.10'}, + 'start': 71080332, + 'end': 71080333, + 'id': 'ga4gh:SL.s01bDxQQkHA1YafVKjPKqmPdy_BCgwWp'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.Exskx3X6aUlO_msp7n5jyBYUDkiHG2Xw'}} + VRS_mappings_for_alt_genomic_loci = [{ + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.EZG3Q7xvM2XQ-HQTX5w1BEEmWo-AGqtB', + 'label': 'NW_004070871.1'}, + 'start': 574545, + 'end': 574546, + 'id': 'ga4gh:SL.c9V2DUbRs4P15CWnthnRRgptVohYcy__'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.BzVOTYucxGEVNsslkihaJ_kgH9GN5Tro'}] + vrs_intronic_genomic_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.EZG3Q7xvM2XQ-HQTX5w1BEEmWo-AGqtB', + 'label': 'NW_004070871.1'}, + 'start': 574545, + 'end': 574546, + 'id': 'ga4gh:SL.c9V2DUbRs4P15CWnthnRRgptVohYcy__'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.BzVOTYucxGEVNsslkihaJ_kgH9GN5Tro'} + assert vrs_out['warnings_and_messages'] == warnings_and_messages + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == \ + VRS_mappings_for_alt_genomic_loci + assert vrs_out['vrs_intronic_genomic_variant'] == \ + vrs_intronic_genomic_variant + + def test_vv_output_to_vrs_output_intergenic_type_data(self): + # test 5 g type input (intergenic/psudo-intergenic variant input) + # taken from input test 1 + intergenic_vv_res = copy.copy(self.dummy_vv_output) + # submitted_variant NC_000017.10:g.48279242G>T + intergenic_vv_res.hgvs_refseqgene_variant = vv.hp.parse( + 'NG_007400.1:g.4759C>A') + hgvs_hg37 = vv.hp.parse('NC_000017.10:g.48279242G>T') + hgvs_hg38 = vv.hp.parse('NC_000017.11:g.50201881G>T') + intergenic_vv_res.hgvs_genomic = hgvs_hg38 + intergenic_vv_res.primary_assembly_loci = { + 'hg19': {'hgvs_genomic_description': hgvs_hg37}, + 'hg38': {'hgvs_genomic_description': hgvs_hg38}, + 'grch37': {'hgvs_genomic_description': hgvs_hg37}, + 'grch38': {'hgvs_genomic_description': hgvs_hg38}} + vrs_out = self.vrs.variant_validator_output_set_to_vrs(intergenic_vv_res) + warnings_and_messages = { + 'validation_warnings': [], + 'lovd_messages': [], + 'lovd_corrections': []} + vrs_refseqgene_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.QHD1sq0MO0ekfVC3dyTMD76jTTuJdHzC', + 'label': 'NG_007400.1'}, + 'start': 4758, + 'end': 4759, + 'id': 'ga4gh:SL.Zo3m-4l8X4ec1R9WJoFNnRZJpsf1vxD-'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'A'}, + 'id': 'ga4gh:VA.T6RVcuFrzTqxCS16D3stnBB8uaf86LoT'} + VRS_mapping_for_grch37 = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.AjWXsI7AkTK35XW9pgd3UbjpC3MAevlz', + 'label': 'NC_000017.10'}, + 'start': 48279241, + 'end': 48279242, + 'id': 'ga4gh:SL.BTi2Q9MAcw-eIZjsascBF4kPH7JFHiwK'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.Mu21lzIQgKp1Bzq0-E-DiVNNVS1GeF9l'} + VRS_mapping_for_grch38 = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.dLZ15tNO1Ur0IcGjwc3Sdi_0A6Yf4zm7', + 'label': 'NC_000017.11'}, + 'start': 50201880, + 'end': 50201881, + 'id': 'ga4gh:SL.4sKmwc3lN8mn6DxNKFmV_piFt6zW4nkD'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.VMA149-dG25GHnAHd76yLApQ_5lMS2-4'} + VRS_mappings_for_primary_assemblies = { + 'grch37': VRS_mapping_for_grch37, + 'grch38': VRS_mapping_for_grch38} + assert vrs_out['vrs_intergenic_genomic_variant'] == \ + VRS_mapping_for_grch38 + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + assert vrs_out['warnings_and_messages'] == warnings_and_messages + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['vrs_refseqgene_variant'] == vrs_refseqgene_variant + + def test_vv_output_to_vrs_output_intronic(self): + # test variant.hgvs_refseqgene_variant used for output with transcript data + ex_vv_res = copy.copy(self.dummy_vv_output) + ex_vv_res.hgvs_transcript_variant = vv.hp.parse( + 'NM_000548.3:c.138+821del') + ex_vv_res.hgvs_refseqgene_variant = vv.hp.parse( + 'NG_005895.1:g.5269del') + hgvs_hg37 = vv.hp.parse('NC_000016.9:g.2099575del') + ex_vv_res.selected_assembly = 'GRCh37' + ex_vv_res.primary_assembly_loci = { + 'hg19': {'hgvs_genomic_description': hgvs_hg37}, + 'grch37': {'hgvs_genomic_description': hgvs_hg37}} + vrs_out = self.vrs.variant_validator_output_set_to_vrs(ex_vv_res) + print(vrs_out) + assert vrs_out['VRS_mappings_for_primary_assemblies'] == { 'grch37': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.W6wLoIFOn4G7cjopxPxYNk2lcEqhLQFb', + 'label': 'NC_000016.9'}, + 'start': 2099572, + 'end': 2099575, + 'id': 'ga4gh:SL.OSRCAmPHLKeVCIccBJw2PE2fjBBZVO5g'}, + 'state': { + 'type': 'ReferenceLengthExpression', + 'length': 2, + 'repeatSubunitLength': 1}, + 'id': 'ga4gh:VA.R4sVDvRvhoB_h0TTNcxbRkEkqL8Iwzww'}} + + assert vrs_out['vrs_refseqgene_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.Zuvuz5j8xEuvGT-iyjeCPW96y9eJ-2Rt', + 'label': 'NG_005895.1'}, + 'start': 5266, + 'end': 5269, + 'id': 'ga4gh:SL.7lSDh7A9L3fdqd91RzMX5JcLXzKoRL4m'}, + 'state': { + 'type': 'ReferenceLengthExpression', + 'length': 2, 'repeatSubunitLength': 1}, + 'id': 'ga4gh:VA.3-TnA4NFzRb1rPNumnbdwe6vzFK93Dy3'} + +class TestVRSOutputRangeTypeFromVV(unittest.TestCase): + """ + Test VRS range response to VV uncertain location input types. + """ + @classmethod + def setup_class(self): + self.vrs = HGVS_to_VRS() + # core set of dummy input + self.dummy_vv_output = MockVVData( + selected_assembly='GRCh38', + original = 'test_input', + warnings=[], + lovd_messages = [], + lovd_corrections = [], + stable_gene_ids = { + 'hgnc_id': 'HGNC:TST', + 'entrez_gene_id': '0000', + 'ucsc_id': 'uc0tst.1', + 'omim_id': ['0000000']}, + gene_symbol = 'Dummy_S', + description = "Text transcript description",) + def _two_span_var(self, ac, var_type, s1, e1, s2, e2, edit): + if var_type in ['g','m','o']: + edit = vv.hp.parse_dna_edit(edit) + else: + edit = vv.hp.parse_rna_edit(edit) + o_start_pos, start_end = _hgvs_offset_pos_from_str_in( + s1, + None, + ref_type=var_type, + end=e1) + start = Interval(start=o_start_pos,end=start_end) + end_start, o_end_pos = _hgvs_offset_pos_from_str_in( + s2, + None, + ref_type=var_type, + end=e2) + end = Interval(start=end_start,end=o_end_pos) + full_obj = hgvs_obj_from_existing_edit( + ac, + var_type, + FEInterval( + start=start, + end=end), + edit) + return full_obj + + # possible data for additional tests? + #SequenceLocation (redundant data == to below skipped) + # in: + # end: [44908822, 44908922] + # start: [44908721, 44908821] + # out: + # ga4gh_digest: 8-sGv9AY7GJT6QVgqbxhMXFNamnWcFJu + # ga4gh_identify: ga4gh:SL.8-sGv9AY7GJT6QVgqbxhMXFNamnWcFJu + # ga4gh_serialize: '{"end":[44908822,44908922],"sequenceReference":{"refgetAccession":"SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","type":"SequenceReference"},"start":[44908721,44908821],"type":"SequenceLocation"}' + # - name: "SequenceLocation w/Definite and Indefinite Ranges" + # in: + # end: [44908822, null] + # start: [44908721, 44908821] + # out: + # ga4gh_digest: XQAXpesghmuDHziAcDCAmESBOPKTBhwD + # ga4gh_identify: ga4gh:SL.XQAXpesghmuDHziAcDCAmESBOPKTBhwD + # ga4gh_1_3_identify: ga4gh:VSL.zGh9Zy42Zu9R0sbyB1rXsxd33BIyiORk + # ga4gh_serialize: '{"end":[44908822,null],"sequenceReference":{"refgetAccession":"SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","type":"SequenceReference"},"start":[44908721,44908821],"type":"SequenceLocation"}' + # ga4gh_1_3_serialize: '{"interval":{"end":{"comparator":">=","type":"IndefiniteRange","value":44908822},"start":{"max":44908821,"min":44908721,"type":"DefiniteRange"},"type":"SequenceInterval"},"sequence_id":"F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","type":"SequenceLocation"}' + # - name: "SequenceLocation w/more Definite and Indefinite Ranges" + # in: + # end: [null, 44908822] + # start: [44908721, 44908821] + # out: + # ga4gh_digest: OYplG0vkUojmK2hDejylSykx-np3HPFP + # ga4gh_identify: ga4gh:SL.OYplG0vkUojmK2hDejylSykx-np3HPFP + # ga4gh_serialize: '{"end":[null,44908822],"sequenceReference":{"refgetAccession":"SQ.F-LrLMe1SRpfUZHkQmvkVKFEGaoDeHul","type":"SequenceReference"},"start":[44908721,44908821],"type":"SequenceLocation"}' + + def test_vrs_range_unc_del(self): + # test unknown range input for del ins and dup + # 'NM_001377405.1:c.(4_246)delN[15]' + # "NC_000003.12:g.(63912602_63912844)delNNNNNNNNNNNNNNN" + tx_unk_var = vv.hp.parse('NM_001377405.1:c.(4_246)delNNNNNNNNNNNNNNN') + gen_unk_var = vv.hp.parse( + "NC_000003.12:g.(63912602_63912844)delNNNNNNNNNNNNNNN") + unc_test = copy.copy(self.dummy_vv_output) + unc_test.hgvs_transcript_variant = tx_unk_var + unc_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs(unc_test) + warnings_and_messages = { + 'validation_warnings': [], + 'lovd_messages': [], + 'lovd_corrections': []} + assert vrs_out['warnings_and_messages'] == warnings_and_messages + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.xVHtmFKoGeLwmbDXLl_nxw0q4c2FRMYg', + 'label': 'NM_001377405.1'}, + 'start': [405, 648], + 'end': [405, 648], + 'id': 'ga4gh:SL.wV0h-QOnWPUSj3T9ImdcphfFnqveZIMq'}, + 'state': { + 'type': 'LengthExpression', + 'length': [0,228]},# this would better be something like -15 + # but does not translate well into VRS + 'id': 'ga4gh:VA.EpZ3bKdZlmN7K4mmRaLPtXavbriZJCT5'} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.Zu7h9AggXxhTaGVsy7h_EZSChSZGcmgX', + 'label': 'NC_000003.12'}, + 'start': [63912601,63912844], + 'end': [63912601,63912844], + 'id': 'ga4gh:SL.u2JpyMzpWPiqASuoO6yvXH_615hlTpRQ'}, + 'state': { + 'type': 'LengthExpression', + 'length': [0,228]},# as before this is the post-change state + # 15 bases del from unc pos maps badly + 'id': 'ga4gh:VA.Nt3i0Fc2JYErd93LznLg1G-l3hYTSN80'}} + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + VRS_mappings_for_alt_genomic_loci = [] + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] ==\ + VRS_mappings_for_alt_genomic_loci + + def test_vrs_range_unc_ins(self): + # now test ins delins dup and sub + # 'NM_001377405.1:c.(4_246)insTTTT' + # "NC_000003.12:g.(63912602_63912844)insTTT" + tx_unk_var = vv.hp.parse('NM_001377405.1:c.(4_246)insTTTT') + gen_unk_var = vv.hp.parse("NC_000003.12:g.(63912602_63912844)insTTTT") + unc_test = copy.copy(self.dummy_vv_output) + unc_test.hgvs_transcript_variant = tx_unk_var + unc_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs(unc_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.xVHtmFKoGeLwmbDXLl_nxw0q4c2FRMYg', + 'label': 'NM_001377405.1'}, + 'start': [405, 648], + 'end': [405, 648], + 'id': 'ga4gh:SL.wV0h-QOnWPUSj3T9ImdcphfFnqveZIMq'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'TTTT'}, + 'id': 'ga4gh:VA.HLa3ZXHcJk2LPUFAX8d7qYE-NJcIVl0D'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.Zu7h9AggXxhTaGVsy7h_EZSChSZGcmgX', + 'label': 'NC_000003.12'}, + 'start': [63912601, 63912844], + 'end': [63912601, 63912844], + 'id': 'ga4gh:SL.u2JpyMzpWPiqASuoO6yvXH_615hlTpRQ'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'TTTT'}, + 'id': 'ga4gh:VA.KsKPm1k9vo1xoWj2AsKbzWrXzcKZ_rdG'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] ==\ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + def test_vrs_range_unc_delins(self): + # 'NM_001377405.1:c.(4_246)delinsTTTT' + # "NC_000003.12:g.(63912602_63912844)delinsTTT" + tx_unk_var = vv.hp.parse('NM_001377405.1:c.(4_246)delinsTTTT') + gen_unk_var = vv.hp.parse( + "NC_000003.12:g.(63912602_63912844)delinsTTTT") + unc_test = copy.copy(self.dummy_vv_output) + unc_test.hgvs_transcript_variant = tx_unk_var + unc_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs(unc_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.xVHtmFKoGeLwmbDXLl_nxw0q4c2FRMYg', + 'label': 'NM_001377405.1'}, + 'start': [405, 648], + 'end': [405, 648], + 'id': 'ga4gh:SL.wV0h-QOnWPUSj3T9ImdcphfFnqveZIMq'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'TTTT'}, + 'id': 'ga4gh:VA.HLa3ZXHcJk2LPUFAX8d7qYE-NJcIVl0D'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.Zu7h9AggXxhTaGVsy7h_EZSChSZGcmgX', + 'label': 'NC_000003.12'}, + 'start': [63912601, 63912844], + 'end': [63912601, 63912844], + 'id': 'ga4gh:SL.u2JpyMzpWPiqASuoO6yvXH_615hlTpRQ'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'TTTT'}, + 'id': 'ga4gh:VA.KsKPm1k9vo1xoWj2AsKbzWrXzcKZ_rdG'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + def test_vrs_range_unc_dup(self): + # 'NM_001377405.1:c.(4_246)dup' + # "NC_000003.12:g.(63912602_63912844)dup" + tx_unk_var = vv.hp.parse('NM_001377405.1:c.(4_246)dup') + gen_unk_var = vv.hp.parse("NC_000003.12:g.(63912602_63912844)dup") + unc_test = copy.copy(self.dummy_vv_output) + unc_test.hgvs_transcript_variant = tx_unk_var + unc_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs(unc_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.xVHtmFKoGeLwmbDXLl_nxw0q4c2FRMYg', + 'label': 'NM_001377405.1'}, + 'start': [405, 648], + 'end': [405, 648], + 'id': 'ga4gh:SL.wV0h-QOnWPUSj3T9ImdcphfFnqveZIMq'}, + 'state': { + 'type': 'LengthExpression', + 'length': [0, 486]}, + 'id': 'ga4gh:VA.oqDJt3ITorI0hrvsS7LmBUx-mBtQZRS7'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.Zu7h9AggXxhTaGVsy7h_EZSChSZGcmgX', + 'label': 'NC_000003.12'}, + 'start': [63912601, 63912844], + 'end': [63912601, 63912844], + 'id': 'ga4gh:SL.u2JpyMzpWPiqASuoO6yvXH_615hlTpRQ'}, + 'state': { + 'type': 'LengthExpression', + 'length': [0, 486]}, + 'id': 'ga4gh:VA.gtya_8q_3eubU0Hijsw_bR1DNU3KoPo4'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + def test_vrs_range_unc_basechanage(self): + # 'NM_001377405.1:c.(4_246)C>G' # probably nonsense but we don't + # (can't) normalise so it works to test the code + # "NC_000003.12:g.(63912602_63912844)C>G" + tx_unk_var = vv.hp.parse('NM_001377405.1:c.(4_246)C>G') + gen_unk_var = vv.hp.parse("NC_000003.12:g.(63912602_63912844)C>G") + unc_test = copy.copy(self.dummy_vv_output) + unc_test.hgvs_transcript_variant = tx_unk_var + unc_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs(unc_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.xVHtmFKoGeLwmbDXLl_nxw0q4c2FRMYg', + 'label': 'NM_001377405.1'}, + 'start': [405, 648], + 'end': [405, 648], + 'id': 'ga4gh:SL.wV0h-QOnWPUSj3T9ImdcphfFnqveZIMq'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'G'}, + 'id': 'ga4gh:VA.6r6uUT5Xopvs9mzMgnoLGnxn0UozdQ69'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.Zu7h9AggXxhTaGVsy7h_EZSChSZGcmgX', + 'label': 'NC_000003.12'}, + 'start': [63912601, 63912844], + 'end': [63912601, 63912844], + 'id': 'ga4gh:SL.u2JpyMzpWPiqASuoO6yvXH_615hlTpRQ'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'G'}, + 'id': 'ga4gh:VA.p4PEk1oAj25G_7C81VjYTNUmzxvgA1pu'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + # test range input with spans for each end + # taken from test_uncertain_3 + # 'NM_006138.4:c.(1_20)_(30_36)del' + # "grch38" 'NC_000011.10:g.(60061161_60061180)_(60061190_60061196)del' + + def test_vrs_range_unc_span_intronic(self): + # test that intronic detection works on span of span types + tx_unk_var_intr = self._two_span_var( + 'NM_006138.4','c','1','20','30','36','del') + tx_unk_var_intr.posedit.pos.start.start.offset = True + assert self.vrs._intronic(tx_unk_var_intr) + + def test_vrs_range_unc_span_del(self): + #'NM_006138.4:c.(1_20)_(30_36)del' + tx_unk_var = self._two_span_var( + 'NM_006138.4','c','1','20','30','36','del') + gen_unk_var = self._two_span_var( + 'NC_000011.10','g', + '60061161','60061180', + '60061190','60061196', + 'del') + unc_multi_end_test = copy.copy(self.dummy_vv_output) + unc_multi_end_test.hgvs_transcript_variant = tx_unk_var + unc_multi_end_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs(unc_multi_end_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.kDpc3QS9BP_MPB7-8c32tLm_ofkpKuki', + 'label': 'NM_006138.4'}, + 'start': [128, 148], + 'end': [157, 164], + 'id': 'ga4gh:SL.VKym0ZocRoqcMIFALbtocM9G-JXI_NPq'}, + 'state': { + 'type': 'LengthExpression', + 'length': [9, 36]}, + 'id': 'ga4gh:VA._QbcvpURg3S57MKEBF5kNXJagBt8LD3Q'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.2NkFm8HK88MqeNkCgj78KidCAXgnsfV1', + 'label': 'NC_000011.10'}, + 'start': [60061160, 60061180], + 'end': [60061189, 60061196], + 'id': 'ga4gh:SL.3SYax-eEvXhMFCS3jWUrqpOQmBwbt5Xd'}, + 'state': { + 'type': 'LengthExpression', + 'length': [9, 36]}, + 'id': 'ga4gh:VA.BimsbJi2CRmY5zw8dTShvosXgdW4KQGf'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + def test_vrs_range_unc_span_delins(self): + #'NM_006138.4:c.(1_30)_(20_36)delinsCCC' + tx_unk_var = self._two_span_var( + 'NM_006138.4','c','1','20','30','36','delinsCCC') + gen_unk_var = self._two_span_var( + 'NC_000011.10','g', + '60061161','60061180', + '60061190','60061196', + 'delinsCCC') + unc_multi_end_test = copy.copy(self.dummy_vv_output) + unc_multi_end_test.hgvs_transcript_variant = tx_unk_var + unc_multi_end_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs( + unc_multi_end_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.kDpc3QS9BP_MPB7-8c32tLm_ofkpKuki', + 'label': 'NM_006138.4'}, + 'start': [128, 148], + 'end': [157, 164], + 'id': 'ga4gh:SL.VKym0ZocRoqcMIFALbtocM9G-JXI_NPq'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CCC'}, + 'id': 'ga4gh:VA.hJy6_vMrmQUVQQzu3ZBmPboypcESYKmk'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.2NkFm8HK88MqeNkCgj78KidCAXgnsfV1', + 'label': 'NC_000011.10'}, + 'start': [60061160, 60061180], + 'end': [60061189, 60061196], + 'id': 'ga4gh:SL.3SYax-eEvXhMFCS3jWUrqpOQmBwbt5Xd'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CCC'}, + 'id': 'ga4gh:VA.W4I8QnB7DOZzL5LFvdVLwWeEI0BdEWvX'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + def test_vrs_range_unc_span_ins(self): + #ins + #'NM_006138.4:c.(1_30)_(20_36)insCCC' + tx_unk_var = self._two_span_var( + 'NM_006138.4','c','1','30','20','36','insCCC') + gen_unk_var = self._two_span_var( + 'NC_000011.10','g', + '60061161','60061190', + '60061180','60061196', + 'insCCC') + unc_multi_end_test = copy.copy(self.dummy_vv_output) + unc_multi_end_test.hgvs_transcript_variant = tx_unk_var + unc_multi_end_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs( + unc_multi_end_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.kDpc3QS9BP_MPB7-8c32tLm_ofkpKuki', + 'label': 'NM_006138.4'}, + 'start': [128, 158], + 'end': [147, 164], + 'id': 'ga4gh:SL.aXpOvtXB05WvJoNnaJEw8XCUJFKJfSRT'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CCC'}, + 'id': 'ga4gh:VA.mTaxUtyrZx27pR7KgWFH392KzesV6PNm'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.2NkFm8HK88MqeNkCgj78KidCAXgnsfV1', + 'label': 'NC_000011.10'}, + 'start': [60061160, 60061190], + 'end': [60061179, 60061196], + 'id': 'ga4gh:SL.HJElyJSuBdeXx6zYpvI6kl2gRrmYYYAw'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CCC'}, + 'id': 'ga4gh:VA.zqcJ7xJM5ucY5wAEEBf-VWcqnyRinBL8'}} + + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + + + def test_vrs_range_unc_span_dup(self): + #'NM_006138.4:c.(1_30)_(20_36)dup' + tx_unk_var = self._two_span_var( + 'NM_006138.4','c','1','20','30','36','dup') + gen_unk_var = self._two_span_var( + 'NC_000011.10','g', + '60061161','60061180', + '60061190','60061196', + 'dup') + unc_multi_end_test = copy.copy(self.dummy_vv_output) + unc_multi_end_test.hgvs_transcript_variant = tx_unk_var + unc_multi_end_test.primary_assembly_loci = { + 'hg38': { + 'hgvs_genomic_description': gen_unk_var, + }, + 'grch38': { + 'hgvs_genomic_description': gen_unk_var, + } + } + vrs_out = self.vrs.variant_validator_output_set_to_vrs( + unc_multi_end_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.kDpc3QS9BP_MPB7-8c32tLm_ofkpKuki', + 'label': 'NM_006138.4'}, + 'start': [128, 148], + 'end': [157, 164], + 'id': 'ga4gh:SL.VKym0ZocRoqcMIFALbtocM9G-JXI_NPq'}, + 'state': { + 'type': 'LengthExpression', + 'length': [18, 72]}, + 'id': 'ga4gh:VA.QldDqxFkU7wNQbB9QJvfiR3apKiWd25L'} + VRS_mappings_for_primary_assemblies = { + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.2NkFm8HK88MqeNkCgj78KidCAXgnsfV1', + 'label': 'NC_000011.10'}, + 'start': [60061160, 60061180], + 'end': [60061189, 60061196], + 'id': 'ga4gh:SL.3SYax-eEvXhMFCS3jWUrqpOQmBwbt5Xd'}, + 'state': { + 'type': 'LengthExpression', + 'length': [18, 72]}, + 'id': 'ga4gh:VA.6_mydc1dT4GgT4lH7hsj1kzL7CuyHi41'}} + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert vrs_out['VRS_mappings_for_primary_assemblies'] == \ + VRS_mappings_for_primary_assemblies + assert vrs_out['VRS_mappings_for_alt_genomic_loci'] == [] + + def test_vrs_range_unc_span_r_type_rna_var(self): + # test same for r. type variants + # prot is dummy but same type as expected + # note that r type are actually C or n type as far as biocommons.hgvs + # is concerned, r is used as similar to ":n.", but without caring about + # coding status + rna_test = copy.copy(self.dummy_vv_output) + tx_unk_var = self._two_span_var( + 'NM_006138.4','c','1','20','30','36','dup') + rna_test.rna_data = { + 'rna_variant':tx_unk_var, + 'translation_slr':vv.hp.parse('NP_055935.4:p.?'), + 'usage_warnings':"RNA warning"} + vrs_out = self.vrs.variant_validator_output_set_to_vrs(rna_test) + vrs_transcript_variant = { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.kDpc3QS9BP_MPB7-8c32tLm_ofkpKuki', + 'label': 'NM_006138.4'}, + 'start': [128, 148], + 'end': [157, 164], + 'id': 'ga4gh:SL.VKym0ZocRoqcMIFALbtocM9G-JXI_NPq'}, + 'state': { + 'type': 'LengthExpression', + 'length': [18, 72]}, + 'id': 'ga4gh:VA.QldDqxFkU7wNQbB9QJvfiR3apKiWd25L'} + print(vrs_out) + assert vrs_out['vrs_transcript_variant'] == vrs_transcript_variant + assert 'VRS_mappings_for_primary_assemblies' not in vrs_out + assert 'VRS_mappings_for_alt_genomic_loci' not in vrs_out + +class TestVRSObjectInit(unittest.TestCase): + """ + Test all remaining untested object init features, these are not normally + exposed by VV, so may be more subject to change than might otherwise be + expected. + """ + + def test_vrs_object_seqrepo(self): + # test setting SR loc + config = ConfigParser() + config.read(CONFIG_DIR) + seq_repo_path = os.path.join(config["seqrepo"]["location"], + config["seqrepo"]["version"]) + vrs = HGVS_to_VRS(seq_repo=SeqRepo(seq_repo_path)) + #do ID fetch based on set seqrepo as above + vrs_seq_id = vrs.id_fetch('NR_110010.2') + assert vrs_seq_id == 'SQ.Ksaa229gzGC4Uc3ytCixai4vziud-MKi' + + # there is an additional (for now untested fallback for when + # VariantValidator.settings import CONFIG_DIR + # fails, but testing this here is hard, and since VV can't work in this case + # is redundant + + def test_vrs_object_init_id_fetch(self): + # test setting ID fetch method + def _test_id_fetch(input_id): + if input_id == 'test_true': + return 'True' + return False + vrs = HGVS_to_VRS(id_fetch=_test_id_fetch) + vrs_seq_id = vrs.id_fetch('NR_110010.2') + assert vrs_seq_id is False + vrs_seq_id = vrs.id_fetch('test_true') + assert vrs_seq_id == 'True' + + def test_vrs_object_init_preset_cache(self): + # dummy set a pre-filled cache with normally used and filled with + # previous hgvs sourced VRS output, due to how common repeats VV output + # can be(multi transcript but same genomic), but with weird contents + # for testing. Test via hgvs_single_var_to_vrs(). + vrs = HGVS_to_VRS(cache = {'not_hgvs':{'not_vrs'}}) + vrs_out = vrs.hgvs_single_var_to_vrs('not_hgvs') + assert vrs_out == {'not_vrs'} + assert vrs.cache == {'not_hgvs':{'not_vrs'}} + + def test_vrs_object_init_normal_cache(self): + # test normal cache behaviour + vrs = HGVS_to_VRS(cache=True) + assert vrs.cache == {} + +class TestVRSObjectVV(unittest.TestCase): + """ + Tests for VV usage of the code working as intended, should mostly be a + simple shim on vv_output_to_vrs_output internally so not a complex test set + at the moment. + + Testing transcript, intronic transcript, genomic, and error type results + """ + @classmethod + def setup_class(self): + self.vv = Validator() + self.vv.testing = True + + def test_format_as_vrs_tx(self): + variant = 'NM_015120.4:c.35T>C' + results = self.vv.validate(variant, 'GRCh37', 'all') + resultsf = results.format_as_vrs() + print(results.format_as_dict()) + print(resultsf) + result = resultsf['ga4gh:VA.x8gKEBS-s638D8uuuQXVJKIgCDcuQJCW'] + assert result['selected_assembly'] == 'GRCh37' + assert result['submitted_variant'] == 'NM_015120.4:c.35T>C' + assert result['warnings_and_messages'] == { + 'validation_warnings': [], + 'lovd_messages': None, + 'lovd_corrections': None} + assert result['transcript_description'] == \ + 'Homo sapiens ALMS1 centrosome and basal body associated '\ + 'protein (ALMS1), transcript variant 1, mRNA' + assert result['gene_ids'] == { + 'hgnc_id': 'HGNC:428', + 'entrez_gene_id': '7840', + 'ensembl_gene_id': 'ENSG00000116127', + 'ucsc_id': 'uc032nrd.1', + 'omim_id': ['606844'], + 'ccds_ids': ['CCDS42697'], + 'current_symbol': 'ALMS1'} + assert result['vrs_transcript_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.mq1Saz29534GzZ6T_jCfUjsqNlifC7pZ', + 'label': 'NM_015120.4' + }, + 'start': 145, + 'end': 146, + 'id': 'ga4gh:SL.x_rdZXUcg7V8Ae5pPVv0OS-faxKyzNQb' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C' + }, + 'id': 'ga4gh:VA.x8gKEBS-s638D8uuuQXVJKIgCDcuQJCW' + } + assert result['vrs_refseqgene_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.r47Jw6neAxxIQgeyZLmdPrqFN4S6h7hy', + 'label': 'NG_011690.1'}, + 'start': 5145, + 'end': 5146, + 'id': 'ga4gh:SL.3CEnQ3JJo2jQbraJ0UHIO-lqBbR4xs15'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C'}, + 'id': 'ga4gh:VA.TjhuWhUG-PJapHphK3r0HWyU5b9CgGG_'} + + assert result['vrs_predicted_protein_consequence'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.ggxH8oENu3WDprE6Z_Qg1HvOiT0d8UNa', + 'label': 'NP_055935.4' + }, + 'start': 11, + 'end': 12, + 'id': 'ga4gh:SL.qOpPs9dwgqK6MuH3CTQUDoRYbi9IWwSh' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'P' + }, + 'id': 'ga4gh:VA.cJPulDXE1G73sZS4dHaZX_27-c4pNjYo' + } + assert result['VRS_mappings_for_primary_assemblies'] == { + 'grch37': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.9KdcA9ZpY1Cpvxvg8bMSLYDUpsX6GDLO', + 'label': 'NC_000002.11' + }, + 'start': 73613030, + 'end': 73613031, + 'id': 'ga4gh:SL.IzA6GY6mvDiJ1EzMbe1Cxv6jyhg4u8t7' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CGGA' + }, + 'id': 'ga4gh:VA.rKc_HxeylEKYQzYo_e_kY1lqYLF6Ch5m' + }, + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.pnAqCRBrTsUoBghSD1yp_jXWSmlbdh4g', + 'label': 'NC_000002.12' + }, + 'start': 73385902, + 'end': 73385903, + 'id': 'ga4gh:SL.ndPBndyntJTTRHuU6tYpgbN6TUsRv94B' + }, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'CGGA' + }, + 'id': 'ga4gh:VA.eDiJCZTUpLf1UQHaUFFP1UaPsHfbOjyC' + } + } + assert result['VRS_mappings_for_alt_genomic_loci'] == [{ + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.8wk0aF1q6kFjIccOvc8bq3AJVuqWersd', + 'label': 'NW_025791766.1'}, + 'start': 55397, + 'end': 55398, + 'id': 'ga4gh:SL.vknogVomLiGh3q7IfPcZDAU9Kg6nSvyY'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'C'}, + 'id': 'ga4gh:VA.pdjaBvFWh8JgChnGQzOTt9s5zxhNudPS'}] + def test_format_as_vrs_tx_intronic(self): + # from test_variant5 + variant = 'NC_000023.10:g.33229673A>T' + results = self.vv.validate(variant, 'GRCh37', 'all').format_as_vrs() + print(results) + #'NM_000109.3:c.7+127703T>A' + result = results['ga4gh:VA.6LfJDlOTAm1QeCDWfbE0986oJwQ25NSd'] + assert result['selected_assembly'] == 'GRCh37' + assert result['submitted_variant'] == 'NC_000023.10:g.33229673A>T' + assert result['warnings_and_messages'] == { + 'validation_warnings': [], + 'lovd_messages': None, 'lovd_corrections': None, + 'vrs_output_warnings': [ + 'VRSIntronWarning: VRS does not handle mappings shared between ' + 'genomic and transcript reference sequences. As such only the ' + 'genomic mappings for this hgvs intronic transcript variant are' + ' preserved.']} + assert result['gene_ids']['hgnc_id'] == 'HGNC:2928' + assert result['gene_ids']['entrez_gene_id'] == '1756' + assert result['gene_ids']['ensembl_gene_id'] =='ENSG00000198947' + assert result['gene_ids']['ucsc_id'] == 'uc004dda.2' + assert result['transcript_description'] == \ + 'Homo sapiens dystrophin (DMD), transcript variant Dp427c, mRNA' + assert result['vrs_intronic_genomic_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.v7noePfnNpK8ghYXEqZ9NukMXW7YeNsm', + 'label': 'NC_000023.10'}, + 'start': 33229672, + 'end': 33229673, + 'id': 'ga4gh:SL.WqDN0NCy9-Y7PVGwqvI0VtcogTVZCsM4'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.6LfJDlOTAm1QeCDWfbE0986oJwQ25NSd'} + assert result['VRS_mappings_for_primary_assemblies'] == { + 'grch37': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.v7noePfnNpK8ghYXEqZ9NukMXW7YeNsm', + 'label': 'NC_000023.10'}, + 'start': 33229672, + 'end': 33229673, + 'id': 'ga4gh:SL.WqDN0NCy9-Y7PVGwqvI0VtcogTVZCsM4'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.6LfJDlOTAm1QeCDWfbE0986oJwQ25NSd'}, + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP', + 'label': 'NC_000023.11'}, + 'start': 33211555, + 'end': 33211556, + 'id': 'ga4gh:SL.wdORKcdcBA4pikHhob-AdKP-T-IlgKbV'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.I70ooeAvjS4QEfhx9ZYZI3ews44FjqiH'}} + def test_format_as_vrs_genomic(self): + # from test_variant16 + variant = 'NC_000017.10:g.48279242G>T' + results = self.vv.validate(variant, 'GRCh37', 'all').format_as_vrs() + print(results) + result = results['ga4gh:VA.Mu21lzIQgKp1Bzq0-E-DiVNNVS1GeF9l'] + assert result['selected_assembly'] == 'GRCh37' + assert result['submitted_variant'] == 'NC_000017.10:g.48279242G>T' + assert result['warnings_and_messages'] == { + 'validation_warnings': [ + 'TranscriptIdentificationWarning: No individual transcripts ' + 'have been identified that fully overlap the described ' + 'variation in the genomic sequence. Large variants might span ' + 'one or more genes and are currently only described at the ' + 'genome (g.) level.'], + 'lovd_messages': None, + 'lovd_corrections': None} + assert result['gene_ids'] == { + 'hgnc_id': 'HGNC:2197', + 'entrez_gene_id': '1277', + 'ensembl_gene_id': 'ENSG00000108821', + 'ucsc_id': 'uc002iqm.4', + 'omim_id': ['120150'], + 'ccds_ids': ['CCDS11561'], + 'current_symbol': 'COL1A1'} + assert result['transcript_description'] == '' + assert result['vrs_intergenic_genomic_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.AjWXsI7AkTK35XW9pgd3UbjpC3MAevlz', + 'label': 'NC_000017.10'}, + 'start': 48279241, + 'end': 48279242, + 'id': 'ga4gh:SL.BTi2Q9MAcw-eIZjsascBF4kPH7JFHiwK'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.Mu21lzIQgKp1Bzq0-E-DiVNNVS1GeF9l'} + + assert result['vrs_refseqgene_variant'] == { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.QHD1sq0MO0ekfVC3dyTMD76jTTuJdHzC', + 'label': 'NG_007400.1'}, + 'start': 4758, + 'end': 4759, + 'id': 'ga4gh:SL.Zo3m-4l8X4ec1R9WJoFNnRZJpsf1vxD-'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'A'}, + 'id': 'ga4gh:VA.T6RVcuFrzTqxCS16D3stnBB8uaf86LoT'} + assert result['VRS_mappings_for_primary_assemblies'] == { + 'grch37': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.AjWXsI7AkTK35XW9pgd3UbjpC3MAevlz', + 'label': 'NC_000017.10'}, + 'start': 48279241, + 'end': 48279242, + 'id': 'ga4gh:SL.BTi2Q9MAcw-eIZjsascBF4kPH7JFHiwK'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.Mu21lzIQgKp1Bzq0-E-DiVNNVS1GeF9l'}, + 'grch38': { + 'type': 'Allele', + 'location': { + 'type': 'SequenceLocation', + 'sequenceReference': { + 'type': 'SequenceReference', + 'refgetAccession': 'SQ.dLZ15tNO1Ur0IcGjwc3Sdi_0A6Yf4zm7', + 'label': 'NC_000017.11'}, + 'start': 50201880, + 'end': 50201881, + 'id': 'ga4gh:SL.4sKmwc3lN8mn6DxNKFmV_piFt6zW4nkD'}, + 'state': { + 'type': 'LiteralSequenceExpression', + 'sequence': 'T'}, + 'id': 'ga4gh:VA.VMA149-dG25GHnAHd76yLApQ_5lMS2-4'}} + + def test_format_as_vrs_err_result(self): + variant = 'NM_007075.3:r.235_236insGCCCACCCACCTGCCAG' + results = self.vv.validate(variant, 'GRCh37', 'all').format_as_vrs() + assert 'error_1' in results + assert results['error_1'] == { + 'selected_assembly': 'GRCh37', + 'submitted_variant': 'NM_007075.3:r.235_236insGCCCACCCACCTGCCAG', + 'warnings_and_messages': { + 'validation_warnings': [ + 'The IUPAC RNA alphabet dictates that RNA variants must ' + 'use the character u in place of t'], + 'lovd_messages': None, + 'lovd_corrections': None}, + 'gene_ids': {}, + 'transcript_description': ''} + + def test_format_as_vrs_multi_err_result(self): + variants = ['NM_007075.3:r.235_236insGCCCACCCACCTGCCAG', + 'NC_000017.10:g.41232400_41236235del383'] + variants = json.dumps(variants) + results = self.vv.validate(variants, 'GRCh37', 'all').format_as_vrs() + assert 'error_1' in results + assert 'error_2' in results + assert results['error_1'] == { + 'selected_assembly': 'GRCh37', + 'submitted_variant': 'NM_007075.3:r.235_236insGCCCACCCACCTGCCAG', + 'warnings_and_messages': { + 'validation_warnings': + ['The IUPAC RNA alphabet dictates that RNA variants must ' + 'use the character u in place of t'], + 'lovd_messages': None, + 'lovd_corrections': None}, + 'gene_ids': {}, + 'transcript_description': ''} + assert results['error_2'] == { + 'selected_assembly': 'GRCh37', + 'submitted_variant': 'NC_000017.10:g.41232400_41236235del383', + 'warnings_and_messages': { + 'validation_warnings': [ + 'Length implied by coordinates must equal sequence deletion length', + 'Trailing digits are not permitted in HGVS variant descriptions', + 'Refer to http://varnomen.hgvs.org/recommendations/DNA/variant/'], + 'lovd_messages': None, + 'lovd_corrections': None}, + 'gene_ids': {}, + 'transcript_description': ''}