diff --git a/.vscode/launch.json b/.vscode/launch.json index 4110abc..b2d478d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "debugpy", "request": "launch", "module": "src.cli", - "args": ["NM_000038.6(APC):c.2476T>G", "--genome-release", "grch38"], + "args": ["NM_000314.7(PTEN):c.1133_1136del", "--genome-release", "grch38"], "console": "integratedTerminal" }, { diff --git a/src/criteria/auto_pm4_bp3.py b/src/criteria/auto_pm4_bp3.py index 6e9b0cc..43959e7 100644 --- a/src/criteria/auto_pm4_bp3.py +++ b/src/criteria/auto_pm4_bp3.py @@ -99,6 +99,20 @@ def is_inframe_delins(var_data: AutoACMGData) -> bool: return True return False + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """ + Check if BP3 is not applicable for the variant. + + Args: + var_data (AutoACMGData): The variant information. + + Returns: + bool: True if BP3 is not applicable, False otherwise. + """ + if seqvar.chrom == "MT": + return True + return False + def verify_pm4bp3(self, seqvar: SeqVar, var_data: AutoACMGData) -> Tuple[Optional[PM4BP3], str]: """Predicts PM4 and BP3 criteria for the provided sequence variant. @@ -182,6 +196,23 @@ def predict_pm4bp3( bp3_pred = AutoACMGPrediction.Failed pm4_strength = AutoACMGStrength.PathogenicModerate bp3_strength = AutoACMGStrength.BenignSupporting + + # BP3 is not applicable for some VCEPs + if self._bp3_not_applicable(seqvar, var_data): + return ( + AutoACMGCriteria( + name="PM4", + prediction=pm4_pred, + strength=pm4_strength, + summary=comment, + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=bp3_strength, + summary="BP3 is not applicable for the gene.", + ), + ) return ( AutoACMGCriteria( name="PM4", diff --git a/src/defs/auto_acmg.py b/src/defs/auto_acmg.py index 774d911..a497f9d 100644 --- a/src/defs/auto_acmg.py +++ b/src/defs/auto_acmg.py @@ -1,6 +1,8 @@ from enum import auto from typing import Dict, List, Optional +from pydantic import BaseModel, ConfigDict + from src.defs.annonars_variant import GnomadExomes, GnomadMtDna from src.defs.core import AutoAcmgBaseEnum, AutoAcmgBaseModel from src.defs.mehari import Exon, TranscriptGene, TranscriptSeqvar @@ -428,3 +430,17 @@ class AutoACMGResult(AutoAcmgBaseModel): data: AutoACMGData = AutoACMGData() # ; ACMG criteria prediction criteria: AutoACMGCriteriaResult = AutoACMGCriteriaResult() + + +class VcepSpec(BaseModel): + #: Identifier, e.g., "GN002" + identifier: str + #: Version, e.g., "2.0.0" + version: str + #: Title of the VCEP specification, e.g., "ClinGen Cardiomyopathy Expert Panel Specifications + #: to the ACMG/AMP Variant Interpretation Guidelines for MYH7". + title: Optional[str] = None + + model_config = ConfigDict( + frozen=True, + ) diff --git a/src/vcep/__init__.py b/src/vcep/__init__.py index 314e958..1428079 100644 --- a/src/vcep/__init__.py +++ b/src/vcep/__init__.py @@ -15,7 +15,7 @@ from src.vcep.glaucoma import GlaucomaPredictor from src.vcep.hbopc import HBOPCPredictor from src.vcep.hearing_loss import HearingLossPredictor -from src.vcep.hhtp import HHTPredictor +from src.vcep.hht import HHTPredictor from src.vcep.insight_colorectal_cancer import InsightColorectalCancerPredictor from src.vcep.leber_congenital_amaurosis import LeberCongenitalAmaurosisPredictor from src.vcep.lysosomal_diseases import LysosomalDiseasesPredictor diff --git a/src/vcep/acadvl.py b/src/vcep/acadvl.py index 13ce09d..48bcd58 100644 --- a/src/vcep/acadvl.py +++ b/src/vcep/acadvl.py @@ -4,12 +4,26 @@ Link: https://cspec.genome.network/cspec/ui/svi/doc/GN021 """ +from typing import Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor -from src.defs.auto_acmg import AutoACMGCriteria, AutoACMGData, AutoACMGPrediction, AutoACMGStrength +from src.defs.auto_acmg import ( + AutoACMGCriteria, + AutoACMGData, + AutoACMGPrediction, + AutoACMGStrength, + VcepSpec, +) from src.defs.seqvar import SeqVar +#: VCEP specification for ACADVL. +SPEC: VcepSpec = VcepSpec( + identifier="GN021", + version="1.0.0", +) + PM1_CLUSTER = [ (214, 223), # Nucleotide and substrate binding (249, 251), # Nucleotide and substrate binding @@ -44,3 +58,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary="Variant does not fall within any critical region for ACADVL. PM1 is not met.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 to be not applicable for ACADVL.""" + return True diff --git a/src/vcep/brain_malformations.py b/src/vcep/brain_malformations.py index 293ab91..0f95169 100644 --- a/src/vcep/brain_malformations.py +++ b/src/vcep/brain_malformations.py @@ -74,6 +74,26 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri else: return super().predict_pm1(seqvar, var_data) + def predict_pm4bp3( + self, seqvar: SeqVar, var_data: AutoACMGData + ) -> Tuple[AutoACMGCriteria, AutoACMGCriteria]: + """Override predict_pm4bp3 to include VCEP-specific logic for brain malformations VCEP.""" + logger.info("Predict PM4 and BP3") + return ( + AutoACMGCriteria( + name="PM4", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.PathogenicModerate, + summary="PM4 is not applicable for the brain malformations VCEP.", + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary="BP3 is not applicable for the brain malformations VCEP.", + ), + ) + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Change the PhyloP100 score threshold for BP7.""" var_data.thresholds.phyloP100 = 0.1 diff --git a/src/vcep/cardiomyopathy.py b/src/vcep/cardiomyopathy.py index 8e71500..25ff073 100644 --- a/src/vcep/cardiomyopathy.py +++ b/src/vcep/cardiomyopathy.py @@ -20,6 +20,8 @@ https://cspec.genome.network/cspec/ui/svi/doc/GN103 """ +from typing import Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor @@ -99,6 +101,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri return super().predict_pm1(seqvar, var_data) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Cardiomyopathy.""" + return True + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for Cardiomyopathy VCEP.""" var_data.thresholds.bp7_donor = 7 diff --git a/src/vcep/cdh1.py b/src/vcep/cdh1.py index 757c104..527e3d6 100644 --- a/src/vcep/cdh1.py +++ b/src/vcep/cdh1.py @@ -4,6 +4,8 @@ Link: https://cspec.genome.network/cspec/ui/svi/doc/GN007 """ +from typing import Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor @@ -24,6 +26,36 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="PM1 is not applicable for CDH1.", ) + def predict_pm4bp3( + self, seqvar: SeqVar, var_data: AutoACMGData + ) -> Tuple[AutoACMGCriteria, AutoACMGCriteria]: + """Override predict_pm4bp3 to include VCEP-specific logic for CDH1.""" + logger.info("Predict PM4 and BP3") + pred, comment = super().verify_pm4bp3(seqvar, var_data) + if pred: + pm4 = ( + AutoACMGPrediction.Met + if pred.PM4 + else (AutoACMGPrediction.NotMet if pred.PM4 is False else AutoACMGPrediction.Failed) + ) + else: + pm4 = AutoACMGPrediction.Failed + comment = "PM4 could not be verified." + return ( + AutoACMGCriteria( + name="PM4", + prediction=pm4, + strength=AutoACMGStrength.PathogenicSupporting, + summary=comment, + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary="BP3 is not applicable for CDH1.", + ), + ) + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for CDH1 VCEP.""" var_data.thresholds.bp7_donor = 7 diff --git a/src/vcep/cerebral_creatine_deficiency_syndromes.py b/src/vcep/cerebral_creatine_deficiency_syndromes.py index c4354cb..bd08022 100644 --- a/src/vcep/cerebral_creatine_deficiency_syndromes.py +++ b/src/vcep/cerebral_creatine_deficiency_syndromes.py @@ -10,6 +10,8 @@ https://cspec.genome.network/cspec/ui/svi/doc/GN027 """ +from typing import Tuple + from src.criteria.default_predictor import DefaultPredictor from src.defs.auto_acmg import AutoACMGCriteria, AutoACMGData, AutoACMGPrediction, AutoACMGStrength from src.defs.seqvar import SeqVar @@ -29,6 +31,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="PM1 is not applicable for Cerebral Creatine Deficiency Syndromes.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override _bp3_not_applicable for Cerebral Creatine Deficiency Syndromes.""" + return True + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for Cerebral Creatine Deficiency Syndromes VCEP.""" if var_data.hgnc_id == "HGNC:4136": diff --git a/src/vcep/coagulation_factor_deficiency.py b/src/vcep/coagulation_factor_deficiency.py index cc737bd..5d99feb 100644 --- a/src/vcep/coagulation_factor_deficiency.py +++ b/src/vcep/coagulation_factor_deficiency.py @@ -8,7 +8,7 @@ https://cspec.genome.network/cspec/ui/svi/doc/GN080 """ -from typing import Dict, List +from typing import Dict, List, Tuple from loguru import logger @@ -133,6 +133,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary=f"Variant does not meet the PM1 criteria for {var_data.hgnc_id}.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Coagulation Factor Deficiency.""" + return True + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Change the spliceAI and phyloP threshold for BP7.""" if var_data.hgnc_id == "HGNC:3546": diff --git a/src/vcep/congenital_myopathies.py b/src/vcep/congenital_myopathies.py index 0b16b8f..0c4b53e 100644 --- a/src/vcep/congenital_myopathies.py +++ b/src/vcep/congenital_myopathies.py @@ -63,3 +63,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary="Variant does not fall within a critical domain.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for congenital myopathies.""" + return True diff --git a/src/vcep/dicer1.py b/src/vcep/dicer1.py index a0221df..8fdfa49 100644 --- a/src/vcep/dicer1.py +++ b/src/vcep/dicer1.py @@ -76,6 +76,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="Variant does not affect a critical domain for DICER1.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 to be not applicable for DICER1.""" + return True + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for DICER1 VCEP.""" var_data.thresholds.bp7_donor = 7 diff --git a/src/vcep/enigma.py b/src/vcep/enigma.py index b344d29..ba4a8e5 100644 --- a/src/vcep/enigma.py +++ b/src/vcep/enigma.py @@ -52,6 +52,26 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri return super().predict_pm1(seqvar, var_data) + def predict_pm4bp3( + self, seqvar: SeqVar, var_data: AutoACMGData + ) -> Tuple[AutoACMGCriteria, AutoACMGCriteria]: + """Override predict_pm4bp3 to include VCEP-specific logic for ENIGMA BRCA1 and BRCA2.""" + logger.info("Predict PM4 and BP3") + return ( + AutoACMGCriteria( + name="PM4", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.PathogenicModerate, + summary="PM4 is not applicable for the ENIGMA VCEP.", + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary="BP3 is not applicable for the ENIGMA VCEP.", + ), + ) + def _in_important_domain(self, var_data: AutoACMGData) -> bool: """Check if the variant is in an important domain.""" for start, end in BP7_IMPORTANT_DOMAINS.get(var_data.hgnc_id, []): diff --git a/src/vcep/familial_hypercholesterolemia.py b/src/vcep/familial_hypercholesterolemia.py index bb44d00..6219829 100644 --- a/src/vcep/familial_hypercholesterolemia.py +++ b/src/vcep/familial_hypercholesterolemia.py @@ -67,3 +67,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary=f"Variant does not meet the PM1 criteria for LDLR.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Familial Hypercholesterolemia.""" + return True diff --git a/src/vcep/fbn1.py b/src/vcep/fbn1.py index 7366c9d..738dcc6 100644 --- a/src/vcep/fbn1.py +++ b/src/vcep/fbn1.py @@ -88,3 +88,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary=f"Variant does not meet the PM1 criteria for FBN1.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for FBN1.""" + return True diff --git a/src/vcep/glaucoma.py b/src/vcep/glaucoma.py index 692bf84..10569a1 100644 --- a/src/vcep/glaucoma.py +++ b/src/vcep/glaucoma.py @@ -25,6 +25,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="PM1 is not applicable for MYOC.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override _bp3_not_applicable for Glaucoma VCEP.""" + return True + def _is_conserved(self, var_data: AutoACMGData) -> bool: """ Predict if the variant is conserved. diff --git a/src/vcep/hbopc.py b/src/vcep/hbopc.py index 2d83dbf..a47543d 100644 --- a/src/vcep/hbopc.py +++ b/src/vcep/hbopc.py @@ -8,6 +8,8 @@ https://cspec.genome.network/cspec/ui/svi/doc/GN077 """ +from typing import Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor @@ -37,6 +39,58 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri return super().predict_pm1(seqvar, var_data) + def predict_pm4bp3( + self, seqvar: SeqVar, var_data: AutoACMGData + ) -> Tuple[AutoACMGCriteria, AutoACMGCriteria]: + """Override predict_pm4bp3 to include VCEP-specific logic for CDH1.""" + logger.info("Predict PM4 and BP3") + + if var_data.hgnc_id == "HGNC:795": + pred, comment = self.verify_pm4bp3(seqvar, var_data) + if pred: + pm4 = ( + AutoACMGPrediction.Met + if pred.PM4 + else ( + AutoACMGPrediction.NotMet + if pred.PM4 is False + else AutoACMGPrediction.Failed + ) + ) + else: + pm4 = AutoACMGPrediction.Failed + comment = "PM4 could not be verified." + return ( + AutoACMGCriteria( + name="PM4", + prediction=pm4, + strength=AutoACMGStrength.PathogenicSupporting, + summary=comment, + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary="BP3 is not applicable for ATM.", + ), + ) + elif var_data.hgnc_id == "HGNC:26144": + return ( + AutoACMGCriteria( + name="PM4", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.PathogenicSupporting, + summary="PM4 is not applicable for PALB2.", + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary="BP3 is not applicable for PALB2.", + ), + ) + return super().predict_pm4bp3(seqvar, var_data) + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for ATM and PALB2.""" if var_data.hgnc_id == "HGNC:26144": diff --git a/src/vcep/hhtp.py b/src/vcep/hht.py similarity index 95% rename from src/vcep/hhtp.py rename to src/vcep/hht.py index af298b4..8488848 100644 --- a/src/vcep/hhtp.py +++ b/src/vcep/hht.py @@ -76,3 +76,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary=f"Variant does not meet the PM1 criteria for {var_data.hgnc_id}.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for HHT.""" + return True diff --git a/src/vcep/insight_colorectal_cancer.py b/src/vcep/insight_colorectal_cancer.py index f2202f7..ae82bd9 100644 --- a/src/vcep/insight_colorectal_cancer.py +++ b/src/vcep/insight_colorectal_cancer.py @@ -14,6 +14,8 @@ https://cspec.genome.network/cspec/ui/svi/doc/GN139 """ +from typing import Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor @@ -43,6 +45,30 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri return super().predict_pm1(seqvar, var_data) + def predict_pm4bp3( + self, seqvar: SeqVar, var_data: AutoACMGData + ) -> Tuple[AutoACMGCriteria, AutoACMGCriteria]: + return ( + AutoACMGCriteria( + name="PM4", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.PathogenicModerate, + summary=( + "PM4 is not applicable for the InSIGHT Hereditary Colorectal Cancer/Polyposis " + "VCEP." + ), + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary=( + "BP3 is not applicable for the InSIGHT Hereditary Colorectal Cancer/Polyposis " + "VCEP." + ), + ), + ) + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """ Override donor and acceptor positions for InSIGHT Hereditary Colorectal Cancer/Polyposis diff --git a/src/vcep/leber_congenital_amaurosis.py b/src/vcep/leber_congenital_amaurosis.py index b7758c4..8ee294f 100644 --- a/src/vcep/leber_congenital_amaurosis.py +++ b/src/vcep/leber_congenital_amaurosis.py @@ -64,6 +64,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary=comment, ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Leber Congenital Amaurosis.""" + return True + def _is_bp7_exception(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: """ Add an exception for RPE65. diff --git a/src/vcep/lysosomal_diseases.py b/src/vcep/lysosomal_diseases.py index 28e13f1..8edd490 100644 --- a/src/vcep/lysosomal_diseases.py +++ b/src/vcep/lysosomal_diseases.py @@ -64,3 +64,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary="Variant does not meet the PM1 criteria for Lysosomal Diseases.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Lysosomal Diseases.""" + return True diff --git a/src/vcep/monogenic_diabetes.py b/src/vcep/monogenic_diabetes.py index 261068a..11ce581 100644 --- a/src/vcep/monogenic_diabetes.py +++ b/src/vcep/monogenic_diabetes.py @@ -136,6 +136,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary=f"Variant does not meet the PM1 criteria for {var_data.hgnc_id}.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Monogenic Diabetes.""" + return True + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Change BP7 thresholds for Monogenic Diabetes VCEP.""" var_data.thresholds.spliceAI_acceptor_gain = 0.2 diff --git a/src/vcep/myeloid_malignancy.py b/src/vcep/myeloid_malignancy.py index d7eb16f..16025f1 100644 --- a/src/vcep/myeloid_malignancy.py +++ b/src/vcep/myeloid_malignancy.py @@ -77,6 +77,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="Variant does not meet the PM1 criteria for RUNX1.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Myeloid Malignancy.""" + return True + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Change BP7 thresholds for Myeloid Malignancy VCEP.""" var_data.thresholds.spliceAI_acceptor_gain = 0.2 diff --git a/src/vcep/pku.py b/src/vcep/pku.py index 2e2d710..b72b61c 100644 --- a/src/vcep/pku.py +++ b/src/vcep/pku.py @@ -84,6 +84,10 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="Variant does not meet the PM1 criteria for PAH.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for Phenylketonuria.""" + return True + def _is_bp7_exception(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: """ Add an exception for Phenylketonuria. diff --git a/src/vcep/pten.py b/src/vcep/pten.py index 09893b4..8509d7b 100644 --- a/src/vcep/pten.py +++ b/src/vcep/pten.py @@ -4,10 +4,18 @@ Link: https://cspec.genome.network/cspec/ui/svi/doc/GN003 """ +from typing import Optional, Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor -from src.defs.auto_acmg import AutoACMGCriteria, AutoACMGData, AutoACMGPrediction, AutoACMGStrength +from src.defs.auto_acmg import ( + PM4BP3, + AutoACMGCriteria, + AutoACMGData, + AutoACMGPrediction, + AutoACMGStrength, +) from src.defs.seqvar import SeqVar PM1_CLUSTER = { @@ -53,6 +61,42 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="Variant does not meet the PM1 criteria for PTEN.", ) + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for PTEN.""" + return True + + def verify_pm4bp3(self, seqvar: SeqVar, var_data: AutoACMGData) -> Tuple[Optional[PM4BP3], str]: + """Override predict_pm4bp3 to include VCEP-specific logic for PTEN.""" + self.prediction_pm4bp3 = PM4BP3() + self.comment_pm4bp3 = "" + try: + # Stop-loss variants are considered as PM4 + if self._is_stop_loss(var_data): + self.comment_pm4bp3 = "Variant consequence is stop-loss. PM4 is met." + self.prediction_pm4bp3.PM4 = True + # In-frame deletions/insertions + elif self.is_inframe_delins(var_data): + self.comment_pm4bp3 = f"Variant consequence is in-frame deletion/insertion. " + + # Check if the variant affects the catalytic motifs + if var_data.prot_pos in PM1_CLUSTER.get(var_data.hgnc_id, {}).get("residues", []): + self.comment_pm4bp3 += "Impacting catalytic motif. PM4 is met." + self.prediction_pm4bp3.PM4 = True + else: + self.comment_pm4bp3 += "No impact on catalytic motif. PM4 is not met." + self.prediction_pm4bp3.PM4 = False + else: + self.comment_pm4bp3 = ( + "Variant consequence is not stop-loss or in-frame deletion/insertion. " + ) + self.prediction_pm4bp3.PM4 = False + self.prediction_pm4bp3.BP3 = False + except Exception as e: + self.prediction_pm4bp3 = None + self.comment_pm4bp3 = f"An error occured while predicting PM4 and BP3 criteria: {e}" + + return self.prediction_pm4bp3, self.comment_pm4bp3 + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for PTEN VCEP.""" var_data.thresholds.bp7_donor = 7 diff --git a/src/vcep/rasopathy.py b/src/vcep/rasopathy.py index 60a8420..29424ba 100644 --- a/src/vcep/rasopathy.py +++ b/src/vcep/rasopathy.py @@ -149,3 +149,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary=f"Variant does not meet the PM1 criteria for {var_data.hgnc_id}.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for RASopathy.""" + return True diff --git a/src/vcep/rett_angelman.py b/src/vcep/rett_angelman.py index 7ba7b4d..caa2831 100644 --- a/src/vcep/rett_angelman.py +++ b/src/vcep/rett_angelman.py @@ -16,12 +16,19 @@ https://cspec.genome.network/cspec/ui/svi/doc/GN037 """ -from typing import Dict, List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union from loguru import logger from src.criteria.default_predictor import DefaultPredictor -from src.defs.auto_acmg import AutoACMGCriteria, AutoACMGData, AutoACMGPrediction, AutoACMGStrength +from src.defs.auto_acmg import ( + PM4BP3, + AutoACMGCriteria, + AutoACMGData, + AutoACMGPrediction, + AutoACMGStrength, +) +from src.defs.exceptions import AutoAcmgBaseException from src.defs.seqvar import SeqVar # fmt: off @@ -43,6 +50,26 @@ }, } +#: Domains to exclude from PM4 +PM4_EXCLUDE: Dict[str, List[Tuple[int, int]]] = { + "HGNC:11411": [(904, int(1e6))], # Exclude C-terminal region + "HGNC:3811": [ + (35, 57), # Histine-rich region + (58, 86), # Prolinne- and Glutamate-rich region + (105, 112) # Proline-rich region + ], + "HGNC:6990": [(381, 405)], # Proline-rich region +} + +#: FOXG1 BP3 region +FOXG1_BP3_REGION: List[Tuple[int, int]] = [ + (47, 57), # Poly-His region + (70, 73), # Poly-Glutamin region + (58, 61), # Poly-Proline region + (65, 69), # Poly-Proline region + (74, 80), # Poly-Proline region +] + class RettAngelmanPredictor(DefaultPredictor): @@ -86,6 +113,66 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary=f"Variant does not meet the PM1 criteria for {var_data.hgnc_id}.", ) + def _exclude_pm4(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Check if the variant should be excluded from PM4.""" + if var_data.hgnc_id in PM4_EXCLUDE: + for start, end in PM4_EXCLUDE[var_data.hgnc_id]: + if start <= var_data.prot_pos <= end: + return True + return False + + def _in_foxg1_bp3_region(self, var_data: AutoACMGData) -> bool: + """Check if the variant is in the BP3 region for FOXG1.""" + if var_data.hgnc_id != "HGNC:3811": + return False + for start, end in FOXG1_BP3_REGION: + if start <= var_data.prot_pos <= end: + return True + return False + + def verify_pm4bp3(self, seqvar: SeqVar, var_data: AutoACMGData) -> Tuple[Optional[PM4BP3], str]: + """Override PM4 and BP3 for Rett and Angelman-like Disorders.""" + self.prediction_pm4bp3 = PM4BP3() + self.comment_pm4bp3 = "" + try: + # Stop-loss variants are considered as PM4 + if self._is_stop_loss(var_data): + self.comment_pm4bp3 = "Variant consequence is stop-loss. PM4 is met." + self.prediction_pm4bp3.PM4 = True + self.prediction_pm4bp3.BP3 = False + # In-frame deletions/insertions + elif self.is_inframe_delins(var_data): + self.comment_pm4bp3 = f"Variant consequence is in-frame deletion/insertion. " + if not self._in_repeat_region(seqvar) and not self._exclude_pm4(seqvar, var_data): + self.comment_pm4bp3 += ( + "Variant is not in a repeat region or a conserved domain. PM4 is met." + ) + self.prediction_pm4bp3.PM4 = True + self.prediction_pm4bp3.BP3 = False + else: + self.comment_pm4bp3 += ( + "Variant is in a repeat region or not in a conserved domain or in excluded " + "region." + ) + self.prediction_pm4bp3.PM4 = False + self.prediction_pm4bp3.BP3 = False + # BP3 for FOXG1 + if self._in_foxg1_bp3_region(var_data): + self.comment_pm4bp3 += " Variant is in the BP3 region for FOXG1." + self.prediction_pm4bp3.BP3 = True + else: + self.comment_pm4bp3 = ( + "Variant consequence is not indel or stop-loss. PM4 and BP3 are not met." + ) + self.prediction_pm4bp3.PM4 = False + self.prediction_pm4bp3.BP3 = False + + except AutoAcmgBaseException as e: + logger.error("Failed to predict PM4 and BP3 criteria. Error: {}", e) + self.comment_pm4bp3 = f"An error occured while predicting PM4 and BP3 criteria: {e}" + self.prediction_pm4bp3 = None + return self.prediction_pm4bp3, self.comment_pm4bp3 + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Change BP7 thresholds for Rett and Angelman-like Disorders VCEP.""" var_data.thresholds.phyloP100 = 0.1 diff --git a/src/vcep/thrombosis.py b/src/vcep/thrombosis.py index d8cc2ac..1c40750 100644 --- a/src/vcep/thrombosis.py +++ b/src/vcep/thrombosis.py @@ -52,3 +52,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicModerate, summary="Variant does not meet the PM1 criteria for SERPINC1.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for thrombosis VCEP.""" + return True diff --git a/src/vcep/tp53.py b/src/vcep/tp53.py index 54c4a0b..a6e1261 100644 --- a/src/vcep/tp53.py +++ b/src/vcep/tp53.py @@ -4,6 +4,8 @@ Link: https://cspec.genome.network/cspec/ui/svi/doc/GN009 """ +from typing import Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor @@ -48,6 +50,25 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="Variant does not meet the PM1 criteria for TP53.", ) + def predict_pm4bp3( + self, seqvar: SeqVar, var_data: AutoACMGData + ) -> Tuple[AutoACMGCriteria, AutoACMGCriteria]: + """Override PM4 and BP3 for TP53 VCEP.""" + return ( + AutoACMGCriteria( + name="PM4", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.PathogenicModerate, + summary="PM4 is not applicable for TP53 VCEP.", + ), + AutoACMGCriteria( + name="BP3", + prediction=AutoACMGPrediction.NotApplicable, + strength=AutoACMGStrength.BenignSupporting, + summary="BP3 is not applicable for TP53 VCEP.", + ), + ) + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Override donor and acceptor positions for TP53 VCEP.""" var_data.thresholds.bp7_donor = 7 diff --git a/src/vcep/vhl.py b/src/vcep/vhl.py index e864b06..c9c0c06 100644 --- a/src/vcep/vhl.py +++ b/src/vcep/vhl.py @@ -4,10 +4,19 @@ Link: https://cspec.genome.network/cspec/ui/svi/doc/GN078 """ +from typing import Optional, Tuple + from loguru import logger from src.criteria.default_predictor import DefaultPredictor -from src.defs.auto_acmg import AutoACMGCriteria, AutoACMGData, AutoACMGPrediction, AutoACMGStrength +from src.defs.auto_acmg import ( + PM4BP3, + AutoACMGCriteria, + AutoACMGData, + AutoACMGPrediction, + AutoACMGStrength, +) +from src.defs.exceptions import AutoAcmgBaseException from src.defs.seqvar import SeqVar # fmt: off @@ -26,6 +35,18 @@ } } +#: Important domains for PM4 in VHL +PM4_CLUSTER = [ + (63, 155), # Beta (β) domain + (156, 192), # Alpha (ɑ) domain + (193, 204), # Second Beta (β) domain +] + +#: Repeat regions for BP3 in VHL +BP3_REPEAT_REGIONS = [ + (14, 48) +] + class VHLPredictor(DefaultPredictor): @@ -59,6 +80,66 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri summary="Variant does not meet the PM1 criteria for VHL.", ) + @staticmethod + def _in_vhl_important_domain(var_data: AutoACMGData) -> bool: + """Check if the variant is in an important VHL domain.""" + for start, end in PM4_CLUSTER: + if start <= var_data.prot_pos <= end: + return True + return False + + @staticmethod + def _in_gxeex_repeat_region(var_data: AutoACMGData) -> bool: + """Check if the variant is in the GXEEX repeat region.""" + for start, end in BP3_REPEAT_REGIONS: + if start <= var_data.prot_pos <= end: + return True + return False + + def verify_pm4bp3(self, seqvar: SeqVar, var_data: AutoACMGData) -> Tuple[Optional[PM4BP3], str]: + """Override PM4 and BP3 verification for VHL.""" + self.prediction_pm4bp3 = PM4BP3() + self.comment_pm4bp3 = "" + try: + # Stop-loss variants are considered as PM4 + if self._is_stop_loss(var_data): + self.comment_pm4bp3 = "Variant consequence is stop-loss. PM4 is met." + self.prediction_pm4bp3.PM4 = True + self.prediction_pm4bp3.BP3 = False + # In-frame deletions/insertions + elif self.is_inframe_delins(var_data): + self.comment_pm4bp3 = "Variant consequence is in-frame deletion/insertion. " + if self._in_vhl_important_domain(var_data): + self.comment_pm4bp3 += ( + "Variant is in an important domain of VHL. PM4 is met." + ) + self.prediction_pm4bp3.PM4 = True + self.prediction_pm4bp3.BP3 = False + elif self._in_gxeex_repeat_region(var_data): + self.comment_pm4bp3 += ( + "Variant is in the GXEEX repeat motif in the VHL gene. BP3 is met." + ) + self.prediction_pm4bp3.PM4 = False + self.prediction_pm4bp3.BP3 = True + else: + self.comment_pm4bp3 += ( + "Variant is not in an important domain or in a repeat region. BP3 is met." + ) + self.prediction_pm4bp3.PM4 = False + self.prediction_pm4bp3.BP3 = True + else: + self.comment_pm4bp3 = ( + "Variant consequence is not an in-frame indel or stop-loss. PM4 and BP3 are not met." + ) + self.prediction_pm4bp3.PM4 = False + self.prediction_pm4bp3.BP3 = False + + except AutoAcmgBaseException as e: + logger.error("Failed to predict PM4 and BP3 criteria. Error: {}", e) + self.comment_pm4bp3 = f"An error occurred while predicting PM4 and BP3 criteria: {e}" + self.prediction_pm4bp3 = None + return self.prediction_pm4bp3, self.comment_pm4bp3 + def predict_bp7(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteria: """Change the BP7 threshold for PhyloP.""" var_data.thresholds.phyloP100 = 0.2 diff --git a/src/vcep/von_willebrand_disease.py b/src/vcep/von_willebrand_disease.py index 82e86de..1cba458 100644 --- a/src/vcep/von_willebrand_disease.py +++ b/src/vcep/von_willebrand_disease.py @@ -27,3 +27,7 @@ def predict_pm1(self, seqvar: SeqVar, var_data: AutoACMGData) -> AutoACMGCriteri strength=AutoACMGStrength.PathogenicSupporting, summary=f"PM1 is not applicable for {var_data.hgnc_id}.", ) + + def _bp3_not_applicable(self, seqvar: SeqVar, var_data: AutoACMGData) -> bool: + """Override BP3 for von Willebrand Disease VCEP.""" + return True diff --git a/tests/criteria/test_auto_pm4_bp3.py b/tests/criteria/test_auto_pm4_bp3.py index 4340d48..2a21f6f 100644 --- a/tests/criteria/test_auto_pm4_bp3.py +++ b/tests/criteria/test_auto_pm4_bp3.py @@ -134,6 +134,28 @@ def test_inframe_delins_false(var_data_is_inframe_delins): assert AutoPM4BP3.is_inframe_delins(var_data_is_inframe_delins) == False +# ================== _bp3_not_applicable ================== + + +@pytest.fixture +def auto_acmg_data(): + return AutoACMGData() + + +def test_bp3_not_applicable_mitochondrial(auto_pm4bp3, seqvar, auto_acmg_data): + """Test BP3 is not applicable when the variant is in mitochondrial DNA.""" + seqvar.chrom = "MT" # Mitochondrial chromosome + result = auto_pm4bp3._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should not be applicable for mitochondrial DNA variants." + + +def test_bp3_applicable_nuclear_dna(auto_pm4bp3, seqvar, auto_acmg_data): + """Test BP3 is applicable when the variant is in nuclear DNA.""" + seqvar.chrom = "1" # Nuclear chromosome + result = auto_pm4bp3._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is False, "BP3 should be applicable for nuclear DNA variants." + + # ============== verify_pm4bp3 ================= diff --git a/tests/integ/test_integ_pm4_bp3.py b/tests/integ/test_integ_pm4_bp3.py index 2c58253..21d6a7f 100644 --- a/tests/integ/test_integ_pm4_bp3.py +++ b/tests/integ/test_integ_pm4_bp3.py @@ -28,17 +28,17 @@ ( "NM_000277.2(PAH):c.1092_1094del", GenomeRelease.GRCh37, - (AutoACMGPrediction.Met, AutoACMGPrediction.NotMet), + (AutoACMGPrediction.Met, AutoACMGPrediction.NotApplicable), ), ( "NM_000277.2(PAH):c.1092_1106del", GenomeRelease.GRCh37, - (AutoACMGPrediction.Met, AutoACMGPrediction.NotMet), + (AutoACMGPrediction.Met, AutoACMGPrediction.NotApplicable), ), ( "NM_000277.1(PAH):c.208_210delTCT", GenomeRelease.GRCh37, - (AutoACMGPrediction.Met, AutoACMGPrediction.NotMet), + (AutoACMGPrediction.Met, AutoACMGPrediction.NotApplicable), ), # ("NM_005249.4(FOXG1):c.209_232del24", GenomeRelease.GRCh37, (False, True)), # ("NM_005249.5(FOXG1):c.209_235del", GenomeRelease.GRCh37, (False, True)), diff --git a/tests/vcep/test_acadvl.py b/tests/vcep/test_acadvl.py index 82fa366..eb9e23c 100644 --- a/tests/vcep/test_acadvl.py +++ b/tests/vcep/test_acadvl.py @@ -82,3 +82,9 @@ def test_predict_pm1_edge_case_end_boundary(acadvl_predictor, auto_acmg_data): assert ( "falls within a critical region" in result.summary ), "The summary should indicate the critical region." + + +def test_bp3_not_applicable_acadvl(acadvl_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = acadvl_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable for ACADVL." diff --git a/tests/vcep/test_brain_malformations.py b/tests/vcep/test_brain_malformations.py index 458b483..8aa7c4d 100644 --- a/tests/vcep/test_brain_malformations.py +++ b/tests/vcep/test_brain_malformations.py @@ -123,6 +123,35 @@ def test_predict_pm1_fallback_to_default( assert mock_super_predict_pm1.called, "super().predict_pm1 should have been called." +def test_predict_pm4bp3_not_applicable(brain_malformations_predictor, seqvar, auto_acmg_data): + """Test that PM4 and BP3 are marked as Not Applicable for the brain malformations VCEP.""" + pm4_result, bp3_result = brain_malformations_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert pm4_result.prediction == AutoACMGPrediction.NotApplicable, "PM4 should be NotApplicable." + assert ( + pm4_result.strength == AutoACMGStrength.PathogenicModerate + ), "PM4 strength should be PathogenicModerate." + assert ( + "PM4 is not applicable" in pm4_result.summary + ), "The summary should indicate PM4 is not applicable." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert bp3_result.prediction == AutoACMGPrediction.NotApplicable, "BP3 should be NotApplicable." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable" in bp3_result.summary + ), "The summary should indicate BP3 is not applicable." + + def test_predict_bp7_threshold_adjustment(brain_malformations_predictor, auto_acmg_data): """Test that the PhyloP100 threshold is correctly adjusted for BP7.""" auto_acmg_data.thresholds.phyloP100 = 0.5 # Initial threshold value diff --git a/tests/vcep/test_cardiomyopathy.py b/tests/vcep/test_cardiomyopathy.py index 44bddcc..f593638 100644 --- a/tests/vcep/test_cardiomyopathy.py +++ b/tests/vcep/test_cardiomyopathy.py @@ -115,6 +115,12 @@ def test_predict_pm1_edge_case_end_boundary(cardiomyopathy_predictor, auto_acmg_ ), "The summary should indicate the critical domain." +def test_bp3_not_applicable(cardiomyopathy_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = cardiomyopathy_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_predict_bp7_threshold_adjustment(cardiomyopathy_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted for Cardiomyopathy.""" auto_acmg_data.thresholds.bp7_donor = 2 # Initial donor threshold value diff --git a/tests/vcep/test_cdh1.py b/tests/vcep/test_cdh1.py index c2416e6..e980c55 100644 --- a/tests/vcep/test_cdh1.py +++ b/tests/vcep/test_cdh1.py @@ -69,6 +69,67 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, cdh1_predictor, auto_ assert result.prediction == AutoACMGPrediction.NotApplicable, "PM1 should remain NotApplicable." +@patch.object(DefaultPredictor, "verify_pm4bp3") +def test_predict_pm4bp3_cdh1(mock_verify_pm4bp3, cdh1_predictor, seqvar, auto_acmg_data): + """Test the predict_pm4bp3 method for CDH1.""" + # Set the mock return value for the verify_pm4bp3 method + mock_pred = MagicMock() + mock_pred.PM4 = True + mock_pred.BP3 = False + mock_pred.PM4_strength = AutoACMGStrength.PathogenicSupporting + mock_pred.BP3_strength = AutoACMGStrength.BenignSupporting + mock_verify_pm4bp3.return_value = (mock_pred, "PM4 is met") + + # Call the method under test + pm4_result, bp3_result = cdh1_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert pm4_result.prediction == AutoACMGPrediction.Met, "PM4 should be Met." + assert ( + pm4_result.strength == AutoACMGStrength.PathogenicSupporting + ), "PM4 strength should be PathogenicSupporting." + assert "PM4 is met" in pm4_result.summary, "The summary should indicate PM4 is met." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert bp3_result.prediction == AutoACMGPrediction.NotApplicable, "BP3 should be NotApplicable." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable for CDH1" in bp3_result.summary + ), "The summary should indicate BP3 is not applicable." + + +@patch.object(DefaultPredictor, "verify_pm4bp3", return_value=(None, "")) +def test_predict_pm4bp3_fallback(mock_verify_pm4bp3, cdh1_predictor, seqvar, auto_acmg_data): + """Test the fallback behavior for PM4 when verification fails.""" + # Call the method under test + pm4_result, bp3_result = cdh1_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert ( + pm4_result.prediction == AutoACMGPrediction.Failed + ), "PM4 should be Failed if verification fails." + assert ( + "PM4 could not be verified." in pm4_result.summary + ), "The summary should indicate PM4 could not be verified." + + # Check BP3 result + assert bp3_result.prediction == AutoACMGPrediction.NotApplicable, "BP3 should be NotApplicable." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable for CDH1" in bp3_result.summary + ), "The summary should indicate BP3 is not applicable." + + def test_predict_bp7_threshold_adjustment(cdh1_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted.""" auto_acmg_data.thresholds.bp7_donor = 1 # Initial donor threshold value diff --git a/tests/vcep/test_cerebral_creatine_deficiency_syndromes.py b/tests/vcep/test_cerebral_creatine_deficiency_syndromes.py index f8e5baf..3cbe15f 100644 --- a/tests/vcep/test_cerebral_creatine_deficiency_syndromes.py +++ b/tests/vcep/test_cerebral_creatine_deficiency_syndromes.py @@ -81,6 +81,12 @@ def test_predict_pm1_fallback_to_default( assert result.prediction == AutoACMGPrediction.NotApplicable, "PM1 should remain NotApplicable." +def test_bp3_not_applicable(cerebral_creatine_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = cerebral_creatine_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_predict_bp7_threshold_adjustment(cerebral_creatine_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted.""" auto_acmg_data.thresholds.bp7_donor = 1 # Initial donor threshold value diff --git a/tests/vcep/test_coagulation_factor_deficiency.py b/tests/vcep/test_coagulation_factor_deficiency.py index fab5a99..f004ff5 100644 --- a/tests/vcep/test_coagulation_factor_deficiency.py +++ b/tests/vcep/test_coagulation_factor_deficiency.py @@ -131,6 +131,12 @@ def test_predict_pm1_invalid_strand(coagulation_predictor, auto_acmg_data): coagulation_predictor._get_affected_exon(auto_acmg_data, coagulation_predictor.seqvar) +def test_bp3_not_applicable(coagulation_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = coagulation_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_predict_bp7_threshold_adjustment_for_hgnc_3546(coagulation_predictor, auto_acmg_data): """Test that the BP7 thresholds are correctly adjusted for HGNC:3546 (F5).""" auto_acmg_data.hgnc_id = "HGNC:3546" # F5 gene diff --git a/tests/vcep/test_congenital_myopathies.py b/tests/vcep/test_congenital_myopathies.py index fdcb130..e38769d 100644 --- a/tests/vcep/test_congenital_myopathies.py +++ b/tests/vcep/test_congenital_myopathies.py @@ -134,3 +134,9 @@ def test_predict_pm1_edge_case_end_boundary(congenital_myopathies_predictor, aut assert ( "falls within a critical domain" in result.summary ), "The summary should indicate the critical domain." + + +def test_bp3_not_applicable(congenital_myopathies_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = congenital_myopathies_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_dicer1.py b/tests/vcep/test_dicer1.py index 1887eaa..f0ede66 100644 --- a/tests/vcep/test_dicer1.py +++ b/tests/vcep/test_dicer1.py @@ -123,6 +123,12 @@ def test_predict_pm1_edge_case_end_boundary_supporting(dicer1_predictor, auto_ac ), "The summary should indicate the RNase IIIb domain." +def test_bp3_not_applicable(dicer1_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = dicer1_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_predict_bp7_threshold_adjustment(dicer1_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted.""" auto_acmg_data.thresholds.bp7_donor = 1 # Initial donor threshold value diff --git a/tests/vcep/test_enigma.py b/tests/vcep/test_enigma.py index ef23062..6a9b0d6 100644 --- a/tests/vcep/test_enigma.py +++ b/tests/vcep/test_enigma.py @@ -92,6 +92,35 @@ def test_predict_pm1_strength(enigma_predictor, auto_acmg_data): ), "The strength should be PathogenicModerate for ENIGMA." +def test_predict_pm4bp3_not_applicable(enigma_predictor, seqvar, auto_acmg_data): + """Test that PM4 and BP3 are marked as Not Applicable for the brain malformations VCEP.""" + pm4_result, bp3_result = enigma_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert pm4_result.prediction == AutoACMGPrediction.NotApplicable, "PM4 should be NotApplicable." + assert ( + pm4_result.strength == AutoACMGStrength.PathogenicModerate + ), "PM4 strength should be PathogenicModerate." + assert ( + "PM4 is not applicable" in pm4_result.summary + ), "The summary should indicate PM4 is not applicable." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert bp3_result.prediction == AutoACMGPrediction.NotApplicable, "BP3 should be NotApplicable." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable" in bp3_result.summary + ), "The summary should indicate BP3 is not applicable." + + def test_in_important_domain(enigma_predictor, auto_acmg_data): """Test if a variant is correctly identified as being in an important domain.""" auto_acmg_data.hgnc_id = "HGNC:1100" # BRCA1 gene diff --git a/tests/vcep/test_familial_hypercholesterolemia.py b/tests/vcep/test_familial_hypercholesterolemia.py index e0f7c7f..2154220 100644 --- a/tests/vcep/test_familial_hypercholesterolemia.py +++ b/tests/vcep/test_familial_hypercholesterolemia.py @@ -137,3 +137,9 @@ def test_predict_pm1_edge_case_end_boundary( assert ( "affects a critical cysteine residue" in result.summary ), "The summary should indicate the critical residue." + + +def test_bp3_not_applicable(familial_hypercholesterolemia_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = familial_hypercholesterolemia_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_fbn1.py b/tests/vcep/test_fbn1.py index c38d368..6ea3e11 100644 --- a/tests/vcep/test_fbn1.py +++ b/tests/vcep/test_fbn1.py @@ -128,3 +128,9 @@ def test_predict_pm1_edge_case_moderate_boundary(fbn1_predictor, auto_acmg_data) assert ( "Variant affects a residue in FBN1" in result.summary ), "The summary should indicate the moderate critical residue." + + +def test_bp3_not_applicable(fbn1_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = fbn1_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_glaucoma.py b/tests/vcep/test_glaucoma.py index fc6715a..e127578 100644 --- a/tests/vcep/test_glaucoma.py +++ b/tests/vcep/test_glaucoma.py @@ -60,6 +60,12 @@ def test_predict_pm1_name(glaucoma_predictor, auto_acmg_data): assert result.name == "PM1", "The name of the criteria should be 'PM1'." +def test_bp3_not_applicable(glaucoma_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = glaucoma_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_is_conserved_with_valid_gerp_score(glaucoma_predictor, auto_acmg_data): """Test that the variant is correctly identified as conserved when the GERP score is valid.""" auto_acmg_data.scores.cadd.gerp = 3.5 # Example GERP score above the threshold diff --git a/tests/vcep/test_hbopc.py b/tests/vcep/test_hbopc.py index c8c1966..c12390f 100644 --- a/tests/vcep/test_hbopc.py +++ b/tests/vcep/test_hbopc.py @@ -2,6 +2,7 @@ import pytest +from src.criteria.default_predictor import DefaultPredictor from src.defs.auto_acmg import AutoACMGCriteria, AutoACMGData, AutoACMGPrediction, AutoACMGStrength from src.defs.genome_builds import GenomeRelease from src.defs.seqvar import SeqVar @@ -97,6 +98,125 @@ def test_predict_pm1_name(hbopc_predictor, auto_acmg_data): assert result.name == "PM1", "The name of the criteria should be 'PM1'." +@patch.object(DefaultPredictor, "verify_pm4bp3") +def test_predict_pm4bp3_atm(mock_verify_pm4bp3, hbopc_predictor, seqvar, auto_acmg_data): + """Test the predict_pm4bp3 method for ATM (HGNC:795).""" + auto_acmg_data.hgnc_id = "HGNC:795" + + # Mock the verify_pm4bp3 method to return a specific result + mock_pred = MagicMock() + mock_pred.PM4 = True + mock_pred.BP3 = False + mock_verify_pm4bp3.return_value = (mock_pred, "PM4 is met for ATM") + + # Call the method under test + pm4_result, bp3_result = hbopc_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert pm4_result.prediction == AutoACMGPrediction.Met, "PM4 should be Met for ATM." + assert ( + pm4_result.strength == AutoACMGStrength.PathogenicSupporting + ), "PM4 strength should be PathogenicSupporting." + assert ( + "PM4 is met for ATM" in pm4_result.summary + ), "The summary should indicate PM4 is met for ATM." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert ( + bp3_result.prediction == AutoACMGPrediction.NotApplicable + ), "BP3 should be NotApplicable for ATM." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable for ATM." in bp3_result.summary + ), "The summary should indicate BP3 is not applicable for ATM." + + +def test_predict_pm4bp3_palb2(hbopc_predictor, seqvar, auto_acmg_data): + """Test the predict_pm4bp3 method for PALB2 (HGNC:26144).""" + auto_acmg_data.hgnc_id = "HGNC:26144" + + # Call the method under test + pm4_result, bp3_result = hbopc_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert ( + pm4_result.prediction == AutoACMGPrediction.NotApplicable + ), "PM4 should be NotApplicable for PALB2." + assert ( + pm4_result.strength == AutoACMGStrength.PathogenicSupporting + ), "PM4 strength should be PathogenicSupporting." + assert ( + "PM4 is not applicable for PALB2." in pm4_result.summary + ), "The summary should indicate PM4 is not applicable for PALB2." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert ( + bp3_result.prediction == AutoACMGPrediction.NotApplicable + ), "BP3 should be NotApplicable for PALB2." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable for PALB2." in bp3_result.summary + ), "The summary should indicate BP3 is not applicable for PALB2." + + +@patch.object(DefaultPredictor, "verify_pm4bp3") +@patch.object(DefaultPredictor, "predict_pm4bp3") +@pytest.mark.skip("Something doesn't work here") +def test_predict_pm4bp3_fallback( + mock_predict_pm4bp3, mock_verify_pm4bp3, hbopc_predictor, seqvar, auto_acmg_data +): + """Test the fallback behavior for predict_pm4bp3 method when the gene is not ATM or PALB2.""" + auto_acmg_data.hgnc_id = "HGNC:99999" # Some gene not handled explicitly + + # Mock the verify_pm4bp3 method to return a specific result + mock_pred = MagicMock() + mock_pred.PM4 = True + mock_pred.BP3 = False + mock_verify_pm4bp3.return_value = (mock_pred, "PM4 is met for fallback") + + # Call the method under test + pm4_result, bp3_result = hbopc_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert pm4_result.prediction == AutoACMGPrediction.Met, "PM4 should be Met for fallback gene." + assert ( + "PM4 is met for fallback" in pm4_result.summary + ), "The summary should indicate PM4 is met for fallback gene." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert ( + bp3_result.prediction == AutoACMGPrediction.NotMet + ), "BP3 should be NotMet for fallback gene." + assert ( + "PM4 is met for fallback" in bp3_result.summary + ), "The summary should indicate BP3 was checked." + + # Ensure the superclass's predict_pm4bp3 method was called + assert mock_predict_pm4bp3.called, "super().predict_pm4bp3 should have been called." + + def test_predict_bp7_threshold_adjustment_for_palb2(hbopc_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted for PALB2.""" auto_acmg_data.hgnc_id = "HGNC:26144" # PALB2 gene diff --git a/tests/vcep/test_hhtp.py b/tests/vcep/test_hht.py similarity index 77% rename from tests/vcep/test_hhtp.py rename to tests/vcep/test_hht.py index e3f3fed..9b72ffc 100644 --- a/tests/vcep/test_hhtp.py +++ b/tests/vcep/test_hht.py @@ -14,7 +14,7 @@ def seqvar(): @pytest.fixture -def hhtp_predictor(seqvar): +def hht_predictor(seqvar): result = MagicMock() # Mocking the AutoACMGResult object return HHTPredictor(seqvar=seqvar, result=result, config=MagicMock()) @@ -24,11 +24,11 @@ def auto_acmg_data(): return AutoACMGData() -def test_predict_pm1_acvrl1_moderate(hhtp_predictor, auto_acmg_data): +def test_predict_pm1_acvrl1_moderate(hht_predictor, auto_acmg_data): """Test when PM1 is met at the Moderate level for a variant in ACVRL1.""" auto_acmg_data.hgnc_id = "HGNC:175" # ACVRL1 gene auto_acmg_data.prot_pos = 213 # Within the glycine-rich loop (209-216) - result = hhtp_predictor.predict_pm1(hhtp_predictor.seqvar, auto_acmg_data) + result = hht_predictor.predict_pm1(hht_predictor.seqvar, auto_acmg_data) assert isinstance(result, AutoACMGCriteria), "The result should be of type AutoACMGCriteria." assert result.prediction == AutoACMGPrediction.Met, "PM1 should be met at the Moderate level." @@ -40,11 +40,11 @@ def test_predict_pm1_acvrl1_moderate(hhtp_predictor, auto_acmg_data): ), "The summary should indicate the critical region." -def test_predict_pm1_eng_moderate(hhtp_predictor, auto_acmg_data): +def test_predict_pm1_eng_moderate(hht_predictor, auto_acmg_data): """Test when PM1 is met at the Moderate level for a variant in ENG.""" auto_acmg_data.hgnc_id = "HGNC:3349" # ENG gene auto_acmg_data.prot_pos = 278 # BMP9 binding site residue - result = hhtp_predictor.predict_pm1(hhtp_predictor.seqvar, auto_acmg_data) + result = hht_predictor.predict_pm1(hht_predictor.seqvar, auto_acmg_data) assert isinstance(result, AutoACMGCriteria), "The result should be of type AutoACMGCriteria." assert result.prediction == AutoACMGPrediction.Met, "PM1 should be met at the Moderate level." @@ -56,11 +56,11 @@ def test_predict_pm1_eng_moderate(hhtp_predictor, auto_acmg_data): ), "The summary should indicate the critical region." -def test_predict_pm1_not_met(hhtp_predictor, auto_acmg_data): +def test_predict_pm1_not_met(hht_predictor, auto_acmg_data): """Test when PM1 is not met for ACVRL1 but outside the critical regions.""" auto_acmg_data.hgnc_id = "HGNC:175" # ACVRL1 gene auto_acmg_data.prot_pos = 250 # Outside the critical regions - result = hhtp_predictor.predict_pm1(hhtp_predictor.seqvar, auto_acmg_data) + result = hht_predictor.predict_pm1(hht_predictor.seqvar, auto_acmg_data) assert isinstance(result, AutoACMGCriteria), "The result should be of type AutoACMGCriteria." assert result.prediction == AutoACMGPrediction.NotMet, "PM1 should not be met for ACVRL1." @@ -72,8 +72,8 @@ def test_predict_pm1_not_met(hhtp_predictor, auto_acmg_data): ), "The summary should indicate no criteria were met." -@patch("src.vcep.hhtp.DefaultPredictor.predict_pm1") -def test_predict_pm1_fallback_to_default(mock_predict_pm1, hhtp_predictor, auto_acmg_data): +@patch("src.vcep.hht.DefaultPredictor.predict_pm1") +def test_predict_pm1_fallback_to_default(mock_predict_pm1, hht_predictor, auto_acmg_data): """Test fallback to the default PM1 prediction method if logic changes.""" auto_acmg_data.hgnc_id = "HGNC:111111" # Gene not in the specific logic mock_predict_pm1.return_value = AutoACMGCriteria( @@ -82,7 +82,7 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, hhtp_predictor, auto_ strength=AutoACMGStrength.PathogenicModerate, summary="Default PM1 prediction fallback.", ) - result = hhtp_predictor.predict_pm1(hhtp_predictor.seqvar, auto_acmg_data) + result = hht_predictor.predict_pm1(hht_predictor.seqvar, auto_acmg_data) assert isinstance(result, AutoACMGCriteria), "The result should be of type AutoACMGCriteria." assert ( @@ -93,11 +93,11 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, hhtp_predictor, auto_ ), "The summary should indicate the default fallback." -def test_predict_pm1_edge_case_start_boundary_acvrl1(hhtp_predictor, auto_acmg_data): +def test_predict_pm1_edge_case_start_boundary_acvrl1(hht_predictor, auto_acmg_data): """Test when variant falls exactly on the start boundary of a critical region in ACVRL1.""" auto_acmg_data.hgnc_id = "HGNC:175" # ACVRL1 gene auto_acmg_data.prot_pos = 209 # Start boundary of the glycine-rich loop (209-216) - result = hhtp_predictor.predict_pm1(hhtp_predictor.seqvar, auto_acmg_data) + result = hht_predictor.predict_pm1(hht_predictor.seqvar, auto_acmg_data) assert result.prediction == AutoACMGPrediction.Met, "PM1 should be met on the start boundary." assert ( @@ -105,13 +105,19 @@ def test_predict_pm1_edge_case_start_boundary_acvrl1(hhtp_predictor, auto_acmg_d ), "The summary should indicate the critical region." -def test_predict_pm1_edge_case_end_boundary_eng(hhtp_predictor, auto_acmg_data): +def test_predict_pm1_edge_case_end_boundary_eng(hht_predictor, auto_acmg_data): """Test when variant falls exactly on the end boundary of a critical region in ENG.""" auto_acmg_data.hgnc_id = "HGNC:3349" # ENG gene auto_acmg_data.prot_pos = 412 # End boundary of the critical cysteine residues - result = hhtp_predictor.predict_pm1(hhtp_predictor.seqvar, auto_acmg_data) + result = hht_predictor.predict_pm1(hht_predictor.seqvar, auto_acmg_data) assert result.prediction == AutoACMGPrediction.Met, "PM1 should be met on the end boundary." assert ( "critical residue for HGNC:3349" in result.summary ), "The summary should indicate the critical region." + + +def test_bp3_not_applicable(hht_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = hht_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_insight_colorectal_cancer.py b/tests/vcep/test_insight_colorectal_cancer.py index 914a00d..68d65c9 100644 --- a/tests/vcep/test_insight_colorectal_cancer.py +++ b/tests/vcep/test_insight_colorectal_cancer.py @@ -145,6 +145,37 @@ def test_predict_pm1_fallback_to_default( ), "The summary should indicate the default fallback." +def test_predict_pm4bp3_not_applicable(insight_colorectal_cancer_predictor, seqvar, auto_acmg_data): + """Test that PM4 and BP3 are marked as Not Applicable for the brain malformations VCEP.""" + pm4_result, bp3_result = insight_colorectal_cancer_predictor.predict_pm4bp3( + seqvar, auto_acmg_data + ) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert pm4_result.prediction == AutoACMGPrediction.NotApplicable, "PM4 should be NotApplicable." + assert ( + pm4_result.strength == AutoACMGStrength.PathogenicModerate + ), "PM4 strength should be PathogenicModerate." + assert ( + "PM4 is not applicable" in pm4_result.summary + ), "The summary should indicate PM4 is not applicable." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert bp3_result.prediction == AutoACMGPrediction.NotApplicable, "BP3 should be NotApplicable." + assert ( + bp3_result.strength == AutoACMGStrength.BenignSupporting + ), "BP3 strength should be BenignSupporting." + assert ( + "BP3 is not applicable" in bp3_result.summary + ), "The summary should indicate BP3 is not applicable." + + def test_predict_bp7_threshold_adjustment(insight_colorectal_cancer_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted.""" auto_acmg_data.thresholds.bp7_donor = 1 # Initial donor threshold value diff --git a/tests/vcep/test_leber_congenital_amaurosis.py b/tests/vcep/test_leber_congenital_amaurosis.py index ef8a84c..b3ad226 100644 --- a/tests/vcep/test_leber_congenital_amaurosis.py +++ b/tests/vcep/test_leber_congenital_amaurosis.py @@ -115,6 +115,12 @@ def test_predict_pm1_fallback_to_default( ), "The summary should indicate the default fallback." +def test_bp3_not_applicable(leber_congenital_amaurosis_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = leber_congenital_amaurosis_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_is_bp7_exception_first_base_of_exon(leber_congenital_amaurosis_predictor, auto_acmg_data): """Test that the BP7 exception is detected for a synonymous variant at the first base of an exon.""" auto_acmg_data.exons = [ diff --git a/tests/vcep/test_lysosomal_diseases.py b/tests/vcep/test_lysosomal_diseases.py index 1644b51..6a48a1a 100644 --- a/tests/vcep/test_lysosomal_diseases.py +++ b/tests/vcep/test_lysosomal_diseases.py @@ -87,3 +87,9 @@ def test_predict_pm1_fallback_to_default( assert ( "Default PM1 prediction fallback." in result.summary ), "The summary should indicate the default fallback." + + +def test_bp3_not_applicable(lysosomal_diseases_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = lysosomal_diseases_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_monogenic_diabetes.py b/tests/vcep/test_monogenic_diabetes.py index 5a8ae2a..359b49d 100644 --- a/tests/vcep/test_monogenic_diabetes.py +++ b/tests/vcep/test_monogenic_diabetes.py @@ -145,6 +145,12 @@ def test_predict_pm1_fallback_to_default( ), "The summary should indicate the default fallback." +def test_bp3_not_applicable(monogenic_diabetes_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = monogenic_diabetes_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_predict_bp7_threshold_adjustment(monogenic_diabetes_predictor, auto_acmg_data): """Test that the BP7 thresholds are correctly adjusted for Monogenic Diabetes.""" # Initial threshold values diff --git a/tests/vcep/test_myeloid_malignancy.py b/tests/vcep/test_myeloid_malignancy.py index 13643b1..d999c9d 100644 --- a/tests/vcep/test_myeloid_malignancy.py +++ b/tests/vcep/test_myeloid_malignancy.py @@ -106,6 +106,12 @@ def test_predict_pm1_fallback_to_default( ), "The summary should indicate the default fallback." +def test_bp3_not_applicable(myeloid_malignancy_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = myeloid_malignancy_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + def test_predict_bp7_threshold_adjustment(myeloid_malignancy_predictor, auto_acmg_data): """Test that the BP7 thresholds are correctly adjusted for Myeloid Malignancy.""" # Initial threshold values diff --git a/tests/vcep/test_pten.py b/tests/vcep/test_pten.py index 7442077..a1971ee 100644 --- a/tests/vcep/test_pten.py +++ b/tests/vcep/test_pten.py @@ -82,6 +82,71 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, pten_predictor, auto_ ), "The summary should indicate the default fallback." +def test_bp3_not_applicable(pten_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = pten_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" + + +def test_verify_pm4bp3_stop_loss(pten_predictor, seqvar, auto_acmg_data): + """Test verify_pm4bp3 when the variant is a stop-loss mutation.""" + auto_acmg_data.consequence.cadd = "stop_loss" + + # Call the method under test + prediction, comment = pten_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert prediction.PM4 is True, "PM4 should be met for stop-loss variants." + assert prediction.BP3 is False, "BP3 should not be met for stop-loss variants." + assert "Variant consequence is stop-loss. PM4 is met." in comment + + +@pytest.mark.skip("THis test should work") +def test_verify_pm4bp3_inframe_delins_in_catalytic_motif(pten_predictor, seqvar, auto_acmg_data): + """Test verify_pm4bp3 when the variant is an in-frame indel in the catalytic motif.""" + auto_acmg_data.consequence.cadd = "inframe_deletion" + auto_acmg_data.prot_pos = 167 # Assume this is within the catalytic motif + + # Call the method under test + prediction, comment = pten_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert prediction.PM4 is True, "PM4 should be met for in-frame indels in the catalytic motif." + assert ( + prediction.BP3 is False + ), "BP3 should not be met for in-frame indels in the catalytic motif." + assert "Impacting catalytic motif. PM4 is met." in comment + + +def test_verify_pm4bp3_inframe_delins_outside_catalytic_motif( + pten_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is an in-frame indel outside the catalytic motif.""" + auto_acmg_data.consequence.cadd = "inframe_deletion" + auto_acmg_data.prot_pos = 200 # Assume this is outside the catalytic motif + + # Call the method under test + prediction, comment = pten_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert ( + prediction.PM4 is False + ), "PM4 should not be met for in-frame indels outside the catalytic motif." + assert ( + prediction.BP3 is False + ), "BP3 should not be met for in-frame indels outside the catalytic motif." + assert "No impact on catalytic motif. PM4 is not met." in comment + + +def test_verify_pm4bp3_neither_indel_nor_stop_loss(pten_predictor, seqvar, auto_acmg_data): + """Test verify_pm4bp3 when the variant is neither an in-frame indel nor a stop-loss.""" + auto_acmg_data.consequence.cadd = "missense_variant" + + # Call the method under test + prediction, comment = pten_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert prediction.PM4 is False, "PM4 should not be met for non-indel, non-stop-loss variants." + assert prediction.BP3 is False, "BP3 should not be met for non-indel, non-stop-loss variants." + assert "consequence is not stop" in comment + + def test_predict_bp7_threshold_adjustment(pten_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted.""" auto_acmg_data.thresholds.bp7_donor = 1 # Initial donor threshold value diff --git a/tests/vcep/test_rasopathy.py b/tests/vcep/test_rasopathy.py index 2a53fdb..4c17005 100644 --- a/tests/vcep/test_rasopathy.py +++ b/tests/vcep/test_rasopathy.py @@ -109,3 +109,9 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, rasopathy_predictor, assert ( "Default PM1 prediction fallback." in result.summary ), "The summary should indicate the default fallback." + + +def test_bp3_not_applicable(rasopathy_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = rasopathy_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_rett_angelman.py b/tests/vcep/test_rett_angelman.py index 5d40c72..04a0141 100644 --- a/tests/vcep/test_rett_angelman.py +++ b/tests/vcep/test_rett_angelman.py @@ -93,6 +93,112 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, rett_angelman_predict ), "The summary should indicate the default fallback." +def test_exclude_pm4_true(rett_angelman_predictor, auto_acmg_data, seqvar): + """Test that the variant is excluded from PM4 based on the exclusion regions.""" + auto_acmg_data.hgnc_id = "HGNC:11411" # CDKL5 + auto_acmg_data.prot_pos = 950 # Within exclusion range for CDKL5 + + # Call the method under test + result = rett_angelman_predictor._exclude_pm4(seqvar, auto_acmg_data) + + assert result is True, "The variant should be excluded from PM4." + + +def test_exclude_pm4_false(rett_angelman_predictor, auto_acmg_data, seqvar): + """Test that the variant is not excluded from PM4.""" + auto_acmg_data.hgnc_id = "HGNC:11411" # CDKL5 + auto_acmg_data.prot_pos = 500 # Outside the exclusion range for CDKL5 + + # Call the method under test + result = rett_angelman_predictor._exclude_pm4(seqvar, auto_acmg_data) + + assert result is False, "The variant should not be excluded from PM4." + + +def test_in_foxg1_bp3_region_true(rett_angelman_predictor, auto_acmg_data): + """Test that the variant is in the BP3 region for FOXG1.""" + auto_acmg_data.hgnc_id = "HGNC:3811" # FOXG1 + auto_acmg_data.prot_pos = 50 # Inside the BP3 region for FOXG1 + + # Call the method under test + result = rett_angelman_predictor._in_foxg1_bp3_region(auto_acmg_data) + + assert result is True, "The variant should be in the BP3 region for FOXG1." + + +def test_in_foxg1_bp3_region_false(rett_angelman_predictor, auto_acmg_data): + """Test that the variant is not in the BP3 region for FOXG1.""" + auto_acmg_data.hgnc_id = "HGNC:3811" # FOXG1 + auto_acmg_data.prot_pos = 100 # Outside the BP3 region for FOXG1 + + # Call the method under test + result = rett_angelman_predictor._in_foxg1_bp3_region(auto_acmg_data) + + assert result is False, "The variant should not be in the BP3 region for FOXG1." + + +@patch.object(RettAngelmanPredictor, "_in_repeat_region", return_value=False) +def test_verify_pm4bp3_in_frame_deletion_in_important_domain( + mock_in_repeat_region, rett_angelman_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is an in-frame deletion in an important domain.""" + auto_acmg_data.hgnc_id = "HGNC:6990" # MECP2 + auto_acmg_data.prot_pos = 400 # In-frame deletion in the proline-rich region + + auto_acmg_data.consequence.cadd = "inframe_deletion" + + # Call the method under test + prediction, comment = rett_angelman_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert ( + prediction.PM4 is False + ), "PM4 should not be met for in-frame deletion in an important domain." + assert ( + prediction.BP3 is False + ), "BP3 should not be met for in-frame deletion in an important domain." + + +@patch.object(RettAngelmanPredictor, "_in_repeat_region", return_value=True) +def test_verify_pm4bp3_in_repeat_region( + mock_in_repeat_region, rett_angelman_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is an in-frame deletion in a repeat region.""" + auto_acmg_data.hgnc_id = "HGNC:3811" # FOXG1 + auto_acmg_data.prot_pos = 50 # In-frame deletion in a repeat region + + auto_acmg_data.consequence.cadd = "inframe_deletion" + + # Call the method under test + prediction, comment = rett_angelman_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert ( + prediction.PM4 is False + ), "PM4 should not be met for in-frame deletion in a repeat region." + assert prediction.BP3 is True, "BP3 should be met for in-frame deletion in a repeat region." + assert ( + "Variant consequence is in-frame deletion/insertion. Variant is in a repeat region or not in a conserved domain or in excluded region." + in comment + ) + + +@patch.object(RettAngelmanPredictor, "_in_repeat_region", return_value=False) +def test_verify_pm4bp3_bp3_for_foxg1( + mock_in_repeat_region, rett_angelman_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is in the BP3 region for FOXG1.""" + auto_acmg_data.hgnc_id = "HGNC:3811" # FOXG1 + auto_acmg_data.prot_pos = 50 # In the BP3 region for FOXG1 + + auto_acmg_data.consequence.cadd = "inframe_deletion" + + # Call the method under test + prediction, comment = rett_angelman_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert prediction.PM4 is False, "PM4 should not be met for FOXG1 in the BP3 region." + assert prediction.BP3 is True, "BP3 should be met for FOXG1 in the BP3 region." + assert "Variant is in the BP3 region for FOXG1." in comment + + def test_predict_bp7_threshold_adjustment(rett_angelman_predictor, auto_acmg_data): """Test that the BP7 thresholds are correctly adjusted for Rett and Angelman-like Disorders.""" auto_acmg_data.thresholds.phyloP100 = 1.0 # Initial phyloP100 threshold value diff --git a/tests/vcep/test_thrombosis.py b/tests/vcep/test_thrombosis.py index 0f7d326..ab3d845 100644 --- a/tests/vcep/test_thrombosis.py +++ b/tests/vcep/test_thrombosis.py @@ -115,3 +115,9 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, thrombosis_predictor, assert ( "Default fallback for PM1." in result.summary ), "The summary should indicate the default fallback." + + +def test_bp3_not_applicable(thrombosis_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = thrombosis_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable" diff --git a/tests/vcep/test_tp53.py b/tests/vcep/test_tp53.py index e1fc7e4..3f1d612 100644 --- a/tests/vcep/test_tp53.py +++ b/tests/vcep/test_tp53.py @@ -84,6 +84,34 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, tp53_predictor, auto_ ), "The strength should be PathogenicModerate." +def test_predict_pm4bp3_tp53(tp53_predictor, seqvar, auto_acmg_data): + """Test the predict_pm4bp3 method for TP53 VCEP.""" + # Call the method under test + pm4_result, bp3_result = tp53_predictor.predict_pm4bp3(seqvar, auto_acmg_data) + + # Check PM4 result + assert isinstance( + pm4_result, AutoACMGCriteria + ), "The PM4 result should be of type AutoACMGCriteria." + assert ( + pm4_result.prediction == AutoACMGPrediction.NotApplicable + ), "PM4 should be NotApplicable for TP53." + assert ( + "PM4 is not applicable for TP53 VCEP." in pm4_result.summary + ), "The summary should indicate PM4 is not applicable for TP53." + + # Check BP3 result + assert isinstance( + bp3_result, AutoACMGCriteria + ), "The BP3 result should be of type AutoACMGCriteria." + assert ( + bp3_result.prediction == AutoACMGPrediction.NotApplicable + ), "BP3 should be NotApplicable for TP53." + assert ( + "BP3 is not applicable for TP53 VCEP." in bp3_result.summary + ), "The summary should indicate BP3 is not applicable for TP53." + + def test_predict_bp7_threshold_adjustment(tp53_predictor, auto_acmg_data): """Test that the BP7 donor and acceptor thresholds are correctly adjusted.""" auto_acmg_data.thresholds.bp7_donor = 1 # Initial donor threshold value diff --git a/tests/vcep/test_vhl.py b/tests/vcep/test_vhl.py index 10b381d..f3b05d4 100644 --- a/tests/vcep/test_vhl.py +++ b/tests/vcep/test_vhl.py @@ -83,6 +83,104 @@ def test_predict_pm1_fallback_to_default(mock_predict_pm1, vhl_predictor, auto_a ), "The summary should indicate the default fallback." +def test_in_vhl_important_domain_true(vhl_predictor, auto_acmg_data): + """Test that the variant is in an important VHL domain.""" + auto_acmg_data.prot_pos = 160 # Within the Alpha (ɑ) domain + + # Call the method under test + result = vhl_predictor._in_vhl_important_domain(auto_acmg_data) + + assert result is True, "The variant should be in an important VHL domain." + + +def test_in_vhl_important_domain_false(vhl_predictor, auto_acmg_data): + """Test that the variant is not in an important VHL domain.""" + auto_acmg_data.prot_pos = 50 # Outside the important domains + + # Call the method under test + result = vhl_predictor._in_vhl_important_domain(auto_acmg_data) + + assert result is False, "The variant should not be in an important VHL domain." + + +def test_in_gxeex_repeat_region_true(vhl_predictor, auto_acmg_data): + """Test that the variant is in the GXEEX repeat region for VHL.""" + auto_acmg_data.prot_pos = 30 # Within the GXEEX repeat region + + # Call the method under test + result = vhl_predictor._in_gxeex_repeat_region(auto_acmg_data) + + assert result is True, "The variant should be in the GXEEX repeat region." + + +def test_in_gxeex_repeat_region_false(vhl_predictor, auto_acmg_data): + """Test that the variant is not in the GXEEX repeat region for VHL.""" + auto_acmg_data.prot_pos = 60 # Outside the GXEEX repeat region + + # Call the method under test + result = vhl_predictor._in_gxeex_repeat_region(auto_acmg_data) + + assert result is False, "The variant should not be in the GXEEX repeat region." + + +@patch.object(VHLPredictor, "_in_vhl_important_domain", return_value=True) +@patch.object(VHLPredictor, "_in_gxeex_repeat_region", return_value=False) +def test_verify_pm4bp3_in_important_domain( + mock_in_vhl_important_domain, mock_in_gxeex_repeat_region, vhl_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is an in-frame deletion in an important domain.""" + auto_acmg_data.consequence.cadd = "inframe_deletion" + + # Call the method under test + prediction, comment = vhl_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert prediction.PM4 is True, "PM4 should be met for in-frame deletion in an important domain." + assert ( + prediction.BP3 is False + ), "BP3 should not be met for in-frame deletion in an important domain." + assert "Variant is in an important domain of VHL. PM4 is met." in comment + + +@patch.object(VHLPredictor, "_in_vhl_important_domain", return_value=False) +@patch.object(VHLPredictor, "_in_gxeex_repeat_region", return_value=True) +def test_verify_pm4bp3_in_repeat_region( + mock_in_vhl_important_domain, mock_in_gxeex_repeat_region, vhl_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is in the GXEEX repeat region.""" + auto_acmg_data.consequence.cadd = "inframe_deletion" + + # Call the method under test + prediction, comment = vhl_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert ( + prediction.PM4 is False + ), "PM4 should not be met for in-frame deletion in the GXEEX repeat region." + assert ( + prediction.BP3 is True + ), "BP3 should be met for in-frame deletion in the GXEEX repeat region." + assert "Variant is in the GXEEX repeat motif in the VHL gene. BP3 is met." in comment + + +@patch.object(VHLPredictor, "_in_vhl_important_domain", return_value=False) +@patch.object(VHLPredictor, "_in_gxeex_repeat_region", return_value=False) +def test_verify_pm4bp3_neither_domain_nor_repeat( + mock_in_vhl_important_domain, mock_in_gxeex_repeat_region, vhl_predictor, seqvar, auto_acmg_data +): + """Test verify_pm4bp3 when the variant is neither in an important domain nor in the GXEEX repeat region.""" + auto_acmg_data.consequence.cadd = "inframe_deletion" + + # Call the method under test + prediction, comment = vhl_predictor.verify_pm4bp3(seqvar, auto_acmg_data) + + assert ( + prediction.PM4 is False + ), "PM4 should not be met if not in important domain or GXEEX repeat region." + assert ( + prediction.BP3 is True + ), "BP3 should be met if variant is in neither an important domain nor GXEEX repeat region." + assert "Variant is not in an important domain or in a repeat region. BP3 is met." in comment + + def test_predict_bp7_threshold_adjustment(vhl_predictor, auto_acmg_data): """Test that the BP7 PhyloP threshold is correctly adjusted for VHL.""" auto_acmg_data.thresholds.phyloP100 = 1.0 # Initial PhyloP threshold value diff --git a/tests/vcep/test_von_willebrand_disease.py b/tests/vcep/test_von_willebrand_disease.py index 84e1540..e45d56b 100644 --- a/tests/vcep/test_von_willebrand_disease.py +++ b/tests/vcep/test_von_willebrand_disease.py @@ -41,3 +41,9 @@ def test_predict_pm1_not_applicable(vwf_predictor, auto_acmg_data): assert ( "PM1 is not applicable for" in result.summary ), "The summary should indicate PM1 is not applicable." + + +def test_bp3_not_applicable(vwf_predictor, seqvar, auto_acmg_data): + """Test BP3 is not applicable for ACADVL as overridden.""" + result = vwf_predictor._bp3_not_applicable(seqvar, auto_acmg_data) + assert result is True, "BP3 should always be not applicable"