Skip to content

Commit 1c56c17

Browse files
committed
apply VariantClinicalSignificanceStatement + evidence line changes
1 parent e64fbf8 commit 1c56c17

File tree

6 files changed

+139
-141
lines changed

6 files changed

+139
-141
lines changed

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[submodule "submodules/va_spec"]
22
path = submodules/va_spec
33
url = https://github.com/ga4gh/va-spec
4-
branch = issue-293
4+
branch = fix-aac

src/ga4gh/va_spec/aac_2017/__init__.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
AMP_ASCO_CAP_EVIDENCE_LINE_STRENGTHS,
77
AmpAscoCapClassificationCode,
88
AmpAscoCapClassificationName,
9+
AmpAscoCapEvidenceLine,
910
AmpAscoCapEvidenceLineStrength,
1011
AsmpAscoCapStrengthCode,
11-
VariantDiagnosticStatement,
12-
VariantPrognosticStatement,
13-
VariantTherapeuticResponseStatement,
12+
DiagnosticEvidenceLine,
13+
PrognosticEvidenceLine,
14+
TherapeuticEvidenceLine,
15+
VariantClinicalSignificanceStatement,
1416
)
1517

1618
__all__ = [
@@ -19,9 +21,11 @@
1921
"AMP_ASCO_CAP_EVIDENCE_LINE_STRENGTHS",
2022
"AmpAscoCapClassificationCode",
2123
"AmpAscoCapClassificationName",
24+
"AmpAscoCapEvidenceLine",
2225
"AmpAscoCapEvidenceLineStrength",
2326
"AsmpAscoCapStrengthCode",
24-
"VariantDiagnosticStatement",
25-
"VariantPrognosticStatement",
26-
"VariantTherapeuticResponseStatement",
27+
"DiagnosticEvidenceLine",
28+
"PrognosticEvidenceLine",
29+
"TherapeuticEvidenceLine",
30+
"VariantClinicalSignificanceStatement",
2731
]

src/ga4gh/va_spec/aac_2017/models.py

