diff --git a/pyproject.toml b/pyproject.toml index a212da6..97bc739 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ requires-python = ">=3.10" dynamic = ["version"] dependencies = [ "ga4gh.vrs==2.*", - "ga4gh.cat_vrs~=0.4.0", + "ga4gh.cat_vrs~=0.5.0", "pydantic==2.*" ] diff --git a/src/ga4gh/va_spec/aac_2017/models.py b/src/ga4gh/va_spec/aac_2017/models.py index f4c92b3..320edcd 100644 --- a/src/ga4gh/va_spec/aac_2017/models.py +++ b/src/ga4gh/va_spec/aac_2017/models.py @@ -9,7 +9,7 @@ from ga4gh.core.models import MappableConcept, iriReference from ga4gh.va_spec.base.core import ( Method, - Statement, + StatementValidatorMixin, VariantDiagnosticProposition, VariantPrognosticProposition, VariantTherapeuticResponseProposition, @@ -17,6 +17,7 @@ from ga4gh.va_spec.base.enums import System from ga4gh.va_spec.base.validators import validate_mappable_concept from pydantic import ( + BaseModel, Field, field_validator, ) @@ -46,8 +47,11 @@ class Classification(str, Enum): AMP_ASCO_CAP_TIERS = [v.value for v in Classification.__members__.values()] -class _ValidatorMixin: - """Mixin class for reusable AMP/ASCO/CAP field validators""" +class AmpAscoCapValidatorMixin(StatementValidatorMixin): + """Mixin class for reusable AMP/ASCO/CAP field validators + + Should be used with classes that inherit from Pydantic BaseModel + """ @field_validator("strength") @classmethod @@ -74,7 +78,7 @@ def validate_classification(cls, v: MappableConcept) -> MappableConcept: return validate_mappable_concept(v, System.AMP_ASCO_CAP, AMP_ASCO_CAP_TIERS) -class VariantDiagnosticStudyStatement(Statement, _ValidatorMixin): +class VariantDiagnosticStudyStatement(BaseModel, AmpAscoCapValidatorMixin): """A statement reporting a conclusion from a single study about whether a variant is associated with a disease (a diagnostic inclusion criterion), or absence of a disease (diagnostic exclusion criterion) - based on interpretation of the study's @@ -99,7 +103,7 @@ class VariantDiagnosticStudyStatement(Statement, _ValidatorMixin): ) -class VariantPrognosticStudyStatement(Statement, _ValidatorMixin): +class VariantPrognosticStudyStatement(BaseModel, AmpAscoCapValidatorMixin): """A statement reporting a conclusion from a single study about whether a variant is associated with a disease prognosis - based on interpretation of the study's results. @@ -123,7 +127,7 @@ class VariantPrognosticStudyStatement(Statement, _ValidatorMixin): ) -class VariantTherapeuticResponseStudyStatement(Statement, _ValidatorMixin): +class VariantTherapeuticResponseStudyStatement(BaseModel, AmpAscoCapValidatorMixin): """A statement reporting a conclusion from a single study about whether a variant is associated with a therapeutic response (positive or negative) - based on interpretation of the study's results. diff --git a/src/ga4gh/va_spec/acmg_2015/models.py b/src/ga4gh/va_spec/acmg_2015/models.py index 18dd24c..e923bb1 100644 --- a/src/ga4gh/va_spec/acmg_2015/models.py +++ b/src/ga4gh/va_spec/acmg_2015/models.py @@ -7,9 +7,9 @@ from ga4gh.core.models import MappableConcept, iriReference from ga4gh.va_spec.base.core import ( - EvidenceLine, + EvidenceLineValidatorMixin, Method, - Statement, + StatementValidatorMixin, VariantPathogenicityProposition, ) from ga4gh.va_spec.base.enums import ( @@ -18,8 +18,10 @@ STRENGTHS, System, ) -from ga4gh.va_spec.base.validators import validate_mappable_concept -from pydantic import Field, field_validator +from ga4gh.va_spec.base.validators import ( + validate_mappable_concept, +) +from pydantic import BaseModel, Field, field_validator, model_validator class EvidenceOutcome(str, Enum): @@ -51,7 +53,9 @@ class AcmgClassification(str, Enum): ACMG_CLASSIFICATIONS = [v.value for v in AcmgClassification.__members__.values()] -class VariantPathogenicityFunctionalImpactEvidenceLine(EvidenceLine): +class VariantPathogenicityFunctionalImpactEvidenceLine( + BaseModel, EvidenceLineValidatorMixin +): """An Evidence Line that describes how information about the functional impact of a variant on a gene or gene product was interpreted as evidence for or against the variant's pathogenicity. @@ -100,23 +104,21 @@ def validate_specified_by(cls, v: Method | iriReference) -> Method | iriReferenc return v - @field_validator("evidenceOutcome") - @classmethod - def validate_evidence_outcome( - cls, v: MappableConcept | None - ) -> MappableConcept | None: - """Validate evidenceOutcome + @model_validator(mode="before") + def validate_evidence_outcome(cls, values: dict) -> dict: # noqa: N805 + """Validate ``evidenceOutcome`` property if it exists - :param v: evidenceOutcome - :raises ValueError: If invalid evidenceOutcome values are provided - :return: Validated evidenceOutcome value + :param values: Input values + :raises ValueError: If ``evidenceOutcome`` exists and is invalid + :return: Validated input values. If ``evidenceOutcome`` exists, then it will be + validated and converted to a ``MappableConcept`` """ - return validate_mappable_concept( - v, System.ACMG, EVIDENCE_OUTCOME_VALUES, mc_is_required=False + return cls._validate_evidence_outcome( + values, System.ACMG, EVIDENCE_OUTCOME_VALUES ) -class VariantPathogenicityStatement(Statement): +class VariantPathogenicityStatement(BaseModel, StatementValidatorMixin): """A Statement describing the role of a variant in causing an inherited condition.""" proposition: VariantPathogenicityProposition | None = Field( @@ -162,9 +164,10 @@ def validate_classification(cls, v: MappableConcept) -> MappableConcept: err_msg = "`primaryCoding` is required." raise ValueError(err_msg) - supported_systems = [System.ACMG.value, System.ACMG.value] + supported_systems = [System.ACMG.value, System.CLIN_GEN.value] if v.primaryCoding.system not in supported_systems: err_msg = f"`primaryCoding.system` must be one of: {supported_systems}." + raise ValueError(err_msg) if v.primaryCoding.system == System.ACMG: if v.primaryCoding.code.root not in ACMG_CLASSIFICATIONS: diff --git a/src/ga4gh/va_spec/base/__init__.py b/src/ga4gh/va_spec/base/__init__.py index 8e2e57b..cf9183f 100644 --- a/src/ga4gh/va_spec/base/__init__.py +++ b/src/ga4gh/va_spec/base/__init__.py @@ -27,9 +27,11 @@ ) from .domain_entities import Condition, ConditionSet, Therapeutic, TherapyGroup from .enums import ( + CCV_CLASSIFICATIONS, CLIN_GEN_CLASSIFICATIONS, STRENGTH_OF_EVIDENCE_PROVIDED_VALUES, STRENGTHS, + CcvClassification, ClinGenClassification, DiagnosticPredicate, MembershipOperator, @@ -42,7 +44,9 @@ __all__ = [ "Agent", + "CCV_CLASSIFICATIONS", "CLIN_GEN_CLASSIFICATIONS", + "CcvClassification", "ClinGenClassification", "ClinGenClassification", "ClinicalVariantProposition", diff --git a/src/ga4gh/va_spec/base/core.py b/src/ga4gh/va_spec/base/core.py index 6512ada..27c59e6 100644 --- a/src/ga4gh/va_spec/base/core.py +++ b/src/ga4gh/va_spec/base/core.py @@ -20,19 +20,24 @@ from ga4gh.va_spec.base.enums import ( DiagnosticPredicate, PrognosticPredicate, + System, TherapeuticResponsePredicate, ) +from ga4gh.va_spec.base.validators import validate_mappable_concept from ga4gh.vrs.models import Allele, MolecularVariation from pydantic import ( + BaseModel, ConfigDict, Field, RootModel, StringConstraints, ValidationError, field_validator, + model_validator, ) -StatementType = TypeVar("StatementType", bound="Statement") +StatementType = TypeVar("StatementType") +EvidenceLineType = TypeVar("EvidenceLineType") ######################################### # Abstract Core Classes @@ -484,7 +489,7 @@ class EvidenceLine(InformationEntity, BaseModelForbidExtra): description="The possible fact against which evidence items contained in an Evidence Line were collectively evaluated, in determining the overall strength and direction of support they provide. For example, in an ACMG Guideline-based assessment of variant pathogenicity, the support provided by distinct lines of evidence are assessed against a target proposition that the variant is pathogenic for a specific disease.", ) hasEvidenceItems: ( - list[StudyResult | StatementType | EvidenceLine | iriReference] | None + list[StudyResult | StatementType | EvidenceLineType | iriReference] | None ) = Field( None, description="An individual piece of information that was evaluated as evidence in building the argument represented by an Evidence Line.", @@ -509,7 +514,7 @@ class EvidenceLine(InformationEntity, BaseModelForbidExtra): @field_validator("hasEvidenceItems", mode="before") def validate_has_evidence_items( cls, # noqa: N805 - v: list[StudyResult, StatementType, EvidenceLine, iriReference] | None, + v: list | None, ) -> list | None: """Ensure hasEvidenceItems is correct type @@ -539,15 +544,12 @@ def validate_has_evidence_items( obj_ for _, obj_ in vars(imported_module).items() if inspect.isclass(obj_) - and issubclass(obj_, Statement) - and obj_ is not Statement + and issubclass(obj_, BaseModel) + and obj_.__name__.endswith(("Statement", "EvidenceLine")) ] ) - has_evidence_items_models.extend( - [Statement, StudyResult, EvidenceLine, iriReference] - ) - + has_evidence_items_models.extend([Statement, StudyResult, EvidenceLine]) for evidence_item in v: if isinstance(evidence_item, dict): found_model = False @@ -561,10 +563,13 @@ def validate_has_evidence_items( found_model = True break if not found_model: - err_msg = "Unable to find valid model" + err_msg = "Unable to find valid model for `hasEvidenceItems`" raise ValueError(err_msg) + elif isinstance(evidence_item, str): + evidence_items.append(iriReference(root=evidence_item)) else: - evidence_items.append(evidence_item) + err_msg = "Unable to find valid model for `hasEvidenceItems`" + raise ValueError(err_msg) return evidence_items @@ -580,9 +585,17 @@ class Statement(InformationEntity, BaseModelForbidExtra): type: Literal["Statement"] = Field( CoreType.STATEMENT.value, description=f"MUST be '{CoreType.STATEMENT.value}'." ) - proposition: Proposition = Field( + proposition: ( + ExperimentalVariantFunctionalImpactProposition + | VariantDiagnosticProposition + | VariantOncogenicityProposition + | VariantPathogenicityProposition + | VariantPrognosticProposition + | VariantTherapeuticResponseProposition + ) = Field( ..., description="A possible fact, the validity of which is assessed and reported by the Statement. A Statement can put forth the proposition as being true, false, or uncertain, and may provide an assessment of the level of confidence/evidence supporting this claim.", + discriminator="type", ) direction: Direction = Field( ..., @@ -624,3 +637,70 @@ class StudyGroup(Entity, BaseModelForbidExtra): None, description="A feature or role shared by all members of the StudyGroup, representing a criterion for membership in the group.", ) + + +class StatementValidatorMixin: + """Mixin class for reusable Statement model validators + + Should be used with classes that inherit from Pydantic BaseModel + """ + + model_config = ConfigDict(extra="allow") + + @model_validator(mode="after") + def statement_validator(cls, model: BaseModel) -> BaseModel: # noqa: N805 + """Validate that the model is a ``Statement``. + + :param model: Pydantic BaseModel to validate + :raises ValueError: If ``model`` does not validate against a ``Statement`` + :return: Validated model + """ + try: + Statement(**model.model_dump()) + except ValidationError as e: + err_msg = f"Must be a `Statement`: {e}" + raise ValueError(err_msg) from e + return model + + +class EvidenceLineValidatorMixin: + """Mixin class for reusable EvidenceLine model validators + + Should be used with classes that inherit from Pydantic BaseModel + """ + + model_config = ConfigDict(extra="allow") + + @staticmethod + def _validate_evidence_outcome( + values: dict, system: System, codes: list[str] + ) -> dict: + """Validate ``evidenceOutcome`` property if it exists + + :param values: Input values + :param system: System that should be used in ``MappableConcept`` + :param codes: Codes that should be used in ``MappableConcept`` + :raises ValueError: If ``evidenceOutcome`` exists and is invalid + :return: Validated input values. If ``evidenceOutcome`` exists, then it will be + validated and converted to a ``MappableConcept`` + """ + if "evidenceOutcome" in values: + mc = MappableConcept(**values["evidenceOutcome"]) + values["evidenceOutcome"] = mc + validate_mappable_concept(mc, system, codes, mc_is_required=False) + return values + + @model_validator(mode="after") + def evidence_line_validator(cls, model: BaseModel) -> BaseModel: # noqa: N805 + """Validate that the model is a ``EvidenceLine``. + + :param model: Pydantic BaseModel to validate + :raises ValueError: If ``model`` does not validate against a ``EvidenceLine`` + :return: Validated model + """ + try: + EvidenceLine(**model.model_dump()) + except ValidationError as e: + err_msg = f"Must be an `EvidenceLine`: {e}" + raise ValueError(err_msg) from e + return model diff --git a/src/ga4gh/va_spec/base/enums.py b/src/ga4gh/va_spec/base/enums.py index d890884..7615066 100644 --- a/src/ga4gh/va_spec/base/enums.py +++ b/src/ga4gh/va_spec/base/enums.py @@ -75,6 +75,19 @@ class ClinGenClassification(str, Enum): CLIN_GEN_CLASSIFICATIONS = [v.value for v in ClinGenClassification.__members__.values()] +class CcvClassification(str, Enum): + """Define constraints for CCV classifications""" + + ONCOGENIC = "oncogenic" + LIKELY_ONCOGENIC = "likely oncogenic" + UNCERTAIN_SIGNIFICANCE = "uncertain significance" + LIKELY_BENIGN = "likely benign" + BENIGN = "benign" + + +CCV_CLASSIFICATIONS = [v.value for v in CcvClassification.__members__.values()] + + class System(str, Enum): """Define constraints for systems""" diff --git a/src/ga4gh/va_spec/ccv_2022/models.py b/src/ga4gh/va_spec/ccv_2022/models.py index 682c442..21ee9fd 100644 --- a/src/ga4gh/va_spec/ccv_2022/models.py +++ b/src/ga4gh/va_spec/ccv_2022/models.py @@ -7,19 +7,19 @@ from ga4gh.core.models import MappableConcept, iriReference from ga4gh.va_spec.base.core import ( - EvidenceLine, + EvidenceLineValidatorMixin, Method, - Statement, + StatementValidatorMixin, VariantOncogenicityProposition, ) from ga4gh.va_spec.base.enums import ( - CLIN_GEN_CLASSIFICATIONS, + CCV_CLASSIFICATIONS, STRENGTH_OF_EVIDENCE_PROVIDED_VALUES, STRENGTHS, System, ) from ga4gh.va_spec.base.validators import validate_mappable_concept -from pydantic import Field, field_validator +from pydantic import BaseModel, Field, field_validator, model_validator class EvidenceOutcome(str, Enum): @@ -38,7 +38,9 @@ class EvidenceOutcome(str, Enum): EVIDENCE_OUTCOME_VALUES = [v.value for v in EvidenceOutcome.__members__.values()] -class VariantOncogenicityFunctionalImpactEvidenceLine(EvidenceLine): +class VariantOncogenicityFunctionalImpactEvidenceLine( + BaseModel, EvidenceLineValidatorMixin +): """An Evidence Line that describes how information about the functional impact of a variant on a gene or gene product was interpreted as evidence for or against the variant's oncogenicity. @@ -72,23 +74,21 @@ def validate_strength_of_evidence_provided( v, System.CCV, STRENGTH_OF_EVIDENCE_PROVIDED_VALUES, mc_is_required=False ) - @field_validator("evidenceOutcome") - @classmethod - def validate_evidence_outcome( - cls, v: MappableConcept | None - ) -> MappableConcept | None: - """Validate evidenceOutcome + @model_validator(mode="before") + def validate_evidence_outcome(cls, values: dict) -> dict: # noqa: N805 + """Validate ``evidenceOutcome`` property if it exists - :param v: evidenceOutcome - :raises ValueError: If invalid evidenceOutcome values are provided - :return: Validated evidenceOutcome value + :param values: Input values + :raises ValueError: If ``evidenceOutcome`` exists and is invalid + :return: Validated input values. If ``evidenceOutcome`` exists, then it will be + validated and converted to a ``MappableConcept`` """ - return validate_mappable_concept( - v, System.CCV, EVIDENCE_OUTCOME_VALUES, mc_is_required=False + return cls._validate_evidence_outcome( + values, System.CCV, EVIDENCE_OUTCOME_VALUES ) -class VariantOncogenicityStudyStatement(Statement): +class VariantOncogenicityStudyStatement(BaseModel, StatementValidatorMixin): """A statement reporting a conclusion from a single study about whether a variant is associated with oncogenicity (positive or negative) - based on interpretation of the study's results. @@ -119,9 +119,7 @@ def validate_strength(cls, v: MappableConcept | None) -> MappableConcept | None: :raises ValueError: If invalid strength values are provided :return: Validated strength value """ - return validate_mappable_concept( - v, System.CLIN_GEN, STRENGTHS, mc_is_required=False - ) + return validate_mappable_concept(v, System.CCV, STRENGTHS, mc_is_required=False) @field_validator("classification") @classmethod @@ -133,5 +131,5 @@ def validate_classification(cls, v: MappableConcept) -> MappableConcept: :return: Validated classification value """ return validate_mappable_concept( - v, System.CLIN_GEN, CLIN_GEN_CLASSIFICATIONS, mc_is_required=True + v, System.CCV, CCV_CLASSIFICATIONS, mc_is_required=True ) diff --git a/tests/validation/test_va_spec_models.py b/tests/validation/test_va_spec_models.py index 058fc5e..93eae0e 100644 --- a/tests/validation/test_va_spec_models.py +++ b/tests/validation/test_va_spec_models.py @@ -1,18 +1,27 @@ """Test VA Spec Pydantic model""" import json +from copy import deepcopy import pytest import yaml -from ga4gh.core.models import iriReference +from ga4gh.core.models import Coding, MappableConcept, code, iriReference from ga4gh.va_spec import acmg_2015, base, ccv_2022 from ga4gh.va_spec.aac_2017.models import VariantTherapeuticResponseStudyStatement +from ga4gh.va_spec.acmg_2015.models import ( + VariantPathogenicityFunctionalImpactEvidenceLine, + VariantPathogenicityStatement, +) from ga4gh.va_spec.base import ( Agent, CohortAlleleFrequencyStudyResult, ExperimentalVariantFunctionalImpactStudyResult, ) -from ga4gh.va_spec.base.core import EvidenceLine, StudyGroup, StudyResult +from ga4gh.va_spec.base.core import EvidenceLine, Method, StudyGroup, StudyResult +from ga4gh.va_spec.ccv_2022.models import ( + VariantOncogenicityFunctionalImpactEvidenceLine, + VariantOncogenicityStudyStatement, +) from pydantic import ValidationError from tests.conftest import SUBMODULES_DIR @@ -199,6 +208,241 @@ def test_evidence_line(caf): el = EvidenceLine(**el_dict) assert isinstance(el.hasEvidenceItems[0], iriReference) + el_dict = { + "type": "EvidenceLine", + "hasEvidenceItems": None, + "directionOfEvidenceProvided": "supports", + } + assert EvidenceLine(**el_dict) + + invalid_params = { + "type": "EvidenceLine", + "hasEvidenceItems": [Agent(name="Joe")], + "directionOfEvidenceProvided": "supports", + } + with pytest.raises( + ValueError, match="Unable to find valid model for `hasEvidenceItems`" + ): + EvidenceLine(**invalid_params) + + invalid_params = { + "type": "EvidenceLine", + "hasEvidenceItems": [{"type": "Statement"}], + "directionOfEvidenceProvided": "supports", + } + with pytest.raises( + ValueError, match="Unable to find valid model for `hasEvidenceItems`" + ): + EvidenceLine(**invalid_params) + + +def test_variant_pathogenicity_stmt(): + """Ensure VariantPathogenicityStatement model works as expected""" + params = { + "direction": "supports", + "proposition": { + "type": "VariantPathogenicityProposition", + "predicate": "isCausalFor", + "objectCondition": "conditions.json#/1", + "subjectVariant": "alleles.json#/1", + }, + "classification": { + "primaryCoding": {"code": "pathogenic", "system": "ACMG Guidelines, 2015"} + }, + "specifiedBy": { + "reportedIn": { + "type": "Document", + "pmid": 25741868, + "name": "ACMG Guidelines, 2015", + } + }, + } + assert VariantPathogenicityStatement(**params) + + invalid_params = deepcopy(params) + del invalid_params["classification"]["primaryCoding"] + invalid_params["classification"]["name"] = "test" + with pytest.raises(ValueError, match="`primaryCoding` is required."): + VariantPathogenicityStatement(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["classification"]["primaryCoding"]["system"] = ( + "AMP/ASCO/CAP (AAC) Guidelines, 2017" + ) + with pytest.raises(ValueError, match="`primaryCoding.system` must be one of"): + VariantPathogenicityStatement(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["classification"]["primaryCoding"]["code"] = ( + "pathogenic, low penetrance" + ) + with pytest.raises(ValueError, match="`primaryCoding.code` must be one of"): + VariantPathogenicityStatement(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["classification"]["primaryCoding"]["system"] = ( + "ClinGen Low Penetrance and Risk Allele Recommendations, 2024" + ) + invalid_params["classification"]["primaryCoding"]["code"] = "pathogenic" + with pytest.raises(ValueError, match="`primaryCoding.code` must be one of"): + VariantPathogenicityStatement(**invalid_params) + + invalid_params = deepcopy(params) + del invalid_params["proposition"] # proposition is required for statement + with pytest.raises(ValueError, match="Must be a `Statement`"): + VariantPathogenicityStatement(**invalid_params) + + +def test_variant_pathogenicity_el(): + """Ensure VariantPathogenicityFunctionalImpactEvidenceLine model works as expected""" + params = { + "type": "EvidenceLine", + "specifiedBy": { + "type": "Method", + "id": "PS3", + "name": "ACMG 2015 PS3 Criterion", + "reportedIn": { + "type": "Document", + "pmid": 25741868, + "name": "ACMG Guidelines, 2015", + }, + }, + "directionOfEvidenceProvided": "supports", + "evidenceOutcome": { + "primaryCoding": { + "code": "PS3_supporting", + "system": "ACMG Guidelines, 2015", + }, + "name": "ACMG 2015 PS3 Supporting Criterion Met", + }, + } + vp = VariantPathogenicityFunctionalImpactEvidenceLine(**params) + + assert isinstance(vp.specifiedBy, Method) + assert vp.evidenceOutcome == MappableConcept( + primaryCoding=Coding( + code=code(root="PS3_supporting"), system="ACMG Guidelines, 2015" + ), + name="ACMG 2015 PS3 Supporting Criterion Met", + ) + + valid_params = deepcopy(params) + valid_params["strengthOfEvidenceProvided"] = None + assert VariantPathogenicityFunctionalImpactEvidenceLine(**valid_params) + + invalid_params = deepcopy(params) + del invalid_params["specifiedBy"]["reportedIn"] + with pytest.raises(ValueError, match="`reportedIn` is required"): + VariantPathogenicityFunctionalImpactEvidenceLine(**invalid_params) + + invalid_params = deepcopy(params) + del invalid_params[ + "directionOfEvidenceProvided" + ] # directionOfEvidenceProvided is required for statement + with pytest.raises(ValueError, match="Must be an `EvidenceLine`"): + VariantPathogenicityFunctionalImpactEvidenceLine(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["strengthOfEvidenceProvided"] = {"name": "test"} + with pytest.raises(ValueError, match="`primaryCoding` is required."): + VariantPathogenicityFunctionalImpactEvidenceLine(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["strengthOfEvidenceProvided"] = { + "primaryCoding": { + "system": "AMP/ASCO/CAP (AAC) Guidelines, 2017", + "code": "strong", + } + } + with pytest.raises(ValueError, match="`primaryCoding.system` must be"): + VariantPathogenicityFunctionalImpactEvidenceLine(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["strengthOfEvidenceProvided"] = { + "primaryCoding": {"system": "ACMG Guidelines, 2015", "code": "PS3"} + } + with pytest.raises(ValueError, match="`primaryCoding.code` must be"): + VariantPathogenicityFunctionalImpactEvidenceLine(**invalid_params) + + +def test_variant_onco_stmt(): + """Ensure VariantOncogenicityStudyStatement model works as expected""" + params = { + "direction": "neutral", + "proposition": { + "type": "VariantOncogenicityProposition", + "predicate": "isCausalFor", + "objectTumorType": "conditions.json#/1", + "subjectVariant": "alleles.json#/1", + }, + "classification": { + "primaryCoding": { + "code": "oncogenic", + "system": "ClinGen/CGC/VICC Guidelines for Oncogenicity, 2022", + } + }, + "specifiedBy": "documents.json#/1", + "strength": { + "primaryCoding": { + "code": "definitive", + "system": "ClinGen/CGC/VICC Guidelines for Oncogenicity, 2022", + } + }, + } + assert VariantOncogenicityStudyStatement(**params) + + invalid_params = deepcopy(params) + invalid_params["strength"]["primaryCoding"]["code"] = "oncogenic" + with pytest.raises(ValueError, match="`primaryCoding.code` must be one of"): + VariantOncogenicityStudyStatement(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["strength"]["primaryCoding"]["system"] = "ACMG Guidelines, 2015" + with pytest.raises(ValueError, match="`primaryCoding.system` must be"): + VariantOncogenicityStudyStatement(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["classification"]["primaryCoding"]["code"] = "pathogenic" + with pytest.raises(ValueError, match="`primaryCoding.code` must be one of"): + VariantOncogenicityStudyStatement(**invalid_params) + + invalid_params = deepcopy(params) + invalid_params["classification"]["primaryCoding"]["system"] = ( + "ACMG Guidelines, 2015" + ) + with pytest.raises(ValueError, match="`primaryCoding.system` must be"): + VariantOncogenicityStudyStatement(**invalid_params) + + +def test_variant_onco_el(): + """Ensure VariantOncogenicityFunctionalImpactEvidenceLine model works as expected""" + vo = VariantOncogenicityFunctionalImpactEvidenceLine( + type="EvidenceLine", + specifiedBy={ + "type": "Method", + "reportedIn": { + "type": "Document", + "pmid": 35101336, + "name": "ClinGen/CGC/VICC Guidelines for Oncogenicity, 2022", + }, + }, + directionOfEvidenceProvided="supports", + scoreOfEvidenceProvided=1, + evidenceOutcome={ + "primaryCoding": { + "code": "OS2_supporting", + "system": "ClinGen/CGC/VICC Guidelines for Oncogenicity, 2022", + }, + }, + ) + assert isinstance(vo.specifiedBy, Method) + assert vo.evidenceOutcome == MappableConcept( + primaryCoding=Coding( + code=code(root="OS2_supporting"), + system="ClinGen/CGC/VICC Guidelines for Oncogenicity, 2022", + ), + ) + def test_examples(test_definitions): """Test VA Spec examples"""