diff --git a/src/fhir/guidanceResponseUtilities.ts b/src/fhir/guidanceResponseUtilities.ts index 1743ead3..b6806e18 100644 --- a/src/fhir/guidanceResponseUtilities.ts +++ b/src/fhir/guidanceResponseUtilities.ts @@ -28,6 +28,7 @@ export class GuidanceResponseUtilities { etasu: Pick< RemsCase, | 'drugName' + | 'auth_number' | 'status' | 'drugCode' | 'patientFirstName' @@ -77,7 +78,7 @@ export class GuidanceResponseUtilities { outputParameters.parameter?.push(parameter); } }); - + outputParameters.parameter?.push({ name: 'auth_number', valueString: etasu?.auth_number }); return outputParameters; } @@ -85,6 +86,7 @@ export class GuidanceResponseUtilities { etasu: Pick< RemsCase, | 'drugName' + | 'auth_number' | 'status' | 'drugCode' | 'patientFirstName' diff --git a/src/fhir/models.ts b/src/fhir/models.ts index b1015386..566f5e26 100644 --- a/src/fhir/models.ts +++ b/src/fhir/models.ts @@ -32,6 +32,7 @@ export interface MetRequirements extends Document { export interface RemsCase extends Document { case_number: string; + auth_number: string; status: string; drugName: string; drugCode: string; @@ -88,6 +89,7 @@ export const metRequirementsCollection = model( const remsCaseCollectionSchema = new Schema({ case_number: { type: String }, + auth_number: { type: String }, status: { type: String }, drugName: { type: String }, patientFirstName: { type: String }, diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index 42f4ee91..32535350 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -1,4 +1,4 @@ -import { MedicationRequest, Coding, FhirResource, Task, Patient } from 'fhir/r4'; +import { MedicationRequest, Coding, FhirResource, Task, Patient, Bundle } from 'fhir/r4'; import Card, { Link, Suggestion, Action } from '../cards/Card'; import { HookPrefetch, TypedRequestBody } from '../rems-cds-hooks/resources/HookTypes'; import config from '../config'; @@ -503,6 +503,154 @@ export function handleHook( } } +// process the MedicationRequests to add the Medication into contained resources +function processMedicationRequests(medicationRequestsBundle: Bundle) { + medicationRequestsBundle?.entry?.forEach(entry => { + if (entry?.resource?.resourceType === 'MedicationRequest') { + if (entry?.resource?.medicationReference) { + const medicationReference = entry?.resource?.medicationReference; + medicationRequestsBundle?.entry?.forEach(e => { + if (e?.resource?.resourceType === 'Medication') { + if ( + e?.resource?.resourceType + '/' + e?.resource?.id === + medicationReference?.reference + ) { + if (entry) { + if (entry.resource) { + const reference = e?.resource; + const request = entry.resource as MedicationRequest; + + // add the reference as a contained resource to the request + if (!request?.contained) { + request.contained = []; + request.contained.push(reference); + } else { + // only add to contained if not already in there + let found = false; + request.contained.forEach(c => { + if (c.id === reference.id) { + found = true; + } + }); + if (!found) { + request.contained.push(reference); + } + } + } + } + } + } + }); + } + } + }); +} + +// handles order-sign and order-select currently +export async function handleCardEncounter( + 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?.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: patientBirth + }); + + // loop through all the rems cases in the list + for (const remsCase of remsCaseList) { + // find the drug in the medicationCollection that matches the REMS case to get the smart links + const drug = await medicationCollection + .findOne({ + code: remsCase.drugCode, + name: remsCase.drugName + }) + .exec(); + + // get the rule summary from the codemap + const codeRule = codeMap[remsCase.drugCode]; + let summary = ''; + for (const rule of codeRule) { + if (rule.stakeholderType === 'patient') { + summary = rule.summary || remsCase.drugName || 'Rems'; + } + } + + // create the card + let smartLinkCount = 0; + const card = new Card(summary, CARD_DETAILS, source, 'info'); + + // process the MedicationRequests to add the Medication into contained resources + if (medicationRequestsBundle) { + processMedicationRequests(medicationRequestsBundle); + } + + // find the matching MedicationRequest for the context + const request = medicationRequestsBundle?.entry?.find(entry => { + if (entry.resource) { + if (entry.resource.resourceType === 'MedicationRequest') { + const medReq: MedicationRequest = entry.resource; + const medicationCode = getDrugCodeFromMedicationRequest(medReq); + return remsCase.drugCode === medicationCode?.code; + } + } + })?.resource; + + // if no valid request or not a MedicationRequest found skip this REMS case + if (!request || (request && request.resourceType !== 'MedicationRequest')) { + continue; + } + + // loop through all of the ETASU requirements for this drug + const requirements = drug?.requirements || []; + for (const requirement of requirements) { + // find all of the matching patient forms + if (requirement?.stakeholderType === 'patient') { + let found = false; + // match the requirement to the metRequirement of the REMS case + for (const metRequirement of remsCase.metRequirements) { + // only add the link if the form is still needed to be completed + if (metRequirement.requirementName === requirement.name) { + found = true; + if (!metRequirement.completed) { + card.addLink(createSmartLink(requirement.name, requirement.appContext, request)); + smartLinkCount++; + } + } + } + + // if not in the list of metRequirements, add it as well + if (!found) { + card.addLink(createSmartLink(requirement.name, requirement.appContext, request)); + smartLinkCount++; + } + } + } + + // only add the card to the list if there is a link + if (smartLinkCount > 0) { + cardArray.push(card); + } + } + + res.json({ + cards: cardArray + }); +} + export function createQuestionnaireSuggestion( card: Card, requirement: Requirement, diff --git a/src/hooks/rems.encounterstart.ts b/src/hooks/rems.encounterstart.ts new file mode 100644 index 00000000..ca110701 --- /dev/null +++ b/src/hooks/rems.encounterstart.ts @@ -0,0 +1,29 @@ +import { EncounterStartHook, SupportedHooks } from '../rems-cds-hooks/resources/HookTypes'; +import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; +import { handleCardEncounter, handleHook } from './hookResources'; + +interface TypedRequestBody extends Express.Request { + body: EncounterStartHook; +} + +const hookPrefetch: ServicePrefetch = { + patient: 'Patient/{{context.patientId}}', + practitioner: '{{context.userId}}', + medicationRequests: + 'MedicationRequest?subject={{context.patientId}}&_include=MedicationRequest:medication' +}; +const definition: CdsService = { + id: 'rems-encounter-start', + hook: SupportedHooks.ENCOUNTER_START, + title: 'REMS Requirement Lookup', + description: 'REMS Requirement Lookup', + prefetch: hookPrefetch +}; + +const handler = (req: TypedRequestBody, res: any) => { + console.log('REMS encounter-start hook'); + const contextRequest = undefined; + handleHook(req, res, hookPrefetch, contextRequest, handleCardEncounter); +}; + +export default { definition, handler }; diff --git a/src/hooks/rems.patientview.ts b/src/hooks/rems.patientview.ts index 9be0cd3c..007e3ee6 100644 --- a/src/hooks/rems.patientview.ts +++ b/src/hooks/rems.patientview.ts @@ -1,20 +1,6 @@ -import Card from '../cards/Card'; -import { - PatientViewHook, - SupportedHooks, - HookPrefetch -} from '../rems-cds-hooks/resources/HookTypes'; -import { medicationCollection, remsCaseCollection } from '../fhir/models'; +import { PatientViewHook, SupportedHooks } from '../rems-cds-hooks/resources/HookTypes'; import { ServicePrefetch, CdsService } from '../rems-cds-hooks/resources/CdsService'; -import { Bundle, FhirResource, MedicationRequest } from 'fhir/r4'; - -import { - codeMap, - CARD_DETAILS, - getDrugCodeFromMedicationRequest, - createSmartLink, - handleHook -} from './hookResources'; +import { handleCardEncounter, handleHook } from './hookResources'; interface TypedRequestBody extends Express.Request { body: PatientViewHook; @@ -33,163 +19,11 @@ 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') -}; const handler = (req: TypedRequestBody, res: any) => { console.log('REMS patient-view hook'); - // process the MedicationRequests to add the Medication into contained resources - function processMedicationRequests(medicationRequestsBundle: Bundle) { - medicationRequestsBundle?.entry?.forEach(entry => { - if (entry?.resource?.resourceType === 'MedicationRequest') { - if (entry?.resource?.medicationReference) { - const medicationReference = entry?.resource?.medicationReference; - medicationRequestsBundle?.entry?.forEach(e => { - if (e?.resource?.resourceType === 'Medication') { - if ( - e?.resource?.resourceType + '/' + e?.resource?.id === - medicationReference?.reference - ) { - if (entry) { - if (entry.resource) { - const reference = e?.resource; - const request = entry.resource as MedicationRequest; - - // add the reference as a contained resource to the request - if (!request?.contained) { - request.contained = []; - request.contained.push(reference); - } else { - // only add to contained if not already in there - let found = false; - request.contained.forEach(c => { - if (c.id === reference.id) { - found = true; - } - }); - if (!found) { - request.contained.push(reference); - } - } - } - } - } - } - }); - } - } - }); - } - - 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?.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: patientBirth - }); - - // loop through all the rems cases in the list - for (const remsCase of remsCaseList) { - // find the drug in the medicationCollection that matches the REMS case to get the smart links - const drug = await medicationCollection - .findOne({ - code: remsCase.drugCode, - name: remsCase.drugName - }) - .exec(); - - // get the rule summary from the codemap - const codeRule = codeMap[remsCase.drugCode]; - let summary = ''; - for (const rule of codeRule) { - if (rule.stakeholderType === 'patient') { - summary = rule.summary || remsCase.drugName || 'Rems'; - } - } - - // create the card - let smartLinkCount = 0; - const card = new Card(summary, CARD_DETAILS, source, 'info'); - - // process the MedicationRequests to add the Medication into contained resources - if (medicationRequestsBundle) { - processMedicationRequests(medicationRequestsBundle); - } - - // find the matching MedicationRequest for the context - const request = medicationRequestsBundle?.entry?.filter(entry => { - if (entry.resource) { - if (entry.resource.resourceType === 'MedicationRequest') { - const medReq: MedicationRequest = entry.resource; - const medicationCode = getDrugCodeFromMedicationRequest(medReq); - return remsCase.drugCode === medicationCode?.code; - } - } - })[0].resource; - - // if no valid request or not a MedicationRequest found skip this REMS case - if (!request || (request && request.resourceType !== 'MedicationRequest')) { - continue; - } - - // loop through all of the ETASU requirements for this drug - const requirements = drug?.requirements || []; - for (const requirement of requirements) { - // find all of the matching patient forms - if (requirement?.stakeholderType === 'patient') { - let found = false; - // match the requirement to the metRequirement of the REMS case - for (const metRequirement of remsCase.metRequirements) { - // only add the link if the form is still needed to be completed - if (metRequirement.requirementName === requirement.name) { - found = true; - if (!metRequirement.completed) { - card.addLink(createSmartLink(requirement.name, requirement.appContext, request)); - smartLinkCount++; - } - } - } - - // if not in the list of metRequirements, add it as well - if (!found) { - card.addLink(createSmartLink(requirement.name, requirement.appContext, request)); - smartLinkCount++; - } - } - } - - // only add the card to the list if there is a link - if (smartLinkCount > 0) { - cardArray.push(card); - } - } - - res.json({ - cards: cardArray - }); - } - - // contextRequest is undefined - handleHook(req, res, hookPrefetch, undefined, handleCard); + const contextRequest = undefined; + handleHook(req, res, hookPrefetch, contextRequest, handleCardEncounter); }; export default { definition, handler }; diff --git a/src/lib/etasu.ts b/src/lib/etasu.ts index f4558116..a397c2a7 100644 --- a/src/lib/etasu.ts +++ b/src/lib/etasu.ts @@ -33,6 +33,11 @@ router.get('/met/:caseId', async (req: Request, res: Response) => { res.send(await remsCaseCollection.findOne({ case_number: req.params.caseId })); }); +router.get('/met/auth/:authNumber', async (req: Request, res: Response) => { + console.log('get etasu by authnumber: ' + req.params.authNumber); + res.send(await remsCaseCollection.findOne({ auth_number: req.params.authNumber })); +}); + export const getCaseInfo = async ( remsCaseSearchDict: FilterQuery, medicationSearchDict: FilterQuery @@ -40,6 +45,7 @@ export const getCaseInfo = async ( RemsCase, | 'status' | 'drugName' + | 'auth_number' | 'drugCode' | 'patientFirstName' | 'patientLastName' @@ -60,6 +66,7 @@ export const getCaseInfo = async ( RemsCase, | 'status' | 'drugName' + | 'auth_number' | 'drugCode' | 'patientFirstName' | 'patientLastName' @@ -68,6 +75,7 @@ export const getCaseInfo = async ( > = { status: 'Approved', drugName: drug?.name, + auth_number: remsCaseSearchDict.auth_number || '', drugCode: drug?.code, patientFirstName: remsCaseSearchDict.patientFirstName || '', patientLastName: remsCaseSearchDict.patientLastName || '', @@ -221,6 +229,7 @@ const createMetRequirementAndNewCase = async ( const remsRequest: Pick< RemsCase, | 'case_number' + | 'auth_number' | 'status' | 'drugName' | 'drugCode' @@ -230,6 +239,7 @@ const createMetRequirementAndNewCase = async ( | 'metRequirements' > = { case_number: case_number, + auth_number: '', status: remsRequestCompletedStatus, drugName: drug?.name, drugCode: drug?.code, @@ -377,6 +387,7 @@ const createMetRequirementAndUpdateCase = async ( if (!foundUncompleted && remsRequestToUpdate?.status === 'Pending') { remsRequestToUpdate.status = 'Approved'; + remsRequestToUpdate.auth_number = uid(); await remsRequestToUpdate.save(); } } diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index eb7fd1e1..1bd0ffcb 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit eb7fd1e1dc68d9d14b1de6e01a57262accfaa7c1 +Subproject commit 1bd0ffcbee4ee302160ff7b38093ee96aaf595ef diff --git a/src/server.ts b/src/server.ts index fd5a90ce..ec5c5b0c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,7 @@ import { CdsService } from './rems-cds-hooks/resources/CdsService'; import remsService from './hooks/rems.ordersign'; import orderSelectService from './hooks/rems.orderselect'; import patientViewService from './hooks/rems.patientview'; +import encounterStartService from './hooks/rems.encounterstart'; import { Server } from '@projecttacoma/node-fhir-server-core'; import Etasu from './lib/etasu'; import env from 'env-var'; @@ -100,6 +101,7 @@ class REMSServer extends Server { this.registerService(remsService); this.registerService(orderSelectService); this.registerService(patientViewService); + this.registerService(encounterStartService); this.app.get(discoveryEndpoint, (req: any, res: { json: (arg0: { services: any }) => any }) => res.json({ services: this.services }) ); diff --git a/src/services/guidanceresponse.service.ts b/src/services/guidanceresponse.service.ts index 2ee16646..c0b5ee70 100644 --- a/src/services/guidanceresponse.service.ts +++ b/src/services/guidanceresponse.service.ts @@ -1,7 +1,7 @@ import { FhirUtilities } from '../fhir/utilities'; import { GuidanceResponseUtilities } from '../fhir/guidanceResponseUtilities'; import GuidanceResponseModel from '../lib/schemas/resources/GuidanceResponse'; -import { Parameters, Medication, Patient, MedicationRequest, FhirResource } from 'fhir/r4'; +import { Parameters, Medication, Patient, MedicationRequest } from 'fhir/r4'; import { getCaseInfo } from '../lib/etasu'; module.exports.searchById = async (args: any) => {