Skip to content

Commit f4a0b6e

Browse files
committed
send communication resource to EHR
1 parent 2679296 commit f4a0b6e

File tree

4 files changed

+259
-5
lines changed

4 files changed

+259
-5
lines changed

src/fhir/models.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface RemsCase extends Document {
3939
patientFirstName: string;
4040
patientLastName: string;
4141
patientDOB: string;
42+
medicationRequestReference?: string;
4243
metRequirements: Partial<MetRequirements>[];
4344
}
4445

@@ -96,6 +97,7 @@ const remsCaseCollectionSchema = new Schema<RemsCase>({
9697
patientLastName: { type: String },
9798
patientDOB: { type: String },
9899
drugCode: { type: String },
100+
medicationRequestReference: { type: String },
99101
metRequirements: [
100102
{
101103
metRequirementId: { type: String },
@@ -107,4 +109,4 @@ const remsCaseCollectionSchema = new Schema<RemsCase>({
107109
]
108110
});
109111

110-
export const remsCaseCollection = model<RemsCase>('RemsCaseCollection', remsCaseCollectionSchema);
112+
export const remsCaseCollection = model<RemsCase>('RemsCaseCollection', remsCaseCollectionSchema);

src/lib/dispense_authorization.ts

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import { Router, Response, Request } from 'express';
2+
import {
3+
medicationCollection,
4+
remsCaseCollection,
5+
Requirement
6+
} from '../fhir/models';
7+
import { Communication, Task, Patient, MedicationRequest } from 'fhir/r4';
8+
import axios from 'axios';
9+
import config from '../config';
10+
import { uid } from 'uid';
11+
import container from '../lib/winston';
12+
import { createQuestionnaireCompletionTask } from '../hooks/hookResources';
13+
14+
const router = Router();
15+
const logger = container.get('application');
16+
17+
router.post('/authorize', async (req: Request, res: Response) => {
18+
try {
19+
const { caseNumber } = req.body;
20+
21+
if (!caseNumber) {
22+
return res.status(400).json({ error: 'caseNumber is required' });
23+
}
24+
25+
logger.info(`Dispense authorization check for case: ${caseNumber}`);
26+
27+
// Find the REMS case
28+
const remsCase = await remsCaseCollection.findOne({ case_number: caseNumber });
29+
30+
if (!remsCase) {
31+
logger.warn(`REMS case not found: ${caseNumber}`);
32+
return res.status(404).json({
33+
approved: false,
34+
error: 'Case not found'
35+
});
36+
}
37+
38+
// Get the medication to check requirements
39+
const medication = await medicationCollection.findOne({
40+
code: remsCase.drugCode,
41+
name: remsCase.drugName
42+
});
43+
44+
if (!medication) {
45+
logger.error(`Medication not found: ${remsCase.drugCode}`);
46+
return res.status(500).json({
47+
approved: false,
48+
error: 'Medication not found'
49+
});
50+
}
51+
52+
// Check which requirements are required for dispensing and not completed
53+
const outstandingRequirements: Requirement[] = [];
54+
55+
for (const requirement of medication.requirements) {
56+
if (requirement.requiredToDispense) {
57+
const metRequirement = remsCase.metRequirements.find(
58+
metReq => metReq.requirementName === requirement.name
59+
);
60+
61+
if (!metRequirement || !metRequirement.completed) {
62+
outstandingRequirements.push(requirement);
63+
}
64+
}
65+
}
66+
67+
// If all required requirements are met, approve
68+
if (outstandingRequirements.length === 0) {
69+
logger.info(`All requirements met for case ${caseNumber}. Approving.`);
70+
71+
// Update dispense status
72+
remsCase.dispenseStatus = 'Approved';
73+
await remsCase.save();
74+
75+
return res.status(200).json({ approved: true });
76+
}
77+
78+
// Outstanding requirements - deny and send Communication
79+
logger.info(
80+
`Outstanding requirements for case ${caseNumber}: ${outstandingRequirements
81+
.map(r => r.name)
82+
.join(', ')}`
83+
);
84+
85+
// Create patient object from REMS case
86+
const patient: Patient = {
87+
resourceType: 'Patient',
88+
id: `${remsCase.patientFirstName}-${remsCase.patientLastName}`.replace(/\s+/g, '-'),
89+
name: [
90+
{
91+
given: [remsCase.patientFirstName],
92+
family: remsCase.patientLastName
93+
}
94+
],
95+
birthDate: remsCase.patientDOB
96+
};
97+
98+
// Get the stored MedicationRequest reference or create a minimal one for Task context
99+
const medicationRequestRef = remsCase.medicationRequestReference ||
100+
`MedicationRequest/${remsCase.case_number}`;
101+
102+
// Create a minimal MedicationRequest for task context if needed
103+
const medicationRequest: MedicationRequest = {
104+
resourceType: 'MedicationRequest',
105+
status: 'active',
106+
intent: 'order',
107+
medicationCodeableConcept: {
108+
coding: [
109+
{
110+
system: 'http://www.nlm.nih.gov/research/umls/rxnorm',
111+
code: remsCase.drugCode,
112+
display: remsCase.drugName
113+
}
114+
]
115+
},
116+
subject: {
117+
reference: `Patient/${patient.id}`
118+
},
119+
requester: {
120+
reference: remsCase.metRequirements.find(mr =>
121+
mr.requirementName?.toLowerCase().includes('prescriber')
122+
)?.stakeholderId
123+
}
124+
};
125+
126+
// Create Tasks using the existing function
127+
const tasks: Task[] = [];
128+
for (const requirement of outstandingRequirements) {
129+
if (requirement.appContext) {
130+
const questionnaireUrl = requirement.appContext;
131+
const task = createQuestionnaireCompletionTask(
132+
requirement,
133+
patient,
134+
questionnaireUrl,
135+
medicationRequest
136+
);
137+
task.id = `task-${uid()}`;
138+
tasks.push(task);
139+
}
140+
}
141+
142+
// Create Communication resource
143+
const communication: Communication = {
144+
resourceType: 'Communication',
145+
id: `comm-${uid()}`,
146+
status: 'completed',
147+
category: [
148+
{
149+
coding: [
150+
{
151+
system: 'http://terminology.hl7.org/CodeSystem/communication-category',
152+
code: 'notification',
153+
display: 'Notification'
154+
}
155+
]
156+
}
157+
],
158+
priority: 'urgent',
159+
subject: {
160+
reference: `Patient/${patient.id}`,
161+
display: `${remsCase.patientFirstName} ${remsCase.patientLastName}`
162+
},
163+
topic: {
164+
coding: [
165+
{
166+
system: 'http://terminology.hl7.org/CodeSystem/communication-topic',
167+
code: 'progress-update',
168+
display: 'Progress Update'
169+
}
170+
],
171+
text: 'Outstanding REMS Requirements for Medication Dispensing'
172+
},
173+
sent: new Date().toISOString(),
174+
recipient: [
175+
{
176+
reference: medicationRequest.requester?.reference || ''
177+
}
178+
],
179+
sender: {
180+
reference: 'Organization/rems-admin',
181+
display: config.server?.name || 'REMS Administrator'
182+
},
183+
payload: [
184+
{
185+
contentString: `Medication dispensing authorization DENIED for ${remsCase.drugName}.\n\n` +
186+
`The following REMS requirements must be completed:\n\n` +
187+
outstandingRequirements
188+
.map((req, idx) => `${idx + 1}. ${req.name} (${req.stakeholderType})`)
189+
.join('\n') +
190+
`\n\nCase Number: ${remsCase.case_number}\n` +
191+
`Patient: ${remsCase.patientFirstName} ${remsCase.patientLastName} (DOB: ${remsCase.patientDOB})`
192+
}
193+
],
194+
contained: tasks,
195+
about: [
196+
// Reference the actual MedicationRequest
197+
{
198+
reference: medicationRequestRef,
199+
display: `Prescription for ${remsCase.drugName}`
200+
},
201+
// Reference the contained Tasks
202+
...tasks.map(task => ({
203+
reference: `#${task.id}`,
204+
display: task.description
205+
}))
206+
]
207+
};
208+
209+
210+
let ehrEndpoint = config.fhirServerConfig?.auth?.resourceServer;
211+
212+
// Send Communication to EHR
213+
if (ehrEndpoint) {
214+
try {
215+
const response = await axios.post(`${ehrEndpoint}/Communication`, communication, {
216+
headers: {
217+
'Content-Type': 'application/fhir+json'
218+
}
219+
});
220+
221+
if (response.status === 200 || response.status === 201) {
222+
logger.info(`Communication sent to EHR: ${ehrEndpoint}`);
223+
}
224+
} catch (error: any) {
225+
logger.error(`Failed to send Communication to EHR: ${error.message}`);
226+
}
227+
} else {
228+
logger.warn('No EHR endpoint configured, Communication not sent');
229+
}
230+
231+
return res.status(200).json({ approved: false });
232+
} catch (error: any) {
233+
logger.error(`Error in dispense authorization: ${error.message}`);
234+
return res.status(500).json({
235+
approved: false,
236+
error: 'Internal server error'
237+
});
238+
}
239+
});
240+
241+
export default router;

src/lib/etasu.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ const createMetRequirementAndNewCase = async (
210210
reqStakeholderReference: string,
211211
practitionerReference: string,
212212
pharmacistReference: string,
213-
patientReference: string
213+
patientReference: string,
214+
medicationRequestReference: string
214215
) => {
215216
const patientFirstName = patient.name?.[0].given?.[0] || '';
216217
const patientLastName = patient.name?.[0].family || '';
@@ -231,6 +232,7 @@ const createMetRequirementAndNewCase = async (
231232
| 'patientFirstName'
232233
| 'patientLastName'
233234
| 'patientDOB'
235+
| 'medicationRequestReference'
234236
| 'metRequirements'
235237
> = {
236238
case_number: case_number,
@@ -241,6 +243,7 @@ const createMetRequirementAndNewCase = async (
241243
patientFirstName: patientFirstName,
242244
patientLastName: patientLastName,
243245
patientDOB: patientDOB,
246+
medicationRequestReference: medicationRequestReference,
244247
metRequirements: []
245248
};
246249

@@ -575,7 +578,8 @@ export const processQuestionnaireResponseSubmission = async (requestBody: Bundle
575578
stakeholderReference,
576579
practitionerReference,
577580
pharmacistReference,
578-
patientReference
581+
patientReference,
582+
prescriptionReference
579583
);
580584
} else {
581585
// If it's not the patient status requirement
@@ -605,4 +609,4 @@ export const processQuestionnaireResponseSubmission = async (requestBody: Bundle
605609

606610
export { getResource, getQuestionnaireResponse };
607611

608-
export default router;
612+
export default router;

src/server.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Server } from '@projecttacoma/node-fhir-server-core';
1111
import Etasu from './lib/etasu';
1212
import Ncpdp from './ncpdp/script';
1313
import Api from './lib/api_routes';
14+
import DispenseAuth from './lib/dispense_authorization';
1415
import env from 'env-var';
1516
import https from 'https';
1617
import fs from 'fs';
@@ -31,6 +32,7 @@ const initialize = (config: any) => {
3132
.configureEtasuEndpoints()
3233
.configureNCPDPEndpoints()
3334
.configureUIEndpoints()
35+
.configureDispenseAuthEndpoints()
3436
.setErrorRoutes();
3537
};
3638

@@ -142,6 +144,11 @@ class REMSServer extends Server {
142144
return this;
143145
}
144146

147+
configureDispenseAuthEndpoints() {
148+
this.app.use('/dispense', DispenseAuth);
149+
return this;
150+
}
151+
145152
/**
146153
* @method listen
147154
* @description Start listening on the configured port
@@ -163,4 +170,4 @@ class REMSServer extends Server {
163170

164171
// Start the application
165172

166-
export { REMSServer, initialize };
173+
export { REMSServer, initialize };

0 commit comments

Comments
 (0)