Skip to content

Commit 584d582

Browse files
Refactor sandbox and add SandboxClient api (#154)
* Add new SandboxClient api * Add dataset registry discovery * Load preformed prefetch from path and replace DocumentReference with xml validation in clindoc and xml loading * Add load_dotenv to cookbook examples * Move data_generation to sandbox * Refactor sandbox module * Update docs * Update tests * Rename save_responses -> save_results * Update api docs * Move data loader work to new branch
1 parent dc75743 commit 584d582

72 files changed

Lines changed: 2089 additions & 2246 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cookbook/cds_discharge_summarizer_hf_chat.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import healthchain as hc
1+
import os
2+
import getpass
3+
24
from healthchain.gateway import HealthChainAPI, CDSHooksService
35
from healthchain.pipeline import SummarizationPipeline
4-
from healthchain.sandbox.use_cases import ClinicalDecisionSupport
5-
from healthchain.models import Prefetch, CDSRequest, CDSResponse
6-
from healthchain.data_generators import CdsDataGenerator
6+
from healthchain.models import CDSRequest, CDSResponse
77

88
from langchain_huggingface.llms import HuggingFaceEndpoint
99
from langchain_huggingface import ChatHuggingFace
1010
from langchain_core.prompts import PromptTemplate
1111
from langchain_core.output_parsers import StrOutputParser
1212

13-
import getpass
14-
import os
13+
from dotenv import load_dotenv
14+
15+
load_dotenv()
1516

1617

1718
if not os.getenv("HUGGINGFACEHUB_API_TOKEN"):
@@ -65,31 +66,37 @@ def discharge_summarizer(request: CDSRequest) -> CDSResponse:
6566
app.register_service(cds, path="/cds")
6667

6768

68-
@hc.sandbox(api="http://localhost:8000")
69-
class DischargeNoteSummarizer(ClinicalDecisionSupport):
70-
def __init__(self):
71-
super().__init__(path="/cds/cds-services/discharge-summarizer")
72-
self.data_generator = CdsDataGenerator()
73-
74-
@hc.ehr(workflow="encounter-discharge")
75-
def load_data_in_client(self) -> Prefetch:
76-
data = self.data_generator.generate_prefetch(
77-
free_text_path="data/discharge_notes.csv", column_name="text"
78-
)
79-
return data
80-
81-
8269
if __name__ == "__main__":
8370
import uvicorn
8471
import threading
8572

73+
from healthchain.sandbox import SandboxClient
74+
8675
# Start the API server in a separate thread
8776
def start_api():
8877
uvicorn.run(app, port=8000)
8978

9079
api_thread = threading.Thread(target=start_api, daemon=True)
9180
api_thread.start()
9281

93-
# Start the sandbox
94-
summarizer = DischargeNoteSummarizer()
95-
summarizer.start_sandbox()
82+
# Create sandbox client and load test data
83+
client = SandboxClient(
84+
api_url="http://localhost:8000",
85+
endpoint="/cds/cds-services/discharge-summarizer",
86+
)
87+
# Load discharge notes from CSV
88+
client.load_free_text(
89+
workflow="encounter-discharge",
90+
csv_path="data/discharge_notes.csv",
91+
column_name="text",
92+
)
93+
# Send requests and get responses
94+
responses = client.send_requests()
95+
96+
# Save results
97+
client.save_results("./output/")
98+
99+
try:
100+
api_thread.join()
101+
except KeyboardInterrupt:
102+
pass

cookbook/cds_discharge_summarizer_hf_trf.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import healthchain as hc
1+
import os
2+
import getpass
3+
24
from healthchain.gateway import HealthChainAPI, CDSHooksService
35
from healthchain.pipeline import SummarizationPipeline
4-
from healthchain.sandbox.use_cases import ClinicalDecisionSupport
5-
from healthchain.models import Prefetch, CDSRequest, CDSResponse
6-
from healthchain.data_generators import CdsDataGenerator
6+
from healthchain.models import CDSRequest, CDSResponse
77

8-
import getpass
9-
import os
8+
from dotenv import load_dotenv
9+
10+
load_dotenv()
1011

1112

1213
if not os.getenv("HUGGINGFACEHUB_API_TOKEN"):
@@ -38,31 +39,37 @@ def discharge_summarizer(request: CDSRequest) -> CDSResponse:
3839
app.register_service(cds, path="/cds")
3940

4041

41-
@hc.sandbox(api="http://localhost:8000")
42-
class DischargeNoteSummarizer(ClinicalDecisionSupport):
43-
def __init__(self):
44-
super().__init__(path="/cds/cds-services/discharge-summarizer")
45-
self.data_generator = CdsDataGenerator()
46-
47-
@hc.ehr(workflow="encounter-discharge")
48-
def load_data_in_client(self) -> Prefetch:
49-
data = self.data_generator.generate_prefetch(
50-
free_text_path="data/discharge_notes.csv", column_name="text"
51-
)
52-
return data
53-
54-
5542
if __name__ == "__main__":
5643
import uvicorn
5744
import threading
5845

46+
from healthchain.sandbox import SandboxClient
47+
5948
# Start the API server in a separate thread
6049
def start_api():
6150
uvicorn.run(app, port=8000)
6251

6352
api_thread = threading.Thread(target=start_api, daemon=True)
6453
api_thread.start()
6554

66-
# Start the sandbox
67-
summarizer = DischargeNoteSummarizer()
68-
summarizer.start_sandbox()
55+
# Create sandbox client and load test data
56+
client = SandboxClient(
57+
api_url="http://localhost:8000",
58+
endpoint="/cds/cds-services/discharge-summarizer",
59+
)
60+
# Load discharge notes from CSV
61+
client.load_free_text(
62+
workflow="encounter-discharge",
63+
csv_path="data/discharge_notes.csv",
64+
column_name="text",
65+
)
66+
# Send requests and get responses
67+
responses = client.send_requests()
68+
69+
# Save results
70+
client.save_results("./output/")
71+
72+
try:
73+
api_thread.join()
74+
except KeyboardInterrupt:
75+
pass

cookbook/notereader_clinical_coding_fhir.py

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,23 @@
1414
"""
1515

1616
import logging
17-
import uvicorn
18-
import healthchain as hc
1917

20-
from fhir.resources.documentreference import DocumentReference
2118
from spacy.tokens import Span
2219
from dotenv import load_dotenv
2320

24-
from healthchain.fhir import create_document_reference, add_provenance_metadata
21+
from healthchain.fhir import add_provenance_metadata
2522
from healthchain.gateway.api import HealthChainAPI
2623
from healthchain.gateway.fhir import FHIRGateway
2724
from healthchain.gateway.clients.fhir.base import FHIRAuthConfig
2825
from healthchain.gateway.soap import NoteReaderService
2926
from healthchain.io import CdaAdapter, Document
3027
from healthchain.models import CdaRequest
3128
from healthchain.pipeline.medicalcodingpipeline import MedicalCodingPipeline
32-
from healthchain.sandbox.use_cases import ClinicalDocumentation
29+
3330

3431
# Suppress Spyne warnings
3532
logging.getLogger("spyne.model.complex").setLevel(logging.ERROR)
3633

37-
3834
load_dotenv()
3935

4036
# Load configuration from environment variables
@@ -115,37 +111,16 @@ def ai_coding_workflow(request: CdaRequest):
115111
return app
116112

117113

118-
def create_sandbox():
119-
@hc.sandbox(api="http://localhost:8000/")
120-
class NotereaderSandbox(ClinicalDocumentation):
121-
"""Sandbox for testing clinical documentation workflows"""
122-
123-
def __init__(self):
124-
super().__init__()
125-
self.data_path = "./data/notereader_cda.xml"
126-
127-
@hc.ehr(workflow="sign-note-inpatient")
128-
def load_clinical_document(self) -> DocumentReference:
129-
"""Load a sample CDA document for processing"""
130-
with open(self.data_path, "r") as file:
131-
xml_content = file.read()
132-
133-
return create_document_reference(
134-
data=xml_content,
135-
content_type="text/xml",
136-
description="Sample CDA document from sandbox",
137-
)
138-
139-
return NotereaderSandbox()
140-
141-
142114
# Create the app
143115
app = create_app()
144116

145117

146118
if __name__ == "__main__":
147119
import threading
120+
import uvicorn
121+
148122
from time import sleep
123+
from healthchain.sandbox import SandboxClient
149124

150125
# Start server
151126
def run_server():
@@ -155,9 +130,18 @@ def run_server():
155130
server_thread.start()
156131
sleep(2) # Wait for startup
157132

158-
# Test sandbox
159-
sandbox = create_sandbox()
160-
sandbox.start_sandbox()
133+
# Create sandbox client for testing
134+
client = SandboxClient(
135+
api_url="http://localhost:8000", endpoint="/notereader/fhir/", protocol="soap"
136+
)
137+
# Load clinical document from file
138+
client.load_from_path("./data/notereader_cda.xml")
139+
140+
# Send request and save response
141+
responses = client.send_requests()
142+
143+
# Save results
144+
client.save_results("./output/")
161145

162146
try:
163147
server_thread.join()

docs/api/clients.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

docs/api/data_generators.md

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# Use Cases
1+
# Sandbox Client
2+
3+
::: healthchain.sandbox.sandboxclient.SandboxClient
4+
5+
::: healthchain.sandbox.generators.cdsdatagenerator
26

3-
::: healthchain.sandbox.use_cases.cds
47
::: healthchain.models.requests.cdsrequest
58
::: healthchain.models.responses.cdsresponse
69

7-
::: healthchain.sandbox.use_cases.clindoc
810
::: healthchain.models.requests.cdarequest
911
::: healthchain.models.responses.cdaresponse

docs/api/service.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

docs/cookbook/clinical_coding.md

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -217,37 +217,21 @@ app.register_service(note_service, path="/notereader")
217217

218218
## Test with Sample Documents
219219

220-
HealthChain provides a [sandbox utility](../reference/utilities/sandbox.md) which simulates the NoteReader workflow end-to-end. It loads your sample CDA document, sends it to your service via the configured endpoint, and saves the request/response exchange in an `output/` directory. This lets you test the complete integration locally before connecting to Epic.
220+
HealthChain provides a [sandbox client utility](../reference/utilities/sandbox.md) which simulates the NoteReader workflow end-to-end. It loads your sample CDA document, sends it to your service via the configured endpoint, and saves the request/response exchange in an `output/` directory. This lets you test the complete integration locally before connecting to Epic.
221221

222222
```python
223-
import healthchain as hc
224-
225-
from healthchain.sandbox.use_cases import ClinicalDocumentation
226-
from healthchain.fhir import create_document_reference
227-
228-
from fhir.resources.documentreference import DocumentReference
229-
230-
def create_sandbox():
231-
@hc.sandbox(api="http://localhost:8000/")
232-
class NotereaderSandbox(ClinicalDocumentation):
233-
"""Sandbox for testing clinical documentation workflows"""
234-
def __init__(self):
235-
super().__init__()
236-
self.data_path = "./data/notereader_cda.xml"
237-
238-
@hc.ehr(workflow="sign-note-inpatient")
239-
def load_clinical_document(self) -> DocumentReference:
240-
"""Load a sample CDA document for processing"""
241-
with open(self.data_path, "r") as file:
242-
xml_content = file.read()
243-
244-
return create_document_reference(
245-
data=xml_content,
246-
content_type="text/xml",
247-
description="Sample CDA document from sandbox",
248-
)
249-
250-
return NotereaderSandbox()
223+
from healthchain.sandbox import SandboxClient
224+
225+
# Create sandbox client for SOAP/CDA testing
226+
client = SandboxClient(
227+
api_url="http://localhost:8000",
228+
endpoint="/notereader/ProcessDocument",
229+
workflow="sign-note-inpatient",
230+
protocol="soap"
231+
)
232+
233+
# Load sample CDA document
234+
client.load_from_path("./data/notereader_cda.xml")
251235
```
252236

253237
## Run the Complete Example
@@ -256,13 +240,18 @@ Now for the moment of truth! Start your service and run the sandbox to see the c
256240

257241
```python
258242
import uvicorn
243+
import threading
259244

260-
# Start the service on port 8000
261-
uvicorn.run(app)
245+
# Start the API server in a separate thread
246+
def start_api():
247+
uvicorn.run(app, port=8000)
262248

263-
# Create and start the sandbox
264-
sandbox = create_sandbox()
265-
sandbox.start_sandbox()
249+
api_thread = threading.Thread(target=start_api, daemon=True)
250+
api_thread.start()
251+
252+
# Send requests and save responses with sandbox client
253+
client.send_requests()
254+
client.save_results("./output/")
266255
```
267256

268257
!!! abstract "What happens when you run this"

0 commit comments

Comments
 (0)