From 85ff34f983190e5344e427ccf3127a4ff33757f2 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Mon, 20 May 2024 11:36:17 -0400 Subject: [PATCH 1/4] Rename variable --- src/views/Patient/MedReqDropDown/MedReqDropDown.tsx | 10 +++++----- src/views/Patient/PatientView.tsx | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx b/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx index 94226fd..70cdbce 100644 --- a/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx +++ b/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx @@ -52,7 +52,7 @@ interface MedReqDropDownProps { client: Client; getFhirResource: (token: string) => Promise; hooksCards: HooksCard[]; - medication: MedicationBundle | null; + medicationBundle: MedicationBundle | null; patient: Patient | null; setHooksCards: React.Dispatch>; tabCallback: (n: ReactElement, m: string, o: string) => void; @@ -79,7 +79,7 @@ function MedReqDropDown({ client, getFhirResource, hooksCards, - medication, + medicationBundle, patient, setHooksCards, tabCallback, @@ -138,7 +138,7 @@ function MedReqDropDown({ useEffect(() => { if (selectedOption != '') { setSelectedMedicationCard( - medication?.data.find(medication => medication.id === selectedOption) + medicationBundle?.data.find(medication => medication.id === selectedOption) ); } }, [selectedOption]); @@ -330,8 +330,8 @@ function MedReqDropDown({ value={selectedOption} onChange={handleOptionSelect} > - {medication ? ( - medication.data.map(medications => ( + {medicationBundle ? ( + medicationBundle.data.map(medications => ( {getDrugCodeFromMedicationRequest(medications)?.display} diff --git a/src/views/Patient/PatientView.tsx b/src/views/Patient/PatientView.tsx index 6eccd48..2f0523d 100644 --- a/src/views/Patient/PatientView.tsx +++ b/src/views/Patient/PatientView.tsx @@ -21,6 +21,7 @@ import axios from 'axios'; import * as env from 'env-var'; import PatientViewHook from '../../cds-hooks/resources/PatientView'; import { hydrate } from '../../cds-hooks/prefetch/PrefetchHydrator'; +import { medicationRequestToRemsAdmins } from '../../util/data'; interface PatientViewProps { client: Client; @@ -118,7 +119,7 @@ function PatientView(props: PatientViewProps) { }, [cdsHook]); // MedicationRequest Prefetching Bundle - const [medication, setMedication] = useState(null); + const [medicationBundle, setMedicationBundle] = useState(null); const getMedicationRequest = () => { client @@ -164,7 +165,7 @@ function PatientView(props: PatientViewProps) { } }); - setMedication(result); + setMedicationBundle(result); }); }; @@ -279,7 +280,7 @@ function PatientView(props: PatientViewProps) { client={client} getFhirResource={getFhirResource} hooksCards={hooksCards} - medication={medication} + medicationBundle={medicationBundle} patient={patient} setHooksCards={setHooksCards} tabCallback={props.tabCallback} From 0092ff94338fb77aed78a2c03d936557c881b255 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Mon, 20 May 2024 15:17:24 -0400 Subject: [PATCH 2/4] Add REMS Admin lookup table and handle multiple REMS admins with patient-view hook --- src/cds-hooks | 2 +- src/util/data.ts | 92 +++++++++++++++++++ src/util/util.ts | 49 ++++++++++ .../Patient/MedReqDropDown/MedReqDropDown.tsx | 25 +++-- src/views/Patient/PatientView.tsx | 53 +++++++---- src/views/Questionnaire/questionnaireUtil.ts | 4 +- 6 files changed, 197 insertions(+), 28 deletions(-) create mode 100644 src/util/data.ts create mode 100644 src/util/util.ts diff --git a/src/cds-hooks b/src/cds-hooks index 94a78e8..80b9762 160000 --- a/src/cds-hooks +++ b/src/cds-hooks @@ -1 +1 @@ -Subproject commit 94a78e8cd27734938ec41858f8d0ca4028da5f21 +Subproject commit 80b976201d4604030adcd3d606d12539be351000 diff --git a/src/util/data.ts b/src/util/data.ts new file mode 100644 index 0000000..ef394b5 --- /dev/null +++ b/src/util/data.ts @@ -0,0 +1,92 @@ +import { SupportedHooks } from '../cds-hooks/resources/HookTypes'; + +export const medicationRequestToRemsAdmins = Object.freeze([ + { + rxnorm: 2183126, + display: 'Turalio 200 MG Oral Capsule', + hookEndpoints: [ + { + hook: SupportedHooks.ORDER_SIGN, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' + }, + { + hook: SupportedHooks.ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + hook: SupportedHooks.PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + hook: SupportedHooks.ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + } + ] + }, + { + rxnorm: 6064, + display: 'Isotretinoin 20 MG Oral Capsule', + hookEndpoints: [ + { + hook: SupportedHooks.ORDER_SIGN, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' + }, + { + hook: SupportedHooks.ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + hook: SupportedHooks.PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + hook: SupportedHooks.ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + } + ] + }, + { + rxnorm: 1237051, + display: 'TIRF 200 UG Oral Transmucosal Lozenge', + hookEndpoints: [ + { + hook: SupportedHooks.ORDER_SIGN, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' + }, + { + hook: SupportedHooks.ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + hook: SupportedHooks.PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + hook: SupportedHooks.ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + } + ] + }, + { + rxnorm: 1666386, + display: 'Addyi 100 MG Oral Tablet', + hookEndpoints: [ + { + hook: SupportedHooks.ORDER_SIGN, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' + }, + { + hook: SupportedHooks.ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + hook: SupportedHooks.PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + hook: SupportedHooks.ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + } + ] + } +]); diff --git a/src/util/util.ts b/src/util/util.ts new file mode 100644 index 0000000..bc3f6c8 --- /dev/null +++ b/src/util/util.ts @@ -0,0 +1,49 @@ +import { MedicationRequest } from 'fhir/r4'; +import { SupportedHooks } from '../cds-hooks/resources/HookTypes'; +import { getDrugCodeableConceptFromMedicationRequest } from '../views/Questionnaire/questionnaireUtil'; +import { medicationRequestToRemsAdmins } from './data'; + +export const getMedicationSpecificRemsAdminUrl = ( + request: MedicationRequest | undefined, + hook: SupportedHooks +) => { + // if empty request, just return + if (request && Object.keys(request).length === 0) { + return null; + } + + 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 null; + } + + // This function never gets called with the PATIENT_VIEW hook, however. + if ( + !( + hook === SupportedHooks.PATIENT_VIEW || + hook === SupportedHooks.ORDER_SIGN || + hook === SupportedHooks.ORDER_SELECT || + hook === SupportedHooks.ENCOUNTER_START + ) + ) { + console.log(`ERROR: unknown hook type: ${hook}`); + return null; + } + + const setting = medicationRequestToRemsAdmins.find( + value => Number(value.rxnorm) === Number(rxnorm) + ); + + const cdsUrl = setting?.hookEndpoints.find(endpoint => endpoint.hook === hook); + + if (!cdsUrl) { + console.log(`Medication ${display} is not a REMS medication`); + return null; + } + + return cdsUrl.remsAdmin; +}; diff --git a/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx b/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx index 70cdbce..a1364ed 100644 --- a/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx +++ b/src/views/Patient/MedReqDropDown/MedReqDropDown.tsx @@ -45,8 +45,7 @@ import EtasuStatus from './etasuStatus/EtasuStatus'; // Adding in Pharmacy import PharmacyStatus from './pharmacyStatus/PharmacyStatus'; import axios from 'axios'; -import MetRequirements from './etasuStatus/MetRequirements'; -import RemsMetEtasuResponse from './etasuStatus/RemsMetEtasuResponse'; +import { getMedicationSpecificRemsAdminUrl } from '../../../util/util'; interface MedReqDropDownProps { client: Client; @@ -94,6 +93,7 @@ function MedReqDropDown({ //CDSHooks const [cdsHook, setCDSHook] = useState(null); + const [cdsUrl, setCDSUrl] = useState(null); //ETASU const [showEtasu, setShowEtasu] = useState(false); @@ -108,7 +108,7 @@ function MedReqDropDown({ useEffect(() => { if (cdsHook) { - submitToREMS(cdsHook, setHooksCards); + submitToREMS(cdsUrl, cdsHook, setHooksCards); } }, [cdsHook]); @@ -128,7 +128,7 @@ function MedReqDropDown({ setShowPharmacy(false); }; - const [selectedMedicationCardBundle, setSelectedMedicationCardBundle] = + const [selectedMedicationCardBundleEntry, setSelectedMedicationCardBundleEntry] = useState>(); const [selectedMedicationCard, setSelectedMedicationCard] = useState(); @@ -150,23 +150,28 @@ function MedReqDropDown({ if (medName) { setMedicationName(medName); } - setSelectedMedicationCardBundle({ resource: selectedMedicationCard }); + setSelectedMedicationCardBundleEntry({ resource: selectedMedicationCard }); } }, [selectedMedicationCard]); useEffect(() => { - if (patient && patient.id && user && selectedMedicationCardBundle) { - const resourceId = `${selectedMedicationCardBundle.resource?.resourceType}/${selectedMedicationCardBundle.resource?.id}`; + if (patient && patient.id && user && selectedMedicationCardBundleEntry) { + const request = selectedMedicationCardBundleEntry.resource; + const resourceId = `${request?.resourceType}/${request?.id}`; + const hook = new OrderSelect( patient.id, user, { resourceType: 'Bundle', type: 'batch', - entry: [selectedMedicationCardBundle] + entry: [selectedMedicationCardBundleEntry] }, [resourceId] ); + const cdsUrl = getMedicationSpecificRemsAdminUrl(request, hook.hookType); + setCDSUrl(cdsUrl); + let tempHook: OrderSelectHook; if (env.get('REACT_APP_SEND_FHIR_AUTH_ENABLED').asBool()) { tempHook = hook.generate(client); @@ -177,7 +182,7 @@ function MedReqDropDown({ setCDSHook(tempHook); }); } - }, [selectedMedicationCardBundle]); + }, [selectedMedicationCardBundleEntry]); useEffect(() => { refreshEtasuBundle(); @@ -378,7 +383,7 @@ function MedReqDropDown({ > submitToREMS(cdsHook, setHooksCards)} + onClick={() => submitToREMS(cdsUrl, cdsHook, setHooksCards)} size="large" > diff --git a/src/views/Patient/PatientView.tsx b/src/views/Patient/PatientView.tsx index 2f0523d..8f89047 100644 --- a/src/views/Patient/PatientView.tsx +++ b/src/views/Patient/PatientView.tsx @@ -16,9 +16,8 @@ import { MedicationRequest, Patient } from 'fhir/r4'; import Client from 'fhirclient/lib/Client'; import { ReactElement, useEffect, useState } from 'react'; import MedReqDropDown from './MedReqDropDown/MedReqDropDown'; -import { Hook, Card as HooksCard } from '../../cds-hooks/resources/HookTypes'; +import { Hook, Card as HooksCard, SupportedHooks } from '../../cds-hooks/resources/HookTypes'; import axios from 'axios'; -import * as env from 'env-var'; import PatientViewHook from '../../cds-hooks/resources/PatientView'; import { hydrate } from '../../cds-hooks/prefetch/PrefetchHydrator'; import { medicationRequestToRemsAdmins } from '../../util/data'; @@ -38,26 +37,39 @@ export interface MedicationBundle { } //CDS-Hook Request to REMS-Admin for cards -export const submitToREMS = ( + +const submitPatientViewHookToAllRemsAdmins = ( + cdsUrls: string[], cdsHook: Hook | null, setHooksCards: React.Dispatch> ) => { - const hookType = (cdsHook && cdsHook.hook) || 'NO_SUCH_HOOK'; - axios({ - method: 'post', - url: - `${env.get('REACT_APP_REMS_ADMIN_SERVER_BASE').asString()}` + - `${env.get('REACT_APP_REMS_HOOKS_PATH').asString()}` + - hookType, - data: cdsHook - }).then( - response => { - setHooksCards(response.data.cards); + const promise = Promise.all(cdsUrls.map(cdsUrl => axios.post(cdsUrl, cdsHook))).then(); + promise.then( + responses => { + setHooksCards(responses.map(response => response.data.cards).flat()); }, error => console.log(error) ); }; +export const submitToREMS = ( + cdsUrl: string | null, + cdsHook: Hook | null, + setHooksCards: React.Dispatch> +) => { + if (cdsUrl) { + const promise = axios.post(cdsUrl, cdsHook); + promise.then( + response => { + setHooksCards(response.data.cards); + }, + error => console.log(error) + ); + } else { + console.error(`No defined CDS URL for '${cdsHook}'.`); + } +}; + function PatientView(props: PatientViewProps) { function getFhirResource(token: string) { console.log('getFhirResource: ' + token); @@ -72,6 +84,13 @@ function PatientView(props: PatientViewProps) { const [cdsHook, setCDSHook] = useState(null); + const cdsUrls = medicationRequestToRemsAdmins + .map( + ({ hookEndpoints }) => + hookEndpoints.find(endpoint => endpoint.hook === SupportedHooks.PATIENT_VIEW)?.remsAdmin + ) + .filter(url => !!url) as string[]; + //Prefetch const [patient, setPatient] = useState(null); @@ -114,7 +133,7 @@ function PatientView(props: PatientViewProps) { useEffect(() => { if (cdsHook) { - submitToREMS(cdsHook, setHooksCards); + submitPatientViewHookToAllRemsAdmins(cdsUrls, cdsHook, setHooksCards); } }, [cdsHook]); @@ -248,7 +267,9 @@ function PatientView(props: PatientViewProps) { submitToREMS(cdsHook, setHooksCards)} + onClick={() => { + submitPatientViewHookToAllRemsAdmins(cdsUrls, cdsHook, setHooksCards); + }} size="large" > diff --git a/src/views/Questionnaire/questionnaireUtil.ts b/src/views/Questionnaire/questionnaireUtil.ts index ae1d870..75ec4bd 100644 --- a/src/views/Questionnaire/questionnaireUtil.ts +++ b/src/views/Questionnaire/questionnaireUtil.ts @@ -158,7 +158,9 @@ export function buildFhirUrl(reference: string, fhirPrefix: string, fhirVersion: * Retrieve the CodeableConcept for the medication from the medicationCodeableConcept if available. * Read CodeableConcept from contained Medication matching the medicationReference otherwise. */ -export function getDrugCodeableConceptFromMedicationRequest(medicationRequest: MedicationRequest) { +export function getDrugCodeableConceptFromMedicationRequest( + medicationRequest: MedicationRequest | undefined +) { if (medicationRequest) { if (medicationRequest?.medicationCodeableConcept) { console.log('Get Medication code from CodeableConcept'); From 0ed34cf43356bb79ede34ef611169a362eb242b0 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Mon, 20 May 2024 15:35:40 -0400 Subject: [PATCH 3/4] Remove unused env var --- .env | 1 - README.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.env b/.env index a2fcba0..c82c190 100644 --- a/.env +++ b/.env @@ -9,5 +9,4 @@ REACT_APP_ETASU_STATUS_ENABLED = true REACT_APP_PHARMACY_SERVER_BASE = http://localhost:5051 REACT_APP_PHARMACY_STATUS_ENABLED = true REACT_APP_REMS_ADMIN_SERVER_BASE = http://localhost:8090 -REACT_APP_REMS_HOOKS_PATH = /cds-services/rems- REACT_APP_SEND_FHIR_AUTH_ENABLED = false diff --git a/README.md b/README.md index 1a6978b..2227498 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Following are a list of modifiable paths: | REACT_APP_PHARMACY_SERVER_BASE | `http://localhost:5051` | | REACT_APP_PHARMACY_STATUS_ENABLED | `true` | | REACT_APP_REMS_ADMIN_SERVER_BASE | `http://localhost:8090` | -| REACT_APP_REMS_HOOKS_PATH | `/cds-services/rems-` | | REACT_APP_SEND_FHIR_AUTH_ENABLED | `false` | _Note that .env values can only be accessed by the React app starting with `REACT_APP_`\_ From 3155775f691952c1b29af3d94194b1a541fc95fb Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Tue, 21 May 2024 13:54:37 -0400 Subject: [PATCH 4/4] Remove duplicate CDS Hooks REMS Admin endpoints --- src/views/Patient/PatientView.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/views/Patient/PatientView.tsx b/src/views/Patient/PatientView.tsx index a5671a4..3e2febf 100644 --- a/src/views/Patient/PatientView.tsx +++ b/src/views/Patient/PatientView.tsx @@ -82,12 +82,14 @@ function PatientView(props: PatientViewProps) { const [cdsHook, setCDSHook] = useState(null); - const cdsUrls = medicationRequestToRemsAdmins - .map( - ({ hookEndpoints }) => - hookEndpoints.find(endpoint => endpoint.hook === SupportedHooks.PATIENT_VIEW)?.remsAdmin + const cdsUrls = Array.from( + new Set( + medicationRequestToRemsAdmins.map( + ({ hookEndpoints }) => + hookEndpoints.find(({ hook }) => hook === SupportedHooks.PATIENT_VIEW)?.remsAdmin + ) ) - .filter(url => !!url) as string[]; + ).filter(url => !!url) as string[]; //Prefetch const [patient, setPatient] = useState(null);