-
-
-
- Summary
-
-
- {summarySection}
-
-
-
- Details
-
- {detailSection}
-
-
- {sourceSection}
-
-
- {linksSection}
- {suggestionsSection}
-
+
+
+
+
+
+ {summarySection}
+
+
+ {/* Forms */}
+ {linksSection.length !== 0 ? (
+
+ Required Forms
+ {detailSection}
+ {linksSection}
+
+ ) : (
+ <>>
+ )}
+
+ {/* Suggestions */}
+ {suggestionsSection.length !== 0 ? (
+
+
+ Suggestions
+
+ {suggestionsSection}
+
+ ) : (
+ <>>
+ )}
+
+ {/* Documentation and Guides */}
+ {documentationSection.length !== 0 ? (
+
+ }>
+
+ View documentation and guides
+
+
+
+ {documentationSection}
+
+
+ ) : (
+ <>>
+ )}
+
+
+ {sourceSection}
+
+
+
+
);
diff --git a/src/components/EtasuStatus/EtasuStatus.jsx b/src/components/EtasuStatus/EtasuStatus.jsx
index 97417ab..1dc0dc7 100644
--- a/src/components/EtasuStatus/EtasuStatus.jsx
+++ b/src/components/EtasuStatus/EtasuStatus.jsx
@@ -2,7 +2,7 @@ import { useState, useEffect, useContext } from 'react';
import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider.jsx';
import { EtasuStatusComponent } from './EtasuStatusComponent.jsx';
import { standardsBasedGetEtasu } from '../../util/util.js';
-import { createMedicationFromMedicationRequest } from '../../util/fhir.js';
+import { createMedicationFromMedicationRequest, getDrugCodeableConceptFromMedicationRequest } from '../../util/fhir.js';
// converts code into etasu for the component to render
// simplifies usage for applications that only know the code, not the case they want to display
@@ -14,26 +14,24 @@ export const EtasuStatus = props => {
const [etasuData, setEtasuData] = useState({});
const [display, setDisplay] = useState('');
- useEffect(() => {
+ useEffect(() => {
const medication = createMedicationFromMedicationRequest(request);
getEtasuStatus(medication);
}, [code]);
- const getEtasuStatus = (medication) => {
+ const getEtasuStatus = medication => {
const body = makeBody(medication);
setEtasuData(body);
- const display = body.parameter[1]?.resource.code.coding[0].display;
+ const display = body.parameter[1]?.resource.code?.coding[0].display;
setDisplay(display);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
standardsBasedGetEtasu(standardEtasuUrl, body, setRemsAdminResponse);
-
};
-
- const makeBody = (medication) => {
+ const makeBody = medication => {
console.log('patient -- > ', globalState.patient);
return {
- resourceType: "Parameters",
+ resourceType: 'Parameters',
parameter: [
{
name: 'patient',
@@ -44,13 +42,20 @@ export const EtasuStatus = props => {
resource: medication
}
]
- }
- }
+ };
+ };
return (
<>
- {remsAdminResponse.contained ? : ""}
+ {remsAdminResponse.contained ? (
+
+ ) : (
+ ''
+ )}
>
);
};
-
diff --git a/src/components/EtasuStatus/EtasuStatusButton.jsx b/src/components/EtasuStatus/EtasuStatusButton.jsx
index ed87d7c..cff24ef 100644
--- a/src/components/EtasuStatus/EtasuStatusButton.jsx
+++ b/src/components/EtasuStatus/EtasuStatusButton.jsx
@@ -3,8 +3,7 @@ import ListIcon from '@mui/icons-material/List';
import './EtasuStatusButton.css';
export const EtasuStatusButton = props => {
- const { baseColor, remsAdminResponse, handleOpenEtasuStatus, lastCheckedEtasuTime } =
- props;
+ const { baseColor, remsAdminResponse, handleOpenEtasuStatus, lastCheckedEtasuTime } = props;
return (
);
+ case 'dropdown':
+ return (
+
+
+
+ Hook to send when selecting a patient
+
+
+
+
+
+
+ )
default:
return (
diff --git a/src/components/SMARTBox/PatientBox.jsx b/src/components/SMARTBox/PatientBox.jsx
index d200735..885252c 100644
--- a/src/components/SMARTBox/PatientBox.jsx
+++ b/src/components/SMARTBox/PatientBox.jsx
@@ -278,6 +278,7 @@ const PatientBox = props => {
response: ''
}));
callback('response', '');
+ clearCallback();
// update prefetch request for the medication
const request = JSON.parse(data);
if (
diff --git a/src/containers/ContextProvider/reducer.js b/src/containers/ContextProvider/reducer.js
index d73f500..1324d92 100644
--- a/src/containers/ContextProvider/reducer.js
+++ b/src/containers/ContextProvider/reducer.js
@@ -1,4 +1,4 @@
-import { headerDefinitions, medicationRequestToRemsAdmins } from '../../util/data';
+import { headerDefinitions, medicationRequestToRemsAdmins, ORDER_SIGN } from '../../util/data';
import { v4 as uuidv4 } from 'uuid';
export const actionTypes = Object.freeze({
@@ -43,7 +43,7 @@ const getNewStateWithNewCdsHookSetting = (state, settingId) => {
newState.medicationRequestToRemsAdmins[uuidv4()] = {
rxnorm: 'Fill out Medication RxNorm Code',
display: 'Fill out Medication Display Name',
- hook: 'order-sign',
+ hook: ORDER_SIGN,
remsAdmin: 'REMS Admin URL for CDS Hook'
};
diff --git a/src/containers/RequestBuilder.jsx b/src/containers/RequestBuilder.jsx
index 60bdb5a..fcf101b 100644
--- a/src/containers/RequestBuilder.jsx
+++ b/src/containers/RequestBuilder.jsx
@@ -6,9 +6,9 @@ import DisplayBox from '../components/DisplayBox/DisplayBox.jsx';
import '../index.css';
import RequestBox from '../components/RequestBox/RequestBox.jsx';
import buildRequest from '../util/buildRequest.js';
-import { types } from '../util/data.js';
+import { types, PATIENT_VIEW } from '../util/data.js';
import { createJwt } from '../util/auth.js';
-import { getMedicationSpecificRemsAdminUrl } from '../util/util.js';
+import { getMedicationSpecificRemsAdminUrl, prepPrefetch } from '../util/util.js';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
@@ -40,7 +40,9 @@ const RequestBuilder = props => {
token: null,
client: client,
medicationDispense: null,
- lastCheckedMedicationTime: null
+ lastCheckedMedicationTime: null,
+ prefetchCompleted: false,
+ medicationRequests: {}
});
const displayRequestBox = !!globalState.patient?.id;
@@ -101,10 +103,50 @@ const RequestBuilder = props => {
}
};
+ const getMedicationRequests = patientId => {
+ client
+ .request(`MedicationRequest?subject=Patient/${patientId}`, {
+ resolveReferences: ['subject', 'performer', 'medicationReference'],
+ graph: false,
+ flat: true
+ })
+ .then(result => {
+ setState(prevState => ({ ...prevState, medicationRequests: result }));
+ });
+ };
+
+ useEffect(() => {
+ const hook = globalState.hookToSend;
+
+ let remsAdminUrls = [];
+ // get all the remsAdminUrl for each MedicationRequest
+ state.medicationRequests?.data?.forEach(request => {
+ const remsAdminUrl = getMedicationSpecificRemsAdminUrl(request, globalState, hook);
+ if (remsAdminUrl) {
+ remsAdminUrls.push(remsAdminUrl);
+ }
+ //sendHook(prefetch, request, patient, hook, remsAdminUrl);
+ });
+ const uniqueUrls = [...new Set(remsAdminUrls.map(item => item))];
+
+ uniqueUrls?.forEach(url => {
+ sendHook(prepPrefetch(state.prefetchedResources), null, globalState.patient, hook, url);
+ });
+ }, [state.medicationRequests]);
+
const submitInfo = (prefetch, request, patient, hook) => {
console.log('Initiating form submission ', types.info);
- const remsAdminUrl = getMedicationSpecificRemsAdminUrl(request, globalState, hook);
+ let remsAdminUrl = null;
+ if (request) {
+ remsAdminUrl = getMedicationSpecificRemsAdminUrl(request, globalState, hook);
+ sendHook(prefetch, request, patient, hook, remsAdminUrl);
+ } else {
+ // get all MedicationRequests for the patient, then continue
+ getMedicationRequests(patient.id);
+ }
+ };
+ const sendHook = (prefetch, request, patient, hook, remsAdminUrl) => {
setState(prevState => ({
...prevState,
loading: !!remsAdminUrl,
@@ -157,7 +199,15 @@ const RequestBuilder = props => {
);
console.log(fhirResponse.message);
} else {
- setState(prevState => ({ ...prevState, response: fhirResponse }));
+ if (response?.url?.includes(PATIENT_VIEW)) {
+ // copy the cards from the old response into the new
+ setState(prevState => ({
+ ...prevState,
+ response: { cards: [...(prevState.response.cards || []), ...fhirResponse.cards] }
+ }));
+ } else {
+ setState(prevState => ({ ...prevState, response: fhirResponse }));
+ }
}
setState(prevState => ({ ...prevState, loading: false }));
});
@@ -292,9 +342,7 @@ const RequestBuilder = props => {
defaultUser={globalState.defaultUser}
loading={state.loading}
patientFhirQuery={globalState.patientFhirQuery}
- getRemsAdminUrl={(request, hook) =>
- getMedicationSpecificRemsAdminUrl(request, globalState, hook)
- }
+ prefetchCompleted={state.prefetchCompleted}
/>
)}
diff --git a/src/util/buildRequest.js b/src/util/buildRequest.js
index e2f8bc2..7cabe93 100644
--- a/src/util/buildRequest.js
+++ b/src/util/buildRequest.js
@@ -1,3 +1,5 @@
+import { ORDER_SIGN, ORDER_SELECT } from "./data";
+
export default function buildRequest(
request,
user,
@@ -43,7 +45,7 @@ export default function buildRequest(
r4json.extension = extension;
}
- if (hook === 'order-select') {
+ if (hook === ORDER_SELECT) {
r4json.context.draftOrders = {
resourceType: 'Bundle',
entry: [
@@ -53,7 +55,7 @@ export default function buildRequest(
]
};
r4json.context.selections = [request.resourceType + '/' + request.id];
- } else if (hook === 'order-sign') {
+ } else if (hook === ORDER_SIGN) {
r4json.context.draftOrders = {
resourceType: 'Bundle',
entry: [
@@ -62,7 +64,7 @@ export default function buildRequest(
}
]
};
- //} else if (hook === "patient-view") {
+ //} else if ((hook === PATIENT_VIEW) || (hook === ENCOUNTER_START)) {
}
if (includePrefetch) {
diff --git a/src/util/buildScript.2017071.js b/src/util/buildScript.2017071.js
index f09232f..cf482f8 100644
--- a/src/util/buildScript.2017071.js
+++ b/src/util/buildScript.2017071.js
@@ -298,7 +298,8 @@ function buildNewRxMedication(doc, medicationRequestResource) {
export default function buildNewRxRequest(
patientResource,
practitionerResource,
- medicationRequestResource
+ medicationRequestResource,
+ authNumber
) {
var doc = document.implementation.createDocument('', '', null);
var message = doc.createElement('Message');
@@ -318,6 +319,8 @@ export default function buildNewRxRequest(
const d1 = new Date();
const messageIdValue = d1.getTime();
xmlAddTextNode(doc, header, 'MessageID', messageIdValue);
+ // Add in auth number here
+ xmlAddTextNode(doc, header, 'AuthorizationNumber', authNumber);
// SentTime
xmlAddTextNode(doc, header, 'SentTime', d1.toISOString());
diff --git a/src/util/data.js b/src/util/data.js
index d308619..29eaadb 100644
--- a/src/util/data.js
+++ b/src/util/data.js
@@ -75,44 +75,58 @@ const headerDefinitions = {
display: 'REMS Admin Server',
type: 'input',
default: env.get('VITE_SERVER').asString()
+ },
+ hookToSend: {
+ display: 'Send hook on patient select',
+ type: 'dropdown',
+ default: env.get('VITE_HOOK_TO_SEND').asString()
}
};
+const ORDER_SIGN = 'order-sign';
+const ORDER_SELECT = 'order-select';
+const PATIENT_VIEW = 'patient-view';
+const ENCOUNTER_START = 'encounter-start';
+
const medicationRequestToRemsAdmins = Object.freeze([
{
rxnorm: 2183126,
display: 'Turalio 200 MG Oral Capsule',
hookEndpoints: [
- { hook: 'order-sign', remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
- { hook: 'order-select', remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
- { hook: 'patient-view', remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }
+ { hook: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
+ { hook: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
+ { hook: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' },
+ { hook: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }
]
},
{
rxnorm: 6064,
display: 'Isotretinoin 20 MG Oral Capsule',
hookEndpoints: [
- { hook: 'order-sign', remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
- { hook: 'order-select', remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
- { hook: 'patient-view', remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }
+ { hook: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
+ { hook: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
+ { hook: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' },
+ { hook: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }
]
},
{
rxnorm: 1237051,
display: 'TIRF 200 UG Oral Transmucosal Lozenge',
hookEndpoints: [
- { hook: 'order-sign', remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
- { hook: 'order-select', remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
- { hook: 'patient-view', remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }
+ { hook: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
+ { hook: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
+ { hook: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' },
+ { hook: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }
]
},
{
rxnorm: 1666386,
display: 'Addyi 100 MG Oral Tablet',
hookEndpoints: [
- { hook: 'order-sign', remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
- { hook: 'order-select', remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
- { hook: 'patient-view', remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }
+ { hook: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' },
+ { hook: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' },
+ { hook: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' },
+ { hook: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }
]
}
]);
@@ -249,5 +263,9 @@ export {
shortNameMap,
stateOptions,
types,
- medicationRequestToRemsAdmins
+ medicationRequestToRemsAdmins,
+ ORDER_SIGN,
+ ORDER_SELECT,
+ PATIENT_VIEW,
+ ENCOUNTER_START
};
diff --git a/src/util/fhir.js b/src/util/fhir.js
index 7c4a1c3..69f751f 100644
--- a/src/util/fhir.js
+++ b/src/util/fhir.js
@@ -68,6 +68,17 @@ function createMedicationFromMedicationRequest(medicationRequest) {
medication.id = medicationRequest?.id + '-med';
if (medicationRequest.medicationCodeableConcept) {
medication.code = medicationRequest.medicationCodeableConcept;
+ } else if (medicationRequest.medicationReference) {
+ const reference = medicationRequest?.medicationReference;
+ medication.code = undefined;
+ medicationRequest?.contained?.every(e => {
+ if (e.resourceType + '/' + e.id === reference.reference) {
+ if (e.resourceType === 'Medication') {
+ console.log('Get Medication code from contained resource');
+ medication.code = e.code;
+ }
+ }
+ });
}
return medication;
}
diff --git a/src/util/util.js b/src/util/util.js
index 44b9286..1373afa 100644
--- a/src/util/util.js
+++ b/src/util/util.js
@@ -1,4 +1,6 @@
import axios from 'axios';
+import { getDrugCodeableConceptFromMedicationRequest } from './fhir';
+import { ORDER_SIGN, ORDER_SELECT, PATIENT_VIEW, ENCOUNTER_START } from './data';
/**
* Retrieves a SMART launch context from an endpoint to append as a "launch" query parameter to a SMART app launch URL (see SMART docs for more about launch context).
@@ -71,7 +73,8 @@ function standardsBasedGetEtasu(etasuUrl, body, responseCallback) {
method: 'post',
url: etasuUrl,
data: body
- }).then(response => {
+ }).then(
+ response => {
// Sorting an array mutates the data in place.
const remsMetRes = response.data;
if (remsMetRes?.parameter[0]?.resource?.contained) {
@@ -85,22 +88,30 @@ function standardsBasedGetEtasu(etasuUrl, body, responseCallback) {
});
}
responseCallback(response.data.parameter[0].resource, body);
- }, error => {
+ },
+ error => {
console.log('error -- > ', error);
}
- )
+ );
}
const getMedicationSpecificRemsAdminUrl = (request, globalState, hook) => {
- const display = request.medicationCodeableConcept?.coding?.[0]?.display;
- const rxnorm = request.medicationCodeableConcept?.coding?.[0]?.code;
+ // if empty request, just return
+ if (Object.keys(request).length === 0) {
+ return undefined;
+ }
+
+ const codeableConcept = getDrugCodeableConceptFromMedicationRequest(request);
+ const display = codeableConcept?.coding?.[0]?.display;
+ const rxnorm = codeableConcept?.coding?.[0]?.code;
if (!rxnorm) {
console.log("ERROR: unknown MedicationRequest code: '", rxnorm);
return undefined;
}
- if (!(hook === 'patient-view' || hook === 'order-sign' || hook === 'order-select')) {
+ // This function never gets called with the PATIENT_VIEW hook, however.
+ if (!(hook === PATIENT_VIEW || hook === ORDER_SIGN || hook === ORDER_SELECT || hook === ENCOUNTER_START)) {
console.log(`ERROR: unknown hook type: ${hook}`);
return undefined;
}
@@ -117,4 +128,26 @@ const getMedicationSpecificRemsAdminUrl = (request, globalState, hook) => {
return cdsUrl;
};
-export { retrieveLaunchContext, standardsBasedGetEtasu, getMedicationSpecificRemsAdminUrl };
+const prepPrefetch = prefetchedResources => {
+ const preppedResources = new Map();
+ Object.keys(prefetchedResources).forEach(resourceKey => {
+ let resourceList = [];
+ if (Array.isArray(prefetchedResources[resourceKey])) {
+ resourceList = prefetchedResources[resourceKey].map(resource => {
+ return resource;
+ });
+ } else {
+ resourceList = prefetchedResources[resourceKey];
+ }
+
+ preppedResources.set(resourceKey, resourceList);
+ });
+ return preppedResources;
+};
+
+export {
+ retrieveLaunchContext,
+ standardsBasedGetEtasu,
+ getMedicationSpecificRemsAdminUrl,
+ prepPrefetch
+};