From 52c14b74fadfb47ea3b87abd5269efe5384b5422 Mon Sep 17 00:00:00 2001 From: KeeyanGhoreshi Date: Fri, 5 Jan 2024 09:33:57 -0500 Subject: [PATCH 1/3] preliminary cleanup --- src/hooks/hookResources.ts | 255 +++++++++++++++++++++++++++++++--- src/hooks/rems.orderselect.ts | 235 ++----------------------------- src/hooks/rems.ordersign.ts | 234 +------------------------------ src/hooks/rems.patientview.ts | 100 +++---------- 4 files changed, 268 insertions(+), 556 deletions(-) diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index 29f6fdda..944595d8 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -1,8 +1,18 @@ -import { MedicationRequest, Coding } from 'fhir/r4'; -import { Link } from '../cards/Card'; -import { TypedRequestBody } from '../rems-cds-hooks/resources/HookTypes'; -import axios from 'axios'; +import { MedicationRequest, Coding, FhirResource, Identifier } from 'fhir/r4'; +import Card, { Link } from '../cards/Card'; +import { HookPrefetch, OrderSignPrefetch, TypedRequestBody } from '../rems-cds-hooks/resources/HookTypes'; +import config from '../config'; +import { medicationCollection, remsCaseCollection } from '../fhir/models'; +import axios from 'axios'; +import { ServicePrefetch } from '../rems-cds-hooks/resources/CdsService'; +import { hydrate } from '../rems-cds-hooks/prefetch/PrefetchHydrator'; +type HandleCallback = ( + res: any, + hydratedPrefetch: HookPrefetch | undefined, + contextRequest: FhirResource | undefined, + patient: FhirResource | undefined, + ) => Promise; export interface CardRule { links: Link[]; summary?: string; @@ -189,21 +199,10 @@ export const validCodes: Coding[] = [ system: 'http://www.nlm.nih.gov/research/umls/rxnorm' } ]; - -export function getFhirResource(token: string, req: TypedRequestBody) { - const ehrUrl = `${req.body.fhirServer}/${token}`; - const access_token = req.body.fhirAuthorization?.access_token; - const options = { - method: 'GET', - headers: { - Authorization: `Bearer ${access_token}` - } - }; - const response = axios(ehrUrl, options); - return response.then(e => { - return e.data; - }); -} +const source = { + label: 'MCODE REMS Administrator Prototype', + url: new URL('https://github.com/mcode/rems-admin') +}; /* * Retrieve the coding for the medication from the medicationCodeableConcept if available. @@ -230,3 +229,221 @@ export function getDrugCodeFromMedicationRequest(medicationRequest: MedicationRe } return null; } +export function getFhirResource(token: string, req: TypedRequestBody) { + const ehrUrl = `${req.body.fhirServer}/${token}`; + const access_token = req.body.fhirAuthorization?.access_token; + const options = { + method: 'GET', + headers: { + Authorization: `Bearer ${access_token}` + } + }; + const response = axios(ehrUrl, options); + return response.then(e => { + return e.data; + }); +} +export function createSmartLink( + requirementName: string, + appContext: string, + request: MedicationRequest | undefined +) { + const newLink: Link = { + label: requirementName + ' Form', + url: new URL(config.smart.endpoint), + type: 'smart', + appContext: `${appContext}&order=${JSON.stringify(request)}&coverage=${ + request?.insurance?.[0].reference + }` + }; + return newLink; +} +export function buildErrorCard(reason: string) { + const errorCard = new Card('Bad Request', reason, source, 'warning'); + const cards = { + cards: [errorCard.card] + }; + return cards; +} + +// handles order-sign and order-select currently +export async function handleCardOrder(res: any, + hydratedPrefetch: HookPrefetch | undefined, + contextRequest: FhirResource | undefined, + patient: FhirResource | undefined) { + const prefetchRequest = hydratedPrefetch?.request; + console.log(' MedicationRequest: ' + prefetchRequest?.id); + // verify there is a contextRequest + if (!contextRequest) { + res.json(buildErrorCard('DraftOrders does not contain a request')); + return; + } + + // verify a MedicationRequest was sent + if (contextRequest && contextRequest.resourceType !== 'MedicationRequest') { + res.json(buildErrorCard('DraftOrders does not contain a MedicationRequest')); + return; + } + if ( + prefetchRequest?.id && + contextRequest && + contextRequest.id && + prefetchRequest.id.replace('MedicationRequest/', '') !== + contextRequest.id.replace('MedicationRequest/', '') + ) { + res.json(buildErrorCard('Context draftOrder does not match prefetch MedicationRequest ID')); + return; + } + + const medicationCode = contextRequest && contextRequest.resourceType === 'MedicationRequest' && getDrugCodeFromMedicationRequest(contextRequest); + if (!medicationCode) { + return; + } + if (medicationCode && medicationCode?.code) { + // find the drug in the medicationCollection to get the smart links + const drug = await medicationCollection + .findOne({ + code: medicationCode.code, + codeSystem: medicationCode.system + }) + .exec(); + + // find a matching rems case for the patient and this drug to only return needed results + const patientName = patient?.resourceType==='Patient' ? patient?.name?.[0] : undefined; + const patientBirth = patient?.resourceType==='Patient' ? patient?.birthDate : undefined; + const etasu = await remsCaseCollection.findOne({ + patientFirstName: patientName?.given?.[0], + patientLastName: patientName?.family, + patientDOB: patientBirth, + drugCode: medicationCode?.code + }); + + const returnCard = validCodes.some(e => { + return e.code === medicationCode.code && e.system === medicationCode.system; + }); + if (returnCard) { + const cardArray: Card[] = []; + const codeRule = codeMap[medicationCode.code]; + for (const rule of codeRule) { + const card = new Card( + rule.summary || medicationCode.display || 'Rems', + CARD_DETAILS, + source, + 'info' + ); + rule.links.forEach(function (e) { + if (e.type == 'absolute') { + // no construction needed + card.addLink(e); + } + }); + + let smartLinkCount = 0; + + // process the smart links from the medicationCollection + // TODO: smart links should be built with discovered questionnaires, not hard coded ones + if (drug) { + for (const requirement of drug.requirements) { + if (requirement.stakeholderType == rule.stakeholderType) { + // only add the link if the form has not already been processed / received + if (etasu) { + let found = false; + for (const metRequirement of etasu.metRequirements) { + if (metRequirement.requirementName == requirement.name) { + found = true; + if (!metRequirement.completed) { + card.addLink( + createSmartLink(requirement.name, requirement.appContext, contextRequest) + ); + smartLinkCount++; + } + } + } + if (!found) { + card.addLink( + createSmartLink(requirement.name, requirement.appContext, contextRequest) + ); + smartLinkCount++; + } + } else { + // add all the required to dispense links if no etasu to check + if (requirement.requiredToDispense) { + card.addLink( + createSmartLink(requirement.name, requirement.appContext, contextRequest) + ); + smartLinkCount++; + } + } + } + } + } + + // only add the card if there are smart links to needed forms + if (smartLinkCount > 0) { + cardArray.push(card); + } + } + res.json({ + cards: cardArray + }); + } else { + res.json(buildErrorCard('Unsupported code')); + } + } else { + res.json(buildErrorCard('MedicationRequest does not contain a code')); + } +} + +// handles preliminary card creation. ALL hooks should go through this function. +// make sure code here is applicable to all supported hooks. +export async function handleCard(req: TypedRequestBody, res: any, hydratedPrefetch: HookPrefetch, contextRequest: FhirResource | undefined, callback: HandleCallback) { + const context = req.body.context; + const patient = hydratedPrefetch?.patient; + const practitioner = hydratedPrefetch?.practitioner; + + console.log(' Patient: ' + patient?.id); + + // verify ids + if ( + patient?.id && + patient.id.replace('Patient/', '') !== context.patientId.replace('Patient/', '') + ) { + res.json(buildErrorCard('Context patientId does not match prefetch Patient ID')); + return; + } + if ( + practitioner?.id && + practitioner.id.replace('Practitioner/', '') !== context.userId.replace('Practitioner/', '') + ) { + res.json(buildErrorCard('Context userId does not match prefetch Practitioner ID')); + return; + } + return callback(res, hydratedPrefetch, contextRequest, patient) + +} + + +// handles all hooks, any supported hook should pass through this function +export function handleHook(req: TypedRequestBody, res: any, hookPrefetch: ServicePrefetch, contextRequest: FhirResource | undefined, callback: HandleCallback) { + try { + const fhirUrl = req.body.fhirServer; + const fhirAuth = req.body.fhirAuthorization; + if (fhirUrl && fhirAuth && fhirAuth.access_token) { + hydrate(getFhirResource, hookPrefetch, req.body).then( + (hydratedPrefetch) => { + handleCard(req, res, hydratedPrefetch, contextRequest, callback); + } + ); + } else { + if (req.body.prefetch) { + handleCard(req, res, req.body.prefetch, contextRequest, callback); + } else { + handleCard(req, res, {}, contextRequest, callback); + } + } + } catch (error) { + console.log(error); + res.json(buildErrorCard('Unknown Error')); + } +} + diff --git a/src/hooks/rems.orderselect.ts b/src/hooks/rems.orderselect.ts index 636a4aee..f4c5aaa5 100644 --- a/src/hooks/rems.orderselect.ts +++ b/src/hooks/rems.orderselect.ts @@ -1,22 +1,12 @@ -import Card from '../cards/Card'; import { SupportedHooks, - OrderSelectPrefetch, OrderSelectHook } from '../rems-cds-hooks/resources/HookTypes'; -import { medicationCollection, remsCaseCollection } from '../fhir/models'; import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; -import { MedicationRequest } from 'fhir/r4'; -import { Link } from '../cards/Card'; -import config from '../config'; -import { hydrate } from '../rems-cds-hooks/prefetch/PrefetchHydrator'; import { - validCodes, - codeMap, - CARD_DETAILS, - getDrugCodeFromMedicationRequest + handleCardOrder, + handleHook } from './hookResources'; -import axios from 'axios'; interface TypedRequestBody extends Express.Request { body: OrderSelectHook; @@ -33,223 +23,18 @@ const definition: CdsService = { description: 'REMS Requirement Lookup', prefetch: hookPrefetch }; -const source = { - label: 'MCODE REMS Administrator Prototype', - url: new URL('https://github.com/mcode/rems-admin') -}; -function buildErrorCard(reason: string) { - const errorCard = new Card('Bad Request', reason, source, 'warning'); - const cards = { - cards: [errorCard.card] - }; - return cards; -} const handler = (req: TypedRequestBody, res: any) => { - function getFhirResource(token: string) { - const ehrUrl = `${req.body.fhirServer}/${token}`; - const access_token = req.body.fhirAuthorization?.access_token; - const options = { - method: 'GET', - headers: { - Authorization: `Bearer ${access_token}` - } - }; - const response = axios(ehrUrl, options); - return response.then(e => { - return e.data; - }); - } - - function createSmartLink( - requirementName: string, - appContext: string, - request: MedicationRequest - ) { - const newLink: Link = { - label: requirementName + ' Form', - url: new URL(config.smart.endpoint), - type: 'smart', - appContext: `${appContext}&order=${JSON.stringify(request)}&coverage=${ - request.insurance?.[0].reference - }` - }; - return newLink; - } - - async function handleCard(hydratedPrefetch: OrderSelectPrefetch) { - console.log(hydratedPrefetch); - const context = req.body.context; - // const contextRequest = context.draftOrders?.entry?.[0].resource; - const selection = context.selections?.[0]; - const contextRequest = context.draftOrders?.entry?.filter(entry => { - if (entry.resource) { - return selection === `${entry.resource.resourceType}/${entry.resource.id}`; - } - })[0].resource; - const patient = hydratedPrefetch?.patient; - const prefetchRequest = context.draftOrders?.entry?.[0].resource; - const practitioner = hydratedPrefetch?.practitioner; - const npi = practitioner?.identifier; - - console.log(' MedicationRequest: ' + prefetchRequest?.id); - console.log(' Practitioner: ' + practitioner?.id + ' NPI: ' + npi); - console.log(' Patient: ' + patient?.id); - - // verify there is a contextRequest - if (!contextRequest) { - res.json(buildErrorCard('DraftOrders does not contain a request')); - return; - } - - // verify a MedicationRequest was sent - if (contextRequest && contextRequest.resourceType !== 'MedicationRequest') { - res.json(buildErrorCard('DraftOrders does not contain a MedicationRequest')); - return; - } - - // verify ids - if ( - patient?.id && - patient.id.replace('Patient/', '') !== context.patientId.replace('Patient/', '') - ) { - res.json(buildErrorCard('Context patientId does not match prefetch Patient ID')); - return; - } - if ( - practitioner?.id && - practitioner.id.replace('Practitioner/', '') !== context.userId.replace('Practitioner/', '') - ) { - res.json(buildErrorCard('Context userId does not match prefetch Practitioner ID')); - return; - } - if ( - prefetchRequest?.id && - contextRequest && - contextRequest.id && - prefetchRequest.id.replace('MedicationRequest/', '') !== - contextRequest.id.replace('MedicationRequest/', '') - ) { - res.json(buildErrorCard('Context draftOrder does not match prefetch MedicationRequest ID')); - return; - } - - const medicationCode = getDrugCodeFromMedicationRequest(contextRequest); - if (medicationCode && medicationCode.code) { - // find the drug in the medicationCollection to get the smart links - const drug = await medicationCollection - .findOne({ - code: medicationCode.code, - codeSystem: medicationCode.system - }) - .exec(); - - // find a matching rems case for the patient and this drug to only return needed results - const patientName = patient?.name?.[0]; - const etasu = await remsCaseCollection.findOne({ - patientFirstName: patientName?.given?.[0], - patientLastName: patientName?.family, - patientDOB: patient?.birthDate, - drugCode: medicationCode?.code - }); - - const returnCard = validCodes.some(e => { - return e.code === medicationCode.code && e.system === medicationCode.system; - }); - if (returnCard) { - const cardArray: Card[] = []; - const codeRule = codeMap[medicationCode.code]; - for (const rule of codeRule) { - const card = new Card( - rule.summary || medicationCode.display || 'Rems', - CARD_DETAILS, - source, - 'info' - ); - rule.links.forEach(function (e) { - if (e.type == 'absolute') { - // no construction needed - card.addLink(e); - } - }); - - let smartLinkCount = 0; - - // process the smart links from the medicationCollection - // TODO: smart links should be built with discovered questionnaires, not hard coded ones - if (drug) { - for (const requirement of drug.requirements) { - if (requirement.stakeholderType == rule.stakeholderType) { - // only add the link if the form has not already been processed / received - if (etasu) { - let found = false; - for (const metRequirement of etasu.metRequirements) { - if (metRequirement.requirementName == requirement.name) { - found = true; - if (!metRequirement.completed) { - card.addLink( - createSmartLink(requirement.name, requirement.appContext, contextRequest) - ); - smartLinkCount++; - } - } - } - if (!found) { - card.addLink( - createSmartLink(requirement.name, requirement.appContext, contextRequest) - ); - smartLinkCount++; - } - } else { - // add all the required to dispense links if no etasu to check - if (requirement.requiredToDispense) { - card.addLink( - createSmartLink(requirement.name, requirement.appContext, contextRequest) - ); - smartLinkCount++; - } - } - } - } - } - - // only add the card if there are smart links to needed forms - if (smartLinkCount > 0) { - cardArray.push(card); - } - } - res.json({ - cards: cardArray - }); - } else { - res.json(buildErrorCard('Unsupported code')); - } - } else { - res.json(buildErrorCard('MedicationRequest does not contain a code')); - } - } - console.log('REMS order-select hook'); - try { - const fhirUrl = req.body.fhirServer; - const fhirAuth = req.body.fhirAuthorization; - if (fhirUrl && fhirAuth && fhirAuth.access_token) { - hydrate(getFhirResource, hookPrefetch, req.body).then( - (hydratedPrefetch: OrderSelectPrefetch) => { - handleCard(hydratedPrefetch); - } - ); - } else { - if (req.body.prefetch) { - handleCard(req.body.prefetch); - } else { - handleCard({}); - } + const context = req.body.context; + const selection = context.selections?.[0]; + const contextRequest = context.draftOrders?.entry?.filter(entry => { + if (entry.resource) { + return selection === `${entry.resource.resourceType}/${entry.resource.id}`; } - } catch (error) { - console.log(error); - res.json(buildErrorCard('Unknown Error')); - } + })[0].resource; + handleHook(req, res, hookPrefetch, contextRequest, handleCardOrder); + }; export default { definition, handler }; diff --git a/src/hooks/rems.ordersign.ts b/src/hooks/rems.ordersign.ts index 2e621aea..1464d154 100644 --- a/src/hooks/rems.ordersign.ts +++ b/src/hooks/rems.ordersign.ts @@ -1,22 +1,11 @@ -import Card from '../cards/Card'; import { OrderSignHook, - SupportedHooks, - OrderSignPrefetch -} from '../rems-cds-hooks/resources/HookTypes'; -import { medicationCollection, remsCaseCollection } from '../fhir/models'; + SupportedHooks} from '../rems-cds-hooks/resources/HookTypes'; import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; -import { MedicationRequest } from 'fhir/r4'; -import { Link } from '../cards/Card'; -import config from '../config'; -import { hydrate } from '../rems-cds-hooks/prefetch/PrefetchHydrator'; import { - validCodes, - codeMap, - CARD_DETAILS, - getDrugCodeFromMedicationRequest + handleCardOrder, + handleHook } from './hookResources'; -import axios from 'axios'; interface TypedRequestBody extends Express.Request { body: OrderSignHook; @@ -33,225 +22,12 @@ const definition: CdsService = { description: 'REMS Requirement Lookup', prefetch: hookPrefetch }; -const source = { - label: 'MCODE REMS Administrator Prototype', - url: new URL('https://github.com/mcode/rems-admin') -}; -function buildErrorCard(reason: string) { - const errorCard = new Card('Bad Request', reason, source, 'warning'); - const cards = { - cards: [errorCard.card] - }; - return cards; -} const handler = (req: TypedRequestBody, res: any) => { - async function getFhirResource(token: string) { - const ehrUrl = `${req.body.fhirServer}/${token}`; - const access_token = req.body.fhirAuthorization?.access_token; - const options = { - method: 'GET', - headers: { - Authorization: `Bearer ${access_token}` - } - }; - // application errors out here if you can't reach out to the EHR and results in server stopping and subsequent requests failing - let response = { data: {} }; - try { - response = await axios(ehrUrl, options); - } catch (error: any) { - console.warn('Could not connect to EHR Server: ' + error); - response = error; - } - return response?.data; - } - - function createSmartLink( - requirementName: string, - appContext: string, - request: MedicationRequest - ) { - const newLink: Link = { - label: requirementName + ' Form', - url: new URL(config.smart.endpoint), - type: 'smart', - appContext: `${appContext}&order=${JSON.stringify(request)}&coverage=${ - request.insurance?.[0].reference - }` - }; - return newLink; - } - - async function handleCard(hydratedPrefetch: OrderSignPrefetch) { - console.log(hydratedPrefetch); - const context = req.body.context; - const contextRequest = context.draftOrders?.entry?.[0].resource; - const patient = hydratedPrefetch?.patient; - const prefetchRequest = hydratedPrefetch?.request; - const practitioner = hydratedPrefetch?.practitioner; - const npi = practitioner?.identifier; - - console.log(' MedicationRequest: ' + prefetchRequest?.id); - console.log(' Practitioner: ' + practitioner?.id + ' NPI: ' + npi); - console.log(' Patient: ' + patient?.id); - - // verify there is a contextRequest - if (!contextRequest) { - res.json(buildErrorCard('DraftOrders does not contain a request')); - return; - } - - // verify a MedicationRequest was sent - if (contextRequest && contextRequest.resourceType !== 'MedicationRequest') { - res.json(buildErrorCard('DraftOrders does not contain a MedicationRequest')); - return; - } - - // verify ids - if ( - patient?.id && - patient.id.replace('Patient/', '') !== context.patientId.replace('Patient/', '') - ) { - res.json(buildErrorCard('Context patientId does not match prefetch Patient ID')); - return; - } - if ( - practitioner?.id && - practitioner.id.replace('Practitioner/', '') !== context.userId.replace('Practitioner/', '') - ) { - res.json(buildErrorCard('Context userId does not match prefetch Practitioner ID')); - return; - } - if ( - prefetchRequest?.id && - contextRequest && - contextRequest.id && - prefetchRequest.id.replace('MedicationRequest/', '') !== - contextRequest.id.replace('MedicationRequest/', '') - ) { - res.json(buildErrorCard('Context draftOrder does not match prefetch MedicationRequest ID')); - return; - } - - const medicationCode = getDrugCodeFromMedicationRequest(contextRequest); - if (!medicationCode) { - return; - } - if (medicationCode && medicationCode?.code) { - // find the drug in the medicationCollection to get the smart links - const drug = await medicationCollection - .findOne({ - code: medicationCode.code, - codeSystem: medicationCode.system - }) - .exec(); - - // find a matching rems case for the patient and this drug to only return needed results - const patientName = patient?.name?.[0]; - const etasu = await remsCaseCollection.findOne({ - patientFirstName: patientName?.given?.[0], - patientLastName: patientName?.family, - patientDOB: patient?.birthDate, - drugCode: medicationCode?.code - }); - - const returnCard = validCodes.some(e => { - return e.code === medicationCode.code && e.system === medicationCode.system; - }); - if (returnCard) { - const cardArray: Card[] = []; - const codeRule = codeMap[medicationCode.code]; - for (const rule of codeRule) { - const card = new Card( - rule.summary || medicationCode.display || 'Rems', - CARD_DETAILS, - source, - 'info' - ); - rule.links.forEach(function (e) { - if (e.type == 'absolute') { - // no construction needed - card.addLink(e); - } - }); - - let smartLinkCount = 0; - - // process the smart links from the medicationCollection - // TODO: smart links should be built with discovered questionnaires, not hard coded ones - if (drug) { - for (const requirement of drug.requirements) { - if (requirement.stakeholderType == rule.stakeholderType) { - // only add the link if the form has not already been processed / received - if (etasu) { - let found = false; - for (const metRequirement of etasu.metRequirements) { - if (metRequirement.requirementName == requirement.name) { - found = true; - if (!metRequirement.completed) { - card.addLink( - createSmartLink(requirement.name, requirement.appContext, contextRequest) - ); - smartLinkCount++; - } - } - } - if (!found) { - card.addLink( - createSmartLink(requirement.name, requirement.appContext, contextRequest) - ); - smartLinkCount++; - } - } else { - // add all the required to dispense links if no etasu to check - if (requirement.requiredToDispense) { - card.addLink( - createSmartLink(requirement.name, requirement.appContext, contextRequest) - ); - smartLinkCount++; - } - } - } - } - } - - // only add the card if there are smart links to needed forms - if (smartLinkCount > 0) { - cardArray.push(card); - } - } - res.json({ - cards: cardArray - }); - } else { - res.json(buildErrorCard('Unsupported code')); - } - } else { - res.json(buildErrorCard('MedicationRequest does not contain a code')); - } - } console.log('REMS order-sign hook'); - try { - const fhirUrl = req.body.fhirServer; - const fhirAuth = req.body.fhirAuthorization; - if (fhirUrl && fhirAuth && fhirAuth.access_token) { - hydrate(getFhirResource, hookPrefetch, req.body).then( - (hydratedPrefetch: OrderSignPrefetch) => { - handleCard(hydratedPrefetch); - } - ); - } else { - if (req.body.prefetch) { - handleCard(req.body.prefetch); - } else { - handleCard({}); - } - } - } catch (error) { - console.log(error); - res.json(buildErrorCard('Unknown Error')); - } + const contextRequest = req.body.context.draftOrders?.entry?.[0]?.resource; + handleHook(req, res, hookPrefetch, contextRequest, handleCardOrder); }; export default { definition, handler }; diff --git a/src/hooks/rems.patientview.ts b/src/hooks/rems.patientview.ts index b84e6dbb..05e26530 100644 --- a/src/hooks/rems.patientview.ts +++ b/src/hooks/rems.patientview.ts @@ -2,15 +2,16 @@ import Card from '../cards/Card'; import { PatientViewHook, SupportedHooks, - PatientViewPrefetch + PatientViewPrefetch, + HookPrefetch } from '../rems-cds-hooks/resources/HookTypes'; import { medicationCollection, remsCaseCollection } from '../fhir/models'; import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; -import { Bundle, MedicationRequest } from 'fhir/r4'; +import { Bundle, FhirResource, MedicationRequest } from 'fhir/r4'; import { Link } from '../cards/Card'; import config from '../config'; import { hydrate } from '../rems-cds-hooks/prefetch/PrefetchHydrator'; -import { codeMap, CARD_DETAILS, getDrugCodeFromMedicationRequest } from './hookResources'; +import { codeMap, CARD_DETAILS, getDrugCodeFromMedicationRequest, createSmartLink, handleHook } from './hookResources'; import axios from 'axios'; interface TypedRequestBody extends Express.Request { @@ -43,36 +44,6 @@ function buildErrorCard(reason: string) { } const handler = (req: TypedRequestBody, res: any) => { - function getFhirResource(token: string) { - const ehrUrl = `${req.body.fhirServer}/${token}`; - const access_token = req.body.fhirAuthorization?.access_token; - const options = { - method: 'GET', - headers: { - Authorization: `Bearer ${access_token}` - } - }; - const response = axios(ehrUrl, options); - return response.then(e => { - return e.data; - }); - } - - function createSmartLink( - requirementName: string, - appContext: string, - request: MedicationRequest | undefined - ) { - const newLink: Link = { - label: requirementName + ' Form', - url: new URL(config.smart.endpoint), - type: 'smart', - appContext: `${appContext}&order=${JSON.stringify(request)}&coverage=${ - request?.insurance?.[0].reference - }` - }; - return newLink; - } // process the MedicationRequests to add the Medication into contained resources function processMedicationRequests(medicationRequestsBundle: Bundle) { @@ -117,44 +88,25 @@ const handler = (req: TypedRequestBody, res: any) => { }); } - async function handleCard(hydratedPrefetch: PatientViewPrefetch) { - console.log(hydratedPrefetch); - const context = req.body.context; - const patient = hydratedPrefetch?.patient; - const practitioner = hydratedPrefetch?.practitioner; - const medicationRequestsBundle = hydratedPrefetch?.medicationRequests; - const npi = practitioner?.identifier; - - console.log(' Practitioner: ' + practitioner?.id + ' NPI: ' + npi); - console.log(' Patient: ' + patient?.id); - - // verify ids - if ( - patient?.id && - patient.id.replace('Patient/', '') !== context.patientId.replace('Patient/', '') - ) { - res.json(buildErrorCard('Context patientId does not match prefetch Patient ID')); - return; - } - if ( - practitioner?.id && - practitioner.id.replace('Practitioner/', '') !== context.userId.replace('Practitioner/', '') - ) { - res.json(buildErrorCard('Context userId does not match prefetch Practitioner ID')); - return; - } - + async function handleCard(res: any, + hookPrefetch: HookPrefetch | undefined, + contextRequest: FhirResource | undefined, + patient: FhirResource | undefined) { //TODO: should we add the other pdf information links to the card, or just have the smart links? + const medResource = hookPrefetch?.medicationRequests; + const medicationRequestsBundle = medResource?.resourceType === 'Bundle' ? medResource : undefined; + // create empty card array const cardArray: Card[] = []; // find all matching rems cases for the patient - const patientName = patient?.name?.[0]; + const patientName = patient?.resourceType === 'Patient' ? patient?.name?.[0]: undefined; + const patientBirth = patient?.resourceType === 'Patient' ? patient?.birthDate : undefined; const remsCaseList = await remsCaseCollection.find({ patientFirstName: patientName?.given?.[0], patientLastName: patientName?.family, - patientDOB: patient?.birthDate + patientDOB: patientBirth }); // loop through all the rems cases in the list @@ -238,27 +190,9 @@ const handler = (req: TypedRequestBody, res: any) => { }); } - console.log('REMS patient-view hook'); - try { - const fhirUrl = req.body.fhirServer; - const fhirAuth = req.body.fhirAuthorization; - if (fhirUrl && fhirAuth && fhirAuth.access_token) { - hydrate(getFhirResource, hookPrefetch, req.body).then( - (hydratedPrefetch: PatientViewPrefetch) => { - handleCard(hydratedPrefetch); - } - ); - } else { - if (req.body.prefetch) { - handleCard(req.body.prefetch); - } else { - handleCard({}); - } - } - } catch (error) { - console.log(error); - res.json(buildErrorCard('Unknown Error')); - } + // contextRequest is undefined + handleHook(req, res, hookPrefetch, undefined, handleCard); + }; export default { definition, handler }; From 4772e35b873fbbf9b10925be17fb3f6784a9a277 Mon Sep 17 00:00:00 2001 From: KeeyanGhoreshi Date: Fri, 5 Jan 2024 13:45:01 -0500 Subject: [PATCH 2/3] linting --- src/hooks/hookResources.ts | 54 +++++++++++++++++++++++------------ src/hooks/rems.orderselect.ts | 11 ++----- src/hooks/rems.ordersign.ts | 10 ++----- src/hooks/rems.patientview.ts | 21 +++++++++----- 4 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index 944595d8..ff7588f6 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -1,6 +1,10 @@ import { MedicationRequest, Coding, FhirResource, Identifier } from 'fhir/r4'; import Card, { Link } from '../cards/Card'; -import { HookPrefetch, OrderSignPrefetch, TypedRequestBody } from '../rems-cds-hooks/resources/HookTypes'; +import { + HookPrefetch, + OrderSignPrefetch, + TypedRequestBody +} from '../rems-cds-hooks/resources/HookTypes'; import config from '../config'; import { medicationCollection, remsCaseCollection } from '../fhir/models'; @@ -11,8 +15,8 @@ type HandleCallback = ( res: any, hydratedPrefetch: HookPrefetch | undefined, contextRequest: FhirResource | undefined, - patient: FhirResource | undefined, - ) => Promise; + patient: FhirResource | undefined +) => Promise; export interface CardRule { links: Link[]; summary?: string; @@ -267,10 +271,12 @@ export function buildErrorCard(reason: string) { } // handles order-sign and order-select currently -export async function handleCardOrder(res: any, +export async function handleCardOrder( + res: any, hydratedPrefetch: HookPrefetch | undefined, contextRequest: FhirResource | undefined, - patient: FhirResource | undefined) { + patient: FhirResource | undefined +) { const prefetchRequest = hydratedPrefetch?.request; console.log(' MedicationRequest: ' + prefetchRequest?.id); // verify there is a contextRequest @@ -295,7 +301,10 @@ export async function handleCardOrder(res: any, return; } - const medicationCode = contextRequest && contextRequest.resourceType === 'MedicationRequest' && getDrugCodeFromMedicationRequest(contextRequest); + const medicationCode = + contextRequest && + contextRequest.resourceType === 'MedicationRequest' && + getDrugCodeFromMedicationRequest(contextRequest); if (!medicationCode) { return; } @@ -309,8 +318,8 @@ export async function handleCardOrder(res: any, .exec(); // find a matching rems case for the patient and this drug to only return needed results - const patientName = patient?.resourceType==='Patient' ? patient?.name?.[0] : undefined; - const patientBirth = patient?.resourceType==='Patient' ? patient?.birthDate : undefined; + const patientName = patient?.resourceType === 'Patient' ? patient?.name?.[0] : undefined; + const patientBirth = patient?.resourceType === 'Patient' ? patient?.birthDate : undefined; const etasu = await remsCaseCollection.findOne({ patientFirstName: patientName?.given?.[0], patientLastName: patientName?.family, @@ -396,7 +405,13 @@ export async function handleCardOrder(res: any, // handles preliminary card creation. ALL hooks should go through this function. // make sure code here is applicable to all supported hooks. -export async function handleCard(req: TypedRequestBody, res: any, hydratedPrefetch: HookPrefetch, contextRequest: FhirResource | undefined, callback: HandleCallback) { +export async function handleCard( + req: TypedRequestBody, + res: any, + hydratedPrefetch: HookPrefetch, + contextRequest: FhirResource | undefined, + callback: HandleCallback +) { const context = req.body.context; const patient = hydratedPrefetch?.patient; const practitioner = hydratedPrefetch?.practitioner; @@ -418,22 +433,24 @@ export async function handleCard(req: TypedRequestBody, res: any, hydratedPrefet res.json(buildErrorCard('Context userId does not match prefetch Practitioner ID')); return; } - return callback(res, hydratedPrefetch, contextRequest, patient) - + return callback(res, hydratedPrefetch, contextRequest, patient); } - // handles all hooks, any supported hook should pass through this function -export function handleHook(req: TypedRequestBody, res: any, hookPrefetch: ServicePrefetch, contextRequest: FhirResource | undefined, callback: HandleCallback) { +export function handleHook( + req: TypedRequestBody, + res: any, + hookPrefetch: ServicePrefetch, + contextRequest: FhirResource | undefined, + callback: HandleCallback +) { try { const fhirUrl = req.body.fhirServer; const fhirAuth = req.body.fhirAuthorization; if (fhirUrl && fhirAuth && fhirAuth.access_token) { - hydrate(getFhirResource, hookPrefetch, req.body).then( - (hydratedPrefetch) => { - handleCard(req, res, hydratedPrefetch, contextRequest, callback); - } - ); + hydrate(getFhirResource, hookPrefetch, req.body).then(hydratedPrefetch => { + handleCard(req, res, hydratedPrefetch, contextRequest, callback); + }); } else { if (req.body.prefetch) { handleCard(req, res, req.body.prefetch, contextRequest, callback); @@ -446,4 +463,3 @@ export function handleHook(req: TypedRequestBody, res: any, hookPrefetch: Servic res.json(buildErrorCard('Unknown Error')); } } - diff --git a/src/hooks/rems.orderselect.ts b/src/hooks/rems.orderselect.ts index f4c5aaa5..9300c1bd 100644 --- a/src/hooks/rems.orderselect.ts +++ b/src/hooks/rems.orderselect.ts @@ -1,12 +1,6 @@ -import { - SupportedHooks, - OrderSelectHook -} from '../rems-cds-hooks/resources/HookTypes'; +import { SupportedHooks, OrderSelectHook } from '../rems-cds-hooks/resources/HookTypes'; import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; -import { - handleCardOrder, - handleHook -} from './hookResources'; +import { handleCardOrder, handleHook } from './hookResources'; interface TypedRequestBody extends Express.Request { body: OrderSelectHook; @@ -34,7 +28,6 @@ const handler = (req: TypedRequestBody, res: any) => { } })[0].resource; handleHook(req, res, hookPrefetch, contextRequest, handleCardOrder); - }; export default { definition, handler }; diff --git a/src/hooks/rems.ordersign.ts b/src/hooks/rems.ordersign.ts index 1464d154..3e0fedc0 100644 --- a/src/hooks/rems.ordersign.ts +++ b/src/hooks/rems.ordersign.ts @@ -1,11 +1,6 @@ -import { - OrderSignHook, - SupportedHooks} from '../rems-cds-hooks/resources/HookTypes'; +import { OrderSignHook, SupportedHooks } from '../rems-cds-hooks/resources/HookTypes'; import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; -import { - handleCardOrder, - handleHook -} from './hookResources'; +import { handleCardOrder, handleHook } from './hookResources'; interface TypedRequestBody extends Express.Request { body: OrderSignHook; @@ -24,7 +19,6 @@ const definition: CdsService = { }; const handler = (req: TypedRequestBody, res: any) => { - console.log('REMS order-sign hook'); const contextRequest = req.body.context.draftOrders?.entry?.[0]?.resource; handleHook(req, res, hookPrefetch, contextRequest, handleCardOrder); diff --git a/src/hooks/rems.patientview.ts b/src/hooks/rems.patientview.ts index 05e26530..da123210 100644 --- a/src/hooks/rems.patientview.ts +++ b/src/hooks/rems.patientview.ts @@ -11,7 +11,13 @@ import { Bundle, FhirResource, MedicationRequest } from 'fhir/r4'; import { Link } from '../cards/Card'; import config from '../config'; import { hydrate } from '../rems-cds-hooks/prefetch/PrefetchHydrator'; -import { codeMap, CARD_DETAILS, getDrugCodeFromMedicationRequest, createSmartLink, handleHook } from './hookResources'; +import { + codeMap, + CARD_DETAILS, + getDrugCodeFromMedicationRequest, + createSmartLink, + handleHook +} from './hookResources'; import axios from 'axios'; interface TypedRequestBody extends Express.Request { @@ -44,7 +50,6 @@ function buildErrorCard(reason: string) { } const handler = (req: TypedRequestBody, res: any) => { - // process the MedicationRequests to add the Medication into contained resources function processMedicationRequests(medicationRequestsBundle: Bundle) { medicationRequestsBundle?.entry?.forEach(entry => { @@ -88,20 +93,23 @@ const handler = (req: TypedRequestBody, res: any) => { }); } - async function handleCard(res: any, + async function handleCard( + res: any, hookPrefetch: HookPrefetch | undefined, contextRequest: FhirResource | undefined, - patient: FhirResource | undefined) { + patient: FhirResource | undefined + ) { //TODO: should we add the other pdf information links to the card, or just have the smart links? const medResource = hookPrefetch?.medicationRequests; - const medicationRequestsBundle = medResource?.resourceType === 'Bundle' ? medResource : undefined; + const medicationRequestsBundle = + medResource?.resourceType === 'Bundle' ? medResource : undefined; // create empty card array const cardArray: Card[] = []; // find all matching rems cases for the patient - const patientName = patient?.resourceType === 'Patient' ? patient?.name?.[0]: undefined; + const patientName = patient?.resourceType === 'Patient' ? patient?.name?.[0] : undefined; const patientBirth = patient?.resourceType === 'Patient' ? patient?.birthDate : undefined; const remsCaseList = await remsCaseCollection.find({ patientFirstName: patientName?.given?.[0], @@ -192,7 +200,6 @@ const handler = (req: TypedRequestBody, res: any) => { // contextRequest is undefined handleHook(req, res, hookPrefetch, undefined, handleCard); - }; export default { definition, handler }; From 33e4ce10efd26d2471443edd571c0357e608d267 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Wed, 10 Jan 2024 15:11:00 -0500 Subject: [PATCH 3/3] Update submodule reference back to dev --- src/rems-cds-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index da1a46d2..94a78e8c 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit da1a46d22b1d5ded3afe1be73dde06d20d080b53 +Subproject commit 94a78e8cd27734938ec41858f8d0ca4028da5f21