Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(elation): new action to create a referral order #535

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
6 changes: 5 additions & 1 deletion extensions/elation/__mocks__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
vitalsResponseExample,
visitNoteExample,
getLetterResponseExample,
referralOrderExample,
} from './constants'
const { makeAPIClient: makeAPIClientActual } = jest.requireActual('../client')

Expand Down Expand Up @@ -119,12 +120,15 @@ export const mockClientReturn = {
...vitalsResponseExample,
}
}),
createReferralOrder: jest.fn(() => {
return referralOrderExample
}),
}
const ElationAPIClientMock = jest.fn((params) => {
return mockClientReturn
})

export const makeAPIClientMockFunc = (args: any): any => {
export const makeAPIClientMockFunc = (args: any) => {
makeAPIClientActual(args)

return new ElationAPIClientMock(args)
Expand Down
18 changes: 18 additions & 0 deletions extensions/elation/__mocks__/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,19 @@ export const findContactResponseExample: FindContactsResponse = {
npi: '2047990827',
practice: 141127173275652,
user: 4067,
specialties: [],
},
],
}

export const searchContactsResponseExample: FindContactsResponse = {
count: 2,
results: [
{ id: 1, first_name: 'Contact 1', last_name: 'Contact 1', middle_name: '', npi: '1234567890', practice: 1, user: 1, specialties: [] },
{ id: 2, first_name: 'Contact 2', last_name: 'Contact 2', middle_name: '', npi: '1234567890', practice: 1, user: 1, specialties: [] },
],
}

