We learned about the standards (FHIR, SMART on FHIR, CDS Hooks) and thought about how they can foster innovation and improvement in the public health sphere. Now let's combine all of that fresh knowledge to create and test our own Public Health CDS Service.
-
We'll be using the official CDS Hooks sandbox that can be accessed here. Open up the page and play around with it a bit!
-
Since each of your computers will be used to temporarily host a CDS Service, we'll need to make sure all of you have a couple things installed.
a. Node.js, npm, and ssh: We'll be booting up a simple server that will listen to incoming CDS requests and respond with the appropriate cards. This server runs on
Node.js
;npm
is the Node.js package manager that allows for a quick setup, and anssh
client will be used to listen to incoming requests from the CDS Sandbox. Follow the instructions here to install them!b. Some capable text editor: If you don't have one, you should quickly download one. Here are some options:
- Atom
- Sublime Text
- Notepad++ (Windows only)
- VS Code (more advanced)
c. Git: (optional) We'll use it to download the server code. If you don't want to install it, you can download the code in a .zip file.
-
To make this tutorial more fun, we're hooking up recomendations from an existant BMI Service - an BMI Calculator API that can be found here: https://market.mashape.com/navii/bmi-calculator
We funded the $1 fee to enable this api for a month for this class. If you're doing this tutorial and this API is not working, you might have to sign up yourself and change the X_MASHAPE_KEY
constant in the cds-hooks.js
file to your own key.
You’re working on a nation-wide project whose goal is to study and combat obesity in the United States. Your job involves designing a CDS Hook that supports the study objectives. The hook should allow you to:
- Report on obesity-related health issues at the point of care
- Provide feedback to the patient on how their health stacks up with their peers
- Allow patients to be easily recruited as participants into a large-scale longitudinal obesity study
In this specific scenario, patient Lisa P. Coleman is visiting her physician. The physician diagnoses her with Hypertensive disorder, and is prescribing Lopressor to treat her high blood pressure.
- Go to https://sandbox.cds-hooks.org
- Click on
Change Patient
on the top menu - Select
Lisa P. Coleman
(SMART-1551992
) - Click the
Save
button - Click on
Rx View
on the top menu - Select
Hypertensive disorder
fromTreating:
dropdown - Type in
Lopressor
intoMedication
box, and selectLopressor
=>Metoprolol Tartrate 50 MG [Lopressor]
=>Metoprolol Tartrate 50 MG Oral Tablet [Lopressor]
. You should see a CMS Price Check card returned by one of the demo CDS services. - This service suggests generic drugs to replace Brand-name options by returning a suggestion card. Check out the Response section to see how this card is formatted. Test out what clicking the
change to generic
button does. - Since we'll be making and testing our own service, we'll want to turn this one off for now to simplify everything. Go to
CDS Services
>Configure CDS Services
in the top menu, find the entry forcms-price-check
, and click the yellowEnabled?
button to disable the service. - Make sure the card doesn't show up anymore!
We want our hook to trigger on the same type of action as the CMS Price Check: medication-prescribe
. In the CDS Hooks Sandbox, any time we make a change to the form in Rx View
, the medication-prescribe
hook is triggered, and a request is sent to all CDS Services that have this hook in their configuration. Our configuration, which can be found in cds-hooks.js in the services
variable, has this hook specified and gets this exact treatment. The Sandbox then listens to responses from these services in the form of Cards.
We want Lisa and her physician to receive two Cards after Lisa is prescribed a medication for her hypertension:
- An informational card giving Lisa some feedback on her BMI
- An app card that allows Lisa to access a companion SMART on FHIR app to your obesity study. We're obviously not going to build out this app, but we can imagine that it could provide informational services and advice about achieving and keeping a healthy weight, and perhaps be able to consent willing participants to take part in obesity-related studies. For example, information automatically received through FHIR could be used in such a study to pair participants with similar demographics and health metrics who are undergoing different treatments and compare their progression.
So, how do we get all of this up and running?
To save you lots of setup, we created a skeleton CDS Service application for you that has all of individual pieces up and running. You can find it at https://github.com/uwbhi/phi533-cdshook. You'll download the code on your computer and make sure it's running. Then, we will walk through modifying this CDS service and connecting the different pieces to return the two Cards mentioned above.
Lot's to do, so let's go!
Clone this repo:
git clone https://github.com/uwbhi/phi533-cdshook.git && cd phi533-cdshook
Or download directly from this link: https://github.com/uwbhi/phi533-cdshook/archive/master.zip
Install the dependencies:
npm install
Run the application:
npm start
Now go to http://localhost:3003
. You should get the following message:
PHI 533 CDS Hook Running!
Nice! Your Service is up and running!
Now that we have a CDS Service that's available on the web, we need to tell the Sandbox about it.
-
Your CDS Service endpoint provides a list of CDS Services when accessed with the following path:
http://localhost:3003/cds-services
. Go to this url in your browser to see what it returns. -
Click on the
CDS Services
button in the top menu, and chooseAdd CDS Service
from the dropdown -
Fill out the
Discover Endpoint URL:
textbox with your Service endpoint:http://localhost:3003/cds-services
. -
Click the
Save
button. After a small pause, you should get the following message:Success: Configured CDS Service(s) found at the discovery endpoint.
-
Go through Exercise 1 again. Make sure that instead of the
Price Check
card, you're receiving a card titledBMI Information and Recommendations
.
And Voila! The CDS Hooks Sandbox is communicating with your CDS Service and getting a nice Card!
If you open the phi533-cdshook
that you either created with git checkout
or extracted from the zip file, you'll see a couple of files. By far the most important for this tutorial is the cds-hook.js
file. This file contains all of the code for our application, and it's the file we'll be modifying later in the tutorial. Make sure you can open and edit it with your chosen text editor!
So, what does our simple, ~130-line CDS Service actually do?
The application itself is an Express JS Web Application. We don't really need to go into all the setup details - all you need to know is that when we run npm start
, we make this app run on our local computer and listen to specific incoming web requests (for those interested, it's running on port 3003
).
Our app actually only responds to three specific url patterns. Notice how the cds-hooks.js
file has app.get('/'...
, app.get('/cds-services'...
, and app.post('/cds-services/phi533-prescribe'...
sections. Our app will respond to two GET
requests and one POST
request on these three paths.
Also, notice all of the console.log()
sections in the cds-hook.js
file. We put these in to output some intermediate results to the terminal where npm start
was run. This way, you can follow the app live by looking at the terminal!
All of the CDS Hooks logic lives in the code section preceded by this aptly-named comment:
/**
* Actual CDS Hook logic here
*/
This is a quick overview of what's happening:
-
The app receives a request from the Sandbox that has some data. You can see what data the sandbox sends by unfurling the
Request
tab on theCDS Service Exchange
section of the Sandbox. -
The app queries a hardcoded FHIR Endpoint whose url is stored in the
FHIR_SERVER_PREFIX
variable three times to get gender, age, height, and weight data on a patient with the hardcoded idSMART-1551992
. It then saves the relevant info in some variables, outputs the variables to the console, and for now, promptly disregards them. -
Next, the app sets up some information that the BMI Calculator API requires to send back BMI recommendations. Currently, this information is also hard-coded and stored in the
bmiData
variable. The info is sent to the BMI Calculator, and the results are passed to thepublicHealthResponse
function. -
The
publicHealthResponse
function reads the BMI recommendations, formats them into a human-readable message, and returns an Information Card, created to CDS Hook specifications, that contains the message. -
Finally, this recommendation Card is returned by the app to the CDS Hooks Sandbox, where it is read and displayed to the user.
Right now, the recommendations sent back to the Sandbox never change, because we're using static data to send to the BMI Calculator API. See this line.
You can check this by switching Patients in the Sandbox and noticing the the BMI recommendations never change.
Let's fix this, and send information about the patient that we're actually treating!
To save you the time of searching through API documentations and returned data objects, we pulled out all the information you'll need to connect the dots in the sample code. The useful variables are defined in these lines:
/cds-hook.js
//...
const hook = req.body.hook; // Type of hook
const fhirServer = req.body.fhirServer; // URL for FHIR Server endpoint
const patient = req.body.patient; // Patient Identifier
const reason = req.body.context.medications[0].reasonCodeableConcept.text // Chosen Problem to Treat
//...
//...
const gender = patientReq.gender;
const birthDate = patientReq.birthDate;
const weight = weightReq.entry[0].resource.valueQuantity.value;
const height = heightReq.entry[0].resource.valueQuantity.value;
const age = moment().diff(birthDate, 'years');
//...
We're going to use these variables to make the calls for Patient data and BMI recommendations depend on the information sent from the Sandbox.
Our patient id is stored in the variable patient
, and the FHIR server endpoing where this patient's information exists is stored in fhirServer
. We want the requests for patient age, gender, weight, and height to use these variables.
-
Change the following lines in /cds-hook.js:
const patientReq = await getAsync(buildPatientURL('SMART-1551992')); const weightReq = await getAsync(buildObsURL('SMART-1551992', 'weight')); const heightReq = await getAsync(buildObsURL('SMART-1551992', 'height'));
to:
const patientReq = await getAsync(buildPatientURL(patient, fhirServer)); const weightReq = await getAsync(buildObsURL(patient, 'weight', fhirServer)); const heightReq = await getAsync(buildObsURL(patient, 'height', fhirServer));
-
Change the helper functions to accept a
fhir_server
parameter. These lines in /cds-hook.js:const buildObsURL = (patientId, text) => `${FHIR_SERVER_PREFIX}/Observation?patient=${patientId}&code:text=${text}&_sort:desc=date&_count=1`; const buildPatientURL = (patientId) => `${FHIR_SERVER_PREFIX}/Patient/${patientId}`;
should be replaced with these lines:
const buildObsURL = (patientId, text, fhir_server) => `${fhir_server}/Observation?patient=${patientId}&code:text=${text}&_sort:desc=date&_count=1`; const buildPatientURL = (patientId, fhir_server) => `${fhir_server}/Patient/${patientId}`;
-
We want to send the BMI Tool the correct units. Currently, we're ignoring the unit information that's sent back from the FHIR Endpoint. To pass this info on to the BMI Calculator, we need to change the
weight
andheight
variable definition up a bit. Change the following lines in /cds-hook.js:const weight = weightReq.entry[0].resource.valueQuantity.value; const height = heightReq.entry[0].resource.valueQuantity.value; //... console.log('Weight: ', weight); console.log('Height: ', height);
To:
const weight = weightReq.entry[0].resource.valueQuantity; const height = heightReq.entry[0].resource.valueQuantity; //... console.log('Weight: ', weight.value); console.log('Height: ', height.value);
We're creating the information that we send to the BMI Calculator API with the following code:
const bmiData = await bmiPostAsync({
age: 24,
sex: 'f',
weight: {
value: "85.00",
unit: "kg"
},
height: {
value: "170.00",
unit: "cm"
}
});
As you can see, this code has many hard-coded values. We need to replace them with the variables we just created.
Replace these lines with the following code in the /cds-hook.js file:
const bmiData = await bmiPostAsync({
age: age,
sex: (gender == 'female' ? 'f' : 'm'),
weight: {
value: Math.round(weight.value),
unit: weight.unit
},
height: {
value: Math.round(height.value),
unit: height.unit
}
});
-
Trigger a
medication-prescibe
event by selecting a different medication (or re-selecting the same one) on https://sandbox.cds-hooks.org/. -
Open Daniel X. Adams's chart (
SMART-1288992
). Go to theRx View
, and again run through our scenario from Exercise 1. Note the information returned by the card. -
Switch patients to Lisa P. Coleman (
SMART-1551992
) and make sure the card you receive has patient-specific information that's different from Daniel's.
Right now, our CDS Service returns cards for all incoming requests. However, we're making a CDS Hook that should only apply to individuals with certain metabolic-related diagnoses and related treatments. We're going to add a bit of logic that will allow our Service to only return Cards to those patients with Hypertensive disorder or Essential hypertension.
The specs (https://cds-hooks.org/specification/1.0/) mention that when no decision support is available, the CDS Service should return and empty array of cards. So let's make that happen!
-
In the right side of the Sandbox screen labeled CDS Service Exchange, select your
phi533-prescribe
service from theSelect a Service
dropdown. Then, click on theRequest
section to see what data is being sent to our application by the Sandbox. -
Look for the
reasonCodeableConcept
key and note thetext
field. It should be the same as the option you chose in theTreating
dropdown. We're going to only send back a card for requests where thistext
field is equal toHypertensive disorder
orEssential hypertension
-
Find this line in the /cds-hook.js file:
const bmiInfo = publicHealthResponse(bmiData);
-
Replace it with the following
if/else
statement:var cardArray = {}; if (reason == "Hypertensive disorder" || reason == "Essential hypertension") { cardArray = publicHealthResponse(bmiData); } else { cardArray = { "cards": [] } }
-
Update these lines in /cds-hook.js:
console.log("Responding with: \n" + JSON.stringify(bmiInfo, null, ' ')); res.send(bmiInfo);
with:
console.log("Responding with: \n" + JSON.stringify(cardArray, null, ' ')); res.send(cardArray);
This allows us to send back the updated variable, which either contains an empty Card array or our relevant cards.
-
In the Sandbox, test out the new functionality; You should be only see cards when
Hypertensive disorder
is chosen in theTreating:
dropdown!
Pretend you have a SMART on FHIR app that can be launched at the following url:
https://smart.nationalobesitystudy.com/launch
.
You don't have to worry about the details of this app; just know that as part of the SMART specs, SMART apps have a launch URL that an EHR can hit, and which is able to launch the app with the context of the current EHR patient.
-
Visit http://cds-hooks.org/specification/1.0/#card-attributes
-
Search the docs for information about the
links
attribute attribute. This attribute can be used to return cards that can launch specific SMART on FHIR apps straight from the EHR. Search forExample response
on the page to see an example - very self-explanatory. -
Add the following card - modeled on the example we just saw - to your
cards
array.{ "summary": "Obesity Companion", "indicator": "info", "detail": "You're Eligible for the Obesity Companion App!", "source": { "label": "National Obesity Study" }, "links": [ { "label": "SMART Obesity Companion App", "url": "https://smart.nationalobesitystudy.com/launch", "type": "smart" } ] }
This array can be found in the the
publicHealthResponse
function in the cds-hook.js file. It should look like this:var cards = { "cards": [ { "summary": "👨🏻⚕️ BMI Information and Recommendations", "detail": bmiMessage, "source": { "label": "WA DOH" }, "indicator": "info", "suggestions": [] }, // Put 2nd Card Here! ] }
-
Test out your card on the CDS Hooks Sandbox by again running through the Scenario