Lines changed: 104 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44
sequence variants in cancer.
55
"""
66

7-
from abc import ABC
87
from enum import Enum
98
from types import MappingProxyType
109

11-
from pydantic import Field, model_validator
10+
from pydantic import Field, field_validator, model_validator
1211
from pydantic.dataclasses import dataclass
1312
from typing_extensions import Self
1413

@@ -27,6 +26,57 @@
2726
from ga4gh.va_spec.base.validators import validate_mappable_concept
2827

2928

29+
class AmpAscoCapEvidenceLineStrength(str, Enum):
30+
"""Define constraints for AMP/ASCO/CAP `EvidenceLine.strengthOfEvidenceProvided`"""
31+
32+
A = "A"
33+
B = "B"
34+
C = "C"
35+
D = "D"
36+
37+
38+
class AmpAscoCapEvidenceLine(EvidenceLine):
39+
"""Evidence line for AMP/ASCO/CAP"""
40+
41+
targetProposition: (
42+
VariantDiagnosticProposition
43+
| VariantPrognosticProposition
44+
| VariantTherapeuticResponseProposition
45+
)
46+
47+
@field_validator("strengthOfEvidenceProvided", mode="after")
48+
@classmethod
49+
def validate_strength_of_evidence_provided(
50+
cls, v: MappableConcept | None
51+
) -> MappableConcept | None:
52+
"""Validate strengthOfEvidenceProvided"""
53+
validate_mappable_concept(
54+
v,
55+
System.AMP_ASCO_CAP,
56+
valid_codes=AMP_ASCO_CAP_EVIDENCE_LINE_STRENGTHS,
57+
mc_is_required=False,
58+
)
59+
return v
60+
61+
62+
class DiagnosticEvidenceLine(AmpAscoCapEvidenceLine):
63+
"""Diagnostic evidence line for AMP/ASCO/CAP"""
64+
65+
targetProposition: VariantDiagnosticProposition
66+
67+
68+
class PrognosticEvidenceLine(AmpAscoCapEvidenceLine):
69+
"""Prognostic evidence line for AMP/ASCO/CAP"""
70+
71+
targetProposition: VariantPrognosticProposition
72+
73+
74+
class TherapeuticEvidenceLine(AmpAscoCapEvidenceLine):
75+
"""Therapeutic evidence line for AMP/ASCO/CAP"""
76+
77+
targetProposition: VariantTherapeuticResponseProposition
78+
79+
3080
class AsmpAscoCapStrengthCode(str, Enum):
3181
"""Define constraints for AMP/ASCO/CAP strength coding"""
3282

@@ -37,10 +87,10 @@ class AsmpAscoCapStrengthCode(str, Enum):
3787
class AmpAscoCapClassificationCode(str, Enum):
3888
"""Define constraints for AMP/ASCO/CAP classification coding"""
3989

40-
TIER_1 = "tier 1"
41-
TIER_2 = "tier 2"
42-
TIER_3 = "tier 3"
43-
TIER_4 = "tier 4"
90+
TIER_1 = "tier i"
91+
TIER_2 = "tier ii"
92+
TIER_3 = "tier iii"
93+
TIER_4 = "tier iv"
4494

4595

4696
AMP_ASCO_CAP_CLASSIFICATION_CODES = [
@@ -57,15 +107,6 @@ class AmpAscoCapClassificationName(str, Enum):
57107
TIER_4 = "Tier IV"
58108

59109

60-
class AmpAscoCapEvidenceLineStrength(str, Enum):
61-
"""Define constraints for AMP/ASCO/CAP `EvidenceLine.strengthOfEvidenceProvided`"""
62-
63-
A = "A"
64-
B = "B"
65-
C = "C"
66-
D = "D"
67-
68-
69110
AMP_ASCO_CAP_EVIDENCE_LINE_STRENGTHS = [
70111
v.value for v in AmpAscoCapEvidenceLineStrength.__members__.values()
71112
]
@@ -106,20 +147,39 @@ class AmpAscoCapConfig:
106147
)
107148

108149

109-
class _AmpAscoCapStatement(Statement, ABC):
110-
"""Abstract base class for AAC 2017 statements"""
150+
class VariantClinicalSignificanceStatement(Statement):
151+
"""A statement reporting a conclusion from a single study about whether a variant is
152+
associated with a disease (a diagnostic inclusion criterion), or absence of a
153+
disease (diagnostic exclusion criterion) - based on interpretation of the study's
154+
results.
155+
"""
156+
157+
proposition: VariantClinicalSignificanceProposition = Field(
158+
...,
159+
description="A proposition about a diagnostic association between a variant and condition, for which the study provides evidence. The validity of this proposition, and the level of confidence/evidence supporting it, may be assessed and reported by the Statement.",
160+
)
161+
strength: MappableConcept | None = Field(
162+
default=None,
163+
description="The strength of support that the Statement is determined to provide for or against the Diagnostic Proposition for the assessed variant, based on the curation and reporting conventions of the AMP/ASCO/CAP 2017 Guidelines.",
164+
)
165+
classification: MappableConcept = Field(
166+
...,
167+
description="A single term or phrase classifying the subject variant based on the outcome of direction and strength assessments of the Statement's Proposition, using terms from the AMP/ASCO/CAP 2017 Guidelines.",
168+
)
169+
specifiedBy: Method | iriReference
111170

112171
@model_validator(mode="after")
113172
def validate_aac_statement(self) -> Self:
114-
"""Validate AMP/ASCO/CAP statements"""
173+
"""Validate VariantClinicalSignificanceStatement"""
115174

116175
def _validate_classification_config(
117176
classification_code: AmpAscoCapClassificationCode,
118177
classification_name: str,
119178
direction: str,
120179
strength_code: MappableConcept | None,
180+
has_evidence_lines: list,
121181
) -> None:
122-
"""Validate that classificati"""
182+
"""Validate that classification config is correct"""
123183
expected_config = AMP_ASCO_CAP_CLASSIFICATION_MAP[classification_code]
124184
actual_strength = (
125185
strength_code.primaryCoding.code.root
@@ -143,6 +203,30 @@ def _validate_classification_config(
143203
msg = f"`direction` must be: {expected_config.direction.value}"
144204
raise ValueError(msg)
145205

206+
if classification_code in {
207+
AmpAscoCapClassificationCode.TIER_1,
208+
AmpAscoCapClassificationCode.TIER_2,
209+
}:
210+
for evidence_line in has_evidence_lines:
211+
found_approved_el_clas = False
212+
for approved_el_cls in [
213+
DiagnosticEvidenceLine,
214+
PrognosticEvidenceLine,
215+
TherapeuticEvidenceLine,
216+
iriReference,
217+
]:
218+
try:
219+
approved_el_cls(**evidence_line.model_dump())
220+
except Exception: # noqa: S110
221+
pass
222+
else:
223+
found_approved_el_clas = True
224+
break
225+
226+
if not found_approved_el_clas:
227+
msg = "`hasEvidenceLines` must be one of: `DiagnosticEvidenceLine`, `PrognosticEvidenceLine`, `TherapeuticEvidenceLine`, or `iriReference`"
228+
raise ValueError(msg)
229+
146230
# Validate strength
147231
validate_mappable_concept(
148232
self.strength,
@@ -162,99 +246,7 @@ def _validate_classification_config(
162246
self.classification.name,
163247
self.direction,
164248
self.strength,
249+
self.hasEvidenceLines or [],
165250
)
166251

167-
# Validate hasEvidenceLines
168-
for evidence_line in self.hasEvidenceLines or []:
169-
if isinstance(evidence_line, EvidenceLine):
170-
target_proposition = evidence_line.targetProposition
171-
if target_proposition and not isinstance(
172-
target_proposition,
173-
VariantDiagnosticProposition
174-
| VariantPrognosticProposition
175-
| VariantTherapeuticResponseProposition,
176-
):
177-
msg = "`targetProposition` must be one of: `VariantDiagnosticProposition`, `VariantPrognosticProposition`, `VariantTherapeuticResponseProposition`"
178-
raise ValueError(msg)
179-
180-
validate_mappable_concept(
181-
evidence_line.strengthOfEvidenceProvided,
182-
System.AMP_ASCO_CAP,
183-
valid_codes=AMP_ASCO_CAP_EVIDENCE_LINE_STRENGTHS,
184-
mc_is_required=False,
185-
)
186-
187252
return self
188-
189-
190-
class VariantDiagnosticStatement(_AmpAscoCapStatement):
191-
"""A statement reporting a conclusion from a single study about whether a variant is
192-
associated with a disease (a diagnostic inclusion criterion), or absence of a
193-
disease (diagnostic exclusion criterion) - based on interpretation of the study's
194-
results.
195-
"""
196-
197-
proposition: VariantClinicalSignificanceProposition = Field(
198-
...,
199-
description="A proposition about a diagnostic association between a variant and condition, for which the study provides evidence. The validity of this proposition, and the level of confidence/evidence supporting it, may be assessed and reported by the Statement.",
200-
)
201-
strength: MappableConcept | None = Field(
202-
default=None,
203-
description="The strength of support that the Statement is determined to provide for or against the Diagnostic Proposition for the assessed variant, based on the curation and reporting conventions of the AMP/ASCO/CAP (AAC) 2017 Guidelines.",
204-
)
205-
classification: MappableConcept = Field(
206-
...,
207-
description="A single term or phrase classifying the subject variant based on the outcome of direction and strength assessments of the Statement's Proposition - reported here using terms from the AMP/ASCO/CAP (AAC) 2017 Guidelines.",
208-
)
209-
specifiedBy: Method | iriReference = Field(
210-
...,
211-
description="A method that specifies how the diagnostic classification was ultimately assigned to the variant, based on assessment of evidence.",
212-
)
213-
214-
215-
class VariantPrognosticStatement(_AmpAscoCapStatement):
216-
"""A statement reporting a conclusion from a single study about whether a variant is
217-
associated with a disease prognosis - based on interpretation of the study's
218-
results.
219-
"""
220-
221-
proposition: VariantClinicalSignificanceProposition = Field(
222-
...,
223-
description="A proposition about a prognostic association between a variant and condition, for which the study provides evidence. The validity of this proposition, and the level of confidence/evidence supporting it, may be assessed and reported by the Statement.",
224-
)
225-
strength: MappableConcept | None = Field(
226-
default=None,
227-
description="The strength of support that the Statement is determined to provide for or against the Prognostic Proposition for the assessed variant, based on the curation and reporting conventions of the AMP/ASCO/CAP (AAC) 2017 Guidelines.",
228-
)
229-
classification: MappableConcept = Field(
230-
...,
231-
description="A single term or phrase classifying the subject variant based on the outcome of direction and strength assessments of the Statement's Proposition - reported here using terms from the AMP/ASCO/CAP (AAC) 2017 Guidelines. Note that the enumerated value set here is bound to the `code` field of the Coding object that is nested inside a MappableConcept's primary coding.",
232-
)
233-
specifiedBy: Method | iriReference = Field(
234-
...,
235-
description="A method that specifies how the prognostic classification was ultimately assigned to the variant, based on assessment of evidence.",
236-
)
237-
238-
239-
class VariantTherapeuticResponseStatement(_AmpAscoCapStatement):
240-
"""A statement reporting a conclusion from a single study about whether a variant is
241-
associated with a therapeutic response (positive or negative) - based on
242-
interpretation of the study's results.
243-
"""
244-
245-
proposition: VariantClinicalSignificanceProposition = Field(
246-
...,
247-
description="A proposition about the therapeutic response associated with a variant, for which the study provides evidence. The validity of this proposition, and the level of confidence/evidence supporting it, may be assessed and reported by the Statement.",
248-
)
249-
strength: MappableConcept | None = Field(
250-
default=None,
251-
description="The strength of support that the Statement is determined to provide for or against the Therapeutic Response Proposition for the assessed variant, based on the curation and reporting conventions of the AMP/ASCO/CAP (AAC) 2017 Guidelines.",
252-
)
253-
classification: MappableConcept = Field(
254-
...,
255-
description="A single term or phrase classifying the subject variant based on the outcome of direction and strength assessments of the Statement's Proposition - reported here using terms from the AMP/ASCO/CAP (AAC) 2017 Guidelines.",
256-
)
257-
specifiedBy: Method | iriReference = Field(
258-
...,
259-
description="A method that specifies how the therapeutic response classification was ultimately assigned to the variant, based on assessment of evidence.",
260-
)

src/ga4gh/va_spec/base/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ class ClinicalVariantProposition(_SubjectVariantPropositionBase):
392392
)
393393
alleleOriginQualifier: MappableConcept | iriReference | None = Field(
394394
default=None,
395-
description="Reports whether the Proposition should be interpreted in the context of a heritable 'germline' variant, an acquired 'somatic' variant in a tumor, post-zygotic 'mosaic' variant. While these are the most commonly reported allele origins, other more nuanced concepts can be captured (e.g. 'maternal' vs 'paternal' allele origin'). In practice, populating this field may be complicated by the fact that some sources report allele origin based on the type of tissue that was sequenced to identify the variant, and others use it more generally to specify a category of variant for which the proposition holds. The stated intent of this attribute is the latter. However, if an implementer is not sure about which is reported in their data, it may be safer to create an Extension to hold this information, where they can explicitly acknowledge this ambiguity.",
395+
description='Reports whether the Proposition should be interpreted in the context of a heritable "germline" variant, an acquired "somatic" variant in a tumor, or a post-zygotic "mosaic" variant. While these are the most commonly reported allele origins, other more nuanced concepts can be captured (e.g. "maternal" vs "paternal" allele origin). In practice, populating this field may be complicated by the fact that some sources report allele origin based on the type of tissue that was sequenced to identify the variant, and others use it more generally to specify a category of variant for which the proposition holds. The stated intent of this attribute is the latter. However, if an implementer is not sure about which is reported in their data, it may be safer to create an Extension to hold this information, where they can explicitly acknowledge this ambiguity.',
396396
)
397397

398398

@@ -438,7 +438,7 @@ class VariantClinicalSignificanceProposition(
438438
description="The predicate associating the subject variant to clinical significance for the object Condition. MUST be 'hasClinicalSignificanceFor'.",
439439
)
440440
objectCondition: Condition | iriReference = Field(
441-
..., description="The disease that is evaluated."
441+
..., description="The condition that is evaluated."
442442
)
443443

444444

submodules/va_spec

Submodule va_spec updated 40 files

0 commit comments

Comments
 (0)