forked from OpenFn/openfn-lime
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
user [email protected] initiated a sync from Lightning
- Loading branch information
1 parent
ac3b87c
commit 4bfa16a
Showing
3 changed files
with
8 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -231,8 +231,8 @@ | |
"wf2-omrs-dhis2": { | ||
"id": "c886121c-0a91-45b5-ae30-7d0922b693be", | ||
"name": "wf2-omrs-dhis2", | ||
"inserted_at": "2024-11-12T16:16:40.298307Z", | ||
"lock_version": 104, | ||
"inserted_at": "2024-11-12T17:40:15.020397Z", | ||
"lock_version": 114, | ||
"triggers": { | ||
"cron": { | ||
"enabled": false, | ||
|
@@ -266,7 +266,7 @@ | |
"Get-TEIs-and-Map-Answers": { | ||
"id": "793f4fe1-d717-4caa-a617-2b5e2b19880d", | ||
"name": "Get TEIs and Map Answers", | ||
"body": "const delay = ms => new Promise(resolve => setTimeout(resolve, ms));\n\neach(\n $.encounters,\n get(\n 'tracker/trackedEntities',\n {\n orgUnit: $.orgUnit,\n program: $.program,\n filter: [`AYbfTPYMNJH:Eq:${$.data.patient.uuid}`],\n fields: '*',\n },\n {},\n async state => {\n const encounter = state.references.at(-1);\n console.log(encounter.patient.uuid, 'Encounter patient uuid');\n\n const { trackedEntity, enrollments } = state.data?.instances?.[0] || {};\n if (trackedEntity && enrollments) {\n state.TEIs ??= {};\n state.TEIs[encounter.patient.uuid] = {\n trackedEntity,\n enrollments,\n enrollment: enrollments[0]?.enrollment,\n };\n }\n\n await delay(2000);\n return state;\n }\n )\n);\n\nconst processAnswer = (\n answer,\n conceptUuid,\n dataElement,\n optsMap,\n optionSetKey\n) => {\n // console.log('Has answer', conceptUuid, dataElement);\n return typeof answer.value === 'object'\n ? processObjectAnswer(\n answer,\n conceptUuid,\n dataElement,\n optsMap,\n optionSetKey\n )\n : processOtherAnswer(answer, conceptUuid, dataElement);\n};\n\nconst processObjectAnswer = (\n answer,\n conceptUuid,\n dataElement,\n optsMap,\n optionSetKey\n) => {\n if (isDiagnosisByPsychologist(conceptUuid, dataElement)) {\n return '' + answer.value.uuid === '278401ee-3d6f-4c65-9455-f1c16d0a7a98';\n }\n return findMatchingOption(answer, optsMap, optionSetKey);\n};\n\nconst processOtherAnswer = (answer, conceptUuid, dataElement) => {\n if (isPhq9Score(answer.value, conceptUuid, dataElement)) {\n return getRangePhq(answer.value);\n }\n return answer.value;\n};\n\nconst processNoAnswer = (data, conceptUuid, dataElement) => {\n // console.log('No answer', conceptUuid, dataElement);\n if (isEncounterDate(conceptUuid, dataElement)) {\n return data.encounterDatetime.replace('+0000', '');\n }\n return '';\n};\n\nconst findMatchingOption = (answer, optsMap, optionSetKey) => {\n const optionKey = `${answer.formUuid}-${answer.concept.uuid}`; \n\n //const matchingOptionSet = optionSetKey[answer.concept.uuid];\n const matchingOptionSet = optionSetKey[optionKey];\n console.log('optionKey', optionKey);\n console.log('conceptUid', answer.concept.uuid);\n console.log('value uid', answer.value.uuid);\n console.log('value', answer.value.display);\n console.log('matchingOptionSet', matchingOptionSet);\n\n //const answerKey = answerMappingUid\n\n const matchingOption = optsMap.find(\n o =>\n o['value.uuid - External ID'] === answer.value.uuid &&\n o['DHIS2 Option Set UID'] === matchingOptionSet\n )?.['DHIS2 Option Code'] || answer.value.display; //TODO: revisit this logic if optionSet not found\n\n console.log('matchingOption value', matchingOption)\n\n // to convert ALL caps to lowercase per DHIS2 api\n if (matchingOption === 'FALSE') {\n console.log('false option', matchingOption)\n return 'false';\n }\n if (matchingOption === 'TRUE') {\n console.log('true option', matchingOption)\n return 'true';\n }\n ////=========================================//\n\n return matchingOption || '';\n};\n\nconst isEncounterDate = (conceptUuid, dataElement) => {\n return (\n conceptUuid === 'encounter-date' &&\n ['CXS4qAJH2qD', 'I7phgLmRWQq', 'yUT7HyjWurN'].includes(dataElement)\n );\n};\n\nconst isDiagnosisByPsychologist = (conceptUuid, dataElement) =>\n conceptUuid === '722dd83a-c1cf-48ad-ac99-45ac131ccc96' &&\n dataElement === 'pN4iQH4AEzk';\n\nconst isPhq9Score = (value, conceptUuid, dataElement) =>\n typeof value === 'number' &&\n conceptUuid === '5f3d618e-5c89-43bd-8c79-07e4e98c2f23' &&\n dataElement === 'tsFOVnlc6lz';\n\nconst getRangePhq = input => {\n if (input >= 20) return '>20';\n if (input >= 15) return '15_19';\n if (input >= 10) return '10_14';\n if (input >= 5) return '5_9';\n return '0_4';\n};\n\nconst dataValuesMapping = (data, dataValueMap, optsMap, optionSetKey) => {\n return Object.keys(dataValueMap)\n .map(dataElement => {\n const conceptUuid = dataValueMap[dataElement];\n const obsAnswer = data.obs.find(o => o.concept.uuid === conceptUuid);\n const answer = {\n ...obsAnswer,\n formUuid: data.form.uuid\n };\n const value = answer\n ? processAnswer(answer, conceptUuid, dataElement, optsMap, optionSetKey)\n : processNoAnswer(data, conceptUuid, dataElement);\n\n return { dataElement, value };\n })\n .filter(d => d);\n};\n\n// Prepare DHIS2 data model for create events\nfn(state => {\n const createEvent = (data, state) => {\n const { trackedEntity, enrollment } = state.TEIs[data.patient.uuid] || {};\n\n if (!trackedEntity || !enrollment) {\n return null;\n }\n\n return {\n program: state.program,\n orgUnit: state.orgUnit,\n trackedEntity,\n enrollment,\n occurredAt: data.encounterDatetime.replace('+0000', ''),\n };\n };\n\n const handleMissingRecord = (data, state) => {\n const { uuid, display } = data.patient;\n\n console.log(uuid, 'Patient is missing trackedEntity && enrollment');\n\n state.missingRecords ??= {};\n state.missingRecords[uuid] ??= {\n encounters: [],\n patient: display,\n };\n\n state.missingRecords[uuid].encounters.push(data);\n };\n\n const processEncounter = (data, state) => {\n const event = createEvent(data, state);\n if (!event) {\n handleMissingRecord(data, state);\n return null;\n }\n\n const form = state.formMaps[data.form.uuid];\n if (!form?.dataValueMap) {\n return null;\n }\n\n return {\n ...event,\n programStage: form.programStage,\n dataValues: dataValuesMapping(\n data,\n form.dataValueMap,\n state.optsMap,\n state.optionSetKey\n ),\n };\n };\n\n state.encountersMapping = state.encounters\n .map(data => processEncounter(data, state))\n .filter(Boolean);\n\n return state;\n});\n// const findMatchingOption = (answer, optsMap, optionSetKey) => {\n// const answerKeyUid = optionSetKey[answer.concept.uuid];\n\n// const matchingOption = optsMap.find(\n// (o) => o[\"DHIS2 answerKeyUid\"] === answerKeyUid\n// )?.[\"DHIS2 Option Code\"];\n\n// //TBD if we want this.. TODO: revisit this logic\n// if (matchingOption === \"no\") {\n// return \"FALSE\";\n// }\n// if (matchingOption === \"yes\") {\n// return \"TRUE\";\n// }\n// //======//\n// return matchingOption || \"\";\n// };\n\n//=== Original logic modified on Nov 11 =========//\n// const findMatchingOption = (answer, optsMap) => {\n// const matchingOption = optsMap.find(\n// o => o['value.uuid - External ID'] === answer.value.uuid\n// )?.['DHIS2 Option Code'];\n\n// if (matchingOption === 'no') {\n// return 'FALSE';\n// }\n// if (matchingOption === 'yes') {\n// return 'TRUE';\n// }\n// return matchingOption || '';\n// };\n", | ||
"body": "const delay = ms => new Promise(resolve => setTimeout(resolve, ms));\n\neach(\n $.encounters,\n get(\n 'tracker/trackedEntities',\n {\n orgUnit: $.orgUnit,\n program: $.program,\n filter: [`AYbfTPYMNJH:Eq:${$.data.patient.uuid}`],\n fields: '*',\n },\n {},\n async state => {\n const encounter = state.references.at(-1);\n console.log(encounter.patient.uuid, 'Encounter patient uuid');\n\n const { trackedEntity, enrollments } = state.data?.instances?.[0] || {};\n if (trackedEntity && enrollments) {\n state.TEIs ??= {};\n state.TEIs[encounter.patient.uuid] = {\n trackedEntity,\n enrollments,\n enrollment: enrollments[0]?.enrollment,\n };\n }\n\n await delay(2000);\n return state;\n }\n )\n);\n\nconst processAnswer = (\n answer,\n conceptUuid,\n dataElement,\n optsMap,\n optionSetKey\n) => {\n // console.log('Has answer', conceptUuid, dataElement);\n return typeof answer.value === 'object'\n ? processObjectAnswer(\n answer,\n conceptUuid,\n dataElement,\n optsMap,\n optionSetKey\n )\n : processOtherAnswer(answer, conceptUuid, dataElement);\n};\n\nconst processObjectAnswer = (\n answer,\n conceptUuid,\n dataElement,\n optsMap,\n optionSetKey\n) => {\n if (isDiagnosisByPsychologist(conceptUuid, dataElement)) {\n console.log('Yes done by psychologist..')\n return '' + answer.value.uuid === '278401ee-3d6f-4c65-9455-f1c16d0a7a98';\n }\n //return 'true'; \n return findMatchingOption(answer, optsMap, optionSetKey);\n};\n\nconst processOtherAnswer = (answer, conceptUuid, dataElement) => {\n if (isPhq9Score(answer.value, conceptUuid, dataElement)) {\n console.log('isPhq9Score', isPhq9Score);\n return getRangePhq(answer.value);\n }\n return answer.value;\n};\n\nconst processNoAnswer = (data, conceptUuid, dataElement) => {\n // console.log('No answer', conceptUuid, dataElement);\n if (isEncounterDate(conceptUuid, dataElement)) {\n return data.encounterDatetime.replace('+0000', '');\n }\n return '';\n};\n\nconst findMatchingOption = (answer, optsMap, optionSetKey) => {\n const optionKey = `${answer.formUuid}-${answer.concept.uuid}`; \n\n //const matchingOptionSet = optionSetKey[answer.concept.uuid];\n const matchingOptionSet = optionSetKey[optionKey];\n console.log('optionKey', optionKey);\n console.log('conceptUid', answer.concept.uuid);\n console.log('value uid', answer.value.uuid);\n console.log('value', answer.value.display);\n console.log('matchingOptionSet', matchingOptionSet);\n\n //const answerKey = answerMappingUid\n\n const matchingOption = optsMap.find(\n o =>\n o['value.uuid - External ID'] === answer.value.uuid &&\n o['DHIS2 Option Set UID'] === matchingOptionSet\n )?.['DHIS2 Option Code'] || answer.value.display; //TODO: revisit this logic if optionSet not found\n\n console.log('matchingOption value', matchingOption)\n\n // to convert ALL caps to lowercase per DHIS2 api\n if (matchingOption === 'FALSE') {\n console.log('false option', matchingOption)\n return 'false';\n }\n if (matchingOption === 'TRUE') {\n console.log('true option', matchingOption)\n return 'true';\n }\n ////=========================================//\n\n return matchingOption || '';\n};\n\nconst isEncounterDate = (conceptUuid, dataElement) => {\n return (\n conceptUuid === 'encounter-date' &&\n ['CXS4qAJH2qD', 'I7phgLmRWQq', 'yUT7HyjWurN'].includes(dataElement)\n );\n};\n\nconst isDiagnosisByPsychologist = (conceptUuid, dataElement) =>\n conceptUuid === '722dd83a-c1cf-48ad-ac99-45ac131ccc96' &&\n dataElement === 'pN4iQH4AEzk';\n\nconst isPhq9Score = (value, conceptUuid, dataElement) =>\n typeof value === 'number' &&\n conceptUuid === '5f3d618e-5c89-43bd-8c79-07e4e98c2f23' &&\n dataElement === 'tsFOVnlc6lz';\n\nconst getRangePhq = input => {\n if (input >= 20) return '>20';\n if (input >= 15) return '15_19';\n if (input >= 10) return '10_14';\n if (input >= 5) return '5_9';\n return '0_4';\n};\n\nconst dataValuesMapping = (data, dataValueMap, optsMap, optionSetKey) => {\n return Object.keys(dataValueMap)\n .map(dataElement => {\n const conceptUuid = dataValueMap[dataElement];\n const obsAnswer = data.obs.find(o => o.concept.uuid === conceptUuid);\n const answer = {\n ...obsAnswer,\n formUuid: data.form.uuid\n };\n const value = answer\n ? processAnswer(answer, conceptUuid, dataElement, optsMap, optionSetKey)\n : processNoAnswer(data, conceptUuid, dataElement);\n\n return { dataElement, value };\n })\n .filter(d => d);\n};\n\n// Prepare DHIS2 data model for create events\nfn(state => {\n const createEvent = (data, state) => {\n const { trackedEntity, enrollment } = state.TEIs[data.patient.uuid] || {};\n\n if (!trackedEntity || !enrollment) {\n return null;\n }\n\n return {\n program: state.program,\n orgUnit: state.orgUnit,\n trackedEntity,\n enrollment,\n occurredAt: data.encounterDatetime.replace('+0000', ''),\n };\n };\n\n const handleMissingRecord = (data, state) => {\n const { uuid, display } = data.patient;\n\n console.log(uuid, 'Patient is missing trackedEntity && enrollment');\n\n state.missingRecords ??= {};\n state.missingRecords[uuid] ??= {\n encounters: [],\n patient: display,\n };\n\n state.missingRecords[uuid].encounters.push(data);\n };\n\n const processEncounter = (data, state) => {\n const event = createEvent(data, state);\n if (!event) {\n handleMissingRecord(data, state);\n return null;\n }\n\n const form = state.formMaps[data.form.uuid];\n if (!form?.dataValueMap) {\n return null;\n }\n\n return {\n ...event,\n programStage: form.programStage,\n dataValues: dataValuesMapping(\n data,\n form.dataValueMap,\n state.optsMap,\n state.optionSetKey\n ),\n };\n };\n\n state.encountersMapping = state.encounters\n .map(data => processEncounter(data, state))\n .filter(Boolean);\n\n return state;\n});\n// const findMatchingOption = (answer, optsMap, optionSetKey) => {\n// const answerKeyUid = optionSetKey[answer.concept.uuid];\n\n// const matchingOption = optsMap.find(\n// (o) => o[\"DHIS2 answerKeyUid\"] === answerKeyUid\n// )?.[\"DHIS2 Option Code\"];\n\n// //TBD if we want this.. TODO: revisit this logic\n// if (matchingOption === \"no\") {\n// return \"FALSE\";\n// }\n// if (matchingOption === \"yes\") {\n// return \"TRUE\";\n// }\n// //======//\n// return matchingOption || \"\";\n// };\n\n//=== Original logic modified on Nov 11 =========//\n// const findMatchingOption = (answer, optsMap) => {\n// const matchingOption = optsMap.find(\n// o => o['value.uuid - External ID'] === answer.value.uuid\n// )?.['DHIS2 Option Code'];\n\n// if (matchingOption === 'no') {\n// return 'FALSE';\n// }\n// if (matchingOption === 'yes') {\n// return 'TRUE';\n// }\n// return matchingOption || '';\n// };\n", | ||
"adaptor": "@openfn/[email protected]", | ||
"project_credential_id": "9716980f-1b35-4295-adde-b05bc47002da" | ||
}, | ||
|
@@ -280,7 +280,7 @@ | |
"Mappings": { | ||
"id": "f022ca80-7da0-4fdb-9728-38e34280c516", | ||
"name": "Mappings", | ||
"body": "get(\n 'https://raw.githubusercontent.com/OpenFn/openfn-lime-pilot/refs/heads/collections/metadata/collections.json',\n { parseAs: 'json' },\n state => {\n const { cursor, lastRunDateTime, patients, data } = state;\n\n return {\n ...data,\n cursor,\n patients,\n lastRunDateTime,\n };\n }\n);\n\n// Validates if a string matches UUID v4 format\nconst isValidUUID = id => {\n if (!id || typeof id !== 'string') return false;\n\n const UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n return UUID_PATTERN.test(id);\n};\n\nfn(state => {\n const { formMetadata, identifiers, ...rest } = state;\n\n rest.formUuids = formMetadata\n .filter(form => isValidUUID(form['OMRS form.uuid']))\n .map(form => form['OMRS form.uuid']);\n\n rest.orgUnit = identifiers.find(i => i.type === 'ORG_UNIT')?.[\n 'dhis2 attribute id'\n ];\n rest.program = identifiers.find(i => i.type === 'PROGRAM')?.[\n 'dhis2 attribute id'\n ];\n\n rest.patientProgramStage = state.formMaps.patient.programStage;\n\n rest.dhis2PatientNumber = identifiers.find(\n i => i.type === 'DHIS2_PATIENT_NUMBER'\n )?.['omrs identifierType']; //DHIS2 ID or DHIS2 Patient Number\n\n rest.openmrsAutoId = identifiers.find(i => i.type === 'OPENMRS_AUTO_ID')?.[\n 'omrs identifierType'\n ]; //MSF ID or OpenMRS Patient Number\n\n return rest;\n});\n\nfn(state => {\n state.genderOptions = {\n M: 'male',\n F: 'female',\n U: 'unknown',\n O: 'prefer_not_to_answer',\n };\n\n state.placeOflivingMap = state.optsMap\n .filter(o => o['OptionSet name'] === 'Place of Living')\n .reduce((acc, value) => {\n acc[value['value.display - Answers']] = value['DHIS2 Option Code'];\n return acc;\n }, {});\n\n return state;\n});\n", | ||
"body": "get(\n 'https://raw.githubusercontent.com/OpenFn/openfn-lime-pilot/refs/heads/collections/metadata/collections.json',\n { parseAs: 'json' },\n state => {\n const { cursor, lastRunDateTime, patients, data } = state;\n\n return {\n ...data,\n cursor,\n patients,\n lastRunDateTime,\n };\n }\n);\n\n// Validates if a string matches UUID v4 format\nconst isValidUUID = id => {\n if (!id || typeof id !== 'string') return false;\n\n const UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n return UUID_PATTERN.test(id);\n};\n\nfn(state => {\n const { formMetadata, identifiers, ...rest } = state;\n\n rest.formUuids = formMetadata\n .filter(form => isValidUUID(form['OMRS form.uuid']))\n .map(form => form['OMRS form.uuid']);\n\n rest.orgUnit = identifiers.find(i => i.type === 'ORG_UNIT')?.[\n 'dhis2 attribute id'\n ];\n rest.program = identifiers.find(i => i.type === 'PROGRAM')?.[\n 'dhis2 attribute id'\n ];\n\n rest.patientProgramStage = state.formMaps.patient.programStage;\n\n rest.dhis2PatientNumber = identifiers.find(\n i => i.type === 'DHIS2_PATIENT_NUMBER'\n )?.['omrs identifierType']; //DHIS2 ID or DHIS2 Patient Number\n\n rest.openmrsAutoId = identifiers.find(i => i.type === 'OPENMRS_AUTO_ID')?.[\n 'omrs identifierType'\n ]; //MSF ID or OpenMRS Patient Number\n\n return rest;\n});\n\nfn(state => {\n //TODO: Update dynamically like placeOfLivingMap?\n state.genderOptions = {\n M: 'male',\n F: 'female',\n U: 'unknown',\n O: 'prefer_not_to_answer',\n };\n\n state.placeOflivingMap = state.optsMap\n .filter(o => o['OptionSet name'] === 'Place of Living')\n .reduce((acc, value) => {\n acc[value['value.display - Answers']] = value['DHIS2 Option Code'];\n return acc;\n }, {});\n\n return state;\n});\n", | ||
"adaptor": "@openfn/language-http@latest", | ||
"project_credential_id": null | ||
}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters