diff --git a/workflows/wf2/1-get-patients.js b/workflows/wf2/1-get-patients.js index 48cf932..793b947 100644 --- a/workflows/wf2/1-get-patients.js +++ b/workflows/wf2/1-get-patients.js @@ -1,16 +1,19 @@ -// here we define the date cursor -fn(state => { - //manualCursor at beggining of the project 2023-05-20T06:01:24.000+0000 - const manualCursor = '2023-07-27T07:16:24.544Z'; - - state.cursor = state.lastRunDateTime || manualCursor; - - console.log( - 'Date cursor to filter & get only recent OMRS records ::', - state.cursor - ); +const clear = (state, keys) => { + if (keys?.length > 0) { + keys.forEach(key => { + delete state[key]; + }); + } return state; +}; +//Here we define the date cursor +//$.cursor at beggining of the project 2023-05-20T06:01:24.000+0000 +cursor($.lastRunDateTime || $.manualCursor || '2023-05-20T06:01:24.000+0000'); +// Update the lastRunDateTime for the next run +cursor('today', { + key: 'lastRunDateTime', + format: c => dateFns.format(new Date(c), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), }); searchPatient({ q: 'Katrina', v: 'full', limit: '100' }); @@ -26,14 +29,12 @@ fn(state => { //console.log('dateCreated for patient uuid ...2c6dbfc5acc8',getPatientByUuid("31b4d9c8-f7cc-4c26-ae61-2c6dbfc5acc8")) //console.log(JSON.stringify(state.data, null, 2)); - console.log('Filtering patients to only sync most recent records...'); + console.log('Filtering patients since:', state.cursor); - state.patients = results.filter( - patient => - (patient.auditInfo.dateChanged === null - ? patient.auditInfo.dateCreated - : patient.auditInfo.dateChanged) > state.cursor - ); + state.patients = results.filter(({ auditInfo }) => { + const lastModified = auditInfo?.dateChanged || auditInfo?.dateCreated; + return lastModified > state.cursor; + }); console.log('# of patients to sync to dhis2 ::', state.patients.length); console.log( 'uuids of patients to sync to dhis2 ::', @@ -43,8 +44,5 @@ fn(state => { state.lastRunDateTime = new Date().toISOString(); console.log('Updating cursor; next sync start date:', state.lastRunDateTime); - state.data = {}; - state.response = {}; - state.references = []; - return state; + return clear(state, ['data', 'response', 'references']); }); diff --git a/workflows/wf2/2-upsert-teis.js b/workflows/wf2/2-upsert-teis.js index ae92bb0..d5609d4 100644 --- a/workflows/wf2/2-upsert-teis.js +++ b/workflows/wf2/2-upsert-teis.js @@ -18,20 +18,6 @@ fn(state => { return matchingIdentifier ? matchingIdentifier.identifier : undefined; } - const calculateDOB = age => { - const currentDate = new Date(); - const currentYear = currentDate.getFullYear(); - const birthYear = currentYear - age; - - const birthday = new Date( - birthYear, - currentDate.getMonth(), - currentDate.getDay() - ); - - return birthday.toISOString().replace(/\.\d+Z$/, '+0000'); - }; - const enrollments = [ { orgUnit: 'OPjuJMZFLop', @@ -98,31 +84,33 @@ fn(state => { a => a.attributeType.uuid === '24d1fa23-9778-4a8e-9f7b-93f694fc25e2' - )?.value.uuid + )?.value?.uuid ], //input.attributeType = "24d1fa23-9778-4a8e-9f7b-93f694fc25e2" }, - { - attribute: 'YUIQIA2ClN6', //current status - value: - statusMap[ - patient.person.attributes.find( - a => - a.attributeType.uuid === - 'e0b6ed99-72c4-4847-a442-e9929eac4a0f' - )?.value.uuid - ], //input.attributeType = "e0b6ed99-72c4-4847-a442-e9929eac4a0f" - }, - { - attribute: 'Qq6xQ2s6LO8', //legal status - value: - statusMap[ - patient.person.attributes.find( - a => - a.attributeType.uuid === - 'a9b2c642-097f-43f8-b96b-4d2f50ffd9b1' - )?.value.uuid - ], //input.attributeType = "a9b2c642-097f-43f8-b96b-4d2f50ffd9b1" - }, + // TODO: YUIQIA2ClN6 has an error, Aleksa to ask the client, + // { + // attribute: 'YUIQIA2ClN6', //current status + // value: + // statusMap[ + // patient.person.attributes.find( + // a => + // a.attributeType.uuid === + // 'e0b6ed99-72c4-4847-a442-e9929eac4a0f' + // )?.value?.uuid + // ], //input.attributeType = "e0b6ed99-72c4-4847-a442-e9929eac4a0f" + // }, + // TODO: Qq6xQ2s6LO8 has an error, Aleksa to ask the client + // { + // attribute: 'Qq6xQ2s6LO8', //legal status + // value: + // statusMap[ + // patient.person.attributes.find( + // a => + // a.attributeType.uuid === + // 'a9b2c642-097f-43f8-b96b-4d2f50ffd9b1' + // )?.value?.uuid + // ], //input.attributeType = "a9b2c642-097f-43f8-b96b-4d2f50ffd9b1" + // }, { attribute: 'FpuGAOu6itZ', //marital status value: @@ -131,7 +119,7 @@ fn(state => { a => a.attributeType.uuid === '3884dc76-c271-4bcb-8df8-81c6fb897f53' - )?.value.uuid + )?.value?.uuid ], //input.attributeType = "3884dc76-c271-4bcb-8df8-81c6fb897f53" }, { @@ -142,7 +130,7 @@ fn(state => { a => a.attributeType.uuid === 'dd1f7f0f-ccea-4228-9aa8-a8c3b0ea4c3e' - )?.value.uuid + )?.value?.uuid ], //input.attributeType = "dd1f7f0f-ccea-4228-9aa8-a8c3b0ea4c3e" }, { @@ -165,12 +153,14 @@ fn(state => { return patientsUpsert.push(payload); }; + // const patients = state.patients.slice(0, 1); return { ...state, genderOptions, patientsUpsert, buildPatientsUpsert, + references: [], }; }); @@ -206,7 +196,7 @@ each( 'tracker/trackedEntities', { orgUnit: 'OPjuJMZFLop', - filter: [`AYbfTPYMNJH:Eq:${$.data.uuid}`], + filter: [`AYbfTPYMNJH:Eq:${$.data?.uuid}`], program: 'w9MSPn5oSqp', }, {}, diff --git a/workflows/wf2/3-get-encounters.js b/workflows/wf2/3-get-encounters.js index 529e77f..7a5f7da 100644 --- a/workflows/wf2/3-get-encounters.js +++ b/workflows/wf2/3-get-encounters.js @@ -2,6 +2,7 @@ fn(state => { state.formUuids = [ '82db23a1-4eb1-3f3c-bb65-b7ebfe95b19b', '6a3e1e0e-dd13-3465-b8f5-ee2d42691fe5', + 'be8c12f9-e6fd-369a-9bc7-46a191866f15', ]; console.log('cursor datetime::', state.cursor); diff --git a/workflows/wf2/4-get-options-map.js b/workflows/wf2/4-get-options-map.js index 64e0d6f..f531b4d 100644 --- a/workflows/wf2/4-get-options-map.js +++ b/workflows/wf2/4-get-options-map.js @@ -59,7 +59,49 @@ fn(state => { state.mhgapMap = { f6FhkzfZ5j3: '4dae5b12-070f-4153-b1ca-fbec906106e1', //Admission type R3g94vJ2yFR: '22809b19-54ca-4d88-8d26-9577637c184e', //Clinical diagnosis + Yt4NhxZU5Vo: '819f79e7-b9af-4afd-85d4-2ab677223113', //Clinical diagnosis - If other, specify + pHoZYTrR7N0: '2be92591-da1b-4418-ba49-43b3fc0e4ce5', //Pregnant / breastfeeding + hMcCdEkhhjZ: 'f6cefc80-506a-44b0-ab5f-d6f5908cf7a5', //Child / adolescent + VnXwFYqHNqM: '15748787-7372-4022-b5d4-81ff8d6887ca', //Older adult + xRuC0NQRqZk: '99a8b512-17f9-4a5d-9fd4-80c27500995b', //Patient already on psychotropic / psychiatric medication (not prescribed by MSF)? + DCEJHFQvPWa: '5f3d618e-5c89-43bd-8c79-07e4e98c2f23', //PHQ-9 score + CcA8pc2YqWz: 'd9454e9c-6e3c-45ab-8a9a-834a9353ae11', //Session number + TZGKlSVIsN8: '5f6e245c-83fc-421b-8d46-061ac773ae71', //Follow-up required }; + state.mhpssFollowup = { + CcA8pc2YqWz: 'd9454e9c-6e3c-45ab-8a9a-834a9353ae11', //Session number + d8Dok4D8Fl4: '1a8bf24f-4f36-4971-aad9-ae77f3525738', //Type of consultation + E7MVMTTdvPi: 'b87a93ff-a4a1-4601-b35d-1e42bfa7e194', //Total number of beneficiaries in family consultation + aCTIvKSNndK: '722dd83a-c1cf-48ad-ac99-45ac131ccc96', //Consultation done by + UlSJrSD78HS: '82978311-bef9-46f9-9a9a-cc62254b00a6', //Location of intervention + sGEOde9q9p9: '0a0c70d2-2ba5-4cb3-941f-b4a9a4a7ec6d', //Location of intervention - If Health Facility, specify + LCdm2xe86ln: '41e68dee-a2a3-4e6c-9d96-53def5caff52', //Location of intervention - If MSF Health Facility, specify + rGS6S4jyKu3: '08cd4b4a-4b0b-4391-987b-b5b3d770d30f', //Location of intervention - If Mobile Clinic, specify + L6Jj7Tppr5q: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Location of intervention - If other, specify + vWWl7izQpqd: '82978311-bef9-46f9-9a9a-cc62254b00a6', //Type of intervention + G0hLyxqgcO7: '54e8c1b6-6397-4822-89a4-cf81fbc68ce9', //The patient did not come + tloy0Bd9qDf: 'd7410cd3-29be-4f8b-93d6-eb4de005db29', //Number of appointments missed + AZUFlZzPN6V: '278d3d1e-c02a-4db1-8ab3-8db3b82eb9b5', //Patient rescheduled + HNZHetAtowR: 'd34d5e93-03d6-494e-8f4c-2d7221227162', //Reason for missed appointment + t4HoHWCYdvm: '790b41ce-e1e7-11e8-b02f-0242ac130002', //Reason for missed appointment - If other, specify + MF3RML0HLbP: 'b2c5b6e0-66f0-4b9d-8576-b6f48e0a06df', //MHOS score + tsFOVnlc6lz: '5f3d618e-5c89-43bd-8c79-07e4e98c2f23', //PHQ-9 score + C5XtlggtVmd: 'a1a75011-0fef-460a-b666-dda2d171f39b', //CGI-S score + yTFUtaFJ1QU: 'f94de17e-9771-4711-aabb-c5bb0c022be2', //CGI-I score + F6q03Gan7Ro: '22809b19-54ca-4d88-8d26-9577637c184e', //Clinical diagnosis + OeA71vAyGZV: '819f79e7-b9af-4afd-85d4-2ab677223113', //Clinical diagnosis - If other, specify + OZViJk8FPVd: 'c2664992-8a5a-4a6d-9238-5df591307d55', //Has the patient had thoughts of death or suicide? + piKsOVnFIXO: 'a6c5188c-29f0-4d3d-8cf5-7852998df86f', //Has the patient attempted suicide or tried to kill themselves? + llBTRwwM94C: 'abede172-ba87-4ebe-8054-3afadb181ea3', //Is the patient currently at risk of hurting himself/herself or attempting suicide? + j8IYwKvxK4q: 'ccc4f06c-b76a-440d-9b7e-c48ba2c4a0ab', //Is the patient currently at risk of hurting others? + Lw2Kkl2y6mj: 'd516de07-979b-411c-b7e4-bd09cf7d9d91', //Does the patient regularly use alcohol / substances to become intoxicated? + a9J7luvOwhF: '3e97c2d0-15c1-4cfd-884f-7a4721079217', //Has the patient experienced an act of aggression or violence? + EM4ouSS9Kxe: 'd8c84af2-bd9b-4bf3-a815-81652cb0b0bc', //Patient experienced an act of aggression or violence - What type(s) of violence? + KjOAmUFJJgs: 'aae000c3-5242-4e3c-bd1f-7e922a6d3d34', //Patient experienced an act of aggression or violence - Time between violence event and consultation + GVTXoz0VrAd: '5f6e245c-83fc-421b-8d46-061ac773ae71', //Follow up session required? + N6GYmCjAhfh: '6d3876be-0a27-466d-ad58-92edcc8c31fb', //Referral done + wvVn2LfmNDO: '8fb3bb7d-c935-4b57-8444-1b953470e109', //Type of referral + }; return state; }); diff --git a/workflows/wf2/6-create-events.js b/workflows/wf2/6-create-events.js index bca2e1d..8e9591b 100644 --- a/workflows/wf2/6-create-events.js +++ b/workflows/wf2/6-create-events.js @@ -1,80 +1,109 @@ -// Prepare DHIS2 data model for create events -fn(state => { - const { TEIs, mhpssMap, mhgapMap } = state; - const optsMap = JSON.parse(state.optsMap); +const processAnswer = (answer, conceptUuid, dataElement, optsMap) => { + if (typeof answer.value === 'object') { + return processObjectAnswer(answer.value, conceptUuid, dataElement, optsMap); + } else { + return processOtherAnswer(answer.value, conceptUuid, dataElement); + } +}; + +const processObjectAnswer = ( + answerValue, + conceptUuid, + dataElement, + optsMap +) => { + const matchingOption = optsMap.find( + o => o['value.uuid - External ID'] === answerValue.uuid + ); + switch (true) { + case isDiagnosisByPsychologist(conceptUuid, dataElement): + return answerValue.uuid === '278401ee-3d6f-4c65-9455-f1c16d0a7a98' + ? 'TRUE' + : 'FALSE'; + + case matchingOption && Object.keys(matchingOption).length > 0: + return matchingOption['DHIS2 Option Code']; + } +}; +const processOtherAnswer = (answerValue, conceptUuid, dataElement) => { + switch (true) { + case isPhq9Score(answerValue, conceptUuid, dataElement): + return getRangePhq(answerValue); + + default: + return answerValue; + } +}; + +const isDiagnosisByPsychologist = (conceptUuid, dataElement) => + conceptUuid === '722dd83a-c1cf-48ad-ac99-45ac131ccc96' && + dataElement === 'pN4iQH4AEzk'; + +const isPhq9Score = (value, conceptUuid, dataElement) => + typeof value === 'number' && + conceptUuid === '5f3d618e-5c89-43bd-8c79-07e4e98c2f23' && + dataElement === 'tsFOVnlc6lz'; - function getRangePhq(input) { - if (input >= 0 && input <= 4) { +const getRangePhq = input => { + switch (true) { + case input >= 0 && input <= 4: return '0_4'; - } else if (input >= 5 && input <= 9) { + case input >= 5 && input <= 9: return '5_9'; - } else if (input >= 10 && input <= 14) { + case input >= 10 && input <= 14: return '10_14'; - } else if (input >= 15 && input <= 19) { + case input >= 15 && input <= 19: return '15_19'; - } else if (input >= 20) { + case input >= 20: return '>20'; - } else { - return ''; - } } +}; + +const dataValuesMapping = (data, dataValueMap, optsMap) => { + return Object.keys(dataValueMap) + .map(dataElement => { + let value; + const conceptUuid = dataValueMap[dataElement]; + const answer = data.obs.find(o => o.concept.uuid === conceptUuid); + + answer + ? (value = processAnswer(answer, conceptUuid, dataElement, optsMap)) + : (value = ''); + + return { dataElement, value }; + }) + .filter(d => d); +}; + +fn(state => { + const { mhpssFollowup, mhpssMap, mhgapMap } = state; - const dataValuesMapping = (data, formsMap) => { - return Object.keys(formsMap) - .map(k => { - let value; - const dataElement = k; - const conceptUuid = mhpssMap[k]; - const answer = data.obs.find(o => o.concept.uuid === conceptUuid); - - if (answer) { - if (typeof answer.value === 'object') { - value = optsMap.find( - o => o['value.uuid - External ID'] == answer?.value?.uuid - )?.['DHIS2 Option Code']; //Changed from 'DHIS2 Option UID' - if ( - //mapping: diagnosis done by psychologist - - conceptUuid === '722dd83a-c1cf-48ad-ac99-45ac131ccc96' && - dataElement === 'pN4iQH4AEzk' - ) { - if ( - answer.value.uuid === '278401ee-3d6f-4c65-9455-f1c16d0a7a98' - ) { - value = 'TRUE'; - } else { - value = 'FALSE'; - } - } else { - value = optsMap.find( - o => o['value.uuid - External ID'] == answer?.value?.uuid - )?.['DHIS2 Option Code']; //Changed from 'DHIS2 Option UID' - console.log(answer.value.uuid, { - dataElement, - value, - conceptUuid, - }); - } - } else if ( - typeof answer.value === 'number' && - conceptUuid === '5f3d618e-5c89-43bd-8c79-07e4e98c2f23' && - dataElement === 'tsFOVnlc6lz' //mapping: phq9 score - ) { - value = getRangePhq(answer.value); - } else if (!answer) { - value = ''; - } else { - value = answer.value; - } - } - return { dataElement, value }; - }) - .filter(d => d); + state.formMaps = { + '82db23a1-4eb1-3f3c-bb65-b7ebfe95b19b': { + //mhgap baseline* + programStage: 'EZJ9FsNau7Q', + dataValueMap: mhgapMap, + }, + '6a3e1e0e-dd13-3465-b8f5-ee2d42691fe5': { + programStage: 'MdTtRixaC1B', + dataValueMap: mhpssMap, + }, + 'be8c12f9-e6fd-369a-9bc7-46a191866f15': { + programStage: 'eUCtSH80vMe', + dataValueMap: mhpssFollowup, + }, }; + return state; +}); + +// Prepare DHIS2 data model for create events +fn(state => { + const optsMap = JSON.parse(state.optsMap); state.encountersMapping = state.encounters.map(data => { + const form = state.formMaps[data.form.uuid]; const eventDate = data.encounterDatetime.replace('+0000', ''); - const { trackedEntityInstance, enrollment } = TEIs[data.patient.uuid]; + const { trackedEntityInstance, enrollment } = state.TEIs[data.patient.uuid]; const event = { program: 'w9MSPn5oSqp', @@ -83,18 +112,11 @@ fn(state => { enrollment, eventDate, }; - if (data.form.uuid === '6a3e1e0e-dd13-3465-b8f5-ee2d42691fe5') { + if (form) { return { ...event, - programStage: 'MdTtRixaC1B', - dataValues: dataValuesMapping(data, mhpssMap), - }; - } - if (data.form.uuid === '82db23a1-4eb1-3f3c-bb65-b7ebfe95b19b') { - return { - ...event, - programStage: 'EZJ9FsNau7Q', //mhgap baseline* - dataValues: dataValuesMapping(data, mhgapMap), + programStage: form.programStage, + dataValues: dataValuesMapping(data, form.dataValueMap, optsMap), }; } }); @@ -107,22 +129,35 @@ fn(state => { return state; }); -// Create events fore each encounter -each( - '$.encountersMapping[*]', - create( - 'events', - state => { - // console.log(state.data); - return state.data; - }, - { - params: { - dataElementIdScheme: 'UID', - }, - } - ) +fn( + ({ + data, + references, + optsMap, + mhpssMap, + mhgapMap, + TEIs, + mhpssFollowup, + ...state + }) => state ); +// Create events fore each encounter +// each( +// '$.encountersMapping[*]', +// create( +// 'events', +// state => { +// // console.log(state.data); +// return state.data; +// }, +// { +// params: { +// dataElementIdScheme: 'UID', +// }, +// } +// ) +// ); + // Clean up state // fn(({ data, references, ...state }) => state); diff --git a/workflows/wf2/workflow.json b/workflows/wf2/workflow.json index f73494e..458f265 100644 --- a/workflows/wf2/workflow.json +++ b/workflows/wf2/workflow.json @@ -53,7 +53,7 @@ "configuration": "../tmp/dhis2-creds.json", "expression": "5-get-teis.js", "next": { - "create-events": "!state.errors" + "create-events": "!state.errors && state.encounters.length > 0" } }, {