Skip to content

Commit

Permalink
Merge pull request #2 from barabo/v3.0
Browse files Browse the repository at this point in the history
implement a cds hook service to launch the smart app
  • Loading branch information
barabo authored Nov 20, 2019
2 parents b345d67 + 902b947 commit 7f86a92
Show file tree
Hide file tree
Showing 15 changed files with 817 additions and 193 deletions.
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,31 @@
A demonstration SMART app that serves AUC external guidance for PAMA.

Instructions
------------
============

```sh
npm install
npm run launch &
npm run demo
npm run all
```

CDS Hooks Sandbox
_________________

<img src="./images/v3_pama_tab.png" width="512">

To launch the app via the CDS Hooks Sanbox, [click here](https://sandbox.cds-hooks.org) and configure the `demo-auc-app` service if it isn't already listed in the `PAMA Imaging` tab.

<img src="./images/v3_pama_tab_new_service.png" width="256">
<img src="./images/v3_discovery_endpoint.png" width="256">
<img src="./images/v3_select_service.png" width="256">
<img src="./images/v3_pama_imaging_tab.png" width="256">
<img src="./images/v3_pama_tab_messages.png" width="256">
<img src="./images/v3_select_hook.png" width="256">


SMART App Launcher
__________________

To launch the app via SMART launch, [click here](http://launch.smarthealthit.org/?auth_error=&fhir_version_1=r4&fhir_version_2=r4&iss=&launch_ehr=1&launch_url=http%3A%2F%2Flocalhost%3A8899%2Flaunch.html&patient=&prov_skip_auth=1&provider=&pt_skip_auth=1&public_key=&sb=&sde=&sim_ehr=1&token_lifetime=15&user_pt=) and then click the green Launch App button.

<img src="./images/v2launch.png" alt="alt text" width="256">
Expand All @@ -24,6 +41,17 @@ determine whether it is within recommended guidelines.
<img src="./images/v2procedure.png" alt="alt text" width="256">
<img src="./images/v2rating.png" alt="alt text" width="256">

V3 TODO
-------

- [x] Make a CDS Hook Service to *always* launch a SMART card for draft orders
- [x] Refactor the AUC logic into a module, enabling use in the hook too.
- [x] Write a new CDS Hook server to be run with npm
- [x] Complete the App
- [x] Enable the EHR to close the form with a `ui.done` message
- [x] Enable the form to read the draft order from the fhir client on load
- [x] Enable writes to the form to update the draft order in the EHR

V2 TODO
-------

Expand Down
69 changes: 69 additions & 0 deletions auc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const CPT = {
_FHIR_CODING_SYSTEM: 'http://www.ama-assn.org/go/cpt',
CARDIAC_MRI: '75561',
CT_HEAD_NO_CONTRAST: '70450',
CTA_WITH_CONTRAST: '71275',
LUMBAR_SPINE_CT: '72133',
MRA_HEAD: '70544',
};

const SNOMED = {
_FHIR_CODING_SYSTEM: 'http://snomed.info/sct',
CONGENITAL_HEART_DISEASE: '13213009',
HEADACHE: '25064002',
LOW_BACK_PAIN: '279039007',
OPTIC_DISC_EDEMA: '423341008',
TOOTHACHE: '27355003'
};

class Criterion {
static covers(subset, set) {
if (subset.size > set.size) {
return false;
}
for (const member of subset) {
if (!set.has(member)) {
return false;
}
}
return true;
}

constructor(appropriate, notAppropriate) {
this.appropriate = appropriate.map(x => new Set(x));
this.notAppropriate = notAppropriate.map(x => new Set(x));
}

getRating(reasons) {
if (this.appropriate.filter(s => Criterion.covers(s, reasons)).length) {
return 'appropriate';
}
if (this.notAppropriate.filter(s => Criterion.covers(s, reasons)).length) {
return 'not-appropriate';
}
return 'no-guidelines-apply';
}
};

function evaluate(order, reasons) {
if (order) {
const criterion = criteria[order];
if (criterion && reasons.length) {
return criterion.getRating(new Set(reasons));
}
}
return 'no-guidelines-apply';
}

const criteria = {
'no-procedures-for': new Criterion([[SNOMED.TOOTHACHE]], []),
[CPT.CT_HEAD_NO_CONTRAST]: new Criterion([[SNOMED.HEADACHE, SNOMED.OPTIC_DISC_EDEMA]], []),
[CPT.MRA_HEAD]: new Criterion([], []),
[CPT.CTA_WITH_CONTRAST]: new Criterion([], [[SNOMED.CONGENITAL_HEART_DISEASE]]),
[CPT.LUMBAR_SPINE_CT]: new Criterion([], [[SNOMED.LOW_BACK_PAIN]]),
[CPT.CARDIAC_MRI]: new Criterion([[SNOMED.CONGENITAL_HEART_DISEASE]], []),
};

module.exports = {
CPT, SNOMED, criteria, evaluate, getRating: Criterion.prototype.getRating,
};
85 changes: 85 additions & 0 deletions cds-services.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// BONUS: run this hosted in glitch.com
const auc = require('./auc.js');
const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');
const port = process.env.PORT || '3003';
const launchUrl = 'http://localhost:8899/launch.html';

app = express();
app.use(cors());
app.use(bodyParser.urlencoded({extended : true}));
app.use(bodyParser.json());
app.listen(port);

const cardDetails = {
'appropriate': {
detail: 'This order meets AUC guidelines.',
icon: 'https://lh3.googleusercontent.com/bYNztCIytfV24Hzu9O11XcMhcHdcjRbX0TX2JfxX_LN-tKOOvqQi1lAhmBv7hY8UbMBmshW9vq3jFMO5kbCbq4fTsSWmlccsknxRvtGNunP_IktjIAn38prInj-6f6qWcTk3A9C3kkkJIilgeWOeOy3j8rifvViasCyhm5AGxeBs0TwyLIS-rV7LJDEWI9PjhTPcalYmvZBpl5Yti073BFHlBdMQH98D4CLQle9ZOR8zvXs43NGQdDFRk6QYplv7WFy6veT8b_sGeGxssvPVQoL9Kzcdagd1ftrvsY9vEzm9U79x4u9sBH_kw-BjyJilBh3ZLcTISbuSN-zT3Xn77uOh-aIemSKnr7jl33Ex3i0JyqxMcmj1Wc7cH4sCEHJ7bku3yZYndzVZgjvyPOV44EWo5OyDNVdQ22Hv4LbbSGQku0NCiSbm8g17rb3agKRnjmgioe6kWpa4lH16Q80FuRmAyYZ0GbBJl407qRrmMN-oO9R_LnzIABoV8BPyfU4BvsTDO_GZMBlaoy2K1Qv-0RL9faAjpN6iNQdofpJ28qn07MjJM-vHqPyD3U-eA8RgpSsofQscgE57GrSDDLRhgev3iyhhu2XU4pHbvmd4UUf82a2W0_OYkOlkeiTXM_YdrrW7E2Zpp2pWoIhZJIgXAVuaIz4v6k6EREPeBUuYiAoJHNs-UNXg3cvz=s100-no',
indicator: 'success',
},
'no-guidelines-apply': {
detail: 'This order is not specified by AUC guidelines.',
icon: 'https://lh3.googleusercontent.com/fLQElMed2CFqAHxwRlIe5gJoHp9PFyZUPEcjgdB2Jj2dL7tHKfg4potyo39gsQeP_6wQ7zkWXaMGR7KnfkDrW1ks0jYaHmMdFi-AUer53CQCkKBwkgIEtgRSlozLDqiqpTBMIGRfsj_6oYtruy8O1cFEI54xSjXGtC3l3HThdk93lOjwbxdmqshbkYvm1XYTNcJuBQBdYgIEJusjX_RDBTvJiAxQ0lBeYSHjGBE8JibvQjLboSqNwefLVbIZ24LUenGoub9Y8mhVlxUeQMQPYclnn5W5Nj509L0Oc65ZAvLhoqAJs_sI6gr7ViJqkgdGfRe2pAXbikX2vBvLRHMktcGD5J_YVYTst5vrvfnhfhE0P-kPSp4XLt4WrfP5OQe7lysg-EFyiK6F7REP7NmiPy8x8hf9O9GuUoPFqXwU4psdoXbuNpCsuPA6tfnU6QwC-vUvzOWJgF1bMpu8NimhqPrOgBAUAHh0eVh9p3_ix8OwKBZ70EX8a7E9mq67k0-rTtMoElwBkFNzArTsMd1xp6QboF0F-oV_Li7xje_7Fokn-nDbB72ULX9K-MVbSkZvhD7Y_hJwQPG6wWF4kHaX_zzZsdD5botGT2VMrF-wGklzRrh2bBS-jT12IAeBECGgO_XKOu3Ire3WQ0SUdYGpE5Ebs4KrWAOykznsL9OqfkVuD1SIwjKgR-A2=s100-no',
indicator: 'warning'
},
'not-appropriate': {
detail: 'This order does not meet AUC guidelines.',
icon: 'https://lh3.googleusercontent.com/6f3-twVm-TIZr0IWsb4Yuiys58PkeV3RU89XSDjRtV2beBiKxsoSg4-7qNgzLkZZf-Vy-1OoG5pswPQSK8XRuW3lE56wQgkItirK-iBozZMoD5JvcOR6ZxusPjrvMOE-iwaOKYZxnk0qyoDu7W4WQfaTDaGXAL26OuV_VBIxL0SSXzONWCrk70ckTJPzGy7detQTWknOZJNLuLEv0N45q_EoOu6SLgUlf3woVe-zm0JMNV68JsXH5FIlRwidaplY19MnY40If0fGtQkvnrNS4lwZ9pnx_PK_wiTJhhnpfuNRvrhy_Bdphsy_CN0fMj2FfrwhOJl48jlg8IvIkMzDMlEZGthzryIhN2B-94Eq2oNZS9JaAixqLg2P06_HcaVWMi5x6slPMCal_sU78yuta5lNZdxS7XYw9xJWLjW8Mu_LPKoA6m0onGcYLnwYkdXLFvqdF4dWKYOx1uFvPAMOsucST4orXqVTFWFiEm39et-ZUVOArLm9yXh8i4zl5PKTs4sA7c4u8CLwLyW04Unkg59KnL0ZNKX0xRNm052pW_WhX2g48oKjPIqnQ6Zl1omgNgzIyHo2mvpIKstENpGJwsIqNO456kWEbgUmydTZn40d-VdG47d5Xg5R0jl-WGrK_FbG8u0DupeeCE9kRo2GTrqUuVZWOPZnHJqLnJRWsvEdybsWqC5_m8Fz=s100-no',
indicator: 'critical',
},
};

function getOrders(serviceRequest) {
return serviceRequest.code.coding
.map(order => order.code)
.filter(x => x);
}

function getReasons(serviceRequest) {
return serviceRequest.reasonCode
.map(reason => reason.coding).flat()
.map(coding => coding.code).flat();
}

app.post('/cds-services/demo-auc-app', function(request, response) {
const draftOrder = request.body.context.draftOrders.entry[0];
const serviceRequest = draftOrder.resource;
const orders = getOrders(serviceRequest);
const indications = getReasons(serviceRequest);

if ([...orders, ...indications].length === 0) {
response.json({cards: [], actions: []});
return;
}

const rating = auc.evaluate(orders.slice().pop(), reasons);
const card = cardDetails[rating];
const label = 'Click to view the source presentation.';
const sourceUrl = 'https://docs.google.com/presentation/d/1QT6HWPW1Kix656s8hZzCMY7fgkTEEck0_aApBASc7rk/edit?usp=sharing';
const cards = [{
summary: 'Demo AUC Guideline Consultation App',
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'
}],
}];
response.json({ cards });
});

// 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'
}]
})
});
Binary file added images/v3_discovery_endpoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/v3_pama_imaging_tab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/v3_pama_tab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/v3_pama_tab_messages.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/v3_pama_tab_new_service.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/v3_select_hook.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/v3_select_service.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 7f86a92

Please sign in to comment.