Skip to content

Commit

Permalink
(refactor) Add workflows to pmtct summary in patient chart
Browse files Browse the repository at this point in the history
  • Loading branch information
CynthiaKamau committed Jun 19, 2024
1 parent 532817f commit acdb03b
Show file tree
Hide file tree
Showing 22 changed files with 625 additions and 546 deletions.
2 changes: 1 addition & 1 deletion packages/esm-commons-lib/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export async function fetchMambaReportData(reportId: string) {
}
}

export function useDataFetch(
export function fetchEtlData(
reportType: 'fetchMambaAncData' | 'MotherHivStatus',
reportId?: string,
patientUuid?: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fetchPatientLastEncounter } from '../../api/api';

export function fetchLatestEncountersOfTypes(patientUuid: string, encounterTypes: string[]) {
return Promise.all(encounterTypes.map((type) => fetchPatientLastEncounter(patientUuid, type)));
return Promise.all(encounterTypes?.map((type) => fetchPatientLastEncounter(patientUuid, type)));
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,27 @@ export const SummaryCard: React.FC<SummaryCardProps> = ({ patientUuid, columns,
const [groupedEncounterMappings, setGroupedEncounterMappings] = useState<Array<any>>([]);

useEffect(() => {
Promise.all(columns.map((column) => fetchLatestEncountersOfTypes(patientUuid, column.encounterTypes))).then(
(results) => {
const filteredResults = results.map((result) => result.filter(Boolean));
setColumnEncountersMappings(columns.map((column, index) => ({ column, encounters: filteredResults[index] })));
setIsLoading(false);
},
);
Promise.all(
columns.map((column) => {
const encounterTypes = Array.isArray(column?.encounterTypes)
? column.encounterTypes
: column?.encounterTypes
? [column.encounterTypes]
: [];

return fetchLatestEncountersOfTypes(patientUuid, encounterTypes);
}),
).then((results) => {
const filteredResults = results.map((result) => result.filter(Boolean));
setColumnEncountersMappings(
columns.map((column, index) => ({
column,
encounters: filteredResults[index],
})),
);

setIsLoading(false);
});
}, [columns, patientUuid]);

useEffect(() => {
Expand Down Expand Up @@ -88,7 +102,7 @@ function SummaryItem({ column, encounters }) {
<div className={styles.tileBoxColumn}>
<span className={styles.tileTitle}> {column.header} </span>
<span className={styles.tileValue}>
<LazyCell lazyValue={column.getObsValue(encounters)} />
{column?.getObsValue ? <LazyCell lazyValue={column?.getObsValue(encounters)} /> : '--'}
</span>
{column.getObsSummary && (
<span className={styles.tileTitle}>
Expand Down
2 changes: 1 addition & 1 deletion packages/esm-commons-lib/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const daysDurationUnit = {
export const basePath = '${openmrsSpaBase}/patient/';
export const encounterRepresentation =
'custom:(uuid,encounterDatetime,encounterType,location:(uuid,name),' +
'patient:(uuid,display),encounterProviders:(uuid,provider:(uuid,name)),' +
'patient:(uuid,display,age),encounterProviders:(uuid,provider:(uuid,name)),' +
'obs:(uuid,obsDatetime,voided,groupMembers,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name),' +
'names:(uuid,conceptNameType,name))),form:(uuid,name))';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ interface ConditionalActionProps {
label: string;
package: string;
formName: string;
dependsOn: string;
dependantConcept: string;
dependsOn?: string;
dependantConcept?: string;
dependantEncounter?: string;
}

interface ColumnDefinition {
Expand All @@ -52,6 +53,12 @@ interface ColumnDefinition {
statusColorMappings?: Record<string, string>;
isConditionalConcept?: boolean;
conditionalConceptMappings?: Record<string, string>;
conditionalEncounterMappings?: Record<string, ConditionalEncounterMapping>;
}

export interface ConditionalEncounterMapping {
concept: string;
isDate?: boolean;
}

interface LaunchOptions {
Expand Down Expand Up @@ -86,14 +93,13 @@ export const getTabColumns = (columnsDefinition: Array<ColumnDefinition>) => {
getValue: (encounter) => {
if (column.id === 'actions') {
const conditionalActions = [];
const baseActions = column.actionOptions.map((action: ActionProps) => ({
const baseActions = column.actionOptions?.map((action: ActionProps) => ({
form: { name: action.formName, package: action.package },
encounterUuid: encounter.uuid,
intent: '*',
label: action.label,
mode: action.mode,
}));

if (column?.conditionalActionOptions?.length) {
column?.conditionalActionOptions?.map((action) => {
const dependantObsValue = getObsFromEncounter(encounter, action.dependantConcept);
Expand All @@ -107,6 +113,18 @@ export const getTabColumns = (columnsDefinition: Array<ColumnDefinition>) => {
mode: action.mode,
});
}

const dependantEncounterValue = encounter.encounterType.uuid;

if (dependantEncounterValue === action.dependantEncounter) {
return conditionalActions.push({
form: { name: action.formName, package: action.package },
encounterUuid: encounter.uuid,
intent: '*',
label: action.label,
mode: action.mode,
});
}
});
}
return [...baseActions, ...conditionalActions];
Expand Down
20 changes: 18 additions & 2 deletions packages/esm-commons-lib/src/utils/encounter-list-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function obsArrayDateComparator(left, right) {
}

export function findObs(encounter, obsConcept): Record<string, any> {
const allObs = encounter?.obs?.filter((observation) => observation.concept.uuid === obsConcept) || [];
const allObs = encounter?.obs?.filter((observation) => observation.concept.uuid == obsConcept) || [];
return allObs?.length == 1 ? allObs[0] : allObs?.sort(obsArrayDateComparator)[0];
}

Expand Down Expand Up @@ -88,6 +88,10 @@ export function getObsFromEncounter(
) {
let obs = findObs(encounter, obsConcept);

if (!encounter || !obsConcept) {
return '--';
}

if (isTrueFalseConcept) {
if (
(obs?.value?.uuid != 'cf82933b-3f3f-45e7-a5ab-5d31aaee3da3' && obs?.value?.name?.name !== 'Unknown') ||
Expand All @@ -113,6 +117,14 @@ export function getObsFromEncounter(
return fetchMotherName(encounter.patient.uuid);
}

if (type === 'visitType') {
return encounter.encounterType.name;
}

if (type === 'ageAtHivTest') {
return encounter.patient.age;
}

if (secondaryConcept && typeof obs.value === 'object' && obs.value.names) {
const primaryValue =
obs.value.names.find((conceptName) => conceptName.conceptNameType === 'SHORT')?.name || obs.value.name.name;
Expand All @@ -132,7 +144,11 @@ export function getObsFromEncounter(
}

if (isDate) {
return formatDate(parseDate(obs.value), { mode: 'wide' });
if (typeof obs.value === 'object' && obs.value?.names) {
return formatDate(parseDate(obs.obsDatetime), { mode: 'wide' });
} else {
return formatDate(parseDate(obs.value), { mode: 'wide' });
}
}

if (typeof obs.value === 'object' && obs.value?.names) {
Expand Down
2 changes: 1 addition & 1 deletion packages/esm-commons-lib/src/utils/schema-manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function extractSchemaValues(schema) {
if (obj === null || obj === undefined || typeof obj !== 'object') {
return;
}
Object.entries(obj)?.forEach(([key, value]) => {
Object.entries(obj).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
if (typeof value === 'object' && !Array.isArray(value)) {
traverse(value);
Expand Down
75 changes: 67 additions & 8 deletions packages/esm-commons-lib/src/utils/summary-card-config-builder.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import isEmpty from 'lodash-es/isEmpty';
import { fetchEtlData } from '../api/api';
import { getObsFromEncounter, getConditionalConceptValue } from './encounter-list-utils';
import { extractSchemaValues, replaceWithConfigDefaults } from './schema-manipulation';

Expand All @@ -13,6 +15,27 @@ export const getSummaryCardProps = (schemaConfig, config = null) => {
if (column.isConditionalConcept) {
return getConditionalConceptValue(encounter, column.conditionalConceptMappings, column.isDate);
}

if (column?.mambaEtlData === 'ancVisits') {
const response = fetchEtlData('fetchMambaAncData', 'no_of_anc_visits', encounter?.patient?.uuid);
return response?.data;
}

if (column?.mambaEtlData === 'motherStatus') {
const response = fetchEtlData('fetchMambaAncData', 'mother_status', encounter?.patient?.uuid);
return response?.data;
}

if (column?.mambaEtlData === 'deliveryDate') {
const response = fetchEtlData('fetchMambaAncData', 'estimated_date_of_delivery', encounter?.patient?.uuid);
return response?.data;
}

if (column?.mambaEtlData === 'motherHivStatus') {
const response = fetchEtlData('fetchMambaAncData', 'mother_hiv_status', encounter?.patient?.uuid);
return response?.data;
}

return getObsFromEncounter(
encounter,
column.concept,
Expand All @@ -23,16 +46,52 @@ export const getSummaryCardProps = (schemaConfig, config = null) => {
);
},
getObsSummary: async (encounters) => {
const summaryValues = encounters.map((encounter) => {
if (encounter && encounter.observation && encounter.observation.value) {
return encounter.observation.value.join(', ');
} else {
return '';
}
});
return summaryValues.join(' | ');
if (column?.conditionalEncounterMappings && Object.keys(column?.conditionalEncounterMappings)?.length > 0 && column.encounterTypes?.length > 0) {
const filteredEncounters = encounters.filter((encounter) =>
column.encounterTypes.includes(encounter.encounterType.uuid),
);
let latestEncounter = null;
let latestValue = null;

filteredEncounters.forEach((encounter) => {
if (encounter.obs && Array.isArray(encounter.obs)) {
encounter.obs.forEach((observation) => {
if (observation.concept.uuid === column.concept) {
if (!latestEncounter || encounter.encounterDatetime > latestEncounter.encounterDatetime) {
latestEncounter = encounter;
}
latestValue = getObsFromEncounter(latestEncounter, column.concept, column.isDate);
}
});
}
});
return latestValue;
} else {
const summaryValues = encounters.map((encounter) => {
if (column.type === 'nextAppointmentDate') {
let nextVisitDate = getObsFromEncounter(encounter[0], column.concept, true);
if (nextVisitDate !== '--') {
const days = calculateDateDifferenceInDate(nextVisitDate);
nextVisitDate = nextVisitDate > 0 ? `In ${days}` : '';
}
return nextVisitDate;
}
if (encounter && encounter.observation && !isEmpty(encounter.observation.value)) {
return encounter.observation.value.join(', ');
} else {
return '';
}
});
return summaryValues?.length > 0 ? summaryValues.filter((val) => val !== '').join(' | ') : '';
}
},
}));

return columns;
};

export const calculateDateDifferenceInDate = (givenDate: string): string => {
const dateDifference = new Date(givenDate).getTime() - new Date().getTime();
const totalDays = Math.floor(dateDifference / (1000 * 3600 * 24));
return `${totalDays} day(s)`;
};
27 changes: 14 additions & 13 deletions packages/esm-ohri-pmtct-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,31 @@ export const configSchema = {
_type: Type.Object,
_description: 'List of forms for PMTCT.',
_default: {
antenatal: 'Antenatal Form',
labourAndDelivery: 'Labour & Delivery Form',
motherPostnatal: 'Mother - Postnatal Form',
infantPostnatal: 'Infant - Postanal Form',
antenatalFormName: 'Antenatal Form',
labourAndDeliveryFormName: 'Labour & Delivery Form',
motherPostnatalFormName: 'Mother - Postnatal Form',
infantPostnatalFormName: 'Infant - Postanal Form',
},
},
formUuids: {
_type: Type.Object,
_description: 'List of uuids for PMTCT forms.',
_default: {
antenatal: '5255a535-2acb-3f44-bd0a-3f80595dece1',
labourAndDelivery: '1e5614d6-5306-11e6-beb8-9e71128cae77',
motherPostnatal: 'e6b67aa4-6c59-4470-8ad5-b994efeda553',
infantPostnatal: '5022c5d7-ea45-47ce-bd65-1ba1d8ad2467',
antenatalFormUuid: '5255a535-2acb-3f44-bd0a-3f80595dece1',
labourAndDeliveryFormUuid: '1e5614d6-5306-11e6-beb8-9e71128cae77',
motherPostnatalFormUuid: 'e6b67aa4-6c59-4470-8ad5-b994efeda553',
infantPostnatalFormUuid: '120048e5-4122-3c6d-8f77-c79e75b7b3fc',
},
},
encounterTypes: {
_type: Type.Object,
_description: 'List of PMTCT encounter type UUIDs',
_default: {
antenatal: '677d1a80-dbbe-4399-be34-aa7f54f11405',
laborAndDelivery: '6dc5308d-27c9-4d49-b16f-2c5e3c759757',
infantPostnatal: 'af1f1b24-d2e8-4282-b308-0bf79b365584',
motherPostnatal: '269bcc7f-04f8-4ddc-883d-7a3a0d569aad',
mchEncounterType: '12de5bc5-352e-4faf-9961-a2125085a75c',
antenatalEncounterType: '677d1a80-dbbe-4399-be34-aa7f54f11405',
laborAndDeliveryEncounterType: '6dc5308d-27c9-4d49-b16f-2c5e3c759757',
infantPostnatalEncounterType: 'af1f1b24-d2e8-4282-b308-0bf79b365584',
motherPostnatalEncounterType: '269bcc7f-04f8-4ddc-883d-7a3a0d569aad',
mchEncounterTypeEncounterType: '12de5bc5-352e-4faf-9961-a2125085a75c',
},
},
obsConcepts: {
Expand Down Expand Up @@ -110,4 +110,5 @@ export interface ConfigObject {
encounterTypes: Object;
obsConcepts: Object;
formNames: Object;
formUuids: Object;
}
Loading

0 comments on commit acdb03b

Please sign in to comment.