Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0a92395
Cleaned up empty folders to avoid confusion
May 17, 2024
ec2b84b
Merge branch 'main' into integration/data-generator-and-api
adamkells May 17, 2024
3b5c8ed
Added value sets for data generator
May 18, 2024
691ffed
generator orchestration
adamkells May 18, 2024
4bd6463
Merge branch 'integration/data-generator-and-api' of github.com:dotim…
adamkells May 18, 2024
8b9fdae
Added default factory to generate random id in hook models
May 18, 2024
1037316
Merge branch 'integration/data-generator-and-api' of github.com:dotim…
adamkells May 18, 2024
594efc4
modifications to orchestrator
adamkells May 18, 2024
2f877e0
Added default factory to generate uuid in CDSRequest
May 18, 2024
5177835
Merge branch 'integration/data-generator-and-api' of https://github.c…
May 18, 2024
5b6bd18
Removed _validate_data method in strategy classes
May 19, 2024
b9c359f
Updated data generator signature and add set_workflow method
May 19, 2024
42a049a
Update tests
May 19, 2024
165db95
Update example use
May 19, 2024
99a9821
adding value sets
adamkells May 19, 2024
573eedf
Merge branch 'integration/data-generator-and-api' of github.com:dotim…
adamkells May 19, 2024
be94e01
Added param to save data in sandbox runs
May 19, 2024
71391fb
Merge branch 'integration/data-generator-and-api' of https://github.c…
May 19, 2024
43fb0a7
Update data generator name
May 20, 2024
be4f45d
Ignore output folder
May 20, 2024
fdeed53
Added startup and shutdown lifespan events to service and colorful lo…
May 20, 2024
8d20dcc
adding top level generator and integration tests
adamkells May 20, 2024
d6fc227
Hide shutdown method in schema and remove text on dashboard path
May 20, 2024
e34a2c6
refactor data generator
adamkells May 20, 2024
8ff4326
Merge branch 'integration/data-generator-and-api' of github.com:dotim…
adamkells May 20, 2024
08a929a
renaming of files in bundle test
adamkells May 20, 2024
b13eea3
add free text json loading
adamkells May 20, 2024
ee32705
add example free text
adamkells May 20, 2024
82be1e2
add document references
adamkells May 23, 2024
5396e96
add cli and document reference
adamkells May 23, 2024
8956e87
skip test in ci
adamkells May 23, 2024
e2de526
minor tidying
adamkells May 23, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

output/
68 changes: 0 additions & 68 deletions example_use.py

This file was deleted.

7 changes: 6 additions & 1 deletion healthchain/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import logging
from .utils.logger import add_handlers

from healthchain.decorators import ehr, api, sandbox
from healthchain.data_generator.data_generator import DataGenerator
from healthchain.models.requests.cdsrequest import CDSRequest

logger = logging.getLogger(__name__)
add_handlers(logger)
logger.setLevel(logging.INFO)

# Export them at the top level
__all__ = ["ehr", "api", "sandbox", "DataGenerator", "CDSRequest"]
4 changes: 0 additions & 4 deletions healthchain/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ class BaseStrategy(ABC):
- the format of the request it constructs (CDS Hook or NoteReader workflows)
"""

@abstractmethod
def _validate_data(self, data) -> bool:
pass

@abstractmethod
def construct_request(self, data, workflow: Workflow) -> Dict:
pass
Expand Down
28 changes: 28 additions & 0 deletions healthchain/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import argparse
import subprocess


def run_file(filename):
try:
result = subprocess.run(["poetry", "run", "python", filename], check=True)
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"An error occurred while trying to run the file: {e}")


def main():
parser = argparse.ArgumentParser(description="HealthChain command-line interface")
subparsers = parser.add_subparsers(dest="command", required=True)

# Subparser for the 'run' command
run_parser = subparsers.add_parser("run", help="Run a specified file")
run_parser.add_argument("filename", type=str, help="The filename to run")

args = parser.parse_args()

if args.command == "run":
run_file(args.filename)


if __name__ == "__main__":
main()
5 changes: 3 additions & 2 deletions healthchain/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from typing import Any, Callable, List, Dict

from .models.requests.cdsrequest import CDSRequest
from .base import BaseStrategy, BaseClient, Workflow

log = logging.getLogger(__name__)
Expand All @@ -24,7 +25,7 @@ def __init__(
self.data_generator_func: Callable[..., Any] = func
self.workflow: Workflow = workflow
self.strategy: BaseStrategy = strategy
self.request_data: List[Dict] = []
self.request_data: List[CDSRequest] = []

def generate_request(self, *args: Any, **kwargs: Any) -> None:
"""
Expand Down Expand Up @@ -58,7 +59,7 @@ async def send_request(self, url: str) -> List[Dict]:
for request in self.request_data:
try:
response = await client.post(
url=url, data=request.model_dump_json(exclude_none=True)
url=url, json=request.model_dump(exclude_none=True)
)
json_responses.append(response.json())
except Exception as e:
Expand Down
19 changes: 19 additions & 0 deletions healthchain/data_generator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .encounter_generators import EncounterGenerator
from .condition_generators import ConditionGenerator
from .patient_generators import PatientGenerator
from .practitioner_generators import PractitionerGenerator
from .procedure_generators import ProcedureGenerator
from .medication_administration_generators import MedicationAdministrationGenerator
from .medication_request_generators import MedicationRequestGenerator
from .data_generator import DataGenerator

