From f4a0b6ed6d79b8a39f56f3a7c56a00b9f2e667e2 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Fri, 14 Nov 2025 13:07:26 -0500 Subject: [PATCH 1/6] send communication resource to EHR --- src/fhir/models.ts | 4 +- src/lib/dispense_authorization.ts | 241 ++++++++++++++++++++++++++++++ src/lib/etasu.ts | 10 +- src/server.ts | 9 +- 4 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 src/lib/dispense_authorization.ts diff --git a/src/fhir/models.ts b/src/fhir/models.ts index b94727b..c380fc6 100644 --- a/src/fhir/models.ts +++ b/src/fhir/models.ts @@ -39,6 +39,7 @@ export interface RemsCase extends Document { patientFirstName: string; patientLastName: string; patientDOB: string; + medicationRequestReference?: string; metRequirements: Partial[]; } @@ -96,6 +97,7 @@ const remsCaseCollectionSchema = new Schema({ patientLastName: { type: String }, patientDOB: { type: String }, drugCode: { type: String }, + medicationRequestReference: { type: String }, metRequirements: [ { metRequirementId: { type: String }, @@ -107,4 +109,4 @@ const remsCaseCollectionSchema = new Schema({ ] }); -export const remsCaseCollection = model('RemsCaseCollection', remsCaseCollectionSchema); +export const remsCaseCollection = model('RemsCaseCollection', remsCaseCollectionSchema); \ No newline at end of file diff --git a/src/lib/dispense_authorization.ts b/src/lib/dispense_authorization.ts new file mode 100644 index 0000000..cb90bcd --- /dev/null +++ b/src/lib/dispense_authorization.ts @@ -0,0 +1,241 @@ +import { Router, Response, Request } from 'express'; +import { + medicationCollection, + remsCaseCollection, + Requirement +} from '../fhir/models'; +import { Communication, Task, Patient, MedicationRequest } from 'fhir/r4'; +import axios from 'axios'; +import config from '../config'; +import { uid } from 'uid'; +import container from '../lib/winston'; +import { createQuestionnaireCompletionTask } from '../hooks/hookResources'; + +const router = Router(); +const logger = container.get('application'); + +router.post('/authorize', async (req: Request, res: Response) => { + try { + const { caseNumber } = req.body; + + if (!caseNumber) { + return res.status(400).json({ error: 'caseNumber is required' }); + } + + logger.info(`Dispense authorization check for case: ${caseNumber}`); + + // Find the REMS case + const remsCase = await remsCaseCollection.findOne({ case_number: caseNumber }); + + if (!remsCase) { + logger.warn(`REMS case not found: ${caseNumber}`); + return res.status(404).json({ + approved: false, + error: 'Case not found' + }); + } + + // Get the medication to check requirements + const medication = await medicationCollection.findOne({ + code: remsCase.drugCode, + name: remsCase.drugName + }); + + if (!medication) { + logger.error(`Medication not found: ${remsCase.drugCode}`); + return res.status(500).json({ + approved: false, + error: 'Medication not found' + }); + } + + // Check which requirements are required for dispensing and not completed + const outstandingRequirements: Requirement[] = []; + + for (const requirement of medication.requirements) { + if (requirement.requiredToDispense) { + const metRequirement = remsCase.metRequirements.find( + metReq => metReq.requirementName === requirement.name + ); + + if (!metRequirement || !metRequirement.completed) { + outstandingRequirements.push(requirement); + } + } + } + + // If all required requirements are met, approve + if (outstandingRequirements.length === 0) { + logger.info(`All requirements met for case ${caseNumber}. Approving.`); + + // Update dispense status + remsCase.dispenseStatus = 'Approved'; + await remsCase.save(); + + return res.status(200).json({ approved: true }); + } + + // Outstanding requirements - deny and send Communication + logger.info( + `Outstanding requirements for case ${caseNumber}: ${outstandingRequirements + .map(r => r.name) + .join(', ')}` + ); + + // 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 or create a minimal one for Task context + const medicationRequestRef = remsCase.medicationRequestReference || + `MedicationRequest/${remsCase.case_number}`; + + // Create a minimal MedicationRequest for task context if needed + 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 => + mr.requirementName?.toLowerCase().includes('prescriber') + )?.stakeholderId + } + }; + + // Create Tasks using the existing function + const tasks: Task[] = []; + for (const requirement of outstandingRequirements) { + if (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', + category: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/communication-category', + code: 'notification', + display: 'Notification' + } + ] + } + ], + priority: 'urgent', + 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(), + 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.stakeholderType})`) + .join('\n') + + `\n\nCase Number: ${remsCase.case_number}\n` + + `Patient: ${remsCase.patientFirstName} ${remsCase.patientLastName} (DOB: ${remsCase.patientDOB})` + } + ], + contained: tasks, + about: [ + // Reference the actual MedicationRequest + { + reference: medicationRequestRef, + display: `Prescription for ${remsCase.drugName}` + }, + // Reference the contained Tasks + ...tasks.map(task => ({ + reference: `#${task.id}`, + display: task.description + })) + ] + }; + + + let ehrEndpoint = config.fhirServerConfig?.auth?.resourceServer; + + // Send Communication to EHR + if (ehrEndpoint) { + try { + const response = await axios.post(`${ehrEndpoint}/Communication`, communication, { + headers: { + 'Content-Type': 'application/fhir+json' + } + }); + + if (response.status === 200 || response.status === 201) { + logger.info(`Communication sent to EHR: ${ehrEndpoint}`); + } + } catch (error: any) { + logger.error(`Failed to send Communication to EHR: ${error.message}`); + } + } else { + logger.warn('No EHR endpoint configured, Communication not sent'); + } + + return res.status(200).json({ approved: false }); + } catch (error: any) { + logger.error(`Error in dispense authorization: ${error.message}`); + return res.status(500).json({ + approved: false, + error: 'Internal server error' + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/lib/etasu.ts b/src/lib/etasu.ts index 63ef37b..85ba3f6 100644 --- a/src/lib/etasu.ts +++ b/src/lib/etasu.ts @@ -210,7 +210,8 @@ const createMetRequirementAndNewCase = async ( reqStakeholderReference: string, practitionerReference: string, pharmacistReference: string, - patientReference: string + patientReference: string, + medicationRequestReference: string ) => { const patientFirstName = patient.name?.[0].given?.[0] || ''; const patientLastName = patient.name?.[0].family || ''; @@ -231,6 +232,7 @@ const createMetRequirementAndNewCase = async ( | 'patientFirstName' | 'patientLastName' | 'patientDOB' + | 'medicationRequestReference' | 'metRequirements' > = { case_number: case_number, @@ -241,6 +243,7 @@ const createMetRequirementAndNewCase = async ( patientFirstName: patientFirstName, patientLastName: patientLastName, patientDOB: patientDOB, + medicationRequestReference: medicationRequestReference, metRequirements: [] }; @@ -575,7 +578,8 @@ export const processQuestionnaireResponseSubmission = async (requestBody: Bundle stakeholderReference, practitionerReference, pharmacistReference, - patientReference + patientReference, + prescriptionReference ); } else { // If it's not the patient status requirement @@ -605,4 +609,4 @@ export const processQuestionnaireResponseSubmission = async (requestBody: Bundle export { getResource, getQuestionnaireResponse }; -export default router; +export default router; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index b5ad26b..9184ee4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,6 +11,7 @@ import { Server } from '@projecttacoma/node-fhir-server-core'; import Etasu from './lib/etasu'; import Ncpdp from './ncpdp/script'; import Api from './lib/api_routes'; +import DispenseAuth from './lib/dispense_authorization'; import env from 'env-var'; import https from 'https'; import fs from 'fs'; @@ -31,6 +32,7 @@ const initialize = (config: any) => { .configureEtasuEndpoints() .configureNCPDPEndpoints() .configureUIEndpoints() + .configureDispenseAuthEndpoints() .setErrorRoutes(); }; @@ -142,6 +144,11 @@ class REMSServer extends Server { return this; } + configureDispenseAuthEndpoints() { + this.app.use('/dispense', DispenseAuth); + return this; + } + /** * @method listen * @description Start listening on the configured port @@ -163,4 +170,4 @@ class REMSServer extends Server { // Start the application -export { REMSServer, initialize }; +export { REMSServer, initialize }; \ No newline at end of file From 1536e690024c15ac74f6918abbf911259348fd44 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra <88040167+smalho01@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:26:44 -0500 Subject: [PATCH 2/6] Potential fix for code scanning alert no. 32: Database query built from user-controlled sources Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- src/lib/dispense_authorization.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/dispense_authorization.ts b/src/lib/dispense_authorization.ts index cb90bcd..786dee1 100644 --- a/src/lib/dispense_authorization.ts +++ b/src/lib/dispense_authorization.ts @@ -25,7 +25,7 @@ router.post('/authorize', async (req: Request, res: Response) => { logger.info(`Dispense authorization check for case: ${caseNumber}`); // Find the REMS case - const remsCase = await remsCaseCollection.findOne({ case_number: caseNumber }); + const remsCase = await remsCaseCollection.findOne({ case_number: { $eq: caseNumber } }); if (!remsCase) { logger.warn(`REMS case not found: ${caseNumber}`); From e56aad932d91e98589ab64e8649803f56e0daeca Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Fri, 5 Dec 2025 10:09:32 -0500 Subject: [PATCH 3/6] create case on cds hook --- src/fhir/models.ts | 2 + src/hooks/hookResources.ts | 47 +++++++++++++-- src/lib/etasu.ts | 113 ++++++++++++++++++++++++++++++++++++- src/server.ts | 2 +- 4 files changed, 155 insertions(+), 9 deletions(-) diff --git a/src/fhir/models.ts b/src/fhir/models.ts index c380fc6..4873016 100644 --- a/src/fhir/models.ts +++ b/src/fhir/models.ts @@ -40,6 +40,7 @@ export interface RemsCase extends Document { patientLastName: string; patientDOB: string; medicationRequestReference?: string; + originatingFhirServer?: string; metRequirements: Partial[]; } @@ -98,6 +99,7 @@ const remsCaseCollectionSchema = new Schema({ patientDOB: { type: String }, drugCode: { type: String }, medicationRequestReference: { type: String }, + originatingFhirServer: { type: String }, metRequirements: [ { metRequirementId: { type: String }, diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index 7646a44..0787b5d 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -24,12 +24,14 @@ import { 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; export interface CardRule { @@ -366,7 +368,8 @@ export const handleCardOrder = async ( res: any, hydratedPrefetch: HookPrefetch | undefined, contextRequest: FhirResource | undefined, - resource: FhirResource | undefined + resource: FhirResource | undefined, + fhirServer?: string ): Promise => { const patient = resource?.resourceType === 'Patient' ? resource : undefined; @@ -396,13 +399,43 @@ export const handleCardOrder = async ( // 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) { + 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( @@ -594,6 +627,7 @@ export async function handleCard( const context = req.body.context; const patient = hydratedPrefetch?.patient; const practitioner = hydratedPrefetch?.practitioner; + const fhirServer = req.body.fhirServer; console.log(' Patient: ' + patient?.id); @@ -612,7 +646,7 @@ export async function handleCard( 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 @@ -836,7 +870,8 @@ export const handleCardEncounter = async ( res: any, hookPrefetch: HookPrefetch | undefined, _contextRequest: FhirResource | undefined, - resource: FhirResource | undefined + resource: FhirResource | undefined, + fhirServer?: string ): Promise => { const patient = resource?.resourceType === 'Patient' ? resource : undefined; const medResource = hookPrefetch?.medicationRequests; @@ -962,4 +997,4 @@ export function createQuestionnaireCompletionTask( ] }; return taskResource; -} +} \ No newline at end of file diff --git a/src/lib/etasu.ts b/src/lib/etasu.ts index 85ba3f6..3c6338e 100644 --- a/src/lib/etasu.ts +++ b/src/lib/etasu.ts @@ -183,6 +183,113 @@ const pushMetRequirements = ( }); }; +export const createNewRemsCaseFromCDSHook = async ( + patient: Patient, + drug: Medication, + practitionerReference: string, + pharmacistReference: string, + patientReference: string, + medicationRequestReference: string, + originatingFhirServer?: string +) => { + const patientFirstName = patient.name?.[0].given?.[0] || ''; + const patientLastName = patient.name?.[0].family || ''; + const patientDOB = patient.birthDate || ''; + const case_number = uid(); + + // Check if case already exists + const existingCase = await remsCaseCollection.findOne({ + patientFirstName: patientFirstName, + patientLastName: patientLastName, + patientDOB: patientDOB, + drugCode: drug?.code + }); + + if (existingCase) { + console.log(`Case already exists for patient ${patientFirstName} ${patientLastName} and drug ${drug?.name}`); + return existingCase; + } + + // Create new case with all requirements pending + const remsRequest: Pick< + RemsCase, + | 'case_number' + | 'status' + | 'dispenseStatus' + | 'drugName' + | 'drugCode' + | 'patientFirstName' + | 'patientLastName' + | 'patientDOB' + | 'medicationRequestReference' + | 'metRequirements' + > & { originatingFhirServer?: string } = { + case_number: case_number, + status: 'Pending', // All requirements unmet, so status is Pending + dispenseStatus: 'Pending', + drugName: drug?.name, + drugCode: drug?.code, + patientFirstName: patientFirstName, + patientLastName: patientLastName, + patientDOB: patientDOB, + medicationRequestReference: medicationRequestReference, + originatingFhirServer: originatingFhirServer, + metRequirements: [] + }; + + // Iterate through ALL requirements and create as unmet (or link to existing if already completed) + for (const requirement of drug.requirements) { + // Only process requirements that are required to dispense + if (requirement.requiredToDispense) { + // Figure out which stakeholder the requirement corresponds to + const stakeholderType = requirement.stakeholderType; + const stakeholderReference = + stakeholderType === 'prescriber' + ? practitionerReference + : stakeholderType === 'pharmacist' + ? pharmacistReference + : patientReference; + + // Check if this stakeholder has already completed this requirement + const existingMetReq = await metRequirementsCollection + .findOne({ + stakeholderId: stakeholderReference, + requirementName: requirement.name, + drugName: drug?.name + }) + .exec(); + + if (existingMetReq) { + // Requirement already exists (e.g., prescriber or pharmacist enrolled previously) + pushMetRequirements(existingMetReq, remsRequest); + existingMetReq.case_numbers.push(case_number); + await existingMetReq.save(); + } else { + // Create new unmet requirement + const newMetReq = { + completed: false, + requirementName: requirement.name, + requirementDescription: requirement.description, + drugName: drug?.name, + stakeholderId: stakeholderReference, + case_numbers: [case_number] + }; + + if (!(await createAndPushMetRequirements(newMetReq, remsRequest))) { + console.log('ERROR: failed to create unmet requirement for new case'); + } + } + } + } + + // Save the new case + remsRequest.status = remsRequest.metRequirements.every(req => req.completed) ? 'Approved' : 'Pending'; + const newCase = await remsCaseCollection.create(remsRequest); + + console.log(`Created new REMS case ${case_number} with all requirements unmet (or linked to existing)`); + return newCase; +}; + const createMetRequirements = async (metReq: Partial) => { return await metRequirementsCollection.create(metReq); }; @@ -211,7 +318,8 @@ const createMetRequirementAndNewCase = async ( practitionerReference: string, pharmacistReference: string, patientReference: string, - medicationRequestReference: string + medicationRequestReference: string, + originatingFhirServer?: string ) => { const patientFirstName = patient.name?.[0].given?.[0] || ''; const patientLastName = patient.name?.[0].family || ''; @@ -234,7 +342,7 @@ const createMetRequirementAndNewCase = async ( | 'patientDOB' | 'medicationRequestReference' | 'metRequirements' - > = { + > & { originatingFhirServer?: string } = { case_number: case_number, status: remsRequestCompletedStatus, dispenseStatus: dispenseStatusDefault, @@ -244,6 +352,7 @@ const createMetRequirementAndNewCase = async ( patientLastName: patientLastName, patientDOB: patientDOB, medicationRequestReference: medicationRequestReference, + originatingFhirServer: originatingFhirServer, metRequirements: [] }; diff --git a/src/server.ts b/src/server.ts index 9184ee4..6f65dff 100644 --- a/src/server.ts +++ b/src/server.ts @@ -135,7 +135,7 @@ class REMSServer extends Server { } }) ); - this.app.use('/', Ncpdp); + this.app.use('/ncpdp', Ncpdp); return this; } From 25babbe02e5b2eb6c6176bc8e7d66a3f3b59a4d4 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Fri, 5 Dec 2025 12:25:09 -0500 Subject: [PATCH 4/6] update dispense auth --- src/lib/dispense_authorization.ts | 159 +++++++++--------------------- 1 file changed, 45 insertions(+), 114 deletions(-) diff --git a/src/lib/dispense_authorization.ts b/src/lib/dispense_authorization.ts index 786dee1..60dba37 100644 --- a/src/lib/dispense_authorization.ts +++ b/src/lib/dispense_authorization.ts @@ -1,87 +1,22 @@ -import { Router, Response, Request } from 'express'; -import { - medicationCollection, - remsCaseCollection, - Requirement -} from '../fhir/models'; import { Communication, Task, Patient, MedicationRequest } from 'fhir/r4'; import axios from 'axios'; import config from '../config'; import { uid } from 'uid'; import container from '../lib/winston'; import { createQuestionnaireCompletionTask } from '../hooks/hookResources'; +import { Requirement } from '../fhir/models'; -const router = Router(); const logger = container.get('application'); -router.post('/authorize', async (req: Request, res: Response) => { - try { - const { caseNumber } = req.body; - - if (!caseNumber) { - return res.status(400).json({ error: 'caseNumber is required' }); - } - - logger.info(`Dispense authorization check for case: ${caseNumber}`); - - // Find the REMS case - const remsCase = await remsCaseCollection.findOne({ case_number: { $eq: caseNumber } }); - - if (!remsCase) { - logger.warn(`REMS case not found: ${caseNumber}`); - return res.status(404).json({ - approved: false, - error: 'Case not found' - }); - } - - // Get the medication to check requirements - const medication = await medicationCollection.findOne({ - code: remsCase.drugCode, - name: remsCase.drugName - }); - - if (!medication) { - logger.error(`Medication not found: ${remsCase.drugCode}`); - return res.status(500).json({ - approved: false, - error: 'Medication not found' - }); - } - - // Check which requirements are required for dispensing and not completed - const outstandingRequirements: Requirement[] = []; - - for (const requirement of medication.requirements) { - if (requirement.requiredToDispense) { - const metRequirement = remsCase.metRequirements.find( - metReq => metReq.requirementName === requirement.name - ); - - if (!metRequirement || !metRequirement.completed) { - outstandingRequirements.push(requirement); - } - } - } - - // If all required requirements are met, approve - if (outstandingRequirements.length === 0) { - logger.info(`All requirements met for case ${caseNumber}. Approving.`); - - // Update dispense status - remsCase.dispenseStatus = 'Approved'; - await remsCase.save(); - - return res.status(200).json({ approved: true }); - } - - // Outstanding requirements - deny and send Communication - logger.info( - `Outstanding requirements for case ${caseNumber}: ${outstandingRequirements - .map(r => r.name) - .join(', ')}` - ); +export async function sendCommunicationToEHR( + remsCase: any, + medication: any, + outstandingRequirements: any[] +): Promise { + try { + logger.info(`Creating Communication for case ${remsCase.case_number}`); + // Create patient object from REMS case const patient: Patient = { resourceType: 'Patient', @@ -95,11 +30,10 @@ router.post('/authorize', async (req: Request, res: Response) => { birthDate: remsCase.patientDOB }; - // Get the stored MedicationRequest reference or create a minimal one for Task context - const medicationRequestRef = remsCase.medicationRequestReference || - `MedicationRequest/${remsCase.case_number}`; + // Get the stored MedicationRequest reference + const medicationRequestRef = remsCase.medicationRequestReference; - // Create a minimal MedicationRequest for task context if needed + // Create a minimal MedicationRequest for task context const medicationRequest: MedicationRequest = { resourceType: 'MedicationRequest', status: 'active', @@ -117,16 +51,19 @@ router.post('/authorize', async (req: Request, res: Response) => { reference: `Patient/${patient.id}` }, requester: { - reference: remsCase.metRequirements.find(mr => + reference: remsCase.metRequirements.find((mr: any) => mr.requirementName?.toLowerCase().includes('prescriber') )?.stakeholderId } }; - // Create Tasks using the existing function + // Create Tasks for each outstanding requirement const tasks: Task[] = []; - for (const requirement of outstandingRequirements) { - if (requirement.appContext) { + for (const outstandingReq of outstandingRequirements) { + const requirement = outstandingReq.requirement || + medication.requirements.find((r: Requirement) => r.name === outstandingReq.name); + + if (requirement && requirement.appContext) { const questionnaireUrl = requirement.appContext; const task = createQuestionnaireCompletionTask( requirement, @@ -143,7 +80,7 @@ router.post('/authorize', async (req: Request, res: Response) => { const communication: Communication = { resourceType: 'Communication', id: `comm-${uid()}`, - status: 'completed', + status: 'completed', category: [ { coding: [ @@ -155,7 +92,7 @@ router.post('/authorize', async (req: Request, res: Response) => { ] } ], - priority: 'urgent', + priority: 'urgent', subject: { reference: `Patient/${patient.id}`, display: `${remsCase.patientFirstName} ${remsCase.patientLastName}` @@ -170,7 +107,7 @@ router.post('/authorize', async (req: Request, res: Response) => { ], text: 'Outstanding REMS Requirements for Medication Dispensing' }, - sent: new Date().toISOString(), + sent: new Date().toISOString(), recipient: [ { reference: medicationRequest.requester?.reference || '' @@ -185,7 +122,7 @@ router.post('/authorize', async (req: Request, res: Response) => { 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.stakeholderType})`) + .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})` @@ -193,12 +130,10 @@ router.post('/authorize', async (req: Request, res: Response) => { ], contained: tasks, about: [ - // Reference the actual MedicationRequest { reference: medicationRequestRef, display: `Prescription for ${remsCase.drugName}` }, - // Reference the contained Tasks ...tasks.map(task => ({ reference: `#${task.id}`, display: task.description @@ -206,36 +141,32 @@ router.post('/authorize', async (req: Request, res: Response) => { ] }; - - let ehrEndpoint = config.fhirServerConfig?.auth?.resourceServer; + // Determine EHR endpoint: use originatingFhirServer if available, otherwise default + const ehrEndpoint = remsCase.originatingFhirServer || + config.fhirServerConfig?.auth?.resourceServer; - // Send Communication to EHR - if (ehrEndpoint) { - try { - const response = await axios.post(`${ehrEndpoint}/Communication`, communication, { - headers: { - 'Content-Type': 'application/fhir+json' - } - }); + if (!ehrEndpoint) { + logger.warn('No EHR endpoint configured, Communication not sent'); + return; + } - if (response.status === 200 || response.status === 201) { - logger.info(`Communication sent to EHR: ${ehrEndpoint}`); - } - } catch (error: any) { - logger.error(`Failed to send Communication to EHR: ${error.message}`); + // 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('No EHR endpoint configured, Communication not sent'); + logger.warn(`Unexpected response status from EHR: ${response.status}`); } - - return res.status(200).json({ approved: false }); + } catch (error: any) { - logger.error(`Error in dispense authorization: ${error.message}`); - return res.status(500).json({ - approved: false, - error: 'Internal server error' - }); + logger.error(`Failed to send Communication to EHR: ${error.message}`); + throw error; } -}); - -export default router; \ No newline at end of file +} \ No newline at end of file From 89bdb150e54be76485f61fd7d1f65e1501754614 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Fri, 5 Dec 2025 12:26:21 -0500 Subject: [PATCH 5/6] remove dispense auth endpoint - replacing with ncpdp --- src/server.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/server.ts b/src/server.ts index 6f65dff..f2970f2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,7 +11,6 @@ import { Server } from '@projecttacoma/node-fhir-server-core'; import Etasu from './lib/etasu'; import Ncpdp from './ncpdp/script'; import Api from './lib/api_routes'; -import DispenseAuth from './lib/dispense_authorization'; import env from 'env-var'; import https from 'https'; import fs from 'fs'; @@ -32,7 +31,6 @@ const initialize = (config: any) => { .configureEtasuEndpoints() .configureNCPDPEndpoints() .configureUIEndpoints() - .configureDispenseAuthEndpoints() .setErrorRoutes(); }; @@ -144,11 +142,6 @@ class REMSServer extends Server { return this; } - configureDispenseAuthEndpoints() { - this.app.use('/dispense', DispenseAuth); - return this; - } - /** * @method listen * @description Start listening on the configured port From 01e7447acf3c112d355fa3a8e711434cbe695708 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Tue, 9 Dec 2025 10:21:12 -0500 Subject: [PATCH 6/6] rename dispense to communcation resource --- src/lib/{dispense_authorization.ts => communication.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/lib/{dispense_authorization.ts => communication.ts} (99%) diff --git a/src/lib/dispense_authorization.ts b/src/lib/communication.ts similarity index 99% rename from src/lib/dispense_authorization.ts rename to src/lib/communication.ts index 60dba37..55c4c4f 100644 --- a/src/lib/dispense_authorization.ts +++ b/src/lib/communication.ts @@ -2,7 +2,7 @@ import { Communication, Task, Patient, MedicationRequest } from 'fhir/r4'; import axios from 'axios'; import config from '../config'; import { uid } from 'uid'; -import container from '../lib/winston'; +import container from './winston'; import { createQuestionnaireCompletionTask } from '../hooks/hookResources'; import { Requirement } from '../fhir/models';