Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions civicpy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from civicpy.__env__ import LOCAL_CACHE_PATH
from civicpy.exports.civic_gks_record import (
CivicGksRecordError,
CivicGksAssertion
CivicGksClinSigAssertion
)
from civicpy.exports.civic_gks_writer import CivicGksWriter, GksAssertionError
from civicpy.exports.civic_vcf_writer import CivicVcfWriter
Expand Down Expand Up @@ -98,7 +98,7 @@ def create_gks_json(organization_id: int, output_json: Path) -> None:
logging.exception("Error getting organization %i", organization_id)
return

records: list[CivicGksAssertion] = []
records: list[CivicGksClinSigAssertion] = []
errors: list[GksAssertionError] = []

for approval in civic.get_all_approvals_ready_for_clinvar_submission_for_org(
Expand All @@ -107,7 +107,7 @@ def create_gks_json(organization_id: int, output_json: Path) -> None:
assertion = approval.assertion
if assertion.is_valid_for_gks_json(emit_warnings=True):
try:
gks_record = CivicGksAssertion(assertion, approval=approval)
gks_record = CivicGksClinSigAssertion(assertion, approval=approval)
except (CivicGksRecordError, NotImplementedError) as e:
errors.append(
GksAssertionError(assertion_id=assertion.id, message=str(e))
Expand Down
Binary file modified civicpy/data/test_cache.pkl
Binary file not shown.
86 changes: 55 additions & 31 deletions civicpy/exports/civic_gks_record.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""Module for representing CIViC assertion record as GKS AAC 2017 Study Statement"""
"""Module for representing CIViC records as GKS representations

* CIViC Predictive, Prognostic, and Diagnostic Assertions map to Variant
Clinical Significance Statements that follow the AMP/ASCO/CAP 2017 guidelines
"""

from enum import Enum
import logging
Expand Down Expand Up @@ -623,6 +627,8 @@ def __init__(


class _CivicGksEvidenceAssertionMixin:
"""Mixin for CIViC Evidence and Assertions"""

@staticmethod
def get_allele_origin_qualifier(record: Evidence | Assertion) -> MappableConcept:
"""Get GKS allele origin qualifier
Expand Down Expand Up @@ -877,10 +883,53 @@ def __init__(self, evidence_item: Evidence) -> None:
)


class CivicGksAssertion(
VariantClinicalSignificanceStatement, _CivicGksEvidenceAssertionMixin
class _CivicGksAssertionMixin:
"""Mixin for CIViC Assertions"""

@staticmethod
def get_contributions(approval: Approval) -> list[Contribution]:
"""Get contributions for an approval

:param approval: Approval for assertion
:return: List of contributions, with one item containing when the approval was
last reviewed an organization.
Will include an extension, `is_approved_vcep`.
"""
organization: Organization = approval.organization
return [
Contribution(
activityType=f"{approval.type}.last_reviewed",
date=approval.last_reviewed.split("T", 1)[0],
contributor=Agent(
id=f"civic.{organization.type}:{organization.id}",
name=organization.name,
description=organization.description,
extensions=[
Extension(
name="is_approved_vcep", value=organization.is_approved_vcep
)
],
),
)
]

@staticmethod
def get_reported_in(assertion: Assertion) -> list[iriReference]:
"""Get reported in information for an assertion

:param assertion: CIViC assertion record
:return: List of CIViC links to records which the assertion is reported in
"""
return [iriReference(f"{LINKS_URL}/assertion/{assertion.id}")]


class CivicGksClinSigAssertion(
VariantClinicalSignificanceStatement,
_CivicGksAssertionMixin,
_CivicGksEvidenceAssertionMixin,
):
"""Class for CIViC assertion record represented as GKS
"""Class for CIViC predictive, prognostic, or diagnostic assertion record
represented as GKS

:param assertion: CIViC assertion record
:raises CivicGksRecordError: If CIViC assertion is not able to be represented as
Expand All @@ -892,7 +941,7 @@ def __init__(
assertion: Assertion,
approval: Approval | None = None,
) -> None:
"""Initialize _CivicGksAssertionRecord class
"""Initialize CivicGksClinSigAssertion class

:param assertion: CIViC assertion record
:param approval: CIViC approval for the assertion, defaults to None
Expand Down Expand Up @@ -924,35 +973,10 @@ def __init__(
classification=classification,
strength=strength,
hasEvidenceLines=self.get_evidence_lines(assertion, level),
reportedIn=[iriReference(f"{LINKS_URL}/assertion/{assertion.id}")],
reportedIn=self.get_reported_in(assertion),
extensions=extensions or None,
)

@staticmethod
def get_contributions(approval: Approval) -> list[Contribution]:
"""Get contributions for an approval

:param approval: Approval for assertion
:return: List of contributions containing when the approval was last reviewed
"""
organization: Organization = approval.organization
return [
Contribution(
activityType=f"{approval.type}.last_reviewed",
date=approval.last_reviewed.split("T", 1)[0],
contributor=Agent(
id=f"civic.{organization.type}:{organization.id}",
name=organization.name,
description=organization.description,
extensions=[
Extension(
name="is_approved_vcep", value=organization.is_approved_vcep
)
],
),
)
]

def get_classification_strength_level(
self,
amp_level: str,
Expand Down
6 changes: 3 additions & 3 deletions civicpy/exports/civic_gks_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from pydantic import BaseModel, Field

from civicpy.exports.civic_gks_record import CivicGksAssertion
from civicpy.exports.civic_gks_record import CivicGksClinSigAssertion


def get_pkg_version(name: str) -> str:
Expand Down Expand Up @@ -43,7 +43,7 @@ class GksAssertionError(BaseModel):
class GksOutput(BaseModel):
"""Define model for representing GKS JSON output"""

gks_records: list[CivicGksAssertion]
gks_records: list[CivicGksClinSigAssertion]
metadata: GksOutputMetadata
failed_assertion_ids: list[int] = []
errors: list[GksAssertionError] = []
Expand All @@ -60,7 +60,7 @@ class CivicGksWriter:
def __init__(
self,
filepath: Path,
gks_records: list[CivicGksAssertion],
gks_records: list[CivicGksClinSigAssertion],
errors: list[GksAssertionError] | None = None,
):
"""Initialize CivicGksWriter class
Expand Down
34 changes: 18 additions & 16 deletions civicpy/tests/test_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
CivicGksEvidence,
CivicGksMolecularProfile,
CivicGksRecordError,
CivicGksAssertion,
CivicGksClinSigAssertion,
CivicGksTherapyGroup,
)

Expand Down Expand Up @@ -751,12 +751,12 @@ def test_invalid(self, eid9285):
CivicGksEvidence(eid9285)


class TestCivicGksAssertion(object):
"""Test that CivicGksAssertion works as expected"""
class TestCivicGksClinSigAssertion(object):
"""Test that CivicGksClinSigAssertion works as expected"""

def test_valid_single_therapy(self, aid6, gks_aid6):
"""Test that single therapy works as expected"""
record = CivicGksAssertion(aid6)
record = CivicGksClinSigAssertion(aid6)
assert isinstance(record, VariantClinicalSignificanceStatement)
assert len(record.hasEvidenceLines) == 1

Expand All @@ -778,7 +778,7 @@ def test_valid_single_therapy(self, aid6, gks_aid6):

def test_valid_combination_therapy(self, aid7):
"""Test that combination therapy works as expected"""
record = CivicGksAssertion(aid7)
record = CivicGksClinSigAssertion(aid7)
assert isinstance(record, VariantClinicalSignificanceStatement)
assert len(record.hasEvidenceLines) == 1
assert len(record.hasEvidenceLines[0].hasEvidenceItems) == 4
Expand Down Expand Up @@ -825,7 +825,7 @@ def test_valid_substitution_therapy(
test_evidence_items.return_value = []
test_hgvs_expressions.return_value = None
test_mane_select_transcript.return_value = None
record = CivicGksAssertion(aid19)
record = CivicGksClinSigAssertion(aid19)
assert isinstance(record, VariantClinicalSignificanceStatement)
assert len(record.hasEvidenceLines) == 1
therapy = record.hasEvidenceLines[0].targetProposition.objectTherapeutic.root
Expand All @@ -837,7 +837,7 @@ def test_valid_substitution_therapy(

def test_valid_prognostic(self, aid20):
"""Test that valid prognostic assertion works as expected"""
record = CivicGksAssertion(aid20)
record = CivicGksClinSigAssertion(aid20)
assert isinstance(record, VariantClinicalSignificanceStatement)
assert len(record.hasEvidenceLines) == 1
assert len(record.hasEvidenceLines[0].hasEvidenceItems) == 6
Expand All @@ -850,18 +850,20 @@ def test_valid_prognostic(self, aid20):

@patch.object(civic.Assertion, "evidence_items", new_callable=PropertyMock)
@patch.object(civic.Evidence, "is_valid_for_gks_json")
def test_citations(self, test_is_valid_for_gks_json,test_evidence_items, aid20):
def test_citations(self, test_is_valid_for_gks_json, test_evidence_items, aid20):
"""Test that citations extension is working correctly for EIDs that are not valid for GKS"""
test_evidence_items.return_value = [civic.get_evidence_by_id(11881)]
test_is_valid_for_gks_json.return_value = False

record = CivicGksAssertion(aid20)
record = CivicGksClinSigAssertion(aid20)
assert len(record.hasEvidenceLines) == 1
assert record.hasEvidenceLines[0].hasEvidenceItems is None
assert len(record.hasEvidenceLines[0].extensions) == 1
assert record.hasEvidenceLines[0].extensions[0].model_dump(exclude_none=True) == {
assert record.hasEvidenceLines[0].extensions[0].model_dump(
exclude_none=True
) == {
"name": "citations",
"value": ["https://civicdb.org/links/evidence/11881"]
"value": ["https://civicdb.org/links/evidence/11881"],
}


Expand All @@ -877,7 +879,7 @@ def test_valid(
gks_aid115_object_condition,
):
"""Test that valid diagnostic assertion works as expected"""
record = CivicGksAssertion(aid9)
record = CivicGksClinSigAssertion(aid9)
assert isinstance(record, VariantClinicalSignificanceStatement)
assert len(record.hasEvidenceLines) == 1
assert len(record.hasEvidenceLines[0].hasEvidenceItems) == 2
Expand All @@ -895,7 +897,7 @@ def test_valid(
)

# Single phenotype (complex condition set)
record = CivicGksAssertion(aid93)
record = CivicGksClinSigAssertion(aid93)
assert isinstance(record, VariantClinicalSignificanceStatement)
record_object_condition = record.proposition.objectCondition
assert isinstance(record_object_condition, Condition)
Expand All @@ -908,7 +910,7 @@ def test_valid(
assert diff == {}

# Phenotypes (complex condition set)
record = CivicGksAssertion(aid115)
record = CivicGksClinSigAssertion(aid115)
assert isinstance(record, VariantClinicalSignificanceStatement)
record_object_condition = record.proposition.objectCondition
assert isinstance(record_object_condition, Condition)
Expand All @@ -922,7 +924,7 @@ def test_valid(

def test_clinvar_accession_ext(self):
a = civic.get_assertion_by_id(193)
record = CivicGksAssertion(a, approval=a.approvals[0])
record = CivicGksClinSigAssertion(a, approval=a.approvals[0])
assert isinstance(record, VariantClinicalSignificanceStatement)
assert [ext.model_dump(exclude_none=True) for ext in record.extensions] == [
{"name": "clinvar_accession", "value": "SCV007542591"}
Expand All @@ -934,4 +936,4 @@ def test_invalid(self, aid117):
with pytest.raises(
CivicGksRecordError, match=r"Assertion is not valid for GKS."
):
CivicGksAssertion(aid117)
CivicGksClinSigAssertion(aid117)
18 changes: 9 additions & 9 deletions docs/exports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Knowledge Standards (GKS) objects. GKS JSON exports are maintained via the
namespace::

>>>from civicpy.exports.civic_gks_writer import CivicGksWriter
>>>from civicpy.exports.civic_gks_record import CivicGksAssertion
>>>from civicpy.exports.civic_gks_record import CivicGksClinSigAssertion

Other file formats are planned for future releases. Suggestions are welcome on our
`GitHub issues page <https://github.com/griffithlab/civicpy/issues>`_.
Expand Down Expand Up @@ -212,17 +212,17 @@ GKS JSON
--------

GKS JSON files are written using the :class:`civicpy.exports.civic_gks_writer.CivicGksWriter`
class to which you add :class:`civicpy.exports.civic_gks_record.CivicGksAssertion`
class to which you add :class:`civicpy.exports.civic_gks_record.CivicGksClinSigAssertion`
during initialization.

In order to verify whether an assertion can be converted to a CivicGksAssertion
In order to verify whether an assertion can be converted to a CivicGksClinSigAssertion
object, the convenience method ``is_valid_for_gks_json`` can be called on a
:class:`civic.Assertion` object.

CivicGksAssertion
CivicGksClinSigAssertion
~~~~~~~~~~~~~~~~~~~~~~~~

.. autoclass:: civicpy.exports.civic_gks_record.CivicGksAssertion
.. autoclass:: civicpy.exports.civic_gks_record.CivicGksClinSigAssertion
:members:
:show-inheritance:

Expand All @@ -239,14 +239,14 @@ Here's an example of how to export all assertions to GKS JSON::

from civicpy import civic
from civicpy.exports.civic_gks_writer import CivicGksWriter
from civicpy.exports.civic_gks_record import CivicGksAssertion
from civicpy.exports.civic_gks_record import CivicGksClinSigAssertion

records = []

for assertion in civic.get_all_assertions():
if assertion.is_valid_for_gks_json():
try:
gks_record = CivicGksAssertion(assertion)
gks_record = CivicGksClinSigAssertion(assertion)
except CivicGksRecordError:
continue

Expand All @@ -258,15 +258,15 @@ ready for submission to ClinVar.::

from civicpy import civic
from civicpy.exports.civic_gks_writer import CivicGksWriter
from civicpy.exports.civic_gks_record import CivicGksAssertion
from civicpy.exports.civic_gks_record import CivicGksClinSigAssertion

records = []
organization_id = 1

for assertion in civic.get_all_assertions_ready_for_clinvar_submission_for_org(organization_id):
if assertion.is_valid_for_gks_json():
try:
gks_record = CivicGksAssertion(assertion)
gks_record = CivicGksClinSigAssertion(assertion)
except CivicGksRecordError:
continue

Expand Down
Loading