From 739077a97fb36cdc353708f619d8a2a9121fbc45 Mon Sep 17 00:00:00 2001 From: Emmanuel Evance Date: Wed, 11 Sep 2024 09:33:15 +0300 Subject: [PATCH] Latest changes from staging (#11) * [skip actions] Configure OpenFn * Update second workflow (#5) * add program * code format * wip: update mappings for create encounter * cereate-events * Update workflow file path * remove ocl mapping * Changes accepted by Lightning; updated current project state * Update 4-get-options-map.js add missing mapping * Changes accepted by Lightning; updated current project state * dataValue mapping- return option code not option uid * Changes accepted by Lightning; updated current project state * add identifier mappings * add draft mappings for other TEAs for wf2 * updating identifier mapping, draft mappings for other TEAs * cleaning up comments * mapping fixes per testing * update gist url * update mhpss mappings with question names * update transformation logic Ref #2 improvements * Update 4-get-options-map.js * Update 4-get-options-map.js * Update 4-get-options-map.js * user mtuchi@openfn.org initiated a sync from Lightning * add back config.json file * Changes accepted by Lightning; updated current project state * user mtuchi@openfn.org initiated a sync from Lightning --------- Co-authored-by: mtuchi Co-authored-by: Aleksa Krolls Co-authored-by: aleksa-krolls Co-authored-by: aleksa-krolls Co-authored-by: openfn[bot] --- ...d57-9a3c-4318-bdcb-f57a386cf811-deploy.yml | 21 + .gitignore | 1 + ...57-9a3c-4318-bdcb-f57a386cf811-config.json | 5 + ...dd57-9a3c-4318-bdcb-f57a386cf811-spec.yaml | 571 +++++++++++++++++- ...d57-9a3c-4318-bdcb-f57a386cf811-state.json | 114 +++- workflows/wf2/1-get-patients.js | 10 +- workflows/wf2/2-upsert-teis.js | 103 +++- workflows/wf2/3-get-encounters.js | 20 +- workflows/wf2/4-get-oclmap.js | 65 -- workflows/wf2/4-get-options-map.js | 60 ++ workflows/wf2/5-create-events.js | 107 ---- workflows/wf2/5-get-teis.js | 24 + workflows/wf2/6-create-events.js | 80 +++ workflows/wf2/workflow.json | 20 +- 14 files changed, 947 insertions(+), 254 deletions(-) create mode 100644 .github/workflows/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-deploy.yml create mode 100644 openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-config.json delete mode 100644 workflows/wf2/4-get-oclmap.js create mode 100644 workflows/wf2/4-get-options-map.js delete mode 100644 workflows/wf2/5-create-events.js create mode 100644 workflows/wf2/5-get-teis.js create mode 100644 workflows/wf2/6-create-events.js diff --git a/.github/workflows/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-deploy.yml b/.github/workflows/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-deploy.yml new file mode 100644 index 0000000..f2f3d20 --- /dev/null +++ b/.github/workflows/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-deploy.yml @@ -0,0 +1,21 @@ +on: + push: + branches: + - staging + +concurrency: + group: openfn-deployment + cancel-in-progress: false + +jobs: + deploy-to-lightning: + runs-on: ubuntu-latest + name: A job to deploy to Lightning + permissions: + contents: write + steps: + - name: openfn deploy + uses: openfn/cli-deploy-action@v1.0.0 + with: + secret_input: ${{ secrets.OPENFN_cd92dd57_9a3c_4318_bdcb_f57a386cf811_API_KEY }} + config_path_input: 'openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-config.json' diff --git a/.gitignore b/.gitignore index a578d36..09ea61c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +jsconfig.json # temp tmp diff --git a/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-config.json b/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-config.json new file mode 100644 index 0000000..e49cc74 --- /dev/null +++ b/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-config.json @@ -0,0 +1,5 @@ +{ + "endpoint": "https://app.openfn.org", + "specPath": "openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-spec.yaml", + "statePath": "openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-state.json" +} \ No newline at end of file diff --git a/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-spec.yaml b/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-spec.yaml index 138d39c..dbe2a36 100644 --- a/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-spec.yaml +++ b/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-spec.yaml @@ -1,63 +1,574 @@ name: msf-lime-iraq description: null -credentials: null +credentials: + mtuchi@openfn.org-MSF-DHIS2-UAT: + name: MSF DHIS2 UAT + owner: mtuchi@openfn.org + mtuchi@openfn.org-OpenMRS-Demo: + name: OpenMRS Demo + owner: mtuchi@openfn.org workflows: + wf1: + name: wf1 + jobs: + Get-Teis: + name: Get Teis + adaptor: '@openfn/language-dhis2@latest' + credential: mtuchi@openfn.org-MSF-DHIS2-UAT + body: | + fn(state => { + // const manualCursor = '2023-06-20T17:00:00.00'; + state.cursor = state.manualCursor || state.lastRunDateTime; + console.log('Date cursor to filter TEI extract ::', state.cursor); + + return state; + }); + + // Get trackedEntityInstances that are "active" in the target program + get( + 'tracker/trackedEntities', + { + orgUnit: 'OPjuJMZFLop', + program: 'w9MSPn5oSqp', + programStatus: 'ACTIVE', + }, + {}, + state => { + const trackedEntityInstances = state.data.instances + .filter(tei => tei.createdAt > state.cursor) + .slice(0, 1); + const offset = 2; // GMT+2 (Geneva time) + const currentDateTime = new Date(); + currentDateTime.setHours(currentDateTime.getHours() + offset); + + const lastRunDateTime = currentDateTime.toISOString().replace('Z', ''); + + console.log('# of TEIs extracted ::', trackedEntityInstances.length); + // console.log( + // 'trackedEntityInstance IDs ::', + // trackedEntityInstances.map(tei => tei.trackedEntityInstance) + // ); + + console.log('Next sync start date:', lastRunDateTime); + return { + ...state, + // data: {}, + references: [], + trackedEntityInstances, + lastRunDateTime, + }; + } + ); + + Get-Locations: + name: Get Locations + adaptor: '@openfn/language-dhis2@latest' + credential: mtuchi@openfn.org-MSF-DHIS2-UAT + body: | + get('optionGroups/kdef7pUey9f', { + fields: 'id,displayName,options[id,displayName,code]', + }); + + fn(({ data, ...state }) => { + state.locations = data; + return state; + }); + + fn(state => { + state.nationalityMap = { + afghanistan: '84066564-253e-43d8-b141-76730cffa878', + albania: 'db21f4f9-faf2-4358-8297-0ae76627b3b8', + algeria: '5f6c017f-074c-46b3-92d0-d055e2094366', + angola: '8a2e5a03-8a74-41ae-9a98-2310f9ce400d', + anguilla: 'c911af8a-171c-4ee9-b1ff-934373e8a819', + argentina: 'b83d24e8-34d8-4920-83c0-8ba014467ff4', + armenia: '39f1652a-f2b7-4b65-a7e1-7097ac6cdef0', + aruba: 'f3f1cba1-7c1e-4234-86a2-f27bb5964fee', + azerbaijan: '29750013-0e35-47ca-8f77-9192a923fb07', + bangladesh: 'a99de53c-ce76-4b1e-91b2-461094baf79e', + belarus: '664baba4-c552-47b9-97c0-ff67dafd27d6', + benin: 'cf863e31-bb38-48ed-90dd-f3dedcac304c', + bhutan: 'd45a57c9-994f-4deb-8845-9b785860a2ec', + bolivia: 'd8800d10-862b-42f1-8e22-cac1ce1bbcae', + bosnia_and_herzegovina: '05d8f4ef-45eb-463d-b2f3-8a5a613ee6b9', + botswana: '1304a0de-5b70-4d36-a873-e72a82963316', + brazil: '353ff388-64e6-434c-b78f-ca9636390389', + british_virgin_islands: 'b02c6d20-83a2-4947-8a7d-91d1f9c4d8a2', + bulgaria: '91c85a62-2b02-483a-aefd-e29d368565fe', + burkina_faso: '6c90c1ae-17a4-4e94-a267-4fba4c94efd8', + burundi: '8a2ed0db-eaad-44bc-bf06-5cb1b2a3db0b', + cambodia: '4fd14df8-8279-4dfa-bdd3-e1ab26bc0264', + cameroon: '873552ac-9850-4cc1-ae09-17eb0fccf405', + cape_verde: 'cfbc220a-1d6c-4469-bb6d-a8e3deb4f7e7', + central_african_republic: '61a4c4a4-25c2-4459-a874-ec1d24f8323a', + chad: '9e41e71c-f5d5-456c-a6f9-2129b8055bfc', + chile: '05333883-44e9-4f57-836a-041391803007', + china: '15016874-3e20-484a-baa8-9b94e1a3d358', + colombia: 'a008dff8-ce96-4662-bf8a-372e43d424f0', + comoros: '9d8738c8-40c2-4c66-aabb-ef176a20ffe8', + costa_rica: 'fb52f8c9-40ec-4dc4-92a4-d465612de2ff', + cote_divoire: '513cb36a-3f67-46ea-a789-fcdaca0e26f5', + cuba: '147c2434-5d7e-420c-8053-ba623301f3f5', + djibouti: 'cf5b334f-1c0f-41fc-ab54-53ff1e942830', + dominica: 'f70e51e5-b76c-4c38-9bf2-ef8e1f308ce1', + dominican_republic: 'ce72fc9b-619b-4c32-b865-600e888ad814', + drc_congo: '8f6d3d2a-e09f-473b-99c9-e539f97ceab6', + east_timor: '854f2f66-40e1-4a6a-9dee-09c832a52289', + ecuador: 'f9810f9a-78a7-42a2-99e6-19c629642386', + egypt: 'cc7343f8-9243-4d09-b378-58363850d624', + el_salvador: '9a34935e-5a8a-45be-8ccd-cb23192e420f', + equatorial_guinea: '7e591605-d723-4398-982a-8737af63a2dc', + eritrea: 'c61f03c2-0d1f-444f-a974-0a61063aff71', + ethiopia: '7478d375-014e-410e-a355-090143e88f5b', + gabon: '9f46ae06-114a-47fa-8f8d-e9749f04da25', + gambia: '5ec7ddd7-14a5-48ec-9e7c-8896d1010655', + georgia: 'bc71788f-db69-4b6f-8d1c-57a74395bdd2', + ghana: '2eb4ff46-d908-4148-9b0d-40ccfc1a655a', + greece: 'b75d6bcc-fadf-4141-8d0f-2463154b89f7', + guam: '51fa502b-98a3-4c42-b5fd-7b4d64489bb9', + guatemala: '3725a4d2-b28f-466b-905a-bafeaeb75855', + guinea: 'eeaff39c-8afd-43f7-b9a0-53729f5df1d8', + guinea_bissau: '14e90203-9197-42ea-9222-acafd2fd6984', + guyana: 'ba4dfa7e-f3cd-4e94-8ca7-6b96a93378a8', + haiti: 'f76f7dcb-f82e-4257-a627-1685ff3f3586', + honduras: 'cdd1336e-495b-4868-aace-57a84442d6fd', + india: '378d0107-eb43-485d-930c-0704b4e5aa11', + indonesia: '1cbe17e6-adc2-4680-bee0-54d94af75ebf', + iran: 'b422270e-d8af-4a32-b523-742545a17a3f', + iraq: '03aa7d6e-7656-48e4-8dc0-5e27706722c0', + jordan: '842f963c-f84d-4076-a8db-337295fd9b91', + kazakhstan: '6f6d0e78-2c81-411d-8d13-367e250dc110', + kenya: 'ad351a33-8846-4cad-8195-b07b6041d4a5', + kuwait: '4aee7a88-cda9-454e-9f25-4a6420270417', + kyrgyzstan: 'ace3b851-042b-46a6-8fea-68aae042d614', + laos: 'dfb01b39-c224-459e-b045-dd9461b9a1e5', + lebanon: '7de78f22-f53e-48d2-923f-ae1e4d814f46', + lesotho: 'b35b29c5-9bb7-4b40-ad33-29eecd28a9e6', + liberia: 'a5fd61b4-fd27-433d-8428-7e88a7f27921', + libya: '600c6af4-b767-423c-b942-7f06ca467258', + macedonia: '5837cc40-9ab5-4088-91c8-ca6e4b57e903', + madagascar: '3782bf3c-380e-4b60-b21a-38199073f112', + malawi: 'e8b5f188-6a5c-43ae-b4a5-200abb13153e', + malaysia: '3facca11-fbaa-4c40-8fac-4751d45c3f1b', + mali: '3e844a47-526a-46f9-afea-1af9ff8690aa', + martinique: '051ce04e-05e8-4430-8b75-3e499bbffbc8', + mauritania: '8acb006b-8596-4a98-8177-acb4cb575956', + mauritius: '17ced083-eb2a-4046-a713-26cabc7af95d', + mayotte: '1af148fe-2698-4b89-bf7f-87e5c48b6848', + mexico: '8381208f-01ca-4ed3-8f2c-f73ed1c316e3', + moldova: 'f0e9c8b5-69b5-48df-8cb9-2d089ba04e46', + mongolia: '9f341cb1-dcb5-4f6c-bd21-b57db01b4193', + morocco: 'ef467a17-91e8-4124-a136-7ed8ff7c7d15', + mozambique: '0916133b-4d93-4d60-9c20-e7ee3936f391', + myanmar: 'e81ba700-f9fc-4ed0-b248-578a25717cdb', + namibia: '0cb123dc-8810-4840-b6ab-6a527c5a79ef', + nauru: '3386fe63-2158-4040-a502-9f65fd2079d3', + nepal: 'fb01b01a-6775-423c-8012-7d43f587cb6c', + new_caledonia: 'e67c072b-7707-491f-8c2e-13c914216b61', + nicaragua: 'f6a9521c-596b-49f9-b914-67138e8c17e6', + niger: '7561db90-a866-4443-93f4-95cac1d47e9c', + nigeria: '4134651a-7f53-45fb-8bc6-7fed9cf36f51', + north_korea: '4d3079e4-8568-48e6-9342-665896875a38', + oman: '9b0af037-99d1-43b8-ac06-82137ec4b06d', + other: 'Other', + pakistan: 'f45d93c3-c9b0-4333-a5e6-299b7c425812', + palestine: 'e2a19948-49aa-44c0-98ef-67ae1160ef43', + panama: '1ef5a828-9d0d-4336-91ab-880d5dc0151c', + papua_new_guinea: 'e1e6b451-d7fe-4954-b225-99b2de82a4c0', + paraguay: 'd8412016-82f5-4801-a026-1bdc429850b7', + peru: 'e74fa87f-8469-46b0-975f-6cb37c394564', + philippines: 'bdbd5c9f-1f28-4f4d-a254-4a84f8bb2c8f', + puerto_rico: '39fca1d0-d2e7-4b13-82bd-626fbec71252', + republic_of_congo: '5db9afa5-b57e-4f45-8b1c-af766f14fc58', + reunion: '1dae4b2d-50c9-4bf1-b25a-7063600a5e74', + romania: '457e745e-ae97-463d-95a9-8d5689d3ca2b', + rwanda: '6bc925a1-7699-496a-85b0-c290699381db', + samoa: 'e03b381b-a7f4-40eb-964f-51571dc3c48c', + sao_tome_and_principe: 'f66bbb42-684f-42d7-bfcd-95d586eb7dc9', + saudi_arabia: 'fdf495a4-e60c-46f7-a8a2-61a216849086', + senegal: 'ad948f1b-0733-4f8d-b049-d64289b43a10', + serbia: 'd2e69cef-3bff-4220-ba91-a6a678fb606b', + sierra_leone: 'ffba9caf-b6aa-4078-845e-578f7a7fd566', + somalia: '99c8dccc-4dfa-4d30-86be-42a309ab431f', + south_africa: '75882d62-1c55-480d-b411-8ca40c3307df', + south_korea: '0603d6b9-334f-4443-ab60-7c5d457b95fc', + south_sudan: 'f113e24e-2ea9-49a2-9b28-59241b9adb21', + sri_lanka: 'b0031c01-d242-4410-b98b-cc1511590b85', + sudan: '2f03a932-2b75-4e8b-9f44-0fcd83c75dc4', + suriname: 'c65d3329-98d4-4dd7-89d3-141b70d00eb2', + swaziland: '06a2703b-af17-4e44-83f5-6cc9a8a75320', + switzerland: 'dcdcdc70-a006-4b0a-bac2-7de89b022b65', + syria: '1e34ee55-ef9f-4386-bae6-6995555ded75', + tajikistan: '34836c60-5449-48d6-b3c9-c0b3361b9f2c', + tanzania: '050a8eb1-0d77-4f65-a2da-776a13bcd2a4', + thailand: '289ac5bd-6434-4837-86bf-b54d22970ac8', + togo: '6a583e64-869d-477d-a1c1-746320d45fc4', + tonga: 'd381f06d-2365-4f40-948b-cfe90d8cb532', + tunisia: '56be7864-fde6-4db3-8fa5-b9dd42cd9c53', + turkey: '7429c779-1d3a-4aec-8256-d0b1637e1bd1', + turkmenistan: '3ef17df5-299b-4385-9ea6-572df4b6f9ca', + uganda: 'be3d11d3-446d-440c-a582-d01c7cbb0eda', + ukraine: '38c99c8d-2b93-4848-a537-b1865a260bb2', + unknown: 'Unknown', + uruguay: 'c2e45baf-748b-4d7b-a391-ed6b802b6f94', + uzbekistan: '60512350-d79b-41aa-aff0-1b28ca4aa5f1', + venezuela: '557cea4a-0049-4b7a-b373-ed63f294a2a0', + vietnam: '49509c5f-e533-48a8-bf06-86935e3376b2', + western_sahara: '4086dfd2-f4f5-4107-93e8-07bee235af8f', + yemen: '6a3214e0-f94b-414c-8148-968e24386671', + zambia: '3ec0432d-ea37-4159-a658-29d6f07fe21a', + zimbabwe: 'ce1b0d8d-0a2d-4f93-a6ed-64aca2fd0f45', + }; + state.statusMap = { + asylum_seeker: 'f921ffdd-72ca-4d58-a89b-1fa2e959d110', + no_status: '2bacead2-f280-457c-9d28-e80e106f7d25', + refugee: 'MSF-AAAAAA000000000000001929', + single: '20b8524e-4c26-4fa0-81f0-fa23ebacc54d', + married: 'MSF-AAAAAA000000000000001863', + widowed: 'MSF-AAAAAA000000000000001864', + divorced_separated: 'MSF-AAAAAA000000000000001865', + concubine: '1060AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + not_applicable: 'MSF-AAAAAA000000000000001823', + student: 'MSF-AAAAAA000000000000001871', + permanent_employee: '4a18a820-f3a1-4bb7-9138-558a9ecc81da', + occasional_employee: '2cb73bee-7f94-4695-89c7-c81187dbc90c', + unemployed: 'MSF-AAAAAA000000000000001870', + housewife: '9b14b4d4-b749-4acf-acfe-79c480f3c4b3', + other: 'MSF-AAAAAA000000000000001329', + unknown: '1067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + idp: 'MSF-AAAAAA000000000000001930', + internationally_displaced: '515c5abe-4172-4d0c-a991-0de2888228d7', + resident: 'bbdb287c-4ba1-4944-bd87-eb126c5f9d92', + returnee: 'fc49acaa-ece2-4365-9dfb-70c2105de8b1', + }; + return state; + }); + + Create-Patients: + name: Create Patients + adaptor: '@openfn/language-openmrs@latest' + credential: mtuchi@openfn.org-OpenMRS-Demo + body: | + //Define gender options and prepare newPatientUuid and identifiers + fn(state => { + const genderOptions = { + male: 'M', + female: 'F', + unknown: 'U', + //TODO: Ask MSF for updated category option values + transgender_female: 'O', + transgender_male: 'O', + Prefer_not_to_answer: 'O', + gender_variant_non_conforming: 'O', + }; + + const identifiers = []; + const newPatientUuid = []; + + const { trackedEntityInstances } = state; + if (trackedEntityInstances.length > 0) + console.log( + '# of TEIs to send to OpenMRS: ', + trackedEntityInstances.length + ); + if (trackedEntityInstances.length === 0) + console.log('No data fetched in step prior to sync.'); + + return { + ...state, + genderOptions, + newPatientUuid, + identifiers, + }; + }); + + //First we generate a unique OpenMRS ID for each patient + each( + 'trackedEntityInstances[*]', + post( + 'idgen/identifiersource/8549f706-7e85-4c1d-9424-217d50a2988b/identifier', + {} + ).then(state => { + state.identifiers.push(state.data.identifier); + return state; + }) + ); + + // Then we map trackedEntityInstances to openMRS data model + fn(state => { + const { + trackedEntityInstances, + identifiers, + genderOptions, + nationalityMap, + statusMap, + locations, + } = state; + + const getValueForCode = (attributes, code) => { + const result = attributes.find(attribute => attribute.code === code); + return result ? result.value : 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'); + }; + + state.patients = trackedEntityInstances.map((d, i) => { + const patientNumber = getValueForCode(d.attributes, 'patient_number'); // Add random number for testing + Math.random() + + const nationality = + nationalityMap[getValueForCode(d.attributes, 'origin_nationality')]; + const currentStatus = + statusMap[getValueForCode(d.attributes, 'current_status')]; + const legalStatus = + getValueForCode(d.attributes, 'legal_status') && + statusMap[getValueForCode(d.attributes, 'legal_status')]; + const maritalStatus = + statusMap[getValueForCode(d.attributes, 'marital_status')]; + const employmentStatus = + statusMap[getValueForCode(d.attributes, 'occupation')]; + + const noOfChildren = d.attributes.find( + a => a.attribute === 'SVoT2cVLd5O' + )?.value; + + // const lonlat = d.attributes.find(a => a.attribute === 'rBtrjV1Mqkz')?.value; + // const location = locations.options.find( + // o => o.code === lonlat + // )?.displayName; + + // const [countyDistrict, remainder] = location?.split(' ('); + // const [cityVillage] = remainder?.split(')'); + + return { + patientNumber, + person: { + age: getValueForCode(d.attributes, 'age'), + gender: genderOptions[getValueForCode(d.attributes, 'sex')], + birthdate: + d.attributes.find(a => a.attribute === 'WDp4nVor9Z7')?.value ?? + calculateDOB(getValueForCode(d.attributes, 'age')), + birthdateEstimated: d.attributes.find( + a => a.attribute === 'WDp4nVor9Z7' + ) + ? true + : false, + names: [ + { + familyName: + d.attributes.find(a => a.attribute === 'fa7uwpCKIwa')?.value ?? + 'unknown', + givenName: + d.attributes.find(a => a.attribute === 'Jt9BhFZkvP2')?.value ?? + 'unknown', + }, + ], + addresses: [ + { + country: 'Iraq', + stateProvince: 'Ninewa', + // countyDistrict, + // cityVillage, + }, + ], + attributes: [ + { + attributeType: '24d1fa23-9778-4a8e-9f7b-93f694fc25e2', + value: nationality, + }, + { + attributeType: 'e0b6ed99-72c4-4847-a442-e9929eac4a0f', + value: currentStatus, + }, + legalStatus && { + attributeType: 'a9b2c642-097f-43f8-b96b-4d2f50ffd9b1', + value: legalStatus, + }, + { + attributeType: '3884dc76-c271-4bcb-8df8-81c6fb897f53', + value: maritalStatus, + }, + employmentStatus && { + attributeType: 'dd1f7f0f-ccea-4228-9aa8-a8c3b0ea4c3e', + value: employmentStatus, + }, + noOfChildren && { + attributeType: 'e363161a-9d5c-4331-8463-238938f018ed', + value: noOfChildren, + }, + ].filter(i => i), + }, + + identifiers: [ + { + identifier: identifiers[i], //map ID value from DHIS2 attribute + identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', + location: 'cf6fa7d4-1f19-4c85-ac50-ff824805c51c', //default location old:44c3efb0-2583-4c80-a79e-1f756a03c0a1 + preferred: true, + }, + { + uuid: d.trackedEntity, + identifier: patientNumber, + identifierType: '8d79403a-c2cc-11de-8d13-0010c6dffd0f', //Old Identification number + location: 'cf6fa7d4-1f19-4c85-ac50-ff824805c51c', //default location + preferred: false, //default value for this identifiertype + }, + ], + }; + }); + + return state; + }); + + // Creating patients in openMRS + each( + 'patients[*]', + create( + 'patient', + state => { + const { patientNumber, ...patient } = state.data; + console.log( + 'Creating patient record\n', + JSON.stringify(patient, null, 2) + ); + return patient; + }, + state => { + state.newPatientUuid.push({ + patient_number: state.references.at(-1)?.patientNumber, + uuid: state.data.body.uuid, + }); + return state; + } + ) + ); + + // Clean up state + fn(({ data, references, ...state }) => state); + + Update-Teis: + name: Update Teis + adaptor: '@openfn/language-dhis2@latest' + credential: mtuchi@openfn.org-MSF-DHIS2-UAT + body: | + fn(state => { + if (state.newPatientUuid.length === 0) { + console.log('No data fetched in step prior to sync.'); + } + + console.log( + 'newPatientUuid ::', + JSON.stringify(state.newPatientUuid, null, 2) + ); + return state; + }); + + // Update TEI on DHIS2 + each( + 'newPatientUuid[*]', + upsert( + 'trackedEntityInstances', + state => ({ + ou: 'OPjuJMZFLop', + program: 'w9MSPn5oSqp', + filter: [`P4wdYGkldeG:Eq:${state.data.patient_number}`], + }), + { + orgUnit: 'OPjuJMZFLop', + program: 'w9MSPn5oSqp', + trackedEntityType: 'cHlzCA2MuEF', + attributes: [ + { attribute: 'P4wdYGkldeG', value: `${$.data.patient_number}` }, //DHIS2 patient number to use as lookup key + { attribute: 'AYbfTPYMNJH', value: `${$.data.patient.uuid}` }, //OMRS patient uuid + { + attribute: 'ZBoxuExmxcZ', + value: `${$.data.patient.identifier[0].identifier}`, + }, //id generated in wf1-2 e.g., "IQ146-24-000-027" + ], + } + ) + ); + + triggers: + webhook: + type: webhook + enabled: true + edges: + webhook->Get-Teis: + source_trigger: webhook + target_job: Get-Teis + condition_type: always + enabled: true + Get-Teis->Get-Locations: + source_job: Get-Teis + target_job: Get-Locations + condition_type: on_job_success + enabled: true + Get-Locations->Create-Patients: + source_job: Get-Locations + target_job: Create-Patients + condition_type: on_job_success + enabled: true + Create-Patients->Update-Teis: + source_job: Create-Patients + target_job: Update-Teis + condition_type: on_job_success + enabled: true wf2: name: wf2 jobs: Get-Patients: name: Get Patients adaptor: '@openfn/language-openmrs@latest' - credential: null - body: | - - // Check out the Job Writing Guide for help getting started: - // https://docs.openfn.org/documentation/jobs/job-writing-guide + credential: mtuchi@openfn.org-OpenMRS-Demo + body: + path: workflows/wf2/1-get-patients.js Upsert-TEIs: name: Upsert TEIs adaptor: '@openfn/language-dhis2@latest' - credential: null - body: | - - // Check out the Job Writing Guide for help getting started: - // https://docs.openfn.org/documentation/jobs/job-writing-guide + credential: mtuchi@openfn.org-MSF-DHIS2-UAT + body: + path: workflows/wf2/2-upsert-teis.js Get-Encounters: name: Get Encounters adaptor: '@openfn/language-openmrs@latest' - credential: null - body: | - - // Check out the Job Writing Guide for help getting started: - // https://docs.openfn.org/documentation/jobs/job-writing-guide + credential: mtuchi@openfn.org-OpenMRS-Demo + body: + path: workflows/wf2/3-get-encounters.js Get-Options-Map: name: Get Options Map adaptor: '@openfn/language-http@latest' credential: null - body: | - - // Check out the Job Writing Guide for help getting started: - // https://docs.openfn.org/documentation/jobs/job-writing-guide + body: + path: workflows/wf2/4-get-options-map.js Get-TEIs: name: Get TEIs adaptor: '@openfn/language-dhis2@latest' - credential: null - body: | - - // Check out the Job Writing Guide for help getting started: - // https://docs.openfn.org/documentation/jobs/job-writing-guide + credential: mtuchi@openfn.org-MSF-DHIS2-UAT + body: + path: workflows/wf2/5-get-teis.js Create-Events: name: Create Events adaptor: '@openfn/language-dhis2@latest' - credential: null - body: | - - // Check out the Job Writing Guide for help getting started: - // https://docs.openfn.org/documentation/jobs/job-writing-guide + credential: mtuchi@openfn.org-MSF-DHIS2-UAT + body: + path: workflows/wf2/6-create-events.js triggers: webhook: diff --git a/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-state.json b/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-state.json index d1f32d3..8a2008e 100644 --- a/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-state.json +++ b/openfn-cd92dd57-9a3c-4318-bdcb-f57a386cf811-state.json @@ -3,9 +3,20 @@ "name": "msf-lime-iraq", "description": null, "inserted_at": "2024-09-05T06:59:05Z", - "updated_at": "2024-09-05T07:08:47Z", + "updated_at": "2024-09-10T13:32:46Z", "scheduled_deletion": null, - "project_credentials": {}, + "project_credentials": { + "mtuchi@openfn.org-OpenMRS-Demo": { + "id": "3141d874-5456-4168-9680-ce04efb1089c", + "name": "OpenMRS Demo", + "owner": "mtuchi@openfn.org" + }, + "mtuchi@openfn.org-MSF-DHIS2-UAT": { + "id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d", + "name": "MSF DHIS2 UAT", + "owner": "mtuchi@openfn.org" + } + }, "history_retention_period": null, "dataclip_retention_period": null, "retention_policy": "retain_all", @@ -13,8 +24,8 @@ "wf2": { "id": "0f8ec062-1a2d-4f13-a8c5-b8298004512c", "name": "wf2", - "inserted_at": "2024-09-06T12:17:32.563717Z", - "lock_version": 6, + "inserted_at": "2024-09-10T15:45:40.150142Z", + "lock_version": 52, "triggers": { "webhook": { "enabled": true, @@ -26,44 +37,44 @@ "Get-Patients": { "id": "ab326112-9cdd-4449-8611-b5abc659d4ca", "name": "Get Patients", - "body": "\n// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n", + "body": "// here we define the date cursor\nfn(state => {\n //manualCursor at beggining of the project 2023-05-20T06:01:24.000+0000\n const manualCursor = '2023-07-27T07:16:24.544Z';\n\n state.cursor = state.lastRunDateTime || manualCursor;\n\n console.log(\n 'Date cursor to filter & get only recent OMRS records ::',\n state.cursor\n );\n\n return state;\n});\n\nsearchPatient({ q: 'Aisha', v: 'full', limit: '3' });\n// searchPatient({ q: 'Patient', v: 'full', limit: '100' });\n//Query all patients (q=all) not supported on demo OpenMRS; needs to be configured\n//...so we query all Patients with name \"Patient\" instead\n\nfn(state => {\n const { results } = state.data;\n\n const getPatientByUuid = uuid =>\n results.find(patient => patient.uuid === uuid).auditInfo.dateCreated;\n\n // console.log('dateCreated for patient uuid ...2c6dbfc5acc8',getPatientByUuid(\"31b4d9c8-f7cc-4c26-ae61-2c6dbfc5acc8\"))\n //console.log(JSON.stringify(state.data, null, 2));\n\n console.log('Filtering patients to only sync most recent records...');\n\n state.patients = results.filter(\n patient =>\n (patient.auditInfo.dateChanged === null\n ? patient.auditInfo.dateCreated\n : patient.auditInfo.dateChanged) > state.cursor\n );\n console.log('# of new patients to sync to dhis2 ::', state.patients.length);\n // console.log(JSON.stringify(patients, null, 2));\n\n state.lastRunDateTime = new Date().toISOString();\n console.log('Updating cursor; next sync start date:', state.lastRunDateTime);\n\n state.data = {};\n state.references = [];\n return state;\n});\n", "adaptor": "@openfn/language-openmrs@latest", - "project_credential_id": null + "project_credential_id": "3141d874-5456-4168-9680-ce04efb1089c" }, "Upsert-TEIs": { "id": "6973c510-b36d-4c42-82f5-b26d8cd36a57", "name": "Upsert TEIs", - "body": "\n// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n", + "body": "fn(state => {\n const genderOptions = {\n M: 'male',\n F: 'female',\n U: 'unknown',\n O: 'prefer_not_to_answer',\n };\n\n const DHIS2_PATIENT_NUMBER = '8d79403a-c2cc-11de-8d13-0010c6dffd0f'; //DHIS2 ID or DHIS2 Patient Number\n const OPENMRS_AUTO_ID = '05a29f94-c0ed-11e2-94be-8c13b969e334'; //MSF ID or OpenMRS Patient Number\n const patientsUpsert = [];\n\n const buildPatientsUpsert = (patient, isNewPatient) => {\n const dateCreated = patient.auditInfo.dateCreated.substring(0, 10);\n\n function findIdentifierByUuid(identifiers, targetUuid) {\n // Use the `find` method to locate the matching identifier\n const matchingIdentifier = identifiers.find(\n identifier => identifier.identifierType.uuid === targetUuid\n );\n\n // Return the `identifier` value if a match is found; otherwise, return null\n return matchingIdentifier ? matchingIdentifier.identifier : undefined;\n }\n\n const calculateDOB = age => {\n const currentDate = new Date();\n const currentYear = currentDate.getFullYear();\n const birthYear = currentYear - age;\n\n const birthday = new Date(\n birthYear,\n currentDate.getMonth(),\n currentDate.getDay()\n );\n\n return birthday.toISOString().replace(/\\.\\d+Z$/, '+0000');\n };\n\n const enrollments = [\n {\n orgUnit: 'OPjuJMZFLop',\n program: 'w9MSPn5oSqp',\n programStage: 'EZJ9FsNau7Q',\n enrollmentDate: dateCreated,\n },\n ];\n\n const payload = {\n query: {\n ou: 'OPjuJMZFLop',\n program: 'w9MSPn5oSqp',\n filter: [`AYbfTPYMNJH:Eq:${patient.uuid}`], //upsert on omrs.patient.uid\n },\n data: {\n program: 'w9MSPn5oSqp',\n orgUnit: 'OPjuJMZFLop',\n trackedEntityType: 'cHlzCA2MuEF',\n attributes: [\n {\n attribute: 'fa7uwpCKIwa',\n value: patient.person.names[0].givenName,\n },\n {\n attribute: 'Jt9BhFZkvP2',\n value: patient.person.names[0].familyName,\n },\n {\n attribute: 'P4wdYGkldeG', //DHIS2 ID ==> \"Patient Number\"\n value: findIdentifierByUuid(\n patient.identifiers,\n DHIS2_PATIENT_NUMBER\n ),\n },\n {\n attribute: 'ZBoxuExmxcZ', //MSF ID ==> \"OpenMRS Patient Number\"\n value: findIdentifierByUuid(patient.identifiers, OPENMRS_AUTO_ID),\n },\n {\n attribute: 'AYbfTPYMNJH', //\"OpenMRS Patient UID\"\n value: patient.uuid,\n },\n {\n attribute: 'qptKDiv9uPl',\n value: genderOptions[patient.person.gender],\n },\n {\n attribute: 'Rv8WM2mTuS5',\n value: patient.person.age,\n },\n {\n attribute: 'WDp4nVor9Z7',\n value: patient.person.birthdate,\n },\n // {\n // attribute: 'rBtrjV1Mqkz', //Place of living\n // value: patient.person.address,\n // },\n // {\n // attribute: 'Xvzc9e0JJmp', //nationality\n // value: patient.person.attributes[x].value, //input.attributeType = \"24d1fa23-9778-4a8e-9f7b-93f694fc25e2\"\n // },\n // {\n // attribute: 'YUIQIA2ClN6', //current status\n // value: patient.person.attributes[x].value, //input.attributeType = \"e0b6ed99-72c4-4847-a442-e9929eac4a0f\"\n // },\n // {\n // attribute: 'Qq6xQ2s6LO8', //legal status\n // value: patient.person.attributes[x].value, //input.attributeType = \"a9b2c642-097f-43f8-b96b-4d2f50ffd9b1\"\n // },\n // {\n // attribute: 'FpuGAOu6itZ', //marital status\n // value: patient.person.attributes[x].value, //input.attributeType = \"3884dc76-c271-4bcb-8df8-81c6fb897f53\"\n // },\n // {\n // attribute: 'v7k4OcXrWR8', //employment status\n // value: patient.person.attributes[x].value, //input.attributeType = \"dd1f7f0f-ccea-4228-9aa8-a8c3b0ea4c3e\"\n // },\n // {\n // attribute: 'SVoT2cVLd5O', //employment status\n // value: patient.person.attributes[x].value, //input.attributeType = \"e363161a-9d5c-4331-8463-238938f018ed\"\n // },\n ],\n },\n };\n\n console.log('mapped dhis2 payloads:: ', JSON.stringify(payload, null, 2));\n\n if (isNewPatient) {\n console.log('create enrollment');\n payload.data.enrollments = enrollments;\n }\n\n return patientsUpsert.push(payload);\n };\n\n return {\n ...state,\n genderOptions,\n patientsUpsert,\n buildPatientsUpsert,\n };\n});\n\nfn(async state => {\n const { buildPatientsUpsert, patients } = state;\n\n const getPatient = async patient => {\n await new Promise(resolve => setTimeout(resolve, 2000));\n await get(\n 'trackedEntityInstances',\n {\n ou: 'OPjuJMZFLop',\n filter: [`AYbfTPYMNJH:Eq:${patient.uuid}`],\n program: 'w9MSPn5oSqp',\n },\n {},\n state => {\n const { trackedEntityInstances } = state.data;\n const isNewPatient = trackedEntityInstances.length === 0;\n\n buildPatientsUpsert(patient, isNewPatient);\n return state;\n }\n )(state);\n };\n\n for (const patient of patients) {\n console.log(patient.uuid, 'patient uuid');\n await getPatient(patient);\n }\n return state;\n});\n\n// Upsert TEIs to DHIS2\neach(\n 'patientsUpsert[*]',\n upsert('trackedEntityInstances', $.data.query, $.data.data)\n);\n\n// Clean up state\nfn(({ data, ...state }) => state);\n", "adaptor": "@openfn/language-dhis2@latest", - "project_credential_id": null + "project_credential_id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d" }, "Get-Encounters": { "id": "8fb577e5-d068-4d47-8172-81f08153ced9", "name": "Get Encounters", - "body": "\n// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n", + "body": "// Fetch encounters from the date of cursor\n// OpenMRS demo instance does not support querying ALL records (q=all)\n// getEncounters({ q: 'Patient', v: 'full', limit: 100 });\ngetEncounters({\n q: 'Aisha',\n v: 'full',\n limit: 1,\n encounterType: '95d68645-1b72-4290-be0b-ec1fb64bc067',\n});\n\n// Update cursor and return encounters\nfn(state => {\n const { cursor, data } = state;\n console.log('cursor datetime::', cursor);\n\n console.log('Filtering encounters to only get recent records...');\n // console.log(\n // 'Encounters returned before we filter for most recent ::',\n // JSON.stringify(data, null, 2)\n // );\n const encounters = data.results.filter(\n encounter => encounter.encounterDatetime >= cursor\n );\n console.log('# of new encounters to sync to dhis2 ::', encounters.length);\n\n return { ...state, data: {}, references: [], encounters };\n});\n", "adaptor": "@openfn/language-openmrs@latest", - "project_credential_id": null + "project_credential_id": "3141d874-5456-4168-9680-ce04efb1089c" }, "Get-Options-Map": { "id": "175ac575-ea3d-470e-8faf-61de514e222e", "name": "Get Options Map", - "body": "\n// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n", + "body": "get(\n 'https://gist.githubusercontent.com/aleksa-krolls/b22987f7569bc069e963973401832349/raw/94ae98714c85a6e871fd2fb292c9bff5e324d3a1/msf_mhBaseline_optionsMap.json'\n);\n\nfn(state => {\n state.optsMap = state.data;\n // console.log(JSON.stringify(state.optsMap, null, 2), 'Options Map');\n delete state.data;\n delete state.references;\n delete state.response;\n return state;\n});\n\nfn(state => {\n state.mhpssMap = {\n dfdv3SkeXKe: 'a6c5188c-29f0-4d3d-8cf5-7852998df86f', //Has the patient attempted suicide or tried to kill themselves?\n hWMBCCA2yy1: 'abede172-ba87-4ebe-8054-3afadb181ea3', //Is the patient currently at risk of hurting himself/herself or attempting suicide?\n TWuCY5r2wx7: 'ccc4f06c-b76a-440d-9b7e-c48ba2c4a0ab', //Is the patient currently at risk of hurting others?\n QHrIUMhjZlO: 'd516de07-979b-411c-b7e4-bd09cf7d9d91', //Does the patient regularly use alcohol / substances to become intoxicated?\n H1fMCaOzr8F: '3e97c2d0-15c1-4cfd-884f-7a4721079217', //Has the patient experienced an act of aggression or violence?\n yCwuZ0htrlH: '5f6e245c-83fc-421b-8d46-061ac773ae71', //Follow up session required?\n RiiH9A53rvG: '6d3876be-0a27-466d-ad58-92edcc8c31fb', //Referral done\n OZViJk8FPVd: 'c2664992-8a5a-4a6d-9238-5df591307d55', //Has the patient had thoughts of death or suicide?\n qgfKPlIHjcD: 'd8c84af2-bd9b-4bf3-a815-81652cb0b0bc', //Patient experienced an act of aggression or violence - What type(s) of violence?\n rSIazMFEBjD: '4dae5b12-070f-4153-b1ca-fbec906106e1', //Admission type\n KSBMR1BDGwx: '1a8bf24f-4f36-4971-aad9-ae77f3525738', //Type of consultation\n WDY6MkQWyHb: '722dd83a-c1cf-48ad-ac99-45ac131ccc96', //Consultation done by\n AuDPJg6gZE7: '82978311-bef9-46f9-9a9a-cc62254b00a6', //Location of intervention\n KeyiEPc4pII: '82978311-bef9-46f9-9a9a-cc62254b00a6', //Type of intervention\n qfYPXP76j8g: 'c3c86c1b-07be-4506-ab25-8f35f4389b19', //Patient referred by\n PCGI7EnvCQS: '45b39cbf-0fb2-4682-8544-8aaf3e07a744', //Current symptoms or complaints 1\n RnbiVrrSFdm: 'ee1b7973-e931-494e-a9cb-22b814b4d8ed', //Current symptoms or complaints 2\n CUdI1BJ5W8G: '92a92f62-3ff6-4944-9ea9-a7af23949bad', //Current symptoms or complaints 3\n YfcNA5bvkxT: '9a8204ca-d908-4157-9285-7c970dbb5287', //Main category of symptoms\n vC3bg9NwJ78: '3edcfddb-7988-4ce5-97a0-d4c46b267a04', //Duration of the main symptom\n RqsvaPH9vHt: '22809b19-54ca-4d88-8d26-9577637c184e', //Clinical diagnosis\n qacGXlyyQOS: 'a1a75011-0fef-460a-b666-dda2d171f39b', //CGI-S score\n S22iy8o0iLg: 'aae000c3-5242-4e3c-bd1f-7e922a6d3d34', //Patient experienced an act of aggression or violence - Time between violence event and consultation\n v0qFX0qv1tX: 'd5e3d927-f7ce-4fdd-ac4e-6ad0b510b608', //Main past or precipitating events - 1\n SsQqwDBGxjh: '54a9b20e-bce5-4d4a-8c9c-e0248a182586', //Main past or precipitating events - 2\n FLIlRjAwn4G: 'e0d4e006-85b5-41cb-8a21-e013b1978b8b', //Main past or precipitating events - 3\n JUabDHhT1wJ: 'c1a3ed2d-6d9a-453d-9d93-749164a76413', //Main category of precipitating event\n DlqJSA5VApl: '8fb3bb7d-c935-4b57-8444-1b953470e109', //Type of referral\n DMaLm9u4GCq: 'b87a93ff-a4a1-4601-b35d-1e42bfa7e194', //Total number of beneficiaries in family consultation\n CLGnlnFqqnk: '0a0c70d2-2ba5-4cb3-941f-b4a9a4a7ec6d', //Location of intervention - If Health Facility, specify\n f64XCwzJW02: '41e68dee-a2a3-4e6c-9d96-53def5caff52', //Location of intervention - If MSF Health Facility, specify\n YeaUNruqmca: '08cd4b4a-4b0b-4391-987b-b5b3d770d30f', //Location of intervention - If Mobile Clinic, specify\n KjgDauY9v4J: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Location of intervention - If other, specify\n pj5hIE6iyAR: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Current symptoms or complaints - If other, specify\n pj5hIE6iyAR: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Current symptoms or complaints - If other, specify\n W7cPAi8iXLZ: '819f79e7-b9af-4afd-85d4-2ab677223113', //Clinical diagnosis - If other, specify\n MF3RML0HLbP: 'b2c5b6e0-66f0-4b9d-8576-b6f48e0a06df', //MHOS score\n m8qis4iUOTo: '790b41ce-e1e7-11e8-b02f-0242ac130002', //Past / Precipitating Events - If other, specify\n //pN4iQH4AEzk: '722dd83a-c1cf-48ad-ac99-45ac131ccc96', //Consultation done by //TODO: Return BOOLEAN\n //qptKDiv9uPl: 'ec42d68d-3e23-43de-b8c5-a03bb538e7c7', //Sex //TODO: Remove TEA?\n //tsFOVnlc6lz: '5f3d618e-5c89-43bd-8c79-07e4e98c2f23', //PHQ9 Score //TODO: ass logic\n };\n\n return state;\n});\n", "adaptor": "@openfn/language-http@latest", "project_credential_id": null }, "Get-TEIs": { "id": "99e52cef-d4f2-4c5e-8718-e240bb3deab3", "name": "Get TEIs", - "body": "\n// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n", + "body": "const delay = ms => new Promise(resolve => setTimeout(resolve, ms));\n\neach(\n 'encounters[*]',\n get(\n 'trackedEntityInstances',\n {\n ou: 'OPjuJMZFLop',\n program: 'w9MSPn5oSqp',\n filter: [`AYbfTPYMNJH:Eq:${$.data.patient.uuid}`],\n },\n {},\n async state => {\n const encounter = state.references.at(-1);\n console.log(encounter.patient.uuid, 'Encounter patient uuid');\n state.TEIs ??= {};\n state.TEIs[encounter.patient.uuid] =\n state.data.trackedEntityInstances[0].trackedEntityInstance;\n\n await delay(2000);\n return state;\n }\n )\n);\n", "adaptor": "@openfn/language-dhis2@latest", - "project_credential_id": null + "project_credential_id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d" }, "Create-Events": { "id": "932645d8-ddb8-4d00-841e-7fe7af214837", "name": "Create Events", - "body": "\n// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n", + "body": "// Prepare DHIS2 data model for create events\nfn(state => {\n const { TEIs, mhpssMap } = state;\n const optsMap = JSON.parse(state.optsMap);\n\n const dataValuesMapping = data => {\n return Object.keys(mhpssMap)\n .map(k => {\n let value;\n const dataElement = k;\n const conceptUuid = mhpssMap[k];\n const answer = data.obs.find(o => o.concept.uuid === conceptUuid);\n\n if (answer) {\n if (typeof answer.value === 'string') {\n value = answer.value;\n }\n if (typeof answer.value === 'object') {\n value = optsMap.find(\n o => o['value.uuid - External ID'] == answer?.value?.uuid\n )?.['DHIS2 Option Code']; //Changed from 'DHIS2 Option UID'\n // if (\n // //TODO: this is only true if DE = pN4iQH4AEzk\n // answer.value.uuid === '278401ee-3d6f-4c65-9455-f1c16d0a7a98' &&\n // conceptUuid === '1a8bf24f-4f36-4971-aad9-ae77f3525738'\n // ) {\n // value = 'TRUE';\n // } else {\n // value = optsMap.find(\n // o => o['value.uuid - External ID'] == answer?.value?.uuid\n // )?.['DHIS2 Option Code']; //Changed from 'DHIS2 Option UID'\n // }\n }\n }\n if (!answer) {\n value = '';\n }\n return { dataElement, value };\n })\n .filter(d => d);\n };\n\n state.encountersMapping = state.encounters.map(data => {\n const dataValues = dataValuesMapping(data);\n const encounterDate = data.encounterDatetime.replace('+0000', '');\n\n return {\n program: 'w9MSPn5oSqp',\n orgUnit: 'OPjuJMZFLop',\n programStage: 'MdTtRixaC1B',\n trackedEntityInstance: TEIs[data.patient.uuid],\n eventDate: encounterDate,\n dataValues,\n };\n });\n\n console.log('dhis2 events to import:: ', JSON.stringify(state.encountersMapping, null, 2)); \n\n return state;\n});\n\n// Create events fore each encounter\neach(\n '$.encountersMapping[*]',\n create(\n 'events',\n state => {\n // console.log(state.data);\n return state.data;\n },\n {\n params: {\n dataElementIdScheme: 'UID',\n },\n }\n )\n);\n\n// Clean up state\nfn(({ data, references, ...state }) => state);\n", "adaptor": "@openfn/language-dhis2@latest", - "project_credential_id": null + "project_credential_id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d" } }, "edges": { @@ -110,6 +121,79 @@ "target_job_id": "932645d8-ddb8-4d00-841e-7fe7af214837" } } + }, + "wf1": { + "id": "94e04fb5-d5ae-45ad-be31-98b902f36861", + "name": "wf1", + "inserted_at": "2024-09-10T16:42:25.919683Z", + "lock_version": 9, + "triggers": { + "webhook": { + "enabled": true, + "id": "0e7d525f-c24a-4969-8131-397cc94a6065", + "type": "webhook" + } + }, + "jobs": { + "Get-Teis": { + "id": "be724c80-92c8-4fa9-8c3a-d28c5a298fd5", + "name": "Get Teis", + "body": "fn(state => {\n // const manualCursor = '2023-06-20T17:00:00.00';\n state.cursor = state.manualCursor || state.lastRunDateTime;\n console.log('Date cursor to filter TEI extract ::', state.cursor);\n\n return state;\n});\n\n// Get trackedEntityInstances that are \"active\" in the target program\nget(\n 'tracker/trackedEntities',\n {\n orgUnit: 'OPjuJMZFLop',\n program: 'w9MSPn5oSqp',\n programStatus: 'ACTIVE',\n },\n {},\n state => {\n const trackedEntityInstances = state.data.instances\n .filter(tei => tei.createdAt > state.cursor)\n .slice(0, 1);\n const offset = 2; // GMT+2 (Geneva time)\n const currentDateTime = new Date();\n currentDateTime.setHours(currentDateTime.getHours() + offset);\n\n const lastRunDateTime = currentDateTime.toISOString().replace('Z', '');\n\n console.log('# of TEIs extracted ::', trackedEntityInstances.length);\n // console.log(\n // 'trackedEntityInstance IDs ::',\n // trackedEntityInstances.map(tei => tei.trackedEntityInstance)\n // );\n\n console.log('Next sync start date:', lastRunDateTime);\n return {\n ...state,\n // data: {},\n references: [],\n trackedEntityInstances,\n lastRunDateTime,\n };\n }\n);\n", + "adaptor": "@openfn/language-dhis2@latest", + "project_credential_id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d" + }, + "Get-Locations": { + "id": "2c1dc59e-c8c0-409d-813f-d0eb3f812a07", + "name": "Get Locations", + "body": "get('optionGroups/kdef7pUey9f', {\n fields: 'id,displayName,options[id,displayName,code]',\n});\n\nfn(({ data, ...state }) => {\n state.locations = data;\n return state;\n});\n\nfn(state => {\n state.nationalityMap = {\n afghanistan: '84066564-253e-43d8-b141-76730cffa878',\n albania: 'db21f4f9-faf2-4358-8297-0ae76627b3b8',\n algeria: '5f6c017f-074c-46b3-92d0-d055e2094366',\n angola: '8a2e5a03-8a74-41ae-9a98-2310f9ce400d',\n anguilla: 'c911af8a-171c-4ee9-b1ff-934373e8a819',\n argentina: 'b83d24e8-34d8-4920-83c0-8ba014467ff4',\n armenia: '39f1652a-f2b7-4b65-a7e1-7097ac6cdef0',\n aruba: 'f3f1cba1-7c1e-4234-86a2-f27bb5964fee',\n azerbaijan: '29750013-0e35-47ca-8f77-9192a923fb07',\n bangladesh: 'a99de53c-ce76-4b1e-91b2-461094baf79e',\n belarus: '664baba4-c552-47b9-97c0-ff67dafd27d6',\n benin: 'cf863e31-bb38-48ed-90dd-f3dedcac304c',\n bhutan: 'd45a57c9-994f-4deb-8845-9b785860a2ec',\n bolivia: 'd8800d10-862b-42f1-8e22-cac1ce1bbcae',\n bosnia_and_herzegovina: '05d8f4ef-45eb-463d-b2f3-8a5a613ee6b9',\n botswana: '1304a0de-5b70-4d36-a873-e72a82963316',\n brazil: '353ff388-64e6-434c-b78f-ca9636390389',\n british_virgin_islands: 'b02c6d20-83a2-4947-8a7d-91d1f9c4d8a2',\n bulgaria: '91c85a62-2b02-483a-aefd-e29d368565fe',\n burkina_faso: '6c90c1ae-17a4-4e94-a267-4fba4c94efd8',\n burundi: '8a2ed0db-eaad-44bc-bf06-5cb1b2a3db0b',\n cambodia: '4fd14df8-8279-4dfa-bdd3-e1ab26bc0264',\n cameroon: '873552ac-9850-4cc1-ae09-17eb0fccf405',\n cape_verde: 'cfbc220a-1d6c-4469-bb6d-a8e3deb4f7e7',\n central_african_republic: '61a4c4a4-25c2-4459-a874-ec1d24f8323a',\n chad: '9e41e71c-f5d5-456c-a6f9-2129b8055bfc',\n chile: '05333883-44e9-4f57-836a-041391803007',\n china: '15016874-3e20-484a-baa8-9b94e1a3d358',\n colombia: 'a008dff8-ce96-4662-bf8a-372e43d424f0',\n comoros: '9d8738c8-40c2-4c66-aabb-ef176a20ffe8',\n costa_rica: 'fb52f8c9-40ec-4dc4-92a4-d465612de2ff',\n cote_divoire: '513cb36a-3f67-46ea-a789-fcdaca0e26f5',\n cuba: '147c2434-5d7e-420c-8053-ba623301f3f5',\n djibouti: 'cf5b334f-1c0f-41fc-ab54-53ff1e942830',\n dominica: 'f70e51e5-b76c-4c38-9bf2-ef8e1f308ce1',\n dominican_republic: 'ce72fc9b-619b-4c32-b865-600e888ad814',\n drc_congo: '8f6d3d2a-e09f-473b-99c9-e539f97ceab6',\n east_timor: '854f2f66-40e1-4a6a-9dee-09c832a52289',\n ecuador: 'f9810f9a-78a7-42a2-99e6-19c629642386',\n egypt: 'cc7343f8-9243-4d09-b378-58363850d624',\n el_salvador: '9a34935e-5a8a-45be-8ccd-cb23192e420f',\n equatorial_guinea: '7e591605-d723-4398-982a-8737af63a2dc',\n eritrea: 'c61f03c2-0d1f-444f-a974-0a61063aff71',\n ethiopia: '7478d375-014e-410e-a355-090143e88f5b',\n gabon: '9f46ae06-114a-47fa-8f8d-e9749f04da25',\n gambia: '5ec7ddd7-14a5-48ec-9e7c-8896d1010655',\n georgia: 'bc71788f-db69-4b6f-8d1c-57a74395bdd2',\n ghana: '2eb4ff46-d908-4148-9b0d-40ccfc1a655a',\n greece: 'b75d6bcc-fadf-4141-8d0f-2463154b89f7',\n guam: '51fa502b-98a3-4c42-b5fd-7b4d64489bb9',\n guatemala: '3725a4d2-b28f-466b-905a-bafeaeb75855',\n guinea: 'eeaff39c-8afd-43f7-b9a0-53729f5df1d8',\n guinea_bissau: '14e90203-9197-42ea-9222-acafd2fd6984',\n guyana: 'ba4dfa7e-f3cd-4e94-8ca7-6b96a93378a8',\n haiti: 'f76f7dcb-f82e-4257-a627-1685ff3f3586',\n honduras: 'cdd1336e-495b-4868-aace-57a84442d6fd',\n india: '378d0107-eb43-485d-930c-0704b4e5aa11',\n indonesia: '1cbe17e6-adc2-4680-bee0-54d94af75ebf',\n iran: 'b422270e-d8af-4a32-b523-742545a17a3f',\n iraq: '03aa7d6e-7656-48e4-8dc0-5e27706722c0',\n jordan: '842f963c-f84d-4076-a8db-337295fd9b91',\n kazakhstan: '6f6d0e78-2c81-411d-8d13-367e250dc110',\n kenya: 'ad351a33-8846-4cad-8195-b07b6041d4a5',\n kuwait: '4aee7a88-cda9-454e-9f25-4a6420270417',\n kyrgyzstan: 'ace3b851-042b-46a6-8fea-68aae042d614',\n laos: 'dfb01b39-c224-459e-b045-dd9461b9a1e5',\n lebanon: '7de78f22-f53e-48d2-923f-ae1e4d814f46',\n lesotho: 'b35b29c5-9bb7-4b40-ad33-29eecd28a9e6',\n liberia: 'a5fd61b4-fd27-433d-8428-7e88a7f27921',\n libya: '600c6af4-b767-423c-b942-7f06ca467258',\n macedonia: '5837cc40-9ab5-4088-91c8-ca6e4b57e903',\n madagascar: '3782bf3c-380e-4b60-b21a-38199073f112',\n malawi: 'e8b5f188-6a5c-43ae-b4a5-200abb13153e',\n malaysia: '3facca11-fbaa-4c40-8fac-4751d45c3f1b',\n mali: '3e844a47-526a-46f9-afea-1af9ff8690aa',\n martinique: '051ce04e-05e8-4430-8b75-3e499bbffbc8',\n mauritania: '8acb006b-8596-4a98-8177-acb4cb575956',\n mauritius: '17ced083-eb2a-4046-a713-26cabc7af95d',\n mayotte: '1af148fe-2698-4b89-bf7f-87e5c48b6848',\n mexico: '8381208f-01ca-4ed3-8f2c-f73ed1c316e3',\n moldova: 'f0e9c8b5-69b5-48df-8cb9-2d089ba04e46',\n mongolia: '9f341cb1-dcb5-4f6c-bd21-b57db01b4193',\n morocco: 'ef467a17-91e8-4124-a136-7ed8ff7c7d15',\n mozambique: '0916133b-4d93-4d60-9c20-e7ee3936f391',\n myanmar: 'e81ba700-f9fc-4ed0-b248-578a25717cdb',\n namibia: '0cb123dc-8810-4840-b6ab-6a527c5a79ef',\n nauru: '3386fe63-2158-4040-a502-9f65fd2079d3',\n nepal: 'fb01b01a-6775-423c-8012-7d43f587cb6c',\n new_caledonia: 'e67c072b-7707-491f-8c2e-13c914216b61',\n nicaragua: 'f6a9521c-596b-49f9-b914-67138e8c17e6',\n niger: '7561db90-a866-4443-93f4-95cac1d47e9c',\n nigeria: '4134651a-7f53-45fb-8bc6-7fed9cf36f51',\n north_korea: '4d3079e4-8568-48e6-9342-665896875a38',\n oman: '9b0af037-99d1-43b8-ac06-82137ec4b06d',\n other: 'Other',\n pakistan: 'f45d93c3-c9b0-4333-a5e6-299b7c425812',\n palestine: 'e2a19948-49aa-44c0-98ef-67ae1160ef43',\n panama: '1ef5a828-9d0d-4336-91ab-880d5dc0151c',\n papua_new_guinea: 'e1e6b451-d7fe-4954-b225-99b2de82a4c0',\n paraguay: 'd8412016-82f5-4801-a026-1bdc429850b7',\n peru: 'e74fa87f-8469-46b0-975f-6cb37c394564',\n philippines: 'bdbd5c9f-1f28-4f4d-a254-4a84f8bb2c8f',\n puerto_rico: '39fca1d0-d2e7-4b13-82bd-626fbec71252',\n republic_of_congo: '5db9afa5-b57e-4f45-8b1c-af766f14fc58',\n reunion: '1dae4b2d-50c9-4bf1-b25a-7063600a5e74',\n romania: '457e745e-ae97-463d-95a9-8d5689d3ca2b',\n rwanda: '6bc925a1-7699-496a-85b0-c290699381db',\n samoa: 'e03b381b-a7f4-40eb-964f-51571dc3c48c',\n sao_tome_and_principe: 'f66bbb42-684f-42d7-bfcd-95d586eb7dc9',\n saudi_arabia: 'fdf495a4-e60c-46f7-a8a2-61a216849086',\n senegal: 'ad948f1b-0733-4f8d-b049-d64289b43a10',\n serbia: 'd2e69cef-3bff-4220-ba91-a6a678fb606b',\n sierra_leone: 'ffba9caf-b6aa-4078-845e-578f7a7fd566',\n somalia: '99c8dccc-4dfa-4d30-86be-42a309ab431f',\n south_africa: '75882d62-1c55-480d-b411-8ca40c3307df',\n south_korea: '0603d6b9-334f-4443-ab60-7c5d457b95fc',\n south_sudan: 'f113e24e-2ea9-49a2-9b28-59241b9adb21',\n sri_lanka: 'b0031c01-d242-4410-b98b-cc1511590b85',\n sudan: '2f03a932-2b75-4e8b-9f44-0fcd83c75dc4',\n suriname: 'c65d3329-98d4-4dd7-89d3-141b70d00eb2',\n swaziland: '06a2703b-af17-4e44-83f5-6cc9a8a75320',\n switzerland: 'dcdcdc70-a006-4b0a-bac2-7de89b022b65',\n syria: '1e34ee55-ef9f-4386-bae6-6995555ded75',\n tajikistan: '34836c60-5449-48d6-b3c9-c0b3361b9f2c',\n tanzania: '050a8eb1-0d77-4f65-a2da-776a13bcd2a4',\n thailand: '289ac5bd-6434-4837-86bf-b54d22970ac8',\n togo: '6a583e64-869d-477d-a1c1-746320d45fc4',\n tonga: 'd381f06d-2365-4f40-948b-cfe90d8cb532',\n tunisia: '56be7864-fde6-4db3-8fa5-b9dd42cd9c53',\n turkey: '7429c779-1d3a-4aec-8256-d0b1637e1bd1',\n turkmenistan: '3ef17df5-299b-4385-9ea6-572df4b6f9ca',\n uganda: 'be3d11d3-446d-440c-a582-d01c7cbb0eda',\n ukraine: '38c99c8d-2b93-4848-a537-b1865a260bb2',\n unknown: 'Unknown',\n uruguay: 'c2e45baf-748b-4d7b-a391-ed6b802b6f94',\n uzbekistan: '60512350-d79b-41aa-aff0-1b28ca4aa5f1',\n venezuela: '557cea4a-0049-4b7a-b373-ed63f294a2a0',\n vietnam: '49509c5f-e533-48a8-bf06-86935e3376b2',\n western_sahara: '4086dfd2-f4f5-4107-93e8-07bee235af8f',\n yemen: '6a3214e0-f94b-414c-8148-968e24386671',\n zambia: '3ec0432d-ea37-4159-a658-29d6f07fe21a',\n zimbabwe: 'ce1b0d8d-0a2d-4f93-a6ed-64aca2fd0f45',\n };\n state.statusMap = {\n asylum_seeker: 'f921ffdd-72ca-4d58-a89b-1fa2e959d110',\n no_status: '2bacead2-f280-457c-9d28-e80e106f7d25',\n refugee: 'MSF-AAAAAA000000000000001929',\n single: '20b8524e-4c26-4fa0-81f0-fa23ebacc54d',\n married: 'MSF-AAAAAA000000000000001863',\n widowed: 'MSF-AAAAAA000000000000001864',\n divorced_separated: 'MSF-AAAAAA000000000000001865',\n concubine: '1060AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',\n not_applicable: 'MSF-AAAAAA000000000000001823',\n student: 'MSF-AAAAAA000000000000001871',\n permanent_employee: '4a18a820-f3a1-4bb7-9138-558a9ecc81da',\n occasional_employee: '2cb73bee-7f94-4695-89c7-c81187dbc90c',\n unemployed: 'MSF-AAAAAA000000000000001870',\n housewife: '9b14b4d4-b749-4acf-acfe-79c480f3c4b3',\n other: 'MSF-AAAAAA000000000000001329',\n unknown: '1067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',\n idp: 'MSF-AAAAAA000000000000001930',\n internationally_displaced: '515c5abe-4172-4d0c-a991-0de2888228d7',\n resident: 'bbdb287c-4ba1-4944-bd87-eb126c5f9d92',\n returnee: 'fc49acaa-ece2-4365-9dfb-70c2105de8b1',\n };\n return state;\n});\n", + "adaptor": "@openfn/language-dhis2@latest", + "project_credential_id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d" + }, + "Create-Patients": { + "id": "fd21a322-aca4-404f-8b5c-93f5f23336fc", + "name": "Create Patients", + "body": "//Define gender options and prepare newPatientUuid and identifiers\nfn(state => {\n const genderOptions = {\n male: 'M',\n female: 'F',\n unknown: 'U',\n //TODO: Ask MSF for updated category option values\n transgender_female: 'O',\n transgender_male: 'O',\n Prefer_not_to_answer: 'O',\n gender_variant_non_conforming: 'O',\n };\n\n const identifiers = [];\n const newPatientUuid = [];\n\n const { trackedEntityInstances } = state;\n if (trackedEntityInstances.length > 0)\n console.log(\n '# of TEIs to send to OpenMRS: ',\n trackedEntityInstances.length\n );\n if (trackedEntityInstances.length === 0)\n console.log('No data fetched in step prior to sync.');\n\n return {\n ...state,\n genderOptions,\n newPatientUuid,\n identifiers,\n };\n});\n\n//First we generate a unique OpenMRS ID for each patient\neach(\n 'trackedEntityInstances[*]',\n post(\n 'idgen/identifiersource/8549f706-7e85-4c1d-9424-217d50a2988b/identifier',\n {}\n ).then(state => {\n state.identifiers.push(state.data.identifier);\n return state;\n })\n);\n\n// Then we map trackedEntityInstances to openMRS data model\nfn(state => {\n const {\n trackedEntityInstances,\n identifiers,\n genderOptions,\n nationalityMap,\n statusMap,\n locations,\n } = state;\n\n const getValueForCode = (attributes, code) => {\n const result = attributes.find(attribute => attribute.code === code);\n return result ? result.value : undefined;\n };\n\n const calculateDOB = age => {\n const currentDate = new Date();\n const currentYear = currentDate.getFullYear();\n const birthYear = currentYear - age;\n\n const birthday = new Date(\n birthYear,\n currentDate.getMonth(),\n currentDate.getDay()\n );\n\n return birthday.toISOString().replace(/\\.\\d+Z$/, '+0000');\n };\n\n state.patients = trackedEntityInstances.map((d, i) => {\n const patientNumber = getValueForCode(d.attributes, 'patient_number'); // Add random number for testing + Math.random()\n\n const nationality =\n nationalityMap[getValueForCode(d.attributes, 'origin_nationality')];\n const currentStatus =\n statusMap[getValueForCode(d.attributes, 'current_status')];\n const legalStatus =\n getValueForCode(d.attributes, 'legal_status') &&\n statusMap[getValueForCode(d.attributes, 'legal_status')];\n const maritalStatus =\n statusMap[getValueForCode(d.attributes, 'marital_status')];\n const employmentStatus =\n statusMap[getValueForCode(d.attributes, 'occupation')];\n\n const noOfChildren = d.attributes.find(\n a => a.attribute === 'SVoT2cVLd5O'\n )?.value;\n\n // const lonlat = d.attributes.find(a => a.attribute === 'rBtrjV1Mqkz')?.value;\n // const location = locations.options.find(\n // o => o.code === lonlat\n // )?.displayName;\n\n // const [countyDistrict, remainder] = location?.split(' (');\n // const [cityVillage] = remainder?.split(')');\n\n return {\n patientNumber,\n person: {\n age: getValueForCode(d.attributes, 'age'),\n gender: genderOptions[getValueForCode(d.attributes, 'sex')],\n birthdate:\n d.attributes.find(a => a.attribute === 'WDp4nVor9Z7')?.value ??\n calculateDOB(getValueForCode(d.attributes, 'age')),\n birthdateEstimated: d.attributes.find(\n a => a.attribute === 'WDp4nVor9Z7'\n )\n ? true\n : false,\n names: [\n {\n familyName:\n d.attributes.find(a => a.attribute === 'fa7uwpCKIwa')?.value ??\n 'unknown',\n givenName:\n d.attributes.find(a => a.attribute === 'Jt9BhFZkvP2')?.value ??\n 'unknown',\n },\n ],\n addresses: [\n {\n country: 'Iraq',\n stateProvince: 'Ninewa',\n // countyDistrict,\n // cityVillage,\n },\n ],\n attributes: [\n {\n attributeType: '24d1fa23-9778-4a8e-9f7b-93f694fc25e2',\n value: nationality,\n },\n {\n attributeType: 'e0b6ed99-72c4-4847-a442-e9929eac4a0f',\n value: currentStatus,\n },\n legalStatus && {\n attributeType: 'a9b2c642-097f-43f8-b96b-4d2f50ffd9b1',\n value: legalStatus,\n },\n {\n attributeType: '3884dc76-c271-4bcb-8df8-81c6fb897f53',\n value: maritalStatus,\n },\n employmentStatus && {\n attributeType: 'dd1f7f0f-ccea-4228-9aa8-a8c3b0ea4c3e',\n value: employmentStatus,\n },\n noOfChildren && {\n attributeType: 'e363161a-9d5c-4331-8463-238938f018ed',\n value: noOfChildren,\n },\n ].filter(i => i),\n },\n\n identifiers: [\n {\n identifier: identifiers[i], //map ID value from DHIS2 attribute\n identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334',\n location: 'cf6fa7d4-1f19-4c85-ac50-ff824805c51c', //default location old:44c3efb0-2583-4c80-a79e-1f756a03c0a1\n preferred: true,\n },\n {\n uuid: d.trackedEntity,\n identifier: patientNumber,\n identifierType: '8d79403a-c2cc-11de-8d13-0010c6dffd0f', //Old Identification number\n location: 'cf6fa7d4-1f19-4c85-ac50-ff824805c51c', //default location\n preferred: false, //default value for this identifiertype\n },\n ],\n };\n });\n\n return state;\n});\n\n// Creating patients in openMRS\neach(\n 'patients[*]',\n create(\n 'patient',\n state => {\n const { patientNumber, ...patient } = state.data;\n console.log(\n 'Creating patient record\\n',\n JSON.stringify(patient, null, 2)\n );\n return patient;\n },\n state => {\n state.newPatientUuid.push({\n patient_number: state.references.at(-1)?.patientNumber,\n uuid: state.data.body.uuid,\n });\n return state;\n }\n )\n);\n\n// Clean up state\nfn(({ data, references, ...state }) => state);\n", + "adaptor": "@openfn/language-openmrs@latest", + "project_credential_id": "3141d874-5456-4168-9680-ce04efb1089c" + }, + "Update-Teis": { + "id": "eed2a687-7ef3-4a38-819e-d50319874d03", + "name": "Update Teis", + "body": "fn(state => {\n if (state.newPatientUuid.length === 0) {\n console.log('No data fetched in step prior to sync.');\n }\n\n console.log(\n 'newPatientUuid ::',\n JSON.stringify(state.newPatientUuid, null, 2)\n );\n return state;\n});\n\n// Update TEI on DHIS2\neach(\n 'newPatientUuid[*]',\n upsert(\n 'trackedEntityInstances',\n state => ({\n ou: 'OPjuJMZFLop',\n program: 'w9MSPn5oSqp',\n filter: [`P4wdYGkldeG:Eq:${state.data.patient_number}`],\n }),\n {\n orgUnit: 'OPjuJMZFLop',\n program: 'w9MSPn5oSqp',\n trackedEntityType: 'cHlzCA2MuEF',\n attributes: [\n { attribute: 'P4wdYGkldeG', value: `${$.data.patient_number}` }, //DHIS2 patient number to use as lookup key\n { attribute: 'AYbfTPYMNJH', value: `${$.data.patient.uuid}` }, //OMRS patient uuid\n {\n attribute: 'ZBoxuExmxcZ',\n value: `${$.data.patient.identifier[0].identifier}`,\n }, //id generated in wf1-2 e.g., \"IQ146-24-000-027\"\n ],\n }\n )\n);\n", + "adaptor": "@openfn/language-dhis2@latest", + "project_credential_id": "8a5ead9b-5f9e-49b1-9e9a-9dc3c4ccf72d" + } + }, + "edges": { + "webhook->Get-Teis": { + "enabled": true, + "id": "f54f198a-5fa1-4ee0-803f-35316e32ab92", + "source_trigger_id": "0e7d525f-c24a-4969-8131-397cc94a6065", + "condition_type": "always", + "target_job_id": "be724c80-92c8-4fa9-8c3a-d28c5a298fd5" + }, + "Get-Teis->Get-Locations": { + "enabled": true, + "id": "dd4d3b24-f3c2-4162-895e-970aea5a50dc", + "source_job_id": "be724c80-92c8-4fa9-8c3a-d28c5a298fd5", + "condition_type": "on_job_success", + "target_job_id": "2c1dc59e-c8c0-409d-813f-d0eb3f812a07" + }, + "Get-Locations->Create-Patients": { + "enabled": true, + "id": "e80f8963-3c31-48cc-8df4-6b6dd89fd616", + "source_job_id": "2c1dc59e-c8c0-409d-813f-d0eb3f812a07", + "condition_type": "on_job_success", + "target_job_id": "fd21a322-aca4-404f-8b5c-93f5f23336fc" + }, + "Create-Patients->Update-Teis": { + "enabled": true, + "id": "eb140757-c73f-4f9f-83d6-0ac0c76cf4da", + "source_job_id": "fd21a322-aca4-404f-8b5c-93f5f23336fc", + "condition_type": "on_job_success", + "target_job_id": "eed2a687-7ef3-4a38-819e-d50319874d03" + } + } } }, "requires_mfa": false diff --git a/workflows/wf2/1-get-patients.js b/workflows/wf2/1-get-patients.js index cc8d1cb..24bd41a 100644 --- a/workflows/wf2/1-get-patients.js +++ b/workflows/wf2/1-get-patients.js @@ -13,18 +13,18 @@ fn(state => { return state; }); -searchPatient({ q: 'Patient', v: 'full', limit: '100' }); +searchPatient({ q: 'Aisha', v: 'full', limit: '3' }); +// searchPatient({ q: 'Patient', v: 'full', limit: '100' }); //Query all patients (q=all) not supported on demo OpenMRS; needs to be configured //...so we query all Patients with name "Patient" instead fn(state => { const { results } = state.data; - const getPatientByUuid = uuid => { - return results.find(patient => patient.uuid === uuid); - }; - // console.log('dateCreated for patient uuid ...2c6dbfc5acc8',getPatientByUuid("31b4d9c8-f7cc-4c26-ae61-2c6dbfc5acc8").auditInfo.dateCreated) + const getPatientByUuid = uuid => + results.find(patient => patient.uuid === uuid).auditInfo.dateCreated; + // 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...'); diff --git a/workflows/wf2/2-upsert-teis.js b/workflows/wf2/2-upsert-teis.js index db194a1..3be8552 100644 --- a/workflows/wf2/2-upsert-teis.js +++ b/workflows/wf2/2-upsert-teis.js @@ -6,18 +6,36 @@ fn(state => { O: 'prefer_not_to_answer', }; - const DHIS2_PATIENT_NUMBER = '8d79403a-c2cc-11de-8d13-0010c6dffd0f'; - const OPENMRS_AUTO_ID = '05a29f94-c0ed-11e2-94be-8c13b969e334'; + const DHIS2_PATIENT_NUMBER = '8d79403a-c2cc-11de-8d13-0010c6dffd0f'; //DHIS2 ID or DHIS2 Patient Number + const OPENMRS_AUTO_ID = '05a29f94-c0ed-11e2-94be-8c13b969e334'; //MSF ID or OpenMRS Patient Number const patientsUpsert = []; const buildPatientsUpsert = (patient, isNewPatient) => { const dateCreated = patient.auditInfo.dateCreated.substring(0, 10); - const { identifier } = - patient.identifiers.find( - i => i.identifierType.uuid === DHIS2_PATIENT_NUMBER - ) || - patient.identifiers.find(i => i.identifierType.uuid === OPENMRS_AUTO_ID); + function findIdentifierByUuid(identifiers, targetUuid) { + // Use the `find` method to locate the matching identifier + const matchingIdentifier = identifiers.find( + identifier => identifier.identifierType.uuid === targetUuid + ); + + // Return the `identifier` value if a match is found; otherwise, return null + 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 = [ { @@ -31,7 +49,8 @@ fn(state => { const payload = { query: { ou: 'OPjuJMZFLop', - filter: [`AYbfTPYMNJH:Eq:${patient.uuid}`], + program: 'w9MSPn5oSqp', + filter: [`AYbfTPYMNJH:Eq:${patient.uuid}`], //upsert on omrs.patient.uid }, data: { program: 'w9MSPn5oSqp', @@ -39,11 +58,26 @@ fn(state => { trackedEntityType: 'cHlzCA2MuEF', attributes: [ { - attribute: 'P4wdYGkldeG', - value: identifier, + attribute: 'fa7uwpCKIwa', + value: patient.person.names[0].givenName, + }, + { + attribute: 'Jt9BhFZkvP2', + value: patient.person.names[0].familyName, }, { - attribute: 'AYbfTPYMNJH', + attribute: 'P4wdYGkldeG', //DHIS2 ID ==> "Patient Number" + value: findIdentifierByUuid( + patient.identifiers, + DHIS2_PATIENT_NUMBER + ), + }, + { + attribute: 'ZBoxuExmxcZ', //MSF ID ==> "OpenMRS Patient Number" + value: findIdentifierByUuid(patient.identifiers, OPENMRS_AUTO_ID), + }, + { + attribute: 'AYbfTPYMNJH', //"OpenMRS Patient UID" value: patient.uuid, }, { @@ -51,13 +85,47 @@ fn(state => { value: genderOptions[patient.person.gender], }, { - attribute: 'T1iX2NuPyqS', + attribute: 'Rv8WM2mTuS5', value: patient.person.age, }, + { + attribute: 'WDp4nVor9Z7', + value: patient.person.birthdate, + }, + // { + // attribute: 'rBtrjV1Mqkz', //Place of living + // value: patient.person.address, + // }, + // { + // attribute: 'Xvzc9e0JJmp', //nationality + // value: patient.person.attributes[x].value, //input.attributeType = "24d1fa23-9778-4a8e-9f7b-93f694fc25e2" + // }, + // { + // attribute: 'YUIQIA2ClN6', //current status + // value: patient.person.attributes[x].value, //input.attributeType = "e0b6ed99-72c4-4847-a442-e9929eac4a0f" + // }, + // { + // attribute: 'Qq6xQ2s6LO8', //legal status + // value: patient.person.attributes[x].value, //input.attributeType = "a9b2c642-097f-43f8-b96b-4d2f50ffd9b1" + // }, + // { + // attribute: 'FpuGAOu6itZ', //marital status + // value: patient.person.attributes[x].value, //input.attributeType = "3884dc76-c271-4bcb-8df8-81c6fb897f53" + // }, + // { + // attribute: 'v7k4OcXrWR8', //employment status + // value: patient.person.attributes[x].value, //input.attributeType = "dd1f7f0f-ccea-4228-9aa8-a8c3b0ea4c3e" + // }, + // { + // attribute: 'SVoT2cVLd5O', //employment status + // value: patient.person.attributes[x].value, //input.attributeType = "e363161a-9d5c-4331-8463-238938f018ed" + // }, ], }, }; + console.log('mapped dhis2 payloads:: ', JSON.stringify(payload, null, 2)); + if (isNewPatient) { console.log('create enrollment'); payload.data.enrollments = enrollments; @@ -83,7 +151,8 @@ fn(async state => { 'trackedEntityInstances', { ou: 'OPjuJMZFLop', - filter: [`jGNhqEeXy2L:Eq:${patient.uuid}`], + filter: [`AYbfTPYMNJH:Eq:${patient.uuid}`], + program: 'w9MSPn5oSqp', }, {}, state => { @@ -106,12 +175,8 @@ fn(async state => { // Upsert TEIs to DHIS2 each( 'patientsUpsert[*]', - upsert( - 'trackedEntityInstances', - state => state.data.query, - state => state.data.data - ) + upsert('trackedEntityInstances', $.data.query, $.data.data) ); // Clean up state -fn(state => ({ ...state, data: {} })); +fn(({ data, ...state }) => state); diff --git a/workflows/wf2/3-get-encounters.js b/workflows/wf2/3-get-encounters.js index 9fec695..0fa41d6 100644 --- a/workflows/wf2/3-get-encounters.js +++ b/workflows/wf2/3-get-encounters.js @@ -1,18 +1,24 @@ // Fetch encounters from the date of cursor // OpenMRS demo instance does not support querying ALL records (q=all) -getEncounters({ q: 'Patient', v: 'full', limit: 100 }); +// getEncounters({ q: 'Patient', v: 'full', limit: 100 }); +getEncounters({ + q: 'Aisha', + v: 'full', + limit: 1, + encounterType: '95d68645-1b72-4290-be0b-ec1fb64bc067', +}); // Update cursor and return encounters fn(state => { const { cursor, data } = state; - console.log("cursor datetime::", cursor); + console.log('cursor datetime::', cursor); console.log('Filtering encounters to only get recent records...'); - console.log( - 'Encounters returned before we filter for most recent ::', - JSON.stringify(data, null, 2) - ); - const encounters = data.body.results.filter( + // console.log( + // 'Encounters returned before we filter for most recent ::', + // JSON.stringify(data, null, 2) + // ); + const encounters = data.results.filter( encounter => encounter.encounterDatetime >= cursor ); console.log('# of new encounters to sync to dhis2 ::', encounters.length); diff --git a/workflows/wf2/4-get-oclmap.js b/workflows/wf2/4-get-oclmap.js deleted file mode 100644 index da121b3..0000000 --- a/workflows/wf2/4-get-oclmap.js +++ /dev/null @@ -1,65 +0,0 @@ -// Fetch OCL mappings using ocl get() -get( - 'orgs/MSFOCG/collections/lime-demo/HEAD/expansions/autoexpand-HEAD/mappings/', - { - page: 1, - limit: 1000, - verbose: false, - fromConceptOwner: 'MSFOCG', - toConceptOwner: 'MSFOCG', - toConceptSource: 'DHIS2DataElements', - sortDesc: '_score', - lookupToConcept: true, - verbose: true, - }, - state => { - // Add state oclMappings - const oclMappings = state.data; - console.log(JSON.stringify(oclMappings, null, 2), 'OCL Mappings'); - return { ...state, data: {}, references: [], response: {}, oclMappings }; - } -); -// Job versions if using different adaptor functions -// Fetch mappings using ocl getMappings() function -// getMappings( -// 'MSFOCG', -// 'lime-demo', -// { -// page: 1, -// limit: 1000, -// verbose: false, -// fromConceptOwner: 'MSFOCG', -// toConceptOwner: 'MSFOCG', -// toConceptSource: 'DHIS2DataElements', -// sortDesc: '_score', -// }, -// state => { -// // Add state oclMappings -// const oclMappings = state.data; -// return { ...state, data: {}, references: [], response: {}, oclMappings }; -// } -// ); - -/* - * Fetching mappings using http get() - **/ -// get( -// 'orgs/MSFOCG/collections/lime-demo/HEAD/expansions/autoexpand-HEAD/mappings/', -// { -// query: { -// page: 1, -// exact_match: 'off', -// limit: 1000, -// verbose: false, -// sortDesc: '_score', -// fromConceptOwner: 'MSFOCG', -// toConceptOwner: 'MSFOCG', -// toConceptSource: 'DHIS2DataElements', -// }, -// }, -// state => { -// // Add state oclMappings -// const oclMappings = state.data; -// return { ...state, data: {}, references: [], response: {}, oclMappings }; -// } -// ); diff --git a/workflows/wf2/4-get-options-map.js b/workflows/wf2/4-get-options-map.js new file mode 100644 index 0000000..4c63217 --- /dev/null +++ b/workflows/wf2/4-get-options-map.js @@ -0,0 +1,60 @@ +get( + 'https://gist.githubusercontent.com/aleksa-krolls/b22987f7569bc069e963973401832349/raw/94ae98714c85a6e871fd2fb292c9bff5e324d3a1/msf_mhBaseline_optionsMap.json' +); + +fn(state => { + state.optsMap = state.data; + // console.log(JSON.stringify(state.optsMap, null, 2), 'Options Map'); + delete state.data; + delete state.references; + delete state.response; + return state; +}); + +fn(state => { + state.mhpssMap = { + dfdv3SkeXKe: 'a6c5188c-29f0-4d3d-8cf5-7852998df86f', //Has the patient attempted suicide or tried to kill themselves? + hWMBCCA2yy1: 'abede172-ba87-4ebe-8054-3afadb181ea3', //Is the patient currently at risk of hurting himself/herself or attempting suicide? + TWuCY5r2wx7: 'ccc4f06c-b76a-440d-9b7e-c48ba2c4a0ab', //Is the patient currently at risk of hurting others? + QHrIUMhjZlO: 'd516de07-979b-411c-b7e4-bd09cf7d9d91', //Does the patient regularly use alcohol / substances to become intoxicated? + H1fMCaOzr8F: '3e97c2d0-15c1-4cfd-884f-7a4721079217', //Has the patient experienced an act of aggression or violence? + yCwuZ0htrlH: '5f6e245c-83fc-421b-8d46-061ac773ae71', //Follow up session required? + RiiH9A53rvG: '6d3876be-0a27-466d-ad58-92edcc8c31fb', //Referral done + OZViJk8FPVd: 'c2664992-8a5a-4a6d-9238-5df591307d55', //Has the patient had thoughts of death or suicide? + qgfKPlIHjcD: 'd8c84af2-bd9b-4bf3-a815-81652cb0b0bc', //Patient experienced an act of aggression or violence - What type(s) of violence? + rSIazMFEBjD: '4dae5b12-070f-4153-b1ca-fbec906106e1', //Admission type + KSBMR1BDGwx: '1a8bf24f-4f36-4971-aad9-ae77f3525738', //Type of consultation + WDY6MkQWyHb: '722dd83a-c1cf-48ad-ac99-45ac131ccc96', //Consultation done by + AuDPJg6gZE7: '82978311-bef9-46f9-9a9a-cc62254b00a6', //Location of intervention + KeyiEPc4pII: '82978311-bef9-46f9-9a9a-cc62254b00a6', //Type of intervention + qfYPXP76j8g: 'c3c86c1b-07be-4506-ab25-8f35f4389b19', //Patient referred by + PCGI7EnvCQS: '45b39cbf-0fb2-4682-8544-8aaf3e07a744', //Current symptoms or complaints 1 + RnbiVrrSFdm: 'ee1b7973-e931-494e-a9cb-22b814b4d8ed', //Current symptoms or complaints 2 + CUdI1BJ5W8G: '92a92f62-3ff6-4944-9ea9-a7af23949bad', //Current symptoms or complaints 3 + YfcNA5bvkxT: '9a8204ca-d908-4157-9285-7c970dbb5287', //Main category of symptoms + vC3bg9NwJ78: '3edcfddb-7988-4ce5-97a0-d4c46b267a04', //Duration of the main symptom + RqsvaPH9vHt: '22809b19-54ca-4d88-8d26-9577637c184e', //Clinical diagnosis + qacGXlyyQOS: 'a1a75011-0fef-460a-b666-dda2d171f39b', //CGI-S score + S22iy8o0iLg: 'aae000c3-5242-4e3c-bd1f-7e922a6d3d34', //Patient experienced an act of aggression or violence - Time between violence event and consultation + v0qFX0qv1tX: 'd5e3d927-f7ce-4fdd-ac4e-6ad0b510b608', //Main past or precipitating events - 1 + SsQqwDBGxjh: '54a9b20e-bce5-4d4a-8c9c-e0248a182586', //Main past or precipitating events - 2 + FLIlRjAwn4G: 'e0d4e006-85b5-41cb-8a21-e013b1978b8b', //Main past or precipitating events - 3 + JUabDHhT1wJ: 'c1a3ed2d-6d9a-453d-9d93-749164a76413', //Main category of precipitating event + DlqJSA5VApl: '8fb3bb7d-c935-4b57-8444-1b953470e109', //Type of referral + DMaLm9u4GCq: 'b87a93ff-a4a1-4601-b35d-1e42bfa7e194', //Total number of beneficiaries in family consultation + CLGnlnFqqnk: '0a0c70d2-2ba5-4cb3-941f-b4a9a4a7ec6d', //Location of intervention - If Health Facility, specify + f64XCwzJW02: '41e68dee-a2a3-4e6c-9d96-53def5caff52', //Location of intervention - If MSF Health Facility, specify + YeaUNruqmca: '08cd4b4a-4b0b-4391-987b-b5b3d770d30f', //Location of intervention - If Mobile Clinic, specify + KjgDauY9v4J: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Location of intervention - If other, specify + pj5hIE6iyAR: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Current symptoms or complaints - If other, specify + pj5hIE6iyAR: 'e08d532b-e56c-43dc-b831-af705654d2dc', //Current symptoms or complaints - If other, specify + W7cPAi8iXLZ: '819f79e7-b9af-4afd-85d4-2ab677223113', //Clinical diagnosis - If other, specify + MF3RML0HLbP: 'b2c5b6e0-66f0-4b9d-8576-b6f48e0a06df', //MHOS score + m8qis4iUOTo: '790b41ce-e1e7-11e8-b02f-0242ac130002', //Past / Precipitating Events - If other, specify + //pN4iQH4AEzk: '722dd83a-c1cf-48ad-ac99-45ac131ccc96', //Consultation done by //TODO: Return BOOLEAN + //qptKDiv9uPl: 'ec42d68d-3e23-43de-b8c5-a03bb538e7c7', //Sex //TODO: Remove TEA? + //tsFOVnlc6lz: '5f3d618e-5c89-43bd-8c79-07e4e98c2f23', //PHQ9 Score //TODO: ass logic + }; + + return state; +}); diff --git a/workflows/wf2/5-create-events.js b/workflows/wf2/5-create-events.js deleted file mode 100644 index 0b7bcd2..0000000 --- a/workflows/wf2/5-create-events.js +++ /dev/null @@ -1,107 +0,0 @@ -fn(state => { - const TEIs = {}; - return { ...state, TEIs }; -}); - -fn(async state => { - const { encounters } = state; - - const getTEI = async encounter => { - await new Promise(resolve => setTimeout(resolve, 2000), 'OCL Mappings'); - await get( - 'trackedEntityInstances', - { - ou: 'OPjuJMZFLop', - filter: [`AYbfTPYMNJH:Eq:${encounter.patient.uuid}`], - }, - {}, - state => { - console.log(encounter.patient.uuid, 'Encounter patient uuid'); - state.TEIs[encounter.patient.uuid] = - state.data.trackedEntityInstances[0].trackedEntityInstance; - - return state; - } - )(state); - }; - - for (const encounter of encounters) { - await getTEI(encounter); - } - return state; -}); - -// Prepare DHIS2 data model for create events -fn(state => { - const { oclMappings, TEIs } = state; - - //console.log(JSON.stringify(oclMappings, null, 2)); - - const encountersMapping = state.encounters.map(data => { - const encounterDate = data.encounterDatetime.replace('+0000', ''); - - const pluckObs = arg => data.obs.find(ob => ob.concept.uuid === arg); - //console.log('Observation ::', pluckObs); - // const pluckOcl = arg => - // oclMappings.find(ocl => ocl.from_concept_name_resolved === arg); //TODO: map using concept uid, not name - const pluckOcl = arg => - oclMappings.find(ocl => ocl.from_concept_code === arg); - //console.log('OCL code match ::', pluckOcl); - - const obs1 = pluckObs('da33d74e-33b3-495a-9d7c-aa00a-aa0160'); - const obs2 = pluckObs('da33d74e-33b3-495a-9d7c-aa00a-aa0177'); - - // const oclMap1 = obs1 && pluckOcl(obs1.value.display); - // const oclMap2 = obs2 && pluckOcl(obs2.value.display); - const cleanedObs1 = obs1.value.uuid.split('-').pop().toUpperCase(); - const cleanedObs2 = obs2.value.uuid.split('-').pop().toUpperCase(); - console.log('cleanedObs1 ', cleanedObs1); - console.log('cleanedObs2 ', cleanedObs2); - - const oclMap1 = obs1 && pluckOcl(cleanedObs1); - const oclMap2 = obs2 && pluckOcl(cleanedObs2); - console.log('oclMapping for Obs1 ', JSON.stringify(oclMap1, null, 2)); - console.log('oclMapping for Obs2 ', JSON.stringify(oclMap2, null, 2)); - - // const valueForEncounter1 = oclMap1 ? oclMap1.to_concept_name_resolved : ''; - // const valueForEncounter2 = oclMap2 ? oclMap2.to_concept_name_resolved : ''; - const valueForEncounter1 = oclMap1 - ? oclMap1.to_concept.extras.dhis2_option_code - : ''; - const valueForEncounter2 = oclMap2 - ? oclMap2.to_concept.extras.dhis2_option_code - : ''; - console.log('valueForEncounter1', valueForEncounter1); - console.log('valueForEncounter2', valueForEncounter2); - - return { - program: 'w9MSPn5oSqp', - orgUnit: 'OPjuJMZFLop', - programStage: 'EZJ9FsNau7Q', - trackedEntityInstance: TEIs[data.patient.uuid], - eventDate: encounterDate, - //=== TODO: REPLACE & ADD NEW DATAVALUES TO MAP ====================// - dataValues: [ - { - dataElement: 'ZTSBtZKc8Ff', //diagnosis - value: valueForEncounter1, - }, - { - dataElement: 'vqGFXhDM1XG', //entry triage color - value: valueForEncounter2, - }, - ], - //==================================================================// - }; - }); - return { ...state, encountersMapping }; -}); - -// Create events fore each encounter -each( - 'encountersMapping[*]', - create('events', state => state.data) //TODO: Add query parameter '/events?dataElementIdScheme=UID' -); - -// Clean up state -fn(state => ({ ...state, data: {}, references: [] })); diff --git a/workflows/wf2/5-get-teis.js b/workflows/wf2/5-get-teis.js new file mode 100644 index 0000000..82c9144 --- /dev/null +++ b/workflows/wf2/5-get-teis.js @@ -0,0 +1,24 @@ +const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + +each( + 'encounters[*]', + get( + 'trackedEntityInstances', + { + ou: 'OPjuJMZFLop', + program: 'w9MSPn5oSqp', + filter: [`AYbfTPYMNJH:Eq:${$.data.patient.uuid}`], + }, + {}, + async state => { + const encounter = state.references.at(-1); + console.log(encounter.patient.uuid, 'Encounter patient uuid'); + state.TEIs ??= {}; + state.TEIs[encounter.patient.uuid] = + state.data.trackedEntityInstances[0].trackedEntityInstance; + + await delay(2000); + return state; + } + ) +); diff --git a/workflows/wf2/6-create-events.js b/workflows/wf2/6-create-events.js new file mode 100644 index 0000000..efb8483 --- /dev/null +++ b/workflows/wf2/6-create-events.js @@ -0,0 +1,80 @@ +// Prepare DHIS2 data model for create events +fn(state => { + const { TEIs, mhpssMap } = state; + const optsMap = JSON.parse(state.optsMap); + + const dataValuesMapping = data => { + return Object.keys(mhpssMap) + .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 === 'string') { + value = answer.value; + } + 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 ( + // //TODO: this is only true if DE = pN4iQH4AEzk + // answer.value.uuid === '278401ee-3d6f-4c65-9455-f1c16d0a7a98' && + // conceptUuid === '1a8bf24f-4f36-4971-aad9-ae77f3525738' + // ) { + // value = 'TRUE'; + // } else { + // value = optsMap.find( + // o => o['value.uuid - External ID'] == answer?.value?.uuid + // )?.['DHIS2 Option Code']; //Changed from 'DHIS2 Option UID' + // } + } + } + if (!answer) { + value = ''; + } + return { dataElement, value }; + }) + .filter(d => d); + }; + + state.encountersMapping = state.encounters.map(data => { + const dataValues = dataValuesMapping(data); + const encounterDate = data.encounterDatetime.replace('+0000', ''); + + return { + program: 'w9MSPn5oSqp', + orgUnit: 'OPjuJMZFLop', + programStage: 'MdTtRixaC1B', + trackedEntityInstance: TEIs[data.patient.uuid], + eventDate: encounterDate, + dataValues, + }; + }); + + console.log('dhis2 events to import:: ', JSON.stringify(state.encountersMapping, null, 2)); + + return 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 5350c60..9bb812c 100644 --- a/workflows/wf2/workflow.json +++ b/workflows/wf2/workflow.json @@ -28,14 +28,22 @@ "configuration": "../tmp/openmrs-creds.json", "expression": "3-get-encounters.js", "next": { - "get-oclmap": true + "get-options-map": true } }, { - "id": "get-oclmap", - "adaptor": "ocl", - "configuration": "../tmp/ocl-creds.json", - "expression": "4-get-oclmap.js", + "id": "get-options-map", + "adaptor": "http", + "expression": "4-get-options-map.js", + "next": { + "get-teis": true + } + }, + { + "id": "get-teis", + "adaptor": "dhis2", + "configuration": "../tmp/dhis2-creds.json", + "expression": "5-get-teis.js", "next": { "create-events": true } @@ -44,7 +52,7 @@ "id": "create-events", "adaptor": "dhis2", "configuration": "../tmp/dhis2-creds.json", - "expression": "5-create-events.js" + "expression": "6-create-events.js" } ] }