Skip to content
Open
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
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 },
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