From ca218c18d926c7d6c2ca275eec9125475f9e82a3 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Sun, 6 Oct 2024 14:05:29 +0530 Subject: [PATCH 01/10] Overflow and Type Errors added to concept.py . Required tests for quantity class added in tests. --- .python-version | 1 + healthchain/models/data/concept.py | 18 +++++++++++++- poetry.lock | 4 +-- pyproject.toml | 1 + tests/test_quantity_class.py | 39 ++++++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .python-version create mode 100644 tests/test_quantity_class.py diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..09dcc78 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.11 diff --git a/healthchain/models/data/concept.py b/healthchain/models/data/concept.py index 0ef4d7d..e163733 100644 --- a/healthchain/models/data/concept.py +++ b/healthchain/models/data/concept.py @@ -1,5 +1,5 @@ from enum import Enum -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator from typing import Optional, Dict, Union @@ -20,6 +20,22 @@ class Quantity(DataType): # TODO: validate conversions str <-> float value: Optional[Union[str, float]] = None unit: Optional[str] = None + + @field_validator('value') + @classmethod + def validate_value(cls, value:Union[str,float]): + if value is None: + raise TypeError(f"Value CANNOT be a {type(value)} object. Must be float or string in float format.") + + try : + return float(value) + + except ValueError : + raise ValueError(f"Invalid value '{value}' . Must be a float Number.") + + except OverflowError: + raise OverflowError(f"Invalid value . Value is too large resulting in overflow.") + class Range(DataType): diff --git a/poetry.lock b/poetry.lock index bd41e59..7d58932 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -3752,4 +3752,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "9c31e43a846ad145298179fd2e01eb47e2a824a86857b4d69d7f12ccb8123f6d" +content-hash = "92e7107fce8f07eeea30ef29cfc758f0ef206e796208b6060335c338040868fd" diff --git a/pyproject.toml b/pyproject.toml index 99f7a96..81c0932 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ termcolor = "^2.4.0" spyne = "^2.14.0" lxml = "^5.2.2" xmltodict = "^0.13.0" +pytest = "^8.3.3" [tool.poetry.group.dev.dependencies] ruff = "^0.4.2" diff --git a/tests/test_quantity_class.py b/tests/test_quantity_class.py new file mode 100644 index 0000000..023d843 --- /dev/null +++ b/tests/test_quantity_class.py @@ -0,0 +1,39 @@ +import pytest +from healthchain.models.data.concept import Quantity +from pydantic import ValidationError +#Valid Cases +def test_valid_float_and_integer(): + valid_floats = [1.0, .1, 4., 5.99999, 12455.321, 33, 1234] + for num in valid_floats : + q = Quantity(value = num,unit = "mg"); + assert q.value == num + +def test_valid_string(): + valid_strings = ["100","100.000001",".1","1.",".123","1234.","123989"] + for string in valid_strings: + q = Quantity(value = string,unit = "mg"); + assert q.value == float(string) + +# Invalid Cases +def test_invalid_strings(): + invalid_strings = ["1.0.0", "1..123", "..123","12..","12a.56","1e4.6","12#.45","12.12@3","12@3"] + for string in invalid_strings: + with pytest.raises(ValidationError) as exception_info: + q = Quantity(value = string,unit = "mg") + assert "Invalid value" in str(exception_info.value) + + +#Edge Cases +def test_edge_cases(): + + edge_cases = ["", "None", None] + for val in edge_cases: + with pytest.raises((ValidationError,TypeError)) as exception_info: + q = Quantity(value = val,unit = "mg") + + exception_info_str = str(exception_info.value) + assert any(msg in exception_info_str for msg in ["CANNOT", "Invalid value"]) + +# if __name__ == '__main__': +# q = Quantity("12","mg"); +# print(q); \ No newline at end of file From ad78f8de8bdb3287e25f3457954bf5cb33752336 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Thu, 10 Oct 2024 03:14:35 +0530 Subject: [PATCH 02/10] minor issues fixed --- .gitignore | 1 + poetry.lock | 2 +- pyproject.toml | 1 - tests/test_quantity_class.py | 6 +----- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index d168a8c..aaa3416 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,4 @@ scrap/ .DS_Store .vscode/ .ruff_cache/ +.python-version diff --git a/poetry.lock b/poetry.lock index 7d58932..f653cf9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3752,4 +3752,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "92e7107fce8f07eeea30ef29cfc758f0ef206e796208b6060335c338040868fd" +content-hash = "9c31e43a846ad145298179fd2e01eb47e2a824a86857b4d69d7f12ccb8123f6d" diff --git a/pyproject.toml b/pyproject.toml index 81c0932..99f7a96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ termcolor = "^2.4.0" spyne = "^2.14.0" lxml = "^5.2.2" xmltodict = "^0.13.0" -pytest = "^8.3.3" [tool.poetry.group.dev.dependencies] ruff = "^0.4.2" diff --git a/tests/test_quantity_class.py b/tests/test_quantity_class.py index 023d843..481f59f 100644 --- a/tests/test_quantity_class.py +++ b/tests/test_quantity_class.py @@ -32,8 +32,4 @@ def test_edge_cases(): q = Quantity(value = val,unit = "mg") exception_info_str = str(exception_info.value) - assert any(msg in exception_info_str for msg in ["CANNOT", "Invalid value"]) - -# if __name__ == '__main__': -# q = Quantity("12","mg"); -# print(q); \ No newline at end of file + assert any(msg in exception_info_str for msg in ["CANNOT", "Invalid value"]) \ No newline at end of file From fb1209ace938d5554cace6627007305e98c0d837 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Thu, 10 Oct 2024 03:24:26 +0530 Subject: [PATCH 03/10] code formatted --- .github/pull_request_template.md | 7 ++--- healthchain/models/data/concept.py | 23 ++++++++------- tests/test_quantity_class.py | 45 +++++++++++++++++++----------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bc323ae..84fa182 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,9 +7,9 @@ ## Changes Made -- -- -- +- +- +- ## Testing @@ -27,4 +27,3 @@ ## Additional Notes - diff --git a/healthchain/models/data/concept.py b/healthchain/models/data/concept.py index e163733..fb560ae 100644 --- a/healthchain/models/data/concept.py +++ b/healthchain/models/data/concept.py @@ -20,22 +20,25 @@ class Quantity(DataType): # TODO: validate conversions str <-> float value: Optional[Union[str, float]] = None unit: Optional[str] = None - - @field_validator('value') + + @field_validator("value") @classmethod - def validate_value(cls, value:Union[str,float]): + def validate_value(cls, value: Union[str, float]): if value is None: - raise TypeError(f"Value CANNOT be a {type(value)} object. Must be float or string in float format.") - - try : + raise TypeError( + f"Value CANNOT be a {type(value)} object. Must be float or string in float format." + ) + + try: return float(value) - - except ValueError : + + except ValueError: raise ValueError(f"Invalid value '{value}' . Must be a float Number.") except OverflowError: - raise OverflowError(f"Invalid value . Value is too large resulting in overflow.") - + raise OverflowError( + "Invalid value . Value is too large resulting in overflow." + ) class Range(DataType): diff --git a/tests/test_quantity_class.py b/tests/test_quantity_class.py index 481f59f..375542d 100644 --- a/tests/test_quantity_class.py +++ b/tests/test_quantity_class.py @@ -1,35 +1,48 @@ -import pytest +import pytest from healthchain.models.data.concept import Quantity from pydantic import ValidationError -#Valid Cases + + +# Valid Cases def test_valid_float_and_integer(): - valid_floats = [1.0, .1, 4., 5.99999, 12455.321, 33, 1234] - for num in valid_floats : - q = Quantity(value = num,unit = "mg"); + valid_floats = [1.0, 0.1, 4.0, 5.99999, 12455.321, 33, 1234] + for num in valid_floats: + q = Quantity(value=num, unit="mg") assert q.value == num + def test_valid_string(): - valid_strings = ["100","100.000001",".1","1.",".123","1234.","123989"] + valid_strings = ["100", "100.000001", ".1", "1.", ".123", "1234.", "123989"] for string in valid_strings: - q = Quantity(value = string,unit = "mg"); + q = Quantity(value=string, unit="mg") assert q.value == float(string) + # Invalid Cases def test_invalid_strings(): - invalid_strings = ["1.0.0", "1..123", "..123","12..","12a.56","1e4.6","12#.45","12.12@3","12@3"] + invalid_strings = [ + "1.0.0", + "1..123", + "..123", + "12..", + "12a.56", + "1e4.6", + "12#.45", + "12.12@3", + "12@3", + ] for string in invalid_strings: with pytest.raises(ValidationError) as exception_info: - q = Quantity(value = string,unit = "mg") + Quantity(value=string, unit="mg") assert "Invalid value" in str(exception_info.value) - -#Edge Cases + +# Edge Cases def test_edge_cases(): - edge_cases = ["", "None", None] for val in edge_cases: - with pytest.raises((ValidationError,TypeError)) as exception_info: - q = Quantity(value = val,unit = "mg") - + with pytest.raises((ValidationError, TypeError)) as exception_info: + Quantity(value=val, unit="mg") + exception_info_str = str(exception_info.value) - assert any(msg in exception_info_str for msg in ["CANNOT", "Invalid value"]) \ No newline at end of file + assert any(msg in exception_info_str for msg in ["CANNOT", "Invalid value"]) From 0270c036738b6466bc4b76f1867c315f71fd51de Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Thu, 10 Oct 2024 03:31:49 +0530 Subject: [PATCH 04/10] python-version file removed --- .python-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .python-version diff --git a/.python-version b/.python-version deleted file mode 100644 index 09dcc78..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10.11 From 918551321869243b671c2b7c1aff359fb9976c80 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Fri, 11 Oct 2024 13:59:31 +0530 Subject: [PATCH 05/10] Removed TypeError for None and updated tests --- healthchain/models/data/concept.py | 3 +++ tests/test_quantity_class.py | 18 +++++------------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/healthchain/models/data/concept.py b/healthchain/models/data/concept.py index fb560ae..f6b859c 100644 --- a/healthchain/models/data/concept.py +++ b/healthchain/models/data/concept.py @@ -25,6 +25,9 @@ class Quantity(DataType): @classmethod def validate_value(cls, value: Union[str, float]): if value is None: + return None + + if not isinstance(value, (str, float)): raise TypeError( f"Value CANNOT be a {type(value)} object. Must be float or string in float format." ) diff --git a/tests/test_quantity_class.py b/tests/test_quantity_class.py index 375542d..87b9f01 100644 --- a/tests/test_quantity_class.py +++ b/tests/test_quantity_class.py @@ -4,8 +4,8 @@ # Valid Cases -def test_valid_float_and_integer(): - valid_floats = [1.0, 0.1, 4.0, 5.99999, 12455.321, 33, 1234] +def test_valid(): + valid_floats = [1.0, 0.1, 4.0, 5.99999, 12455.321, 33, 1234, None] for num in valid_floats: q = Quantity(value=num, unit="mg") assert q.value == num @@ -30,19 +30,11 @@ def test_invalid_strings(): "12#.45", "12.12@3", "12@3", + "abc", + "None", + "", ] for string in invalid_strings: with pytest.raises(ValidationError) as exception_info: Quantity(value=string, unit="mg") assert "Invalid value" in str(exception_info.value) - - -# Edge Cases -def test_edge_cases(): - edge_cases = ["", "None", None] - for val in edge_cases: - with pytest.raises((ValidationError, TypeError)) as exception_info: - Quantity(value=val, unit="mg") - - exception_info_str = str(exception_info.value) - assert any(msg in exception_info_str for msg in ["CANNOT", "Invalid value"]) From 59ede20edb0ddae99359fdc4a0f46e2ad0e4a9e0 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Fri, 11 Oct 2024 20:34:18 +0530 Subject: [PATCH 06/10] test_cdaannotator updated --- tests/test_cdaannotator.py | 4 ++-- tests/test_quantity_class.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_cdaannotator.py b/tests/test_cdaannotator.py index dd1562e..86e4d85 100644 --- a/tests/test_cdaannotator.py +++ b/tests/test_cdaannotator.py @@ -68,12 +68,12 @@ def test_extract_medications(cda_annotator): assert medications[0].route.code_system_name == "NCI Thesaurus" assert medications[0].route.display_name == "Oral" - assert medications[0].frequency.period.value == ".5" + assert medications[0].frequency.period.value == 0.5 assert medications[0].frequency.period.unit == "d" assert medications[0].frequency.institution_specified assert medications[0].duration.low is None - assert medications[0].duration.high.value == "20221020" + assert medications[0].duration.high.value == 20221020 assert medications[0].precondition == { "@typeCode": "PRCN", diff --git a/tests/test_quantity_class.py b/tests/test_quantity_class.py index 87b9f01..1413fd5 100644 --- a/tests/test_quantity_class.py +++ b/tests/test_quantity_class.py @@ -5,7 +5,7 @@ # Valid Cases def test_valid(): - valid_floats = [1.0, 0.1, 4.0, 5.99999, 12455.321, 33, 1234, None] + valid_floats = [1.0, 0.1, 4.5, 5.99999, 12455.321, 33, 1234, None] for num in valid_floats: q = Quantity(value=num, unit="mg") assert q.value == num From de24a6bbe3bf07d2f0e5876edef7fd2c204499a0 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Sat, 19 Oct 2024 14:16:02 +0530 Subject: [PATCH 07/10] cdaannotator updated with find various sections using codes functionality --- healthchain/cda_parser/cdaannotator.py | 52 +++- tests/conftest.py | 7 + tests/data/test_cda_without_template_id.xml | 306 ++++++++++++++++++++ tests/test_cdaannotator.py | 109 +++++++ 4 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 tests/data/test_cda_without_template_id.xml diff --git a/healthchain/cda_parser/cdaannotator.py b/healthchain/cda_parser/cdaannotator.py index 1304377..dc71f3d 100644 --- a/healthchain/cda_parser/cdaannotator.py +++ b/healthchain/cda_parser/cdaannotator.py @@ -15,8 +15,8 @@ ) from healthchain.models.data.concept import Concept, Quantity, Range, TimeInterval -from .model.cda import ClinicalDocument -from .model.sections import ( +from healthchain.cda_parser.model.cda import ClinicalDocument +from healthchain.cda_parser.model.sections import ( Entry, Section, EntryRelationship, @@ -138,6 +138,13 @@ class SectionId(Enum): NOTE = "1.2.840.114350.1.72.1.200001" +class SectionCode(Enum): + PROBLEM = "11450-4" + MEDICATION = "10160-0" + ALLERGY = "48765-2" + NOTE = "51847-2" + + class ProblemCodes(Enum): CONDITION = "64572001" SYMPTOM = "418799008" @@ -258,6 +265,31 @@ def _extract_data(self) -> None: self.allergy_list: List[AllergyConcept] = self._extract_allergies() self.note: str = self._extract_note() + def _find_section_by_code(self, section_code: str) -> Optional[Section]: + """ + Finds a section in the clinical document by its code value. + + Args: + section_code (str): The code of the section to find. + + Returns: + Optional[Section]: The section with the specified code, or None if not found. + """ + components = self.clinical_document.component.structuredBody.component + + if not isinstance(components, list): + components = [components] + + for component in components: + code = component.section.code.code + + if code is None: + continue + if code == section_code: + return component.section + log.warning(f"unable to find section with code {section_code}") + return None + def _find_section_by_template_id(self, section_id: str) -> Optional[Section]: """ Finds a section in the clinical document by its template ID. @@ -292,16 +324,24 @@ def _find_section_by_template_id(self, section_id: str) -> Optional[Section]: return None def _find_problems_section(self) -> Optional[Section]: - return self._find_section_by_template_id(SectionId.PROBLEM.value) + return self._find_section_by_template_id( + SectionId.PROBLEM.value + ) or self._find_section_by_code(SectionCode.PROBLEM.value) def _find_medications_section(self) -> Optional[Section]: - return self._find_section_by_template_id(SectionId.MEDICATION.value) + return self._find_section_by_template_id( + SectionId.MEDICATION.value + ) or self._find_section_by_code(SectionCode.MEDICATION.value) def _find_allergies_section(self) -> Optional[Section]: - return self._find_section_by_template_id(SectionId.ALLERGY.value) + return self._find_section_by_template_id( + SectionId.ALLERGY.value + ) or self._find_section_by_code(SectionCode.ALLERGY.value) def _find_notes_section(self) -> Optional[Section]: - return self._find_section_by_template_id(SectionId.NOTE.value) + return self._find_section_by_template_id( + SectionId.NOTE.value + ) or self._find_section_by_code(SectionCode.NOTE.value) def _extract_problems(self) -> List[ProblemConcept]: """ diff --git a/tests/conftest.py b/tests/conftest.py index 6dec773..b63c82a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -356,6 +356,13 @@ def cda_annotator(): return CdaAnnotator.from_xml(test_cda) +@pytest.fixture +def cda_annotator_code(): + with open("./tests/data/test_cda_without_template_id.xml", "r") as file: + test_cda_without_template_id = file.read() + return CdaAnnotator.from_xml(test_cda_without_template_id) + + @pytest.fixture def cdsservices(): return CDSServices() diff --git a/tests/data/test_cda_without_template_id.xml b/tests/data/test_cda_without_template_id.xml new file mode 100644 index 0000000..d618095 --- /dev/null +++ b/tests/data/test_cda_without_template_id.xml @@ -0,0 +1,306 @@ + + + + + + + CDA for NoteReader (NLP integration) + + + + + + +
+ + + Allergies + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EGGS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + Medication + + + + + + + + + + + + + + + + + + + + + Oral + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + Problems + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + Progress Notes + test + +
+
+
+
+
diff --git a/tests/test_cdaannotator.py b/tests/test_cdaannotator.py index 86e4d85..a297bba 100644 --- a/tests/test_cdaannotator.py +++ b/tests/test_cdaannotator.py @@ -1,5 +1,6 @@ from healthchain.cda_parser.cdaannotator import ( SectionId, + SectionCode, ProblemConcept, AllergyConcept, ) @@ -19,30 +20,61 @@ def test_find_notes_section(cda_annotator): assert section.templateId.root == SectionId.NOTE.value +def test_find_notes_section_using_code(cda_annotator_code): + # Test if the notes section is found correctly when no template_id is available in the document. + section = cda_annotator_code._find_notes_section() + assert section is not None + assert section.code.code == SectionCode.NOTE.value + + def test_find_problems_section(cda_annotator): section = cda_annotator._find_problems_section() assert section is not None assert section.templateId.root == SectionId.PROBLEM.value +def test_find_problems_section_using_code(cda_annotator_code): + section = cda_annotator_code._find_problems_section() + assert section is not None + assert section.code.code == SectionCode.PROBLEM.value + + def test_find_medications_section(cda_annotator): section = cda_annotator._find_medications_section() assert section is not None assert section.templateId[0].root == SectionId.MEDICATION.value +def test_find_medications_section_using_code(cda_annotator_code): + section = cda_annotator_code._find_medications_section() + assert section is not None + assert section.code.code == SectionCode.MEDICATION.value + + def test_find_allergies_section(cda_annotator): section = cda_annotator._find_allergies_section() assert section is not None assert section.templateId[0].root == SectionId.ALLERGY.value +def test_find_allergies_section_using_code(cda_annotator_code): + section = cda_annotator_code._find_allergies_section() + assert section is not None + assert section.code.code == SectionCode.ALLERGY.value + + def test_extract_note(cda_annotator): # Test if the note is extracted correctly note = cda_annotator._extract_note() assert note == {"paragraph": "test"} +def test_extract_note_using_code(cda_annotator_code): + # Test if the note is extracted correctly + note = cda_annotator_code._extract_note() + assert note == {"paragraph": "test"} + + def test_extract_problems(cda_annotator): problems = cda_annotator._extract_problems() @@ -52,6 +84,15 @@ def test_extract_problems(cda_annotator): assert problems[0].code_system_name == "SNOMED CT" +def test_extract_problems_using_code(cda_annotator_code): + problems = cda_annotator_code._extract_problems() + + assert len(problems) == 1 + assert problems[0].code == "38341003" + assert problems[0].code_system == "2.16.840.1.113883.6.96" + assert problems[0].code_system_name == "SNOMED CT" + + def test_extract_medications(cda_annotator): medications = cda_annotator._extract_medications() @@ -95,6 +136,49 @@ def test_extract_medications(cda_annotator): } +def test_extract_medications_using_code(cda_annotator_code): + medications = cda_annotator_code._extract_medications() + + assert len(medications) == 1 + assert medications[0].code == "314076" + assert medications[0].code_system == "2.16.840.1.113883.6.88" + assert medications[0].display_name == "lisinopril 10 MG Oral Tablet" + + assert medications[0].dosage.value == 30.0 + assert medications[0].dosage.unit == "mg" + + assert medications[0].route.code == "C38288" + assert medications[0].route.code_system == "2.16.840.1.113883.3.26.1.1" + assert medications[0].route.code_system_name == "NCI Thesaurus" + assert medications[0].route.display_name == "Oral" + + assert medications[0].frequency.period.value == 0.5 + assert medications[0].frequency.period.unit == "d" + assert medications[0].frequency.institution_specified + + assert medications[0].duration.low is None + assert medications[0].duration.high.value == 20221020 + + assert medications[0].precondition == { + "@typeCode": "PRCN", + "criterion": { + "templateId": [ + {"@root": "2.16.840.1.113883.10.20.22.4.25"}, + { + "@extension": "2014-06-09", + "@root": "2.16.840.1.113883.10.20.22.4.25", + }, + ], + "code": {"@code": "ASSERTION", "@codeSystem": "2.16.840.1.113883.5.4"}, + "value": { + "@nullFlavor": "NI", + "@xsi:type": "CD", + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + }, + }, + } + + def test_extract_allergies(cda_annotator): allergies = cda_annotator._extract_allergies() @@ -120,6 +204,31 @@ def test_extract_allergies(cda_annotator): assert allergies[0].severity.display_name == "High" +def test_extract_allergies_using_code(cda_annotator_code): + allergies = cda_annotator_code._extract_allergies() + + assert len(allergies) == 1 + assert allergies[0].code == "102263004" + assert allergies[0].code_system == "2.16.840.1.113883.6.96" + assert allergies[0].code_system_name == "SNOMED-CT" + assert allergies[0].display_name == "EGGS" + assert allergies[0].allergy_type.code == "418471000" + assert allergies[0].allergy_type.code_system == "2.16.840.1.113883.6.96" + assert allergies[0].allergy_type.code_system_name == "SNOMED CT" + assert ( + allergies[0].allergy_type.display_name + == "Propensity to adverse reactions to food" + ) + assert allergies[0].reaction.code == "65124004" + assert allergies[0].reaction.code_system == "2.16.840.1.113883.6.96" + assert allergies[0].reaction.code_system_name == "SNOMED CT" + assert allergies[0].reaction.display_name == "Swelling" + assert allergies[0].severity.code == "H" + assert allergies[0].severity.code_system == "2.16.840.1.113883.5.1063" + assert allergies[0].severity.code_system_name == "SeverityObservation" + assert allergies[0].severity.display_name == "High" + + def test_add_to_empty_sections(cda_annotator, test_ccd_data): cda_annotator._problem_section = None cda_annotator.problem_list = [] From 96032ca360bb064cbf288a434ee372acd5396ba6 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Thu, 24 Oct 2024 11:09:56 +0530 Subject: [PATCH 08/10] timeout member added to ehrclient class --- healthchain/clients/ehrclient.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/healthchain/clients/ehrclient.py b/healthchain/clients/ehrclient.py index bba59b5..b971a02 100644 --- a/healthchain/clients/ehrclient.py +++ b/healthchain/clients/ehrclient.py @@ -99,7 +99,11 @@ def wrapper(self, *args: Any, **kwargs: Any) -> EHRClient: class EHRClient(BaseClient): def __init__( - self, func: Callable[..., Any], workflow: Workflow, strategy: BaseStrategy + self, + func: Callable[..., Any], + workflow: Workflow, + strategy: BaseStrategy, + timeout: Optional[float] = 10.0, ): """ Initializes the EHRClient with a data generator function and optional workflow and use case. @@ -117,6 +121,7 @@ def __init__( self.strategy: BaseStrategy = strategy self.vendor = None self.request_data: List[CDSRequest] = [] + self.timeout = timeout def set_vendor(self, name) -> None: self.vendor = name @@ -150,7 +155,7 @@ async def send_request(self, url: str) -> List[Dict]: async with httpx.AsyncClient() as client: responses: List[Dict] = [] # TODO: pass timeout as config - timeout = httpx.Timeout(10.0, read=None) + timeout = httpx.Timeout(self.timeout, read=None) for request in self.request_data: try: if self.strategy.api_protocol == ApiProtocol.soap: From 3c4c2af515a8cc79ed70d0fbfeceee4db6885161 Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Thu, 24 Oct 2024 19:40:48 +0530 Subject: [PATCH 09/10] docstring updated! --- healthchain/clients/ehrclient.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/healthchain/clients/ehrclient.py b/healthchain/clients/ehrclient.py index b971a02..f330c97 100644 --- a/healthchain/clients/ehrclient.py +++ b/healthchain/clients/ehrclient.py @@ -113,7 +113,9 @@ def __init__( func (Callable[..., Any]): A function to generate data for requests. workflow ([Workflow]): The workflow context to apply to the data generator. strategy (BaseStrategy): The strategy object to construct requests based on the generated data. - + timeout(float and default=10.0) : The maximum time in seconds to wait for a response from the server. + This parameter determines how long the client will wait before considering a request timed out. + A higher timeout value allows for longer-running operations, while a lower value prioritizes faster responses. """ # TODO: Add option to pass in different provider options self.data_generator_func: Callable[..., Any] = func From 99f3707e77b02a47f1458653d9d1e0ad6330337e Mon Sep 17 00:00:00 2001 From: Anshuman Laskar Date: Thu, 24 Oct 2024 23:04:05 +0530 Subject: [PATCH 10/10] updated docstring --- healthchain/clients/ehrclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/healthchain/clients/ehrclient.py b/healthchain/clients/ehrclient.py index f330c97..1bd12c4 100644 --- a/healthchain/clients/ehrclient.py +++ b/healthchain/clients/ehrclient.py @@ -113,7 +113,7 @@ def __init__( func (Callable[..., Any]): A function to generate data for requests. workflow ([Workflow]): The workflow context to apply to the data generator. strategy (BaseStrategy): The strategy object to construct requests based on the generated data. - timeout(float and default=10.0) : The maximum time in seconds to wait for a response from the server. + timeout(Optional[float], default=10.0) : The maximum time in seconds to wait for a response from the server. This parameter determines how long the client will wait before considering a request timed out. A higher timeout value allows for longer-running operations, while a lower value prioritizes faster responses. """