Skip to content
6 changes: 5 additions & 1 deletion src/fhir/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
patientFirstName: string;
patientLastName: string;
patientDOB: string;
medicationRequestReference?: string;
originatingFhirServer?: string;
metRequirements: Partial<MetRequirements>[];
}

Expand Down Expand Up @@ -96,6 +98,8 @@
patientLastName: { type: String },
patientDOB: { type: String },
drugCode: { type: String },
medicationRequestReference: { type: String },
originatingFhirServer: { type: String },
Comment thread
plarocque4 marked this conversation as resolved.
metRequirements: [
{
metRequirementId: { type: String },
Expand All @@ -107,4 +111,4 @@
]
});

export const remsCaseCollection = model<RemsCase>('RemsCaseCollection', remsCaseCollectionSchema);
export const remsCaseCollection = model<RemsCase>('RemsCaseCollection', remsCaseCollectionSchema);

Check failure on line 114 in src/fhir/models.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Insert `⏎`

Check failure on line 114 in src/fhir/models.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Insert `⏎`
47 changes: 41 additions & 6 deletions src/hooks/hookResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import axios from 'axios';
import { ServicePrefetch } from '../rems-cds-hooks/resources/CdsService';
import { hydrate } from '../rems-cds-hooks/prefetch/PrefetchHydrator';
import { createNewRemsCaseFromCDSHook } from '../lib/etasu';

type HandleCallback = (
res: any,
hydratedPrefetch: HookPrefetch | undefined,
contextRequest: FhirResource | undefined,
patient: FhirResource | undefined
patient: FhirResource | undefined,
fhirServer?: string
) => Promise<void>;

export interface CardRule {
Expand Down Expand Up @@ -366,7 +368,8 @@
res: any,
hydratedPrefetch: HookPrefetch | undefined,
contextRequest: FhirResource | undefined,
resource: FhirResource | undefined
resource: FhirResource | undefined,
fhirServer?: string
): Promise<void> => {
const patient = resource?.resourceType === 'Patient' ? resource : undefined;

Expand Down Expand Up @@ -396,13 +399,43 @@
// find a matching REMS case for the patient and this drug to only return needed results
const patientName = patient?.name?.[0];
const patientBirth = patient?.birthDate;
const remsCase = await remsCaseCollection.findOne({
let remsCase = await remsCaseCollection.findOne({
patientFirstName: patientName?.given?.[0],
patientLastName: patientName?.family,
patientDOB: patientBirth,
drugCode: code
});

// If no REMS case exists and drug has requirements, create case with all requirements unmet
if (!remsCase && drug && patient && request) {
const requiresCase = drug.requirements.some(req => req.requiredToDispense);

if (requiresCase && fhirServer) {

Check failure on line 413 in src/hooks/hookResources.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·····`

Check failure on line 413 in src/hooks/hookResources.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·····`
try {
const patientReference = `Patient/${patient.id}`;
const medicationRequestReference = `${request.resourceType}/${request.id}`;
const practitionerReference = request.requester?.reference || '';
const pharmacistReference = pharmacy?.id ? `HealthcareService/${pharmacy.id}` : '';

const newCase = await createNewRemsCaseFromCDSHook(
patient,
drug,
practitionerReference,
pharmacistReference,
patientReference,
medicationRequestReference,
fhirServer
);

remsCase = newCase;

console.log(`Created REMS case from CDS Hook with originating server: ${fhirServer}`);
} catch (error) {
console.error('Failed to create REMS case from CDS Hook:', error);
}
}
}

const codeRule = (code && codeMap[code]) || [];

const cardPromises = codeRule.map(
Expand Down Expand Up @@ -594,6 +627,7 @@
const context = req.body.context;
const patient = hydratedPrefetch?.patient;
const practitioner = hydratedPrefetch?.practitioner;
const fhirServer = req.body.fhirServer;

console.log(' Patient: ' + patient?.id);

Expand All @@ -612,7 +646,7 @@
res.json(buildErrorCard('Context userId does not match prefetch Practitioner ID'));
return;
}
return callback(res, hydratedPrefetch, contextRequest, patient);
return callback(res, hydratedPrefetch, contextRequest, patient, fhirServer);
}

// handles all hooks, any supported hook should pass through this function
Expand Down Expand Up @@ -836,7 +870,8 @@
res: any,
hookPrefetch: HookPrefetch | undefined,
_contextRequest: FhirResource | undefined,
resource: FhirResource | undefined
resource: FhirResource | undefined,
fhirServer?: string

Check warning on line 874 in src/hooks/hookResources.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

'fhirServer' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 874 in src/hooks/hookResources.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

'fhirServer' is defined but never used. Allowed unused args must match /^_/u
): Promise<void> => {
const patient = resource?.resourceType === 'Patient' ? resource : undefined;
const medResource = hookPrefetch?.medicationRequests;
Expand Down Expand Up @@ -962,4 +997,4 @@
]
};
return taskResource;
}
}

Check failure on line 1000 in src/hooks/hookResources.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Insert `⏎`

Check failure on line 1000 in src/hooks/hookResources.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Insert `⏎`
172 changes: 172 additions & 0 deletions src/lib/communication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Communication, Task, Patient, MedicationRequest } from 'fhir/r4';
import axios from 'axios';
import config from '../config';
import { uid } from 'uid';
import container from './winston';
import { createQuestionnaireCompletionTask } from '../hooks/hookResources';
import { Requirement } from '../fhir/models';