export const postLetterResponseExample: PostLetterResponse = {
body: 'This my test',
id: 142251583930586,
Expand Down Expand Up @@ -327,3 +336,12 @@ export const getLetterResponseExample: GetLetterResponseType = {
patient: 140754680086529,
practice: 140754674450436,
}

export const referralOrderExample = {
id: 1234567890,
authorization_for: 'Referral For Treatment, includes Consult Visit.',
auth_number: '1234567890',
consultant_name: 'Dr Testing Mc Testerson',
short_consultant_name: 'TEST',
icd10_codes: ['A01', 'B02'],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type DataPointDefinition } from '@awell-health/extensions-core'

export const dataPoints = {
id: {
key: 'id',
valueType: 'number',
},
} satisfies Record<string, DataPointDefinition>
72 changes: 72 additions & 0 deletions extensions/elation/actions/createReferralOrder/config/fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { z, type ZodTypeAny } from 'zod'
import { NumericIdSchema } from '@awell-health/extensions-core'
import { type Field, FieldType } from '@awell-health/extensions-core'
import { AuthorizationForSchema } from '../../../validation/referralOrder.zod'

export const fields = {
patient: {
id: 'patient',
label: 'Patient ID',
type: FieldType.NUMERIC,
required: true,
description: '',
},
practice: {
id: 'practice',
label: 'Practice ID',
type: FieldType.NUMERIC,
required: true,
description: '',
},
contact_name: {
id: 'contact_name',
label: 'Contact name',
type: FieldType.STRING,
required: true,
description: '',
},
body: {
id: 'body',
label: 'Body',
type: FieldType.TEXT,
required: true,
description: '',
},
authorization_for: {
id: 'authorization_for',
label: 'Authorization for',
type: FieldType.STRING,
required: true,
description: '',
options: {
dropdownOptions: Object.values(AuthorizationForSchema.enum).map((template) => ({
label: template,
value: template,
})),
},
},
consultant_name: {
id: 'consultant_name',
label: 'Consultant name',
type: FieldType.STRING,
required: true,
description: '',
},
specialty: {
id: 'specialty',
label: 'Specialty',
type: FieldType.STRING,
required: false,
description: '',
},
} satisfies Record<string, Field>

export const FieldsValidationSchema = z.object({
patient: NumericIdSchema,
practice: NumericIdSchema,
contact_name: z.string(),
body: z.string(),
authorization_for: AuthorizationForSchema,
consultant_name: z.string(),
specialty: z.string(),
} satisfies Record<keyof typeof fields, ZodTypeAny>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { dataPoints } from './dataPoints'
export { fields, FieldsValidationSchema } from './fields'
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { makeAPIClient } from '../../client'
import { FindContactsResponse } from '../../types/contact'
import { referralOrderExample } from '../../__mocks__/constants'
import { createReferralOrder } from './createReferralOrder'

// Add mock API client
const mockElationAPIClient = {
searchContactsByName: jest.fn().mockResolvedValue({
count: 1,
results: [{ id: 1 }, { id: 2 }]
} as FindContactsResponse),
createReferralOrder: jest.fn().mockResolvedValue(referralOrderExample),
postNewLetter: jest.fn().mockResolvedValue({ id: 'letter_123' })
}

// Mock the makeAPIClient function
jest.mock('../../client', () => ({
makeAPIClient: jest.fn().mockImplementation(() => mockElationAPIClient)
}))

describe('Elation - Create referral order', () => {
const onComplete = jest.fn()
const onError = jest.fn()

beforeAll(() => {
})

beforeEach(() => {
jest.clearAllMocks()
onComplete.mockClear()
onError.mockClear()
})

const payload = {
fields: {
patient: 83658080257,
practice: 75435474948,
body: 'This is a test body',
authorization_for: 'Referral For Treatment, includes Consult Visit',
contact_name: 'A contact',
specialty: 'Unit testing',
consultant_name: 'Dr. Smith',
},
settings: {
client_id: 'clientId',
client_secret: 'clientSecret',
username: 'username',
password: 'password',
auth_url: 'authUrl',
base_url: 'baseUrl',
},
activity: {
id: '123',
},
pathway: {
definition_id: '123',
id: '123',
},
patient: {
id: '123',
},
}

test('Should return the referral order id as data point', async () => {
await createReferralOrder.onActivityCreated!(
payload,
onComplete,
onError,
)

expect(onComplete).toHaveBeenCalledWith({ data_points: { id: String(referralOrderExample.id) } })
})

test('Should create a letter with the referral order id', async () => {
await createReferralOrder.onActivityCreated!(
payload,
onComplete,
onError,
)

expect(mockElationAPIClient.postNewLetter).toHaveBeenCalledWith(expect.objectContaining({
referral_order: referralOrderExample.id
}))
})

test('Should use the first contact found when creating the letter', async () => {
await createReferralOrder.onActivityCreated!(
payload,
onComplete,
onError,
)

expect(mockElationAPIClient.postNewLetter).toHaveBeenCalledWith(
expect.objectContaining({
send_to_contact: { id: 1 } // Should use the first contact's ID
})
)
})

test('Should throw an error if no contact is found', async () => {
mockElationAPIClient.searchContactsByName.mockImplementationOnce(() => {
return {
count: 0,
results: []
} as FindContactsResponse
})

await expect(createReferralOrder.onActivityCreated!(
payload,
onComplete,
onError,
)).rejects.toThrow("No contact found with the name A contact.")
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { type Action, Category } from '@awell-health/extensions-core'
import { type settings } from '../../settings'
import { makeAPIClient } from '../../client'
import { FieldsValidationSchema, fields, dataPoints } from './config'
import { ZodError } from 'zod'
import { fromZodError } from 'zod-validation-error'
import { AxiosError } from 'axios'

/**
* Important to know as the documentation is out of date:
* - The specialty has to refer to an existing specialty in Elation. I can't find documentation on how to get
* the list of specialties which means there is no way for us to validate that the field is OK.
* - The send to contact field is required when creating a letter but again, not documented so unclear how to set it properly.
*/
export const createReferralOrder: Action<
typeof fields,
typeof settings,
keyof typeof dataPoints
> = {
key: 'createReferralOrder',
category: Category.EHR_INTEGRATIONS,
title: 'Create referral order',
description: 'Create a referral order in Elation.',
fields,
previewable: true,
dataPoints,
onActivityCreated: async (payload, onComplete, onError): Promise<void> => {
try {
const { patient, practice, contact_name, body, authorization_for, consultant_name, specialty } = FieldsValidationSchema.parse(payload.fields)
const api = makeAPIClient(payload.settings)

const contactsResponse = await api.searchContactsByName({ name: contact_name })

if (contactsResponse.count === 0) {
throw new Error(`No contact found with the name ${contact_name}.`)
}

const contact = contactsResponse.results[0]

const referralOrderResponse = await api.createReferralOrder({
authorization_for,
consultant_name,
short_consultant_name: consultant_name,
practice,
patient,
specialty: { name: specialty },
// Setting these to null as we don't know whether they are useful or not.
// If needed we can add them to the action fields.
auth_number: null,
date_for_reEval: null,
})

await api.postNewLetter({
patient,
practice,
body,
referral_order: referralOrderResponse.id,
send_to_contact: { id: contact.id },
letter_type: 'referral',
})

await onComplete({
data_points: {
id: String(referralOrderResponse.id),
},
})
} catch (err) {
if (err instanceof AxiosError) {
const responseData = (err as AxiosError).response?.data
await onError({
events: [
{
date: new Date().toISOString(),
text: {
en: `${err.status ?? '(no status code)'} Error: ${err.message}`,
},
error: {
category: 'SERVER_ERROR',
message: `${err.status ?? '(no status code)'} Error data: ${JSON.stringify(responseData, null, 2)}`,
},
},
],
})
} else {
throw err
}
}
},
}
1 change: 1 addition & 0 deletions extensions/elation/actions/createReferralOrder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createReferralOrder } from './createReferralOrder'
4 changes: 3 additions & 1 deletion extensions/elation/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { addAllergy } from './addAllergy/addAllergy'
import { createVisitNote } from './createVisitNote/createVisitNote'
import { addVitals } from './addVitals/addVitals'
import { getLetter } from './getLetter'
import { createReferralOrder } from './createReferralOrder'

export const actions = {
getPatient,
Expand All @@ -41,5 +42,6 @@ export const actions = {
postLetter,
createLabOrder,
createMessageThread,
getLetter
getLetter,
createReferralOrder,
}
Loading
Loading