Skip to content

Commit

Permalink
Adds patient relationship mocks to VAOS (#33349)
Browse files Browse the repository at this point in the history
* Adds getPatientRelationships to the VAOS service

Signed-off-by: Ryan Shaw <[email protected]>

* Stubbed out patient relationship mock

Signed-off-by: Ryan Shaw <[email protected]>

* Updated patient relationship mock data

Signed-off-by: Ryan Shaw <[email protected]>

* Stubs out transformer

* Stub out types

* Flattened relationship object

* Updated types

* Moved typedef’s

* Name changes for consistency

* Removes chained parseApiObject, will not work with endpoint

* Updates types

* Updates transformer to deal with array in response

* Added additional mock

* Updates service

* Stubs out actions and reducers

* Updates patient service

* Adds reducer

* Updates action

* Added TODO and cleaned up

* Adds failed reducer for providers and updates action

* Adds selectors

* Adds comment

* Adds new hook for fetching patient provider relationships

* Adds TODO

* Updates types

* Fixes transformer

* Types reorg

* Adds unit test

* Update comment

* Update src/applications/vaos/services/patient/types.js

Co-authored-by: John Luo <[email protected]>

* Adds links to GH issue

* Updates comment style

---------

Signed-off-by: Ryan Shaw <[email protected]>
Co-authored-by: John Luo <[email protected]>
  • Loading branch information
ryanshaw and JunTaoLuo authored Dec 11, 2024
1 parent 67439c3 commit 2fb08c3
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect } from 'react';
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import { FETCH_STATUS } from '../../utils/constants';
import { useOHDirectScheduling } from './useOHDirectScheduling';
import { getPatientProviderRelationships } from '../redux/selectors';
import { fetchPatientProviderRelationships } from '../redux/actions';