const logger = container.get('application');


Check failure on line 11 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `⏎`

Check failure on line 11 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `⏎`
export async function sendCommunicationToEHR(
remsCase: any,
medication: any,
outstandingRequirements: any[]
): Promise<void> {
try {
logger.info(`Creating Communication for case ${remsCase.case_number}`);

Check failure on line 19 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `····`

Check failure on line 19 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `····`
// Create patient object from REMS case
const patient: Patient = {
resourceType: 'Patient',
id: `${remsCase.patientFirstName}-${remsCase.patientLastName}`.replace(/\s+/g, '-'),
name: [
{
given: [remsCase.patientFirstName],
family: remsCase.patientLastName
}
],
birthDate: remsCase.patientDOB
};

// Get the stored MedicationRequest reference
const medicationRequestRef = remsCase.medicationRequestReference;

// Create a minimal MedicationRequest for task context
const medicationRequest: MedicationRequest = {
resourceType: 'MedicationRequest',
status: 'active',
intent: 'order',
medicationCodeableConcept: {
coding: [
{
system: 'http://www.nlm.nih.gov/research/umls/rxnorm',
code: remsCase.drugCode,
display: remsCase.drugName
}
]
},
subject: {
reference: `Patient/${patient.id}`
},
requester: {
reference: remsCase.metRequirements.find((mr: any) =>
mr.requirementName?.toLowerCase().includes('prescriber')
)?.stakeholderId
}
};

// Create Tasks for each outstanding requirement
const tasks: Task[] = [];
for (const outstandingReq of outstandingRequirements) {
const requirement = outstandingReq.requirement ||

Check failure on line 63 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Replace `·outstandingReq.requirement·||·` with `⏎········outstandingReq.requirement·||`

Check failure on line 63 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Replace `·outstandingReq.requirement·||·` with `⏎········outstandingReq.requirement·||`
medication.requirements.find((r: Requirement) => r.name === outstandingReq.name);

Check failure on line 65 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `······`

Check failure on line 65 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `······`
if (requirement && requirement.appContext) {
const questionnaireUrl = requirement.appContext;
const task = createQuestionnaireCompletionTask(
requirement,
patient,
questionnaireUrl,
medicationRequest
);
task.id = `task-${uid()}`;
tasks.push(task);
}
}

// Create Communication resource
const communication: Communication = {
resourceType: 'Communication',
id: `comm-${uid()}`,
status: 'completed',

Check failure on line 83 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·`

Check failure on line 83 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·`
category: [
{
coding: [
{
system: 'http://terminology.hl7.org/CodeSystem/communication-category',
code: 'notification',
display: 'Notification'
}
]
}
],
priority: 'urgent',

Check failure on line 95 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·`

Check failure on line 95 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·`
subject: {
reference: `Patient/${patient.id}`,
display: `${remsCase.patientFirstName} ${remsCase.patientLastName}`
},
topic: {
coding: [
{
system: 'http://terminology.hl7.org/CodeSystem/communication-topic',
code: 'progress-update',
display: 'Progress Update'
}
],
text: 'Outstanding REMS Requirements for Medication Dispensing'
},
sent: new Date().toISOString(),

Check failure on line 110 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·`

Check failure on line 110 in src/lib/communication.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Delete `·`
recipient: [
{
reference: medicationRequest.requester?.reference || ''
}
],
sender: {
reference: 'Organization/rems-admin',
display: config.server?.name || 'REMS Administrator'
},
payload: [
{
contentString: `Medication dispensing authorization DENIED for ${remsCase.drugName}.\n\n` +
`The following REMS requirements must be completed:\n\n` +
outstandingRequirements
.map((req, idx) => `${idx + 1}. ${req.name} (${req.stakeholder})`)
.join('\n') +
`\n\nCase Number: ${remsCase.case_number}\n` +
`Patient: ${remsCase.patientFirstName} ${remsCase.patientLastName} (DOB: ${remsCase.patientDOB})`
}
],
contained: tasks,
about: [
{
reference: medicationRequestRef,
display: `Prescription for ${remsCase.drugName}`
},
...tasks.map(task => ({
reference: `#${task.id}`,
display: task.description
}))
]
};

// Determine EHR endpoint: use originatingFhirServer if available, otherwise default
const ehrEndpoint = remsCase.originatingFhirServer ||
config.fhirServerConfig?.auth?.resourceServer;

if (!ehrEndpoint) {
logger.warn('No EHR endpoint configured, Communication not sent');
return;
}

// Send Communication to EHR
logger.info(`Sending Communication to EHR: ${ehrEndpoint}`);

const response = await axios.post(`${ehrEndpoint}/Communication`, communication, {
headers: {
'Content-Type': 'application/fhir+json'
}
});

if (response.status === 200 || response.status === 201) {
logger.info(`Communication successfully sent to EHR for case ${remsCase.case_number}`);
} else {
logger.warn(`Unexpected response status from EHR: ${response.status}`);
}

} catch (error: any) {
logger.error(`Failed to send Communication to EHR: ${error.message}`);
throw error;
}
}
Loading
Loading