__all__ = [
"EncounterGenerator",
"ConditionGenerator",
"PatientGenerator",
"PractitionerGenerator",
"ProcedureGenerator",
"MedicationAdministrationGenerator",
"MedicationRequestGenerator",
"DataGenerator",
]
29 changes: 27 additions & 2 deletions healthchain/data_generator/base_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
urlModel,
uuidModel,
)

from healthchain.fhir_resources.general_purpose_resources import (
CodeableConceptModel,
CodingModel,
)
from faker import Faker

faker = Faker()
Expand All @@ -39,7 +42,7 @@ def register(self, cls):
def get(self, name):
if name not in self.registry:
raise ValueError(f"No generator registered for '{name}'")
return self.registry.get(name)
return self.registry.get(name)()


generator_registry = Registry()
Expand Down Expand Up @@ -177,3 +180,25 @@ class UuidGenerator(BaseGenerator):
@staticmethod
def generate():
return uuidModel(faker.uuid4())


class CodeableConceptGenerator(BaseGenerator):
@staticmethod
def generate_from_valueset(ValueSet):
value_set_instance = ValueSet()
return CodeableConceptModel(
coding=[
CodingModel(
system=value_set_instance.system,
code=faker.random_element(value_set_instance.value_set)["code"],
display=faker.random_element(value_set_instance.value_set)[
"display"
],
# extension=[ExtensionModel(value_set_instance.extension)],
)
]
)

@staticmethod
def generate():
pass
48 changes: 31 additions & 17 deletions healthchain/data_generator/condition_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
BaseGenerator,
generator_registry,
register_generator,
CodeableConceptGenerator,
)
from healthchain.fhir_resources.general_purpose_resources import (
CodeableConceptModel,
Expand All @@ -13,6 +14,10 @@
Condition_StageModel,
Condition_ParticipantModel,
)
from healthchain.data_generator.value_sets.condition import (
ConditionCodeSimple,
ConditionCodeComplex,
)
from typing import Optional
from faker import Faker

Expand Down Expand Up @@ -95,18 +100,18 @@ def generate():


@register_generator
class SnomedCodeGenerator(BaseGenerator):
@staticmethod
def generate():
return CodeableConceptModel(
coding=[
CodingModel(
system="http://snomed.info/sct",
code=faker.random_element(elements=("386661006")),
display=faker.random_element(elements=("Fever")),
)
]
)
class SnomedCodeGenerator(CodeableConceptGenerator):
def __init__(self) -> None:
super().__init__()

# @staticmethod
def generate(self, constraints: Optional[list] = None):
# TODO: Factor out the code generation logic to a central place
constraints = constraints or []
if "complex-condition" not in constraints:
return self.generate_from_valueset(ConditionCodeSimple)
elif "complex-condition" in constraints:
return self.generate_from_valueset(ConditionCodeComplex)


@register_generator
Expand Down Expand Up @@ -135,11 +140,19 @@ def generate():


@register_generator
class ConditionModelGenerator(BaseGenerator):
class ConditionGenerator(BaseGenerator):
@staticmethod
def generate(subject_reference: Optional[str], encounter_reference: Optional[str]):
def generate(
subject_reference: Optional[str] = None,
encounter_reference: Optional[str] = None,
constraints: Optional[list] = None,
):
subject_reference = subject_reference or "Patient/123"
encounter_reference = encounter_reference or "Encounter/123"
# TODO - Check whether this is the correct way to handle params
code = generator_registry.get("SnomedCodeGenerator").generate(
constraints=constraints
)
return ConditionModel(
id=generator_registry.get("IdGenerator").generate(),
clinicalStatus=generator_registry.get("ClinicalStatusGenerator").generate(),
Expand All @@ -148,12 +161,13 @@ def generate(subject_reference: Optional[str], encounter_reference: Optional[str
).generate(),
category=[generator_registry.get("CategoryGenerator").generate()],
severity=generator_registry.get("SeverityGenerator").generate(),
code=generator_registry.get("SnomedCodeGenerator").generate(),
code=code,
bodySite=[generator_registry.get("BodySiteGenerator").generate()],
subject=ReferenceModel(reference=subject_reference),
encounter=ReferenceModel(reference=encounter_reference),
onsetDateTime=generator_registry.get(
onsetDateTime=generator_registry.get("DateGenerator").generate(),
abatementDateTime=generator_registry.get(
"DateGenerator"
).generate(), ## Are there more plausible dates to use?
).generate(), ## TODO: Constraint abatementDateTime to be after onsetDateTime
recordedDate=generator_registry.get("DateGenerator").generate(),
)
Loading