cds hooks and smart web messaging - fhir devdays...pama - protecting access to medicare act (of...
TRANSCRIPT
HL7®, FHIR® and the flame Design mark are the registered trademarks of Health Level Seven International and are used with permission.
CDS Hooks and SMART Web Messaging
HL7®, FHIR® and the flame Design mark are the registered trademarks of Health Level Seven International and are used with permission.
November 20-22, Amsterdam | @HL7 @FirelyTeam | #fhirdevdays | www.devdays.com
Carl Anderson
Sr. Software Engineer, Microsoft
SMART Web Messaging https://github.com/smart-on-fhir/smart-web-messaging
Let's have a SMART refresher...
SMART Web Messaging:
• Allows pure "client" IO between the EHR and an app • App can inform the EHR that it's OK to "close me" • App can suggest navigation actions in the EHR • Allows pending EHR data to be read and modified
• Simple protocol • Based on HTML5 Web Messaging
The Goal
The goal of SWM is to enable
seamless integrations between the
EHR and a SMART app, saving
clinicians time and mental effort.
"The software should not get in my
way when I'm trying to practice
medicine."
- Josh Mandel
"Carl, you can make up (almost)
any quote you want and attribute it
to me."
- Josh Mandel
Scenario: Image Ordering in the Present Day
Order image #2
Radiology Dept
Clinic / Hospital
Scan patient
☢
Read image
Clinic / Hospital
Radiology Dept
Clinic / Hospital
Order image
Scan patient
☢☢☢
Read image
So, what's wrong with this system?
• Disconnect between ordering physician and radiologist, on several levels. • Indirect communication via EHR • Different sets of goals • Difficulty sharing context • Physical proximity (or lack
thereof)
• Mistakes are costly for patients, providers, and for facilities. ☢ € ⌚
• Possibly misaligned incentives. • Erring on the side of data
Is this Appropriate Use of the technology?
History Lesson
Sources https://youtu.be/QLENOcS65mE?t=167 https://www.ncbi.nlm.nih.gov/books/NBK82290/figure/background.f1/
PAMA - Protecting Access to Medicare Act (of 2014)
"If we in the United States could lower the prices and per-capita volumes of our CT scans, MRIs, and just the top 25 high-volume-high-price surgical procedures to those of the Netherlands, for example, we would see savings of about $425 per capita, or a total of $137 billion."
Ezekiel Emmanuel, March 2018 in JAMA
Source: https://nationaldecisionsupport.com/pama/
2020 2021
Program Timeline
Reprise: Consequences of Poor Diagnostic Orders
Order image #2
Radiology Dept
Clinic / Hospital
Scan patient
☢
Read image
Clinic / Hospital
Radiology Dept
Clinic / Hospital
Order image
Scan patient
☢☢☢
Read image
Prior Authorization Hell
Quick Recap
• Our users are IMPATIENT • Staying up-to-date on best-practices is HARD • Time, money, safety are at RISK • PAMA requires SOMETHING additional TO BE DONE in 2021
What is that SOMETHING?
Required Evidence of Guideline Consultation
Defined by
experts and researchers.
Implemented by
CDS software vendors.
Consulted by ordering
practitioners before orders are placed.
Appropriate
Use
Criteria
Provider Led
Entities
Qualified
CDS
Mechanisms Evidence of guideline
consultation is required for reimbursement.
External QCDSM
Ordering Clinician
1. Leaves EHR order entry 2. Signs in to QCDSM 3. Enters patient demographics 4. Selects indication 5. Selects service 6. Copies service code
7. Returns to EHR 8. Selects same service 9. Adds order note 10. Pastes service code 11. Signs order
Reprise: What's Good Here, Though?
Order image #2
Radiology Dept
Clinic / Hospital
Scan patient
☢
Read image
Clinic / Hospital
Radiology Dept
Clinic / Hospital
Order image
Scan patient
☢☢☢
Read image
Closing a Loop
Closing a Loop
Let's code!
Let's code!
https://github.com/microsoft-healthcare-madison/demo-auc-app
Goals
● Provide a simple example app that can be modified.
● Show milestones: ○ A regular, non-SMART app ○ Same app with SMART launch
○ Companion CDS Hooks ○ SMART Web Messaging app
● Provide companion helper materials (like codelab documents) ● Include exercises to teach concepts & skills.
Demo App - v1
A stand-alone demo AUC guideline consultation app.
Nothing fancy here.
https://github.com/microsoft-healthcare-madison/demo-auc-app/tree/v1.0
<form action="evaluate" method="POST"> <table>
<caption>Enter Patient Demographics</caption>
<tr>
<th><label for="gender">Gender</label></th>
<td>
<select name="gender" required> <option value="male">Male</option>
<option value="female">Female</option>
<option value="other">Other</option>
</select>
</td>
</tr>
<tr>
<th><label for="age">Age</label></th>
<td><input name="age" required></td> </tr>
Demo App v1 - Relevant Code - index.html <tr>
<th><label for="indication">Indication</label></th>
<td>
<input list="indications" name="indication" required> <datalist id="indications"> <option value="13213009">congenital heart disease</option>
<option value="25064002">headache</option>
<option value="279039007">lower back pain</option>
<option value="423341008">optic disc edema</option>
<option value="27355003">toothache</option>
</datalist>
</td>
</tr>
<tr>
<th><label for="procedure">Procedure</label></th>
<td>
<input list="procedures" name="procedure"> <datalist id="procedures"> <option value="75561">cardiac mri</option>
<option value="70450">ct scan - no contrast material</option>
<option value="71275">cta - with contrast material</option>
<option value="72133">lumbar spine ct scan</option>
<option value="70544">mra - head</option>
</datalist>
</td>
</tr>
</table>
<input type="submit">
</form>
app.post('/evaluate', function(request, response) { const reasons = cptReasons[request.body.procedure]; const indications = new Set([request.body.indication]); const rating = reasons.getRating(indications); response.status(200).send(rating).end(); }); const cptReasons = { ... [CPT.CARDIAC_MRI]: new Reasons([[SNOMED.CONGENITAL_HEART_DISEASE]], []), }; class Reasons { ... getRating(reasons) { if (this.appropriate.filter(s => Reasons.covers(s, reasons)).length) { return 'appropriate'; } if (this.notAppropriate.filter(s => Reasons.covers(s, reasons)).length) { return 'not-appropriate'; } return 'no-guidelines-apply'; } }
Demo App v1 - Relevant Code - server logic
Demo App - v2
Goal: The same app as before, but now capable of a SMART launch. https://github.com/microsoft-healthcare-madison/demo-auc-app/tree/v2.0
Objectives:
● Age and Gender fields will come from the 'current' patient selected in the EHR.
● No more login screen! Current EHR provider will be used. ● User now only selects Indication and Procedure in the form. ● Otherwise the same behavior.
Demo App - v2 - SMART App Launcher
http://launch.smarthealthit.org/
Launch Type
Patient
Provider
App Launch URL
Demo App - v2 - SMART Launch Endpoint
https://www.npmjs.com/package/fhirclient
$ npm install fhirclient
New endpoint: launch.html <html>
<head> <script src="./node_modules/fhirclient/build/fhir-client.js"></script><script> FHIR.oauth2.authorize({ client_id: "demo_auc_guideline_consultation_app",
scope: "launch patient/*.read openid profile" });
</script>
</head><body>Loading...</body>
</html>
Demo App - v2 - SMART App Launcher
Demo App - v2 - Apply Context to the Form
<script src="node_modules/fhirclient/build/fhir-client.js"></script> <script> async function getContext(client) { return { patient: await client.patient.read(), user: await client.user.read(), }; } function populateForm(context) { const patient = context.patient; const then = new Date(patient.birthDate), now = new Date(); const msPerYear = 365.25 * 24 * 60 * 60 * 1000; const age = (now.getTime() - then.getTime()) / msPerYear; document.getElementById('provider').value = context.user.id; document.getElementById('age').value = Math.round(age); document.getElementById(patient.gender).setAttribute('selected', 1); } if (typeof FHIR !== 'undefined') { FHIR.oauth2.ready() .then(getContext) .then(populateForm) .catch(console.error); } </script>
Demo App - v3
Goal: CDS Hook service to launch the SMART app when needed https://github.com/microsoft-healthcare-madison/demo-auc-app/tree/v3.0
Objectives:
● When an order or diagnosis is selected, the user sees our card ● Cards are appropriate and informative ● Clicking a card link launches the app ● Launched app has all fields pre-populated in the form
● App can update the EHR ● Closing the app returns us to the EHR
The New App Interface
The Cards We Want
CDS Hook Sandbox http://sandbox.cds-hooks.org/
cds-services.js
● discovery endpoint
○ hook: order-select
○ id: demo-auc-app
● hook handler endpoint
○ SMART launch card
○ Use AUC logic to rate selection
○ Serialize serviceRequest resource
into appContext
CDS Hook Discovery Endpoint
// Discovery endpoint.
app.get('/cds-services', function(request, response) { response.json({
services: [{ hook: 'order-select', title: 'Demo AUC Guidance Consultation - Click to Insert Evidence',
description: 'Click to insert evidence of AUC guidance consultation.',
id: 'demo-auc-app' }] })
});
CDS Hook Endpoint
app.post('/cds-services/demo-auc-app', function(req, res) { const draftOrder = req.body.context.draftOrders.entry[0]; const serviceRequest = draftOrder.resource; ... const cards = [{ indicator: card.indicator, detail: card.detail,
source: { label, url: sourceUrl, icon: card.icon }, links: [{ appContext: JSON.stringify({indications, orders, draftOrder}), label: 'Edit Order in Demo App', url: launchUrl,
type: 'smart' }], }]; res.json({ cards }); });
index.html
● FHIR.oauth.ready() => client
● read appContext from client
● use client to initialize form
● use AUC module to rate selections.
● provide new buttons
● targetWindow.postMessage
○ Send draftOrder back
○ Close window
index.html
<script src="node_modules/fhirclient/build/fhir-client.js"/> <script>
FHIR.oauth2.ready() .then(getContext) .then(populateForm) .then(showRating) .then(showButtons) .catch(console.error); let appContext = null; async function getContext(client) { const appContextStr = client.state.tokenResponse.appContext || '{}'; appContext = JSON.parse(appContextStr); ...
index.html
const messagingHandle = 'demo_auc_guideline_consultation_app';
const notParent = window.parent !== window.self;
const targetOrigin = 'http://sandbox.cds-hooks.org'; const targetWindow = notParent ? window.parent : window.opener;
function closeApp() {
targetWindow.postMessage({ messagingHandle,
messageId: uuidv4(),
messageType: 'ui.done' }, targetOrigin) }
index.html
function updateApp() {
const draftOrder = appContext.draftOrder; const rating = getRating();
applyFormData(draftOrder); addRating(draftOrder, rating);
targetWindow.postMessage({
payload: draftOrder, messagingHandle,
messageId: uuidv4(),
messageType: 'scratchpad.update' }, targetOrigin);
closeApp();
}
Roadmap?
What's next, after PAMA Imaging, for SMART Web Messaging?
● Better reference documentation & guides
● Navigation Activities
● Closed loop experiences
Community
Zulip: #smart
Argonaut: More info coming soon - tune in to Zulip
PSS: https://confluence.hl7.org/display/FHIRI/SMART+Web+Messaging+PSS
Questions
Notable Implementation Caveats
• Not having a body-parser in express
• CORS configuration
• Using https instead of http for the SMART App Launcher (causes Chrome to block
traffic)
• Using https instead of http for the launch URL :-\
• Issues with the CDS hooks sandbox
• no provider switcher
• UI quirks after update (update needs display data)
• no scratchpad.read supported in cds-hooks sandbox
• no order-sign hook support
• The SMART Web Messaging docs are between 90 and 100% correct
Link Library
Last year's DevDays presentation by Janet Campbell: https://docs.google.com/presentation/d/18DjhW1r80zgtheDqx-GwGn-YrfkaNCTi3JavYWMVnPo/present?slide=id.gcb9a0b074_1_0
CDS Hooks Slides from Dennis: https://github.com/cerner/code-learning-lab/blob/master/cds-hooks/Remote%20Decision%20Support%20with%20CDS%20Hooks.pdf
Project github site: https://github.com/smart-on-fhir/smart-web-messaging
SMART Apps Gallery: https://gallery.smarthealthit.org/
ACR Readme on PAMA: https://www.acr.org/-/media/ACR/NOINDEX/AC/PAMA-CDS-Flyer.pdf
HTML5 Web Messaging: https://en.wikipedia.org/wiki/Web_Messaging