From b7edb8609a4f428c0e462109f556ba2b42425e52 Mon Sep 17 00:00:00 2001 From: Abigaila Ekiert <44117299+sharlotta93@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:49:28 +0100 Subject: [PATCH] ET-493-add-new-property-to-elation-user (#527) --- extensions/elation/__mocks__/constants.ts | 1 + extensions/elation/actions/getPatient.ts | 175 ------------------ .../actions/getPatient/config/dataPoints.ts | 104 +++++++++++ .../actions/getPatient/config/fields.ts | 20 ++ .../actions/getPatient/config/index.ts | 2 + .../getPatient.test.ts} | 80 +++++--- .../elation/actions/getPatient/getPatient.ts | 71 +++++++ .../elation/actions/getPatient/index.ts | 1 + extensions/elation/actions/index.ts | 2 +- extensions/elation/types/patient.ts | 1 + extensions/elation/validation/patient.zod.ts | 1 + 11 files changed, 255 insertions(+), 203 deletions(-) delete mode 100644 extensions/elation/actions/getPatient.ts create mode 100644 extensions/elation/actions/getPatient/config/dataPoints.ts create mode 100644 extensions/elation/actions/getPatient/config/fields.ts create mode 100644 extensions/elation/actions/getPatient/config/index.ts rename extensions/elation/actions/{__tests__/getPatient.ts => getPatient/getPatient.test.ts} (56%) create mode 100644 extensions/elation/actions/getPatient/getPatient.ts create mode 100644 extensions/elation/actions/getPatient/index.ts diff --git a/extensions/elation/__mocks__/constants.ts b/extensions/elation/__mocks__/constants.ts index f768ff7c3..d76b780b3 100644 --- a/extensions/elation/__mocks__/constants.ts +++ b/extensions/elation/__mocks__/constants.ts @@ -21,6 +21,7 @@ export const patientExample: PatientInput = { sexual_orientation: 'unknown', primary_physician: 141127177601026, caregiver_practice: 141127173275652, + preferred_service_location: 141127173275555, dob: '1940-08-29', ssn: '123456789', race: 'Asian', diff --git a/extensions/elation/actions/getPatient.ts b/extensions/elation/actions/getPatient.ts deleted file mode 100644 index 6e0bbc7d3..000000000 --- a/extensions/elation/actions/getPatient.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { - FieldType, - NumericIdSchema, - type Action, - type DataPointDefinition, - type Field, -} from '@awell-health/extensions-core' -import { Category } from '@awell-health/extensions-core' -import { type settings } from '../settings' -import { makeAPIClient } from '../client' -import { elationMobilePhoneToE164 } from '../utils/elationMobilePhoneToE164' -import { getLastEmail } from '../utils/getLastEmail' - -const fields = { - patientId: { - id: 'patientId', - label: 'Patient ID', - description: 'The Elation patient ID', - type: FieldType.NUMERIC, - required: true, - }, -} satisfies Record - -const dataPoints = { - firstName: { - key: 'firstName', - valueType: 'string', - }, - lastName: { - key: 'lastName', - valueType: 'string', - }, - dob: { - key: 'dob', - valueType: 'date', - }, - sex: { - key: 'sex', - valueType: 'string', - }, - primaryPhysicianId: { - key: 'primaryPhysicianId', - valueType: 'number', - }, - caregiverPracticeId: { - key: 'caregiverPracticeId', - valueType: 'number', - }, - mainPhone: { - key: 'mainPhone', - valueType: 'telephone', - }, - mobilePhone: { - key: 'mobilePhone', - valueType: 'telephone', - }, - email: { - key: 'email', - valueType: 'string', - }, - middleName: { - key: 'middleName', - valueType: 'string', - }, - actualName: { - key: 'actualName', - valueType: 'string', - }, - genderIdentity: { - key: 'genderIdentity', - valueType: 'string', - }, - legalGenderMarker: { - key: 'legalGenderMarker', - valueType: 'string', - }, - pronouns: { - key: 'pronouns', - valueType: 'string', - }, - sexualOrientation: { - key: 'sexualOrientation', - valueType: 'string', - }, - ssn: { - key: 'ssn', - valueType: 'string', - }, - ethnicity: { - key: 'ethnicity', - valueType: 'string', - }, - race: { - key: 'race', - valueType: 'string', - }, - preferredLanguage: { - key: 'preferredLanguage', - valueType: 'string', - }, - notes: { - key: 'notes', - valueType: 'string', - }, - previousFirstName: { - key: 'previousFirstName', - valueType: 'string', - }, - previousLastName: { - key: 'previousLastName', - valueType: 'string', - }, - status: { - key: 'status', - valueType: 'string', - }, - patientObject: { - key: 'patientObject', - valueType: 'json', - }, -} satisfies Record - -export const getPatient: Action< - typeof fields, - typeof settings, - keyof typeof dataPoints -> = { - key: 'getPatient', - category: Category.EHR_INTEGRATIONS, - title: 'Get Patient', - description: "Retrieve a patient profile using Elation's patient API.", - fields, - previewable: true, - dataPoints, - onActivityCreated: async (payload, onComplete, onError): Promise => { - const patientId = NumericIdSchema.parse(payload.fields.patientId) - - // API Call should produce AuthError or something dif. - const api = makeAPIClient(payload.settings) - const patientInfo = await api.getPatient(patientId) - - await onComplete({ - data_points: { - firstName: patientInfo.first_name, - lastName: patientInfo.last_name, - dob: patientInfo.dob, - sex: patientInfo.sex, - primaryPhysicianId: String(patientInfo.primary_physician), - caregiverPracticeId: String(patientInfo.caregiver_practice), - mainPhone: elationMobilePhoneToE164( - patientInfo.phones?.find((p) => p.phone_type === 'Main')?.phone - ), - mobilePhone: elationMobilePhoneToE164( - patientInfo.phones?.find((p) => p.phone_type === 'Mobile')?.phone - ), - email: getLastEmail(patientInfo.emails), - middleName: patientInfo.middle_name, - actualName: patientInfo.actual_name, - genderIdentity: patientInfo.gender_identity, - legalGenderMarker: patientInfo.legal_gender_marker, - pronouns: patientInfo.pronouns, - sexualOrientation: patientInfo.sexual_orientation, - ssn: patientInfo.ssn, - ethnicity: patientInfo.ethnicity, - race: patientInfo.race, - preferredLanguage: patientInfo.preferred_language, - notes: patientInfo.notes, - previousFirstName: patientInfo.previous_first_name, - previousLastName: patientInfo.previous_last_name, - status: patientInfo.patient_status.status, - patientObject: JSON.stringify(patientInfo), - }, - }) - }, -} diff --git a/extensions/elation/actions/getPatient/config/dataPoints.ts b/extensions/elation/actions/getPatient/config/dataPoints.ts new file mode 100644 index 000000000..8505fa560 --- /dev/null +++ b/extensions/elation/actions/getPatient/config/dataPoints.ts @@ -0,0 +1,104 @@ +import { type DataPointDefinition } from '@awell-health/extensions-core' + +export const dataPoints = { + firstName: { + key: 'firstName', + valueType: 'string', + }, + lastName: { + key: 'lastName', + valueType: 'string', + }, + dob: { + key: 'dob', + valueType: 'date', + }, + sex: { + key: 'sex', + valueType: 'string', + }, + primaryPhysicianId: { + key: 'primaryPhysicianId', + valueType: 'number', + }, + caregiverPracticeId: { + key: 'caregiverPracticeId', + valueType: 'number', + }, + mainPhone: { + key: 'mainPhone', + valueType: 'telephone', + }, + mobilePhone: { + key: 'mobilePhone', + valueType: 'telephone', + }, + email: { + key: 'email', + valueType: 'string', + }, + middleName: { + key: 'middleName', + valueType: 'string', + }, + actualName: { + key: 'actualName', + valueType: 'string', + }, + genderIdentity: { + key: 'genderIdentity', + valueType: 'string', + }, + legalGenderMarker: { + key: 'legalGenderMarker', + valueType: 'string', + }, + pronouns: { + key: 'pronouns', + valueType: 'string', + }, + sexualOrientation: { + key: 'sexualOrientation', + valueType: 'string', + }, + ssn: { + key: 'ssn', + valueType: 'string', + }, + ethnicity: { + key: 'ethnicity', + valueType: 'string', + }, + race: { + key: 'race', + valueType: 'string', + }, + preferredLanguage: { + key: 'preferredLanguage', + valueType: 'string', + }, + notes: { + key: 'notes', + valueType: 'string', + }, + previousFirstName: { + key: 'previousFirstName', + valueType: 'string', + }, + previousLastName: { + key: 'previousLastName', + valueType: 'string', + }, + status: { + key: 'status', + valueType: 'string', + }, + preferredServiceLocationId: { + key: 'preferredServiceLocationId', + valueType: 'number', + }, + patientObject: { + key: 'patientObject', + valueType: 'json', + }, +} satisfies Record diff --git a/extensions/elation/actions/getPatient/config/fields.ts b/extensions/elation/actions/getPatient/config/fields.ts new file mode 100644 index 000000000..5edf2de93 --- /dev/null +++ b/extensions/elation/actions/getPatient/config/fields.ts @@ -0,0 +1,20 @@ +import { + FieldType, + NumericIdSchema, + type Field, +} from '@awell-health/extensions-core' +import z, { type ZodTypeAny } from 'zod' + +export const fields = { + patientId: { + id: 'patientId', + label: 'Patient ID', + description: 'The Elation patient ID', + type: FieldType.NUMERIC, + required: true, + }, +} satisfies Record + +export const FieldsValidationSchema = z.object({ + patientId: NumericIdSchema, +} satisfies Record) diff --git a/extensions/elation/actions/getPatient/config/index.ts b/extensions/elation/actions/getPatient/config/index.ts new file mode 100644 index 000000000..cd36e4bd6 --- /dev/null +++ b/extensions/elation/actions/getPatient/config/index.ts @@ -0,0 +1,2 @@ +export { fields, FieldsValidationSchema } from './fields' +export { dataPoints } from './dataPoints' diff --git a/extensions/elation/actions/__tests__/getPatient.ts b/extensions/elation/actions/getPatient/getPatient.test.ts similarity index 56% rename from extensions/elation/actions/__tests__/getPatient.ts rename to extensions/elation/actions/getPatient/getPatient.test.ts index 98a536ed4..77279cd67 100644 --- a/extensions/elation/actions/__tests__/getPatient.ts +++ b/extensions/elation/actions/getPatient/getPatient.test.ts @@ -1,12 +1,20 @@ -import { getPatient } from '../getPatient' -import { type ActivityEvent } from '@awell-health/extensions-core' -import { patientExample } from '../../__mocks__/constants' +import { TestHelpers } from '@awell-health/extensions-core' + +import { getPatient as action } from './getPatient' import { ZodError } from 'zod' +import { patientExample } from '../../__mocks__/constants' jest.mock('../../client') -describe('Simple get patient action', () => { - const onComplete = jest.fn() +describe('Elation - Get patient', () => { + const { + extensionAction: getPatient, + onComplete, + onError, + helpers, + clearMocks, + } = TestHelpers.fromAction(action) + const settings = { client_id: 'clientId', client_secret: 'clientSecret', @@ -17,21 +25,20 @@ describe('Simple get patient action', () => { } beforeEach(() => { - onComplete.mockClear() + clearMocks() }) - test('Should return with correct data_points', async () => { - await getPatient.onActivityCreated!( - { - fields: { - patientId: 127385972, - }, + test('Should call onComplete when successful', async () => { + await getPatient.onEvent({ + payload: { + fields: { patientId: 123 }, settings, } as any, onComplete, - jest.fn() - ) - expect(onComplete).toHaveBeenCalled() + onError, + helpers, + }) + expect(onComplete).toHaveBeenCalledWith({ data_points: expect.objectContaining({ firstName: patientExample.first_name, @@ -40,6 +47,7 @@ describe('Simple get patient action', () => { sex: patientExample.sex, primaryPhysicianId: String(patientExample.primary_physician), caregiverPracticeId: String(patientExample.caregiver_practice), + preferredServiceLocationId: String(patientExample.preferred_service_location), mobilePhone: '+12133734253', email: 'john@doe.com', middleName: patientExample.middle_name, @@ -58,24 +66,42 @@ describe('Simple get patient action', () => { status: patientExample.patient_status?.status, }), }) + expect(onError).not.toHaveBeenCalled() }) - test('Should provide good error messaging', async () => { - const onError = jest - .fn() - .mockImplementation((obj: { events: ActivityEvent[] }) => { - return obj.events[0].error?.message - }) - const activity = getPatient.onActivityCreated!( - { + test('Should call onError when required fields are missing', async () => { + const resp = getPatient.onEvent({ + payload: { fields: { - patientId: '', + patientId: undefined }, settings, } as any, onComplete, - onError - ) - await expect(activity).rejects.toThrow(ZodError) + onError, + helpers, + }) + + await expect(resp).rejects.toThrow(ZodError) + expect(onComplete).not.toHaveBeenCalled() + }) + + test('Should call onError when no patientId is provided and name is invalid format', async () => { + const resp = getPatient.onEvent({ + payload: { + fields: { + patientId: { + value: 1234, + }, + }, + settings, + } as any, + onComplete, + onError, + helpers, + }) + + await expect(resp).rejects.toThrow(ZodError) + expect(onComplete).not.toHaveBeenCalled() }) }) diff --git a/extensions/elation/actions/getPatient/getPatient.ts b/extensions/elation/actions/getPatient/getPatient.ts new file mode 100644 index 000000000..3ab16a807 --- /dev/null +++ b/extensions/elation/actions/getPatient/getPatient.ts @@ -0,0 +1,71 @@ +import { z } from 'zod' +import { type Action, Category, validate } from '@awell-health/extensions-core' +import { SettingsValidationSchema, type settings } from '../../settings' +import { makeAPIClient } from '../../client' +import { fields, FieldsValidationSchema, dataPoints } from './config' +import { elationMobilePhoneToE164 } from '../../utils/elationMobilePhoneToE164' +import { getLastEmail } from '../../utils/getLastEmail' + +export const getPatient: Action< + typeof fields, + typeof settings, + keyof typeof dataPoints +> = { + key: 'getPatient', + category: Category.EHR_INTEGRATIONS, + title: 'Get Patient', + description: + 'Retrieve a patient profile using Elation`s patient API.', + fields, + previewable: true, + dataPoints, + onEvent: async ({ payload, onComplete }): Promise => { + const { fields, settings } = validate({ + schema: z.object({ + fields: FieldsValidationSchema, + settings: SettingsValidationSchema, + }), + payload, + }) + // API Call should produce AuthError or something dif. + const api = makeAPIClient(settings) + + const patientInfo = await api.getPatient(fields.patientId) + + + await onComplete({ + data_points: { + firstName: patientInfo.first_name, + lastName: patientInfo.last_name, + dob: patientInfo.dob, + sex: patientInfo.sex, + primaryPhysicianId: String(patientInfo.primary_physician), + caregiverPracticeId: String(patientInfo.caregiver_practice), + mainPhone: elationMobilePhoneToE164( + patientInfo.phones?.find((p) => p.phone_type === 'Main')?.phone + ), + mobilePhone: elationMobilePhoneToE164( + patientInfo.phones?.find((p) => p.phone_type === 'Mobile')?.phone + ), + email: getLastEmail(patientInfo.emails), + middleName: patientInfo.middle_name, + actualName: patientInfo.actual_name, + genderIdentity: patientInfo.gender_identity, + legalGenderMarker: patientInfo.legal_gender_marker, + pronouns: patientInfo.pronouns, + sexualOrientation: patientInfo.sexual_orientation, + ssn: patientInfo.ssn, + ethnicity: patientInfo.ethnicity, + race: patientInfo.race, + preferredLanguage: patientInfo.preferred_language, + notes: patientInfo.notes, + previousFirstName: patientInfo.previous_first_name, + previousLastName: patientInfo.previous_last_name, + status: patientInfo.patient_status.status, + preferredServiceLocationId: + String(patientInfo.preferred_service_location), + patientObject: JSON.stringify(patientInfo), + }, + }) + }, +} diff --git a/extensions/elation/actions/getPatient/index.ts b/extensions/elation/actions/getPatient/index.ts new file mode 100644 index 000000000..534d70be9 --- /dev/null +++ b/extensions/elation/actions/getPatient/index.ts @@ -0,0 +1 @@ +export { getPatient } from './getPatient' diff --git a/extensions/elation/actions/index.ts b/extensions/elation/actions/index.ts index 2ef7ebbef..39d73aacc 100644 --- a/extensions/elation/actions/index.ts +++ b/extensions/elation/actions/index.ts @@ -1,4 +1,4 @@ -import { getPatient } from './getPatient' +import { getPatient } from './getPatient/getPatient' import { updatePatient } from './updatePatient' import { createPatient } from './createPatient' import { createAppointment } from './createAppointment' diff --git a/extensions/elation/types/patient.ts b/extensions/elation/types/patient.ts index ae597b12b..8057ace01 100644 --- a/extensions/elation/types/patient.ts +++ b/extensions/elation/types/patient.ts @@ -30,6 +30,7 @@ export interface PatientResponse extends PatientInput { deleted_date?: string | null merged_into_chart?: number | null tags: string[] | null + preferred_service_location?: number | null } interface Phone extends z.infer { diff --git a/extensions/elation/validation/patient.zod.ts b/extensions/elation/validation/patient.zod.ts index fae913d58..4803ec919 100644 --- a/extensions/elation/validation/patient.zod.ts +++ b/extensions/elation/validation/patient.zod.ts @@ -202,6 +202,7 @@ export const patientSchema = z sexual_orientation: sexualOrientationEnum.nullish(), primary_physician: NumericIdSchema, // required for POST and PUT caregiver_practice: NumericIdSchema, // required for POST and PUT + preferred_service_location: NumericIdSchema.optional().nullish(), dob: DateOnlySchema, // required for POST and PUT ssn: z.string().length(9).nullish(), race: raceEnum.nullish(),