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) O3-3316 Add support for encounter diagnosis #298

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
40 changes: 9 additions & 31 deletions src/adapters/encounter-diagnosis-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const formContext = {
customDependencies: {
patientPrograms: [],
},
deletedFields: [],
getFormField: jest.fn(),
addFormField: jest.fn(),
updateFormField: jest.fn(),
Expand All @@ -39,6 +40,7 @@ const formContext = {
removeInvalidField: jest.fn(),
setInvalidFields: jest.fn(),
setForm: jest.fn(),
setDeletedFields: jest.fn(),
} as FormContextProps;

const field = {
Expand Down Expand Up @@ -173,18 +175,6 @@ describe('EncounterDiagnosisAdapter', () => {

const value = '128138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
field.meta = {
previousValue: {
patient: '833db896-c1f0-11eb-8529-0242ac130003',
condition: null,
diagnosis: {
coded: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
},
certainty: 'PROVISIONAL',
rank: 1,
formFieldPath: 'rfe-forms-DiagNosIS',
formFieldNamespace: 'rfe-forms',
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
},
initialValue: {
omrsObject: {
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
Expand Down Expand Up @@ -215,23 +205,10 @@ describe('EncounterDiagnosisAdapter', () => {
expect(field.meta.submission.voidedValue).toBe(undefined);
});

it('should handle deleting a diagnosis', () => {
it('should void removed diagnosis in edit mode', () => {
formContext.sessionMode = 'edit';

const value = null;
field.meta = {
previousValue: {
patient: '833db896-c1f0-11eb-8529-0242ac130003',
condition: null,
diagnosis: {
coded: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
},
certainty: 'PROVISIONAL',
rank: 1,
formFieldPath: 'rfe-forms-DiagNosIS',
formFieldNamespace: 'rfe-forms',
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
},
initialValue: {
omrsObject: {
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
Expand All @@ -244,11 +221,12 @@ describe('EncounterDiagnosisAdapter', () => {
},
refinedValue: null,
},
repeat: {
wasDeleted: true,
},
};
EncounterDiagnosisAdapter.transformFieldValue(field, value, formContext);
expect(field.meta.submission.voidedValue).toEqual({ uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', voided: true });

EncounterDiagnosisAdapter.transformFieldValue(field, null, formContext);
expect(field.meta.submission.voidedValue).toEqual({
voided: true,
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
});
});
});
82 changes: 50 additions & 32 deletions src/adapters/encounter-diagnosis-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { type OpenmrsResource } from '@openmrs/esm-framework';
import { type DiagnosisPayload, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types';
import { type OpenmrsObs, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types';
import { type FormContextProps } from '../provider/form-provider';
import { type OpenmrsEncounter, type FormField } from '../types';
import { clearSubmission, gracefullySetSubmission } from '../utils/common-utils';
import { gracefullySetSubmission } from '../utils/common-utils';
import { isEmpty } from '../validators/form-validator';
import { voidObs } from './obs-adapter';
import { isTrue } from '../utils/boolean-utils';

export let assignedDiagnosesIds: string[] = [];

export const EncounterDiagnosisAdapter: FormFieldValueAdapter = {
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) {
if (context.sessionMode == 'edit' && field.meta?.previousValue?.uuid) {
return editDiagnosis(value, field, context.patient.id);
if (field.meta.initialValue?.omrsObject && isEmpty(value)) {
return gracefullySetSubmission(field, undefined, voidObs(field.meta.initialValue.omrsObject as OpenmrsObs));
CynthiaKamau marked this conversation as resolved.
Show resolved Hide resolved
}
if (!isEmpty(value)) {
const previousDiagnosis = field.meta.initialValue?.omrsObject as OpenmrsResource;
if (hasPreviousDiagnosisValueChanged(previousDiagnosis, value)) {
return gracefullySetSubmission(
field,
editDiagnosis(value, field, previousDiagnosis, context.patient.id),
undefined,
);
}
}
const newValue = constructNewDiagnosis(value, field, context.patient.id);
gracefullySetSubmission(field, newValue, null);
Expand All @@ -26,7 +39,13 @@ export const EncounterDiagnosisAdapter: FormFieldValueAdapter = {
);

if (matchedDiagnosis) {
field.meta = { ...(field.meta || {}), previousValue: matchedDiagnosis };
field.meta = {
...(field.meta || {}),
initialValue: {
omrsObject: matchedDiagnosis,
refinedValue: matchedDiagnosis.diagnosis?.coded.uuid,
},
};
if (!assignedDiagnosesIds.includes(matchedDiagnosis.diagnosis?.coded?.uuid)) {
assignedDiagnosesIds.push(matchedDiagnosis.diagnosis?.coded?.uuid);
}
Expand All @@ -49,47 +68,46 @@ export const EncounterDiagnosisAdapter: FormFieldValueAdapter = {
},
};

const constructNewDiagnosis = (value: any, field: FormField, patientUuid: string, uuid?: string) => {
const constructNewDiagnosis = (value: any, field: FormField, patientUuid: string) => {
if (!value) {
return null;
}
const diagnosis: DiagnosisPayload = {
return {
patient: patientUuid,
condition: null,
diagnosis: {
coded: value,
},
certainty: field.questionOptions?.diagnosis?.isConfirmed ? 'CONFIRMED' : 'PROVISIONAL',
certainty: isTrue(field.questionOptions?.diagnosis?.isConfirmed) ? 'CONFIRMED' : 'PROVISIONAL',
rank: field.questionOptions.diagnosis?.rank ?? 1, // rank 1 denotes a diagnosis is primary, else secondary
formFieldPath: `rfe-forms-${field.id}`,
formFieldNamespace: 'rfe-forms',
};

if (uuid && uuid.trim() !== '') {
diagnosis.uuid = uuid;
}

return diagnosis;
};

function editDiagnosis(newEncounterDiagnosis: any, field: FormField, patientUuid: string) {
if (newEncounterDiagnosis === field.meta.previousValue?.diagnosis?.coded?.uuid && !field.meta.repeat?.wasDeleted) {
clearSubmission(field);
return null;
}
function editDiagnosis(
newEncounterDiagnosis: any,
field: FormField,
previousDiagnosis: OpenmrsResource,
patientUuid: string,
) {
return {
patient: patientUuid,
condition: null,
diagnosis: {
coded: newEncounterDiagnosis,
},
certainty: isTrue(field.questionOptions?.diagnosis?.isConfirmed) ? 'CONFIRMED' : 'PROVISIONAL',
rank: field.questionOptions.diagnosis?.rank ?? 1, // rank 1 denotes a diagnosis is primary, else secondary
formFieldPath: `rfe-forms-${field.id}`,
formFieldNamespace: 'rfe-forms',
uuid: previousDiagnosis.uuid,
};
}

//the field has been deleted
if (field.meta.repeat?.wasDeleted) {
const voided = {
uuid: field.meta.previousValue?.uuid,
voided: true,
};
gracefullySetSubmission(field, constructNewDiagnosis(newEncounterDiagnosis, field, null), voided);
return field.meta.submission.newValue || null;
} else {
const oldDiagnosis = field.meta.initialValue?.omrsObject as OpenmrsResource;
const newValue = constructNewDiagnosis(newEncounterDiagnosis, field, patientUuid, oldDiagnosis.uuid);
gracefullySetSubmission(field, newValue, null);
return newValue;
export function hasPreviousDiagnosisValueChanged(previousDiagnosis: OpenmrsResource, newValue: any) {
CynthiaKamau marked this conversation as resolved.
Show resolved Hide resolved
if (isEmpty(previousDiagnosis)) {
return false;
}
return previousDiagnosis.value !== newValue;
}
12 changes: 7 additions & 5 deletions src/datasources/concept-data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
const urlParts = searchUrl.split('searchType=fuzzy');
searchUrl = `${urlParts[0]}searchType=fuzzy&class=${config.class}&${urlParts[1]}`;
} else {
return openmrsFetch(searchTerm ? `${searchUrl}&q=${searchTerm}` : searchUrl).then(({ data }) => {
return data.results.filter(
(concept) => concept.conceptClass && config.class.includes(concept.conceptClass.uuid),
);
});
return openmrsFetch(searchTerm ? `${searchUrl}&q=${searchTerm}` : `${searchUrl}&q=${'Diagnosis'}`).then(
({ data }) => {
return data.results.filter(
(concept) => concept.conceptClass && config.class.includes(concept.conceptClass.uuid),
);
},
);
}
}

Expand Down
47 changes: 20 additions & 27 deletions src/processors/encounter/encounter-processor-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function prepareEncounter(
prepareObs(obsForSubmission, allFormFields);
const ordersForSubmission = prepareOrders(allFormFields);
const diagnosesForSubmission = prepareDiagnosis(allFormFields);

let encounterForSubmission: OpenmrsEncounter = {};

if (encounter) {
Expand Down Expand Up @@ -321,38 +322,28 @@ export async function hydrateRepeatField(

const unMappedDiagnoses = encounter.diagnoses.filter((diagnosis) => {
return (
!diagnosis.voided &&
!assignedDiagnosesIds.includes(diagnosis?.diagnosis?.coded.uuid) &&
diagnosis.formFieldPath.startsWith(`rfe-forms-${field.id}_`)
);
});

const sortedDiagnoses = unMappedDiagnoses
.filter((diagnosis) => !diagnosis.voided)
.sort((a, b) => {
// Extract numeric part of formFieldPath for sorting
const numberA = parseInt(a.formFieldPath.split('_')[1], 10);
const numberB = parseInt(b.formFieldPath.split('_')[1], 10);
return numberA - numberB; // Sort numerically based on formFieldPath
});

if (field.type === 'diagnosis') {
CynthiaKamau marked this conversation as resolved.
Show resolved Hide resolved
return Promise.all(
sortedDiagnoses
.filter((diagnosis) => !diagnosis.voided)
.map(async (diagnosis) => {
const clone = cloneRepeatField(field, diagnosis, counter++);
initialValues[clone.id] = await formFieldAdapters[field.type].getInitialValue(
clone,
{ diagnoses: [diagnosis] } as any,
context,
);

if (!assignedDiagnosesIds.includes(diagnosis.diagnosis.coded.uuid)) {
assignedDiagnosesIds.push(diagnosis.diagnosis.coded.uuid);
}
unMappedDiagnoses.map(async (diagnosis) => {
const idSuffix = parseInt(diagnosis.formFieldPath.split('_')[1]);
const clone = cloneRepeatField(field, diagnosis, idSuffix);
initialValues[clone.id] = await formFieldAdapters[field.type].getInitialValue(
clone,
{ diagnoses: [diagnosis] } as any,
context,
);
if (!assignedDiagnosesIds.includes(diagnosis.diagnosis.coded.uuid)) {
assignedDiagnosesIds.push(diagnosis.diagnosis.coded.uuid);
}

return clone;
}),
return clone;
}),
);
}
// handle obs groups
Expand All @@ -375,8 +366,10 @@ export async function hydrateRepeatField(
}

function prepareDiagnosis(fields: FormField[]) {
return fields
const diagnoses = fields
.filter((field) => field.type === 'diagnosis' && hasSubmission(field))
.flatMap((field) => [field.meta.submission.newValue, field.meta.submission.voidedValue])
.filter((d) => d);
.map((field) => field.meta.submission.newValue || field.meta.submission.voidedValue)
.filter((o) => o);

return diagnoses;
}
13 changes: 13 additions & 0 deletions src/transformers/default-schema-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,19 @@ function handleDiagnosis(question: FormField) {
class: question.questionOptions.diagnosis?.conceptClasses,
},
};
if (question.questionOptions.diagnosis?.conceptSet) {
question.questionOptions = {
...question.questionOptions,
concept: question.questionOptions.diagnosis?.conceptSet,
datasource: {
name: 'problem_datasource',
config: {
useSetMembersByConcept: true,
},
},
};
}

delete question.questionOptions['dataSource'];
}
}
1 change: 1 addition & 0 deletions src/types/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export interface FormQuestionOptions {
rank?: number;
isConfirmed?: boolean;
conceptClasses?: Array<string>;
conceptSet?: string;
};
}

Expand Down
Loading