Alternate Approaches to the DHIS2 Integration
This page includes alternate approaches that were developed while scoping the DHIS2 integration. The technical community ultimately choose to publish metrics to the FHIR server so they could be consumed by a third party system and pushed to DHIS2. The approaches on this page document alternative approaches to the integration that were not chosen.
Here are some links that were completed in the decision making process:
- Interface - Interface with DHIS2
- DHIS2 Metric Scope and Definition
- DHIS2 Design Discussion 29 Aug 2018
- DHIS2 Forum Discussion
- 2019-01-25 DHIS2 v3.6 scope
User Experience
This section defines the anticipated user experience for this integration. OpenLMIS will give users a standard set of reports in the reporting stack, an API interface for accessing those reports and Nifi templates that will support the ingestion and mapping process. Phases include one time setup, execution and maintenance.
Setup
Users need to setup the connection between OpenLMIS and DHIS2. This is a one time activity that needs to take place and changes will be managed on a regular basis. System administrators will need to be able to connection OpenLMIS to a live instance of DHIS2, crosswalk the appropriate locations, crosswalk the report metadata to the DHIS2 dataset and activate the connector.
Connect to DHIS2
System administrators will need to gain access to DHIS2 and store those credentials in OpenLMIS.
Development Required: System administrators will need to set the username and password of the DHIS2 system in the SQL database (Reporting stack or OpenLMIS are TBD). We chose to implement this in the database because we don't want to require a restart of the server every time the password is changed. If in OpenLMIS, we will need a way to store information in the microservice through an API.
DECISION POINT: Should we store these credentials in the Data Warehouse, or OpenLMIS ReferenceData Microservice?
Crosswalk and Add Locations
Now that a system administrator has access to DHIS2, we need to crosswalk the locations between DHIS2 and OpenLMIS so we can appropriately apply this information. The end of this activity includes a one-to-one crosswalk between each reporting location in OpenLMIS and DHIS2. Not all locations need to be crosswalked between each system. There are likely many locations in DHIS2 that do not have an associated OpenLMIS location. Developing this crosswalk can happen manually using CSV exports from each system or a third party mapping tool like the Global Open Facility Registry. The crosswalk activity will result in a list of locations that may need to be added to DHIS2. If so, system administrators are responsible for adding these items to DHIS2 and updating OpenLMIS to reflect this.
Assumptions: The act of crosswalking locations will be done outside of OpenLMIS. OpenLMIS will need to have a place to retain this crosswalk either within the reporting data warehouse or as part of the OpenLMIS location.
Development Required: System administrators will need to be able to upload the crosswalk to the OpenLMIS or data warehouse database. This can be done by importing a CSV directly to the database or it can be imported as extraData on OpenLMIS locations.
DECISION POINT: Where do we want to store this map? This could be stored in the OpenLMIS location (extraData) or it can be stored in the data warehouse in a database table.
Crosswalk OpenLMIS Programs to DHIS2 Programs
OpenLMIS programs need to be crosswalked to DHIS2 programs so the data that's sent over to DHIS2 is applied to the appropriate program. This crosswalk needs to take place outside of the system and OpenLMIS will be responsible for retaining this crosswalk.
Assumptions: OpenLMIS retains a list of program codes in the system, which are different than in DHIS2 because DHIS2 has a different system owner.
Development Required: System administrators will need to be able to upload the crosswalk to the OpenLMIS or data warehouse database. This can be done by importing a CSV directly to the database or it can be imported as extraData on OpenLMIS Programs.
DECISION POINT: Where do we want to store this map? This could be stored in the OpenLMIS Program (extraData) or it can be stored in the data warehouse in a database table.
Crosswalk OpenLMIS Orderables to DHIS2 Data Elements
Orderables in OpenLMIS need to be crosswalked to DHIS2 Data Elements. This crosswalk will need to take place outside of the system and OpenLMIS will need to be able to retain this crosswalk so it can be referenced.
Assumptions: OpenLMIS retains a list of codes in the system, which are different than in DHIS2 because DHIS2 has a different system owner.
Development Required: System administrators will need to be able to upload the crosswalk to the OpenLMIS or data warehouse database. This can be done by importing a CSV directly to the database or it can be imported as extraData on OpenLMIS Orderables.
DECISION POINT: Where do we want to store this map? This could be stored in the OpenLMIS Orderable (extraData) or it can be stored in the data warehouse in a database table.
Crosswalk OpenLMIS Report Name and Column Headers to DHIS2 Data Sets and Categories
At this point, we have a standard set of reports that need to go into DHIS2, we have a map of the DHIS2 locations and we have the login credentials. The next step is to crosswalk the standard OpenLMIS reports to this particular instance of DHIS2. This step will require access to both systems simultaneously so a system administrator can identify the dataset in DHIS2 and have the OpenLMIS system access the dataset template.
Definitions:
- OpenLMIS will make the data available as a report either with direct access through an API or through Superset. There should be a one to one match between the OpenLMIS Report and the DHIS2 Dataset.
- The calculated column of each report maps to a DHIS2 category.
- The product name maps to a DHIS2 Data Element.
Sample:
Product_Name | Quantity_On_Hand | Days_Stocked_Out |
---|---|---|
Paracetamol | 5 | 0 |
Ibuprofen | 200 | 3 |
In the table above, the Product_Name represents the data Element, the Quantity_on_Hand and Days_Stocked_Out represent category names and the values 5, 0 represent the values that need to be sent.
Assumptions: OpenLMIS will have a database table with a list of the report in Superset and each field. This database table will need to be populated with the DHIS2 dataset name and indicator.
OpenLMIS_Report_Name | OpenLMIS_Column_Name | DHIS2_Dataset_Name | DHIS2_Dataset_GUID | DHIS2_Category_Name | DHIS2_Category_GUID |
---|---|---|---|---|---|
chart1 | indicator1 | dataSet1 | P9slqy89sYb | category1 | DosHq4vnsYv |
chart1 | indicator2 | dataSet1 | P9slqy89sYb | category2 | Q8lOv2CAUjf |
chart1 | indicator3 | dataSet1 | P9slqy89sYb | category3 | ijIsjbvXFJL |
Steps (for each data set):
- Identify which data sets are available in DHIS2
- Identify which categories are available in DHIS2
- (If necessary, modify DHIS2 to accept these categories)
- Export the data set template
- Copy and paste the values into the OpenLMIS database table based on the table structure above
Execution
Now that the setup is complete, the user will need to view the reports at the end of the month, request any changes from users and send the report to DHIS2. This entire process is captured in the execution phase.
View Report and Change Data
We anticipate the need for system users to view their monthly reports before they are submitted to DHIS2. These reports can be viewed in the embedded OpenLMIS reporting windows that are created from Superset. These reports will present a table to the user so they can see what is calculated to be sent to DHIS2. The user is responsible for viewing each report, identifying problems and making the required change in OpenLMIS to get the report to accurately represent reality. We will not allow users to change the data on the report before it is submitted. They can only change the underlying data source using whatever existing mechanisms are available in DHIS2.
Send the Report to DHIS2
The user will need to be able to send the information to DHIS2 when they are ready. We anticipate the need to have a button in the UI that allows the user to send what they see on their screen to DHIS2 for the selected period and program.
Maintenance
The maintenance phase represents a number of activities that system administrators will need to perform to ensure the interoperable connection is available. We anticipate the need for a system administrator to be able to update indicator calculations, add additional products/orderables as data elements, adjust OpenLMIS crosswalks and reset DHIS2 credentials. All of these items will be available through configuration (not necessarily through a UI).
Scope
The following is within scope for this integration:
- OpenLMIS will make aggregate information available (counts over time) that target the DHIS2 DataSet report. Individual transactions targeted for the DHIS2 tracker capture are out of scope.
- OpenLMIS will focus on providing requisition information as a priority including any additional columns that are added to the RnR template.
- Facility Matching
- OpenLMIS will make a facility list available that can be matched by a third party tool.
- Depending on the transportation portion, OpenLMIS may retain a map of the DHIS2 Code for each facility.
- Indicator Calculations Definition
- Key Performance Indicators (KPI) will be developed in the OpenLMIS reporting system. These indicators will be calculated on a regular schedule and will be ready for review by the user.
- KPIs will need to align to the Categories defined in DHIS2.
- Push reports to FHIR server
- Reports will be sent to the FHIR server where they will be made available to external consumers through an API
- Expose the FHIR server API
- External users need to expose the FHIR API
- Potential: Data exchange/Posting information
- If done, the implementation would be a reference which:
- Requires meta-data alignment of Locations (Facility / Org unit) and Products (Orderable / data set definition?)
- Where does this list sit and whose responsible for administrating it? (core openlmis? reporting stack? openhim?)
- Would use a Reference messaging system (e.g. OpenHIM)
- Requires deployment alignment, either through custom docker/ansible/IaC or aligning with a public DHIS2 deployment (e.g. play.dhis2.org).
- Requires meta-data alignment of Locations (Facility / Org unit) and Products (Orderable / data set definition?)
- The integration with DHIS2 will be done through their Web API using Apache Nifi.
- Apache Nifi will support DHIS2 versions 2.29 to 2.30 (most recent version as of this writing). Backwards compatibility will be retained for no more than 3 versions of DHIS2.
- If done, the implementation would be a reference which:
System Architecture
Systems Involved
OpenLMIS Core - OpenLMIS Core will be the primary data source. We are focusing on generating reports based on requisition information.
Apache Nifi - Nifi is the data ingestion and standardization engine. Nifi will be used to extract information from OpenLMIS and store it in the reporting stack postgres database. It will also be used to get reports from the Superset API into the FHIR server standard. Nifi may or may not be involved in transporting information to DHIS2.
Reporting Stack Postgres Database - The Postgres Database is used to store the reports that are generated using a SQL view. These reports will be accessed through Superset. The database will also retain a map between the Superset reports and the DHIS2 report
Apache Superset - Superset is the visualization engine for reports. Users are able to create charts in Superset and they become available through the API.
HAPI FHIR server - OpenLMIS contains a HAPI FHIR server built in. This FHIR server will be responsible for storing Measure Reports and make them available through the API.
Architecture Diagram
Sequence Diagrams
FHIR Research
The Measure resource provides the definition of a quality measure. While a quality measure is a quantitative tool to assess the performance of an individual or organization with respect to a specified process or outcome via the measurement of actions, processes, or outcomes of clinical care. The logic of a measure resource is usually defined in one or more library resources.
A library resource is a general-purpose container for knowledge asset definitions. It can be used to describe and expose existing knowledge assets such as logic libraries and information model descriptions, as well as to describe a collection of knowledge assets. These assets may be defined using FHIR resources or non-FHIR representations. A library resource is also intended to represent shareable knowledge independent of any particular patient.
The subject
element of the Measure indicates the intended subjects of a measure. The default is Patient, but it could also be something else, like a Location or a Device.
The Measure resource has an $evaluate-measure
operation which returns a MeasureReport.
The MeasureReport resource contains the results of the calculation of a measure for a specific subject or a group of subjects; and optionally a reference to the resources involved in that calculation. The resource is capable of representing three different levels of report: individual, subject-list, and summary. The MeasureReport resource is built to calculate measures on existing resources within the FHIR.
The Medication resource has the ability to represent a product in FHIR. This resource is primarily meant for patient centered workflows to track prescriptions. We could not find a way within FHIR that would allow us to model quantities of medication available within the system. FHIR clearly has the ability to define medications and perform transactions against them, but does not have a way to model quantity available.
MeasureReport is meant to provide a standard interface for reporting against the resources that are defined in FHIR. In this case, we need a resource in FHIR for what information was sent on the requisition. I have been through numerous FHIR resources to see if we could somehow fit it in and there isn’t a clear place. There is a SupplyRequest resource, but it doesn’t provide any link to report historical information like a Requisition does. There is a Questionnaire and QuestionnaireResponse resource that is generic, but the description suggests that they are for patient information primarily similar to an observation.
We haven't confirmed if a a Measure can evaluate against a QuestionnaireResponse. There is an email out to Bryn regarding this.
Field Mapping
This section maps the fields between the OpenLMIS Requisition and the FHIR data resources. We chose to use the requisition, because that is the primary unit of data collection in the majority of OpenLMIS implementations.
Below is a screenshot of a requisition:
Requisition Fields for Metrics
The view above shows the fields from the requisition that need to be mapped to the metrics. This work has already been done for the OpenLMIS reporting stack.
- Program - (Displayed as "EPI" in title) is a grouping of orderables, configuration and facility network associated with a specific funding source
- Facility - Defines the location the requisition was created from
- Reporting Period - The period of the requisition (usually, monthly or quarterly)
- Product - Each row in the requisition represents a product with a Product Code and Product name
- Beginning balance - Based on the Stock On Hand from the previous period quantified in dispensing units
- Total received quantity - Total quantity received in the reporting period quantified in dispensing units
- Total consumed quantity - Quantity dispensed/consumed in the reporting period quantified in dispensing units
- Total losses and adjustments - All kind of losses/adjustments made at the facility
- Total stockout days - Total number of days facility was out of stock
- Stock on hand - Current physical count of stock on hand quantified in dispensing units
Metrics (from DHIS2 Metric Scope and Definition Page)
Below is the list of indicators that our DHIS2 integration will support. Each one is calculated per product, reporting period, facility, and program.
Stock Status
Evaluated based on the logic below:
- Stockout: stockOnHand = 0, totalStockoutDays > 0, beginningBalance = 0, or if maxPeriodsOfStock = 0
- Understocked: 0 < maxPeriodsOfStock < 3
- Adequately Stocked: 3 <= maxPeriodsIOfStock <= 6
- Overstocked: maxPeriodsOfStock > 6
Received Quantities
The amount from the totalReceivedQuantity field on the requisition line item for that product, reporting period, and facility.
Consumed Quantities
The amount from the totalConsumedQuantity field on the requisition line item for that product, reporting period, and facility.
Total Stockout Days
The total number of days in the totalStockoutDays field on the requisition line item for that product, reporting period, and facility.
(ACTION Required) FHIR Measure Resource Crosswalk (Short Term Solution)
ACTION: we need to redo this piece to calculate and report all products for a single column in the requisition template as a single measure. Therefore, we would post "N" measureReports per requisition.
The short term solution is to create a measure that can capture the appropriate information in the requisition and calculate the metrics. The Measure, in this instance, provides a dataset definition showing what needs to be posted and it maps to the R&R template in OpenLMIS. We will have a single Measure definition per R&R template and facilityType approved product combination. So, if OpenLMIS's Essential Medicines program is configured to to have different product lists for district warehouses vs. facilities, we need to have a measure for each Essential_Medicines_District_Warehouse and Essential_Medicines_Facilities. We will build a Nifi process to create one measure per program and provide triggers to update the measure when new facilityTypeApprovedProducts are added to each program.
The measure will define a single group for each product with the group identifier and name mapping to the product code and name in OpenLMIS. The columns in the requisition will be mapped to stratifiers in that group so we can report on.
Below is a crosswalk:
OpenLMIS Requisition Field | Measure Resource Field Name | Notes |
---|---|---|
Program | name & title | The program will be embedded in the name and the title of the measure so we can link the requisition to the R&R template |
Facility | The facility will not be captured within the measure. Instead, it will be captured as the "reporter" in the measureReport field. | |
Product Code | group.identifier | Each product in the requisition will be mapped to a group |
Product | group.name | The name of the product |
Beginning Balance | group.stratifier.code | value=beginning_balance |
Total Received Quantity | group.stratifier.code | value=total_received_quantity |
Total Consumed Quantity | group.stratifier.code | value=total_consumed_quantity |
Total Losses and Adjustments | group.stratifier.code | value=total_losses_and_adjustments |
Total Stock Out Days | group.stratifier.code | value=total_stock_out_days |
Stock On Hand | group.stratifier.code | value=stock_on_hand |
Stock Status (metric) | group.stratifier.code | This metric is not able to be posted to the FHIR server because the current technology can not received non-numeric inputs. |
FHIR SupplyRequest Resource Crosswalk (Long Term Solution)
It's clear that the FHIR resources do not map to the requisition and report process. We have to import Requisitions into the FHIR server if we want to perform measures on the fields collected within the server. In order to do that we would have to create FHIR resources to capture that information. The SupplyRequest resource seems to be the appropriate resource to do the job, but we would have to extend it.
OpenLMIS Requisition Field | FHIR Resource (Maturity Level) | HAPI FHIR | Notes |
---|---|---|---|
Program | Not Available | There isn't a clear logical mapping between an OpenLMIS program and a FHIR resource. We could add this as an extension to the supplyRequest | |
Facility | Location (3) | Available | |
Reporting Period | Available | A reporting period is not defined as an independent resource. Instead, it is a data type that is embedded within the system so it can be used in multiple places. | |
Product | Medication (3) | Available | We would have to load in all of the medications to provide the item field in the supplyRequest resource |
Beginning Balance | Not Available | This would have to be an extension for each item | |
Total Received Quantity | Not Available | This would have to be an extension for each item | |
Total Consumed Quantity | Not Available | This would have to be an extension for each item | |
Total Losses and Adjustments | Not Available | This would have to be an extension for each item | |
Total Stock Out Days | Not Available | This would have to be an extension for each item | |
Stock On Hand | Not Available | This would have to be an extension for each item |
Sample FHIR Messages (Using the Latest FHIR R4)
This section includes hand formed JSON to develop a proof of concept and testing purposes. The goal here is to develop a minimum set of resources to ensure FHIR can be crosswalked to a requisition.
Ordering Messages:
- We need to post the locations to the FHIR server to make sure the location exists
- We need to post a questionnaire, which should match the Requisition Template
- We need to post a measure that references that questionnaire
- We need to visit the measure URL and see if it returns the correct result
Sample Measure
The following Measure matches the requisition above found in OpenLMIS. It can be found at http://hapi.fhir.org/baseR4/Measure/10480/_history/1 unless the HAPI FHIR server has been reset
{ "resourceType": "Measure", "name": "EPI_Health_Center", "status": "draft", "experimental": true, "group": [ { "code": { "text": "bcg20" }, "description": "BCG", "stratifier": [ { "code": { "text": "beginning_balance" }, "description": "Based on the Stock On Hand from the previous period quantified in dispensing units." }, { "code": { "text": "total_received_quantity" }, "description": "Total quantity received in the reporting period quantified in dispensing units." }, { "code": { "text": "total_consumed_quantity" }, "description": "Quantity dispensed/consumed in the reporting period quantified in dispensing units." }, { "code": { "text": "total_losses_and_adjustments" }, "description": "All kind of losses/adjustments made at the facility." }, { "code": { "text": "total_stock_out_days" }, "description": "Total number of days facility was out of stock." }, { "code": { "text": "stock_on_hand" }, "description": "Current physical count of stock on hand quantified in dispensing units." } ] }, { "code": { "text": "penta1" }, "description": "Pentavalent (1 dose)", "stratifier": [ { "code": { "text": "beginning_balance" }, "description": "Based on the Stock On Hand from the previous period quantified in dispensing units." }, { "code": { "text": "total_received_quantity" }, "description": "Total quantity received in the reporting period quantified in dispensing units." }, { "code": { "text": "total_consumed_quantity" }, "description": "Quantity dispensed/consumed in the reporting period quantified in dispensing units." }, { "code": { "text": "total_losses_and_adjustments" }, "description": "All kind of losses/adjustments made at the facility." }, { "code": { "text": "total_stock_out_days" }, "description": "Total number of days facility was out of stock." }, { "code": { "text": "stock_on_hand" }, "description": "Current physical count of stock on hand quantified in dispensing units." } ] }, { "code": { "text": "rota1" }, "description": "Rotavirus", "stratifier": [ { "code": { "text": "beginning_balance" }, "description": "Based on the Stock On Hand from the previous period quantified in dispensing units." }, { "code": { "text": "total_received_quantity" }, "description": "Total quantity received in the reporting period quantified in dispensing units." }, { "code": { "text": "total_consumed_quantity" }, "description": "Quantity dispensed/consumed in the reporting period quantified in dispensing units." }, { "code": { "text": "total_losses_and_adjustments" }, "description": "All kind of losses/adjustments made at the facility." }, { "code": { "text": "total_stock_out_days" }, "description": "Total number of days facility was out of stock." }, { "code": { "text": "stock_on_hand" }, "description": "Current physical count of stock on hand quantified in dispensing units." } ] }, { "code": { "text": "tetanus10" }, "description": "VAT", "stratifier": [ { "code": { "text": "beginning_balance" }, "description": "Based on the Stock On Hand from the previous period quantified in dispensing units." }, { "code": { "text": "total_received_quantity" }, "description": "Total quantity received in the reporting period quantified in dispensing units." }, { "code": { "text": "total_consumed_quantity" }, "description": "Quantity dispensed/consumed in the reporting period quantified in dispensing units." }, { "code": { "text": "total_losses_and_adjustments" }, "description": "All kind of losses/adjustments made at the facility." }, { "code": { "text": "total_stock_out_days" }, "description": "Total number of days facility was out of stock." }, { "code": { "text": "stock_on_hand" }, "description": "Current physical count of stock on hand quantified in dispensing units." } ] } ] }
Sample MeasureReport
The following MeasureReport matches the requisition above. It can be found on the HAPI FHIR demo server at http://hapi.fhir.org/baseR4/MeasureReport/10920/_history/1 (Until it is reset)
{ "resourceType": "MeasureReport", "text": { "status": "generated", "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative with Details</b></p></div>" }, "status": "complete", "type": "summary", "measure": "http://hapi.fhir.org/baseR4/Measure/10480", "reporter": { "reference": "Location/5772" }, "period": { "start": "2019-01-01", "end": "2019-01-31" }, "group": [ { "code": { "text": "bcg20" }, "stratifier": [ { "code": { "text": "beginning_balance" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_consumed_quantity" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_received_quantity" }, "stratum": [ { "measureScore": "50" } ] }, { "code": { "text": "total_losses_and_adjustments" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "total_stock_out_days" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "stock_on_hand" }, "stratum": [ { "measureScore": "50" } ] } ] }, { "code": { "text": "penta1" }, "stratifier": [ { "code": { "text": "beginning_balance" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_consumed_quantity" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_received_quantity" }, "stratum": [ { "measureScore": "50" } ] }, { "code": { "text": "total_losses_and_adjustments" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "total_stock_out_days" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "stock_on_hand" }, "stratum": [ { "measureScore": "50" } ] } ] }, { "code": { "text": "rota1" }, "stratifier": [ { "code": { "text": "beginning_balance" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_consumed_quantity" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_received_quantity" }, "stratum": [ { "measureScore": "50" } ] }, { "code": { "text": "total_losses_and_adjustments" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "total_stock_out_days" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "stock_on_hand" }, "stratum": [ { "measureScore": "50" } ] } ] }, { "code": { "text": "tetanus10" }, "stratifier": [ { "code": { "text": "beginning_balance" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_consumed_quantity" }, "stratum": [ { "measureScore": "100" } ] }, { "code": { "text": "total_received_quantity" }, "stratum": [ { "measureScore": "50" } ] }, { "code": { "text": "total_losses_and_adjustments" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "total_stock_out_days" }, "stratum": [ { "measureScore": "0" } ] }, { "code": { "text": "stock_on_hand" }, "stratum": [ { "measureScore": "50" } ] } ] } ] }
OpenLMIS: the global initiative for powerful LMIS software