Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 149 additions & 1 deletion src/hooks/hookResources.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions src/hooks/rems.encounterstart.ts
Original file line number Diff line number Diff line change
@@ -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 };
174 changes: 4 additions & 170 deletions src/hooks/rems.patientview.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 };
2 changes: 1 addition & 1 deletion src/rems-cds-hooks
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 })
);
Expand Down
Loading