export function useGetPatientRelationships() {
const dispatch = useDispatch();
const featureOHDirectSchedule = useOHDirectScheduling();

const {
patientProviderRelationships,
patientProviderRelationshipsStatus,
} = useSelector(
state => getPatientProviderRelationships(state),
shallowEqual,
);

useEffect(
() => {
if (
featureOHDirectSchedule &&
!patientProviderRelationships.length &&
patientProviderRelationshipsStatus === FETCH_STATUS.notStarted
) {
dispatch(fetchPatientProviderRelationships());
}
},
[
dispatch,
featureOHDirectSchedule,
patientProviderRelationshipsStatus,
patientProviderRelationships,
],
);
return {
patientProviderRelationships,
patientProviderRelationshipsStatus,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { shallowEqual, useSelector } from 'react-redux';
import { getFacilityPageV2Info } from '../redux/selectors';
import { selectFeatureOHDirectSchedule } from '../../redux/selectors';

// Currently we are only allowing OH direct scheduling for Food and Nutrition
// appointments
const OH_DIRECT_SCHEDULE_ENABLED_TYPES_OF_CARE = ['foodAndNutrition'];

export function useOHDirectScheduling() {
Expand Down
29 changes: 28 additions & 1 deletion src/applications/vaos/new-appointment/redux/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ import {
STARTED_NEW_APPOINTMENT_FLOW,
FORM_SUBMIT_SUCCEEDED,
} from '../../redux/sitewide';
import { fetchFlowEligibilityAndClinics } from '../../services/patient';
import {
fetchFlowEligibilityAndClinics,
fetchPatientRelationships,
} from '../../services/patient';
import { getTimezoneByFacilityId } from '../../utils/timezone';
import { getCommunityCareV2 } from '../../services/vaos/index';

Expand Down Expand Up @@ -143,6 +146,12 @@ export const FORM_REQUESTED_PROVIDERS_FAILED =
'newAppointment/FORM_REQUESTED_PROVIDERS_FAILED';
export const FORM_PAGE_CC_FACILITY_SORT_METHOD_UPDATED =
'newAppointment/FORM_PAGE_CC_FACILITY_SORT_METHOD_UPDATED';
export const FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS =
'newAppointment/FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS';
export const FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_SUCCEEDED =
'newAppointment/FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_SUCCEEDED';
export const FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_FAILED =
'newAppointment/FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_FAILED';

export function openFormPage(page, uiSchema, schema) {
return {
Expand Down Expand Up @@ -217,6 +226,23 @@ export function startRequestAppointmentFlow(isCommunityCare) {
};
}

export function fetchPatientProviderRelationships() {
return async dispatch => {
try {
dispatch({ type: FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS });

const patientProviderRelationships = await fetchPatientRelationships();

dispatch({ type: FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_SUCCEEDED });

return patientProviderRelationships;
} catch (e) {
dispatch({ type: FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_FAILED });
return captureError(e);
}
};
}

export function fetchFacilityDetails(facilityId) {
let facilityDetails;

Expand All @@ -241,6 +267,7 @@ export function fetchFacilityDetails(facilityId) {
});
};
}

export function checkEligibility({ location, showModal }) {
return async (dispatch, getState) => {
const state = getState();
Expand Down
21 changes: 21 additions & 0 deletions src/applications/vaos/new-appointment/redux/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ import {
FORM_REQUESTED_PROVIDERS,
FORM_REQUESTED_PROVIDERS_SUCCEEDED,
FORM_REQUESTED_PROVIDERS_FAILED,
FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS,
FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_SUCCEEDED,
FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_FAILED,
} from './actions';

import {
Expand Down Expand Up @@ -87,6 +90,8 @@ const initialState = {
facilityDetails: {},
clinics: {},
eligibility: {},
patientProviderRelationships: [],
patientProviderRelationshipsStatus: FETCH_STATUS.notStarted,
parentFacilities: null,
ccEnabledSystems: null,
pageChangeInProgress: false,
Expand Down Expand Up @@ -587,6 +592,22 @@ export default function formReducer(state = initialState, action) {
},
flowType: FLOW_TYPES.REQUEST,
};
case FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS:
return {
...state,
patientProviderRelationshipsStatus: FETCH_STATUS.loading,
};
case FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_SUCCEEDED:
return {
...state,
patientProviderRelationshipsStatus: FETCH_STATUS.succeeded,
patientProviderRelationships: action.patientProviderRelationships,
};
case FORM_FETCH_PATIENT_PROVIDER_RELATIONSHIPS_FAILED:
return {
...state,
patientProviderRelationshipsStatus: FETCH_STATUS.failed,
};
case FORM_FETCH_FACILITY_DETAILS:
return {
...state,
Expand Down
8 changes: 8 additions & 0 deletions src/applications/vaos/new-appointment/redux/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,14 @@ export function selectChosenFacilityInfo(state) {
);
}

export function getPatientProviderRelationships(state) {
return {
patientProviderRelationships: state.patientProviderRelationships,
patientProviderRelationshipsStatus:
state.patientProviderRelationshipsStatus,
};
}

export function getChosenVACityState(state) {
const schema =
state.newAppointment.pages.ccPreferences?.properties.communityCareSystemId;
Expand Down
4 changes: 4 additions & 0 deletions src/applications/vaos/services/mocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const schedulingConfigurationsCC = require('./v2/scheduling_configurations_cc.js
const schedulingConfigurations = require('./v2/scheduling_configurations.json');
const appointmentSlotsV2 = require('./v2/slots.json');
const clinicsV2 = require('./v2/clinics.json');
const patientProviderRelationships = require('./v2/patient_provider_relationships.json');

// To locally test appointment details null state behavior, comment out
// the inclusion of confirmed.json and uncomment the inclusion of
Expand Down Expand Up @@ -496,6 +497,9 @@ const responses = {
data: [],
});
},
'GET /vaos/v2/relationships': (req, res) => {
return res.json(patientProviderRelationships);
},

// EPS api
'GET /vaos/v2/epsApi/referralDetails': (req, res) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"data": [
{
"type": "relationship",
"attributes": {
"type": "string",
"attributes": {
"provider": {
"cernerId": "Practitioner/123456",
"name": "Doe, John D, MD"
},
"location": {
"vhaFacilityId": "string",
"name": "Marion VA Clinic"
},
"clinic": {
"vistaSite": "534",
"ien": "6569",
"name": "Zanesville Primary Care"
},
"serviceType": {
"coding": [
{
"code": "Routine Follow-up"
}
],
"text": "string"
},
"lastSeen": "2024-11-26T00:32:34.216Z"
}
}
},
{
"type": "relationship",
"attributes": {
"type": "string",
"attributes": {
"provider": {
"cernerId": "Practitioner/1111",
"name": "Doe, Mary D, MD"
},
"location": {
"vhaFacilityId": "string",
"name": "Marion VA Clinic"
},
"clinic": {
"vistaSite": "534",
"ien": "6569",
"name": "Zanesville Primary Care"
},
"serviceType": {
"coding": [
{
"code": "New Problem"
}
],
"text": "string"
},
"lastSeen": "2024-10-15T00:32:34.216Z"
}
}
}
]
}
78 changes: 33 additions & 45 deletions src/applications/vaos/services/patient/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,9 @@ import { captureError } from '../../utils/error';
import { ELIGIBILITY_REASONS } from '../../utils/constants';
import { promiseAllFromObject } from '../../utils/data';
import { getAvailableHealthcareServices } from '../healthcare-service';
import { getPatientEligibility } from '../vaos';
import { getPatientEligibility, getPatientRelationships } from '../vaos';
import { getLongTermAppointmentHistoryV2 } from '../appointment';

/**
* @typedef PatientEligibilityForType
* @global
*
* @property {boolean} hasRequiredAppointmentHistory Has had appointment in the past that meets VATS requirements
* - Mapped from past visits check
* @property {?boolean} isEligibleForNewAppointmentRequest Is under the request limit
* - Mapped from request limits check
*/

/**
* @typedef PatientEligibility
* @global
*
* @property {?PatientEligibilityForType} direct Patient eligibility for direct scheduling
* @property {?PatientEligibilityForType} request Patient eligibility for requests
*/

/**
* @typedef {'error'|'overRequestLimit'|'noEnabled'|'notSupported'|'noRecentVisit'|
* 'noClinics'|'noMatchingClinics'} EligibilityReason
* @global
*/

/**
* @typedef FlowEligibility
* @global
*
* @property {boolean} direct Can the patient use the direct schedule flow
* @property {Array<EligibilityReason>} directReason The reason the patient isn't eligible for direct flow
* @property {boolean} request Can the patient use the request flow
* @property {Array<EligibilityReason>} requestReason The reason the patient isn't eligible for request flow
*/
import { transformPatientRelationships } from './transformers';

function createErrorHandler(errorKey) {
return data => {
Expand Down Expand Up @@ -137,6 +104,37 @@ export async function fetchPatientEligibility({
return output;
}

/**
* Fetch the logged in user's patient/provider relationships
*
* @export
* @async
* @param {Object} params
* @param {TypeOfCare} params.typeOfCare Type of care object for which to check patient relationships
* @param {Location} params.location Location of where patient should have relationships checked,
* @returns {Array<PatientProviderRelationship} Returns an array of PatientProviderRelationship objects
*/

export async function fetchPatientRelationships() {
// TODO: https://github.com/department-of-veterans-affairs/va.gov-team/issues/98864
// Once we are aware of the data that we need and where this needs to be called
// in the flow, we need to add { typeOfCare, location } as a passed attribute.
//
// export async function fetchPatientRelationships({ typeOfCare, location })
//
// const data = await getPatientRelationships(location.id, typeOfCare.idV2);
//
// Currently this will fetch all patient provider relationships for the logged
// in user.

try {
const data = await getPatientRelationships();
return transformPatientRelationships(data || []);
} catch (e) {
return null;
}
}

function locationSupportsDirectScheduling(location, typeOfCare) {
return (
// this check is included due to old two step facilities page
Expand Down Expand Up @@ -247,16 +245,6 @@ function logEligibilityExplanation(
/* eslint-enable no-console */
}

/**
* @typedef FlowEligibilityReturnData
* @global
*
* @property {FlowEligibility} eligibility The eligibility info for the patient
* @property {Array<HealthCareService>} clinics An array of clinics pulled when checking eligibility
* @property {Array<MASAppointment>} pastAppointments An array of untransformed appointments pulled
* when checking eligibility
*/

/**
* Checks eligibility for new appointment flow and returns
* results, plus clinics and past appointments fetched along the way
Expand Down
Loading

0 comments on commit 2fb08c3

Please sign in to comment.