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:


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_NameQuantity_On_HandDays_Stocked_Out
Paracetamol50
Ibuprofen2003

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_NameOpenLMIS_Column_NameDHIS2_Dataset_NameDHIS2_Dataset_GUIDDHIS2_Category_NameDHIS2_Category_GUID
chart1indicator1dataSet1P9slqy89sYbcategory1DosHq4vnsYv
chart1

indicator2

dataSet1P9slqy89sYbcategory2Q8lOv2CAUjf
chart1indicator3dataSet1P9slqy89sYbcategory3ijIsjbvXFJL

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).
    • 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.

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

 Click here to expand...

title Workflow 1: Extract Data from Microservices and store in Data Warehouse

participant OpenLMIS Microservices
participant Nifi
participant Data Warehouse

Nifi->Nifi: Cron Trigger
Nifi->+OpenLMIS Microservices: Query api
OpenLMIS Microservices->-Nifi: Return results
Nifi->Nifi: Transform data
Nifi->Data Warehouse: Load Data

 Click here to expand...

title Workflow 2: Extract Superset report and load to FHIR Server

participant Nifi
participant Superset
participant Data Warehouse
participant FHIR Server

Nifi->Nifi: Cron Trigger
Nifi->Superset: Query Superset API
Superset->Data Warehouse: Query Data
Data Warehouse->Superset: Return Results
Superset->Nifi: Return Results
Nifi->Nifi: Transform results into FHIR Measure
Nifi->FHIR Server: Load results
FHIR Server->FHIR Server: Store Measures

 Click here to expand...

title Workflow 3: External Consumer GET Measure Report

participant External Consumer
participant OpenLMIS Auth Microservice
participant OpenLMIS FHIR API
participant OpenLMIS ReferenceData Microservice
participant FHIR Server


External Consumer->OpenLMIS Auth Microservice: Get Auth Token
OpenLMIS Auth Microservice->OpenLMIS Auth Microservice: Verify Auth
OpenLMIS Auth Microservice->External Consumer: Return Auth Token
External Consumer->OpenLMIS FHIR API: Request Measure with Auth Token
OpenLMIS FHIR API->OpenLMIS ReferenceData Microservice: Get User Role
OpenLMIS ReferenceData Microservice->OpenLMIS FHIR API: Return Role
OpenLMIS FHIR API->OpenLMIS FHIR API: Verify Role
OpenLMIS FHIR API->FHIR Server: Get Measure Report
FHIR Server->OpenLMIS FHIR API: Return Measure Report
OpenLMIS FHIR API->External Consumer: Return Measure Report

 Click here to expand...

title Workflow 4: External Consumer Transform Measure Report and Post to DHIS2

participant External Consumer
participant OpenLMIS
participant DHIS2

External Consumer->OpenLMIS: Request Measure Report (Workflow 3)
OpenLMIS->External Consumer: Return Measure Report
External Consumer->External Consumer: Crosswalk location, program, period & measure
External Consumer->External Consumer: Transform FHIR Measure Report to ADX
External Consumer->DHIS2: POST to DHIS2
DHIS2->DHIS2: Evaluate values and store report

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:

 Click here to expand...
{
  "id": "bc6d01dd-d861-4136-a0a3-2ffbe3cd27c5",
  "createdDate": "2018-01-16T14:34:57.915007Z",
  "modifiedDate": "2018-01-16T18:34:57.915007Z",
  "requisitionLineItems": [
    {
      "id": "93b201ad-942c-4a0e-bd0a-8f002218a2e3",
      "orderable": {
        "programs": [
          {
            "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
            "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
            "orderableCategoryDisplayName": "Vaccines",
            "orderableCategoryDisplayOrder": 5,
            "fullSupply": true,
            "displayOrder": 5,
            "pricePerPack": 12
          }
        ],
        "dispensable": {
          "dispensingUnit": null,
          "displayUnit": "20 dose,injection"
        },
        "identifiers": {
          "commodityType": "99ccf663-3304-44ae-b2e0-a67fd5511e2a"
        },
        "id": "8ef9d4da-b6e5-401c-b433-765a5fd8a0cc",
        "productCode": "bcg20",
        "fullProductName": "BCG",
        "netContent": 20,
        "packRoundingThreshold": 1,
        "roundToZero": true,
        "commodityTypeIdentifier": "99ccf663-3304-44ae-b2e0-a67fd5511e2a"
      },
      "beginningBalance": 100,
      "totalReceivedQuantity": 50,
      "totalLossesAndAdjustments": 0,
      "stockOnHand": 50,
      "requestedQuantity": 100,
      "totalConsumedQuantity": 100,
      "requestedQuantityExplanation": "Need more",
      "approvedQuantity": 100,
      "totalStockoutDays": 0,
      "total": 150,
      "packsToShip": 100,
      "pricePerPack": 6,
      "totalCost": 60,
      "skipped": false,
      "adjustedConsumption": 100,
      "previousAdjustedConsumptions": [],
      "averageConsumption": 100,
      "maxPeriodsOfStock": 3,
      "calculatedOrderQuantity": 250,
      "stockAdjustments": []
    },
    {
      "id": "5ec13936-3cc9-4c88-afcc-b9b636955d5c",
      "orderable": {
        "programs": [
          {
            "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
            "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
            "orderableCategoryDisplayName": "Vaccines",
            "orderableCategoryDisplayOrder": 5,
            "fullSupply": true,
            "displayOrder": 5,
            "pricePerPack": 8.5
          }
        ],
        "dispensable": {
          "dispensingUnit": null,
          "displayUnit": "1 dose,injection"
        },
        "identifiers": {
          "commodityType": "08561b98-2f90-4c4a-a1e3-f180a83abd66"
        },
        "id": "b61c652d-2259-41d7-8bb6-fc5fcdd95626",
        "productCode": "rota1",
        "fullProductName": "Rotavirus",
        "netContent": 1,
        "packRoundingThreshold": 1,
        "roundToZero": true,
        "commodityTypeIdentifier": "08561b98-2f90-4c4a-a1e3-f180a83abd66"
      },
      "beginningBalance": 100,
      "totalReceivedQuantity": 50,
      "totalLossesAndAdjustments": 0,
      "stockOnHand": 50,
      "requestedQuantity": 100,
      "totalConsumedQuantity": 100,
      "requestedQuantityExplanation": "Need more",
      "approvedQuantity": 100,
      "totalStockoutDays": 0,
      "total": 150,
      "packsToShip": 10,
      "pricePerPack": 6,
      "totalCost": 60,
      "skipped": false,
      "adjustedConsumption": 100,
      "previousAdjustedConsumptions": [],
      "averageConsumption": 100,
      "maxPeriodsOfStock": 3,
      "calculatedOrderQuantity": 250,
      "stockAdjustments": []
    },
    {
      "id": "93ad4347-7fa3-43fd-bcdb-53cf0b1f496f",
      "orderable": {
        "programs": [
          {
            "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
            "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
            "orderableCategoryDisplayName": "Vaccines",
            "orderableCategoryDisplayOrder": 5,
            "fullSupply": true,
            "displayOrder": 5,
            "pricePerPack": 2
          }
        ],
        "dispensable": {
          "dispensingUnit": null,
          "displayUnit": "1 dose,injection"
        },
        "identifiers": {
          "commodityType": "43a1cd06-42f1-4e7c-b8ed-594416985381"
        },
        "id": "5f0dd194-aae1-490c-8b51-3cf0c87af983",
        "productCode": "penta1",
        "fullProductName": "Pentavalent (1 dose)",
        "netContent": 1,
        "packRoundingThreshold": 1,
        "roundToZero": true,
        "commodityTypeIdentifier": "43a1cd06-42f1-4e7c-b8ed-594416985381"
      },
      "beginningBalance": 100,
      "totalReceivedQuantity": 50,
      "totalLossesAndAdjustments": 0,
      "stockOnHand": 50,
      "requestedQuantity": 100,
      "totalConsumedQuantity": 100,
      "requestedQuantityExplanation": "Need more",
      "approvedQuantity": 100,
      "totalStockoutDays": 0,
      "total": 150,
      "packsToShip": 10,
      "pricePerPack": 6,
      "totalCost": 60,
      "skipped": false,
      "adjustedConsumption": 100,
      "previousAdjustedConsumptions": [],
      "averageConsumption": 100,
      "maxPeriodsOfStock": 3,
      "calculatedOrderQuantity": 250,
      "stockAdjustments": []
    },
    {
      "id": "ee142aae-2b89-4d90-ab40-12aaa8a3fb7d",
      "orderable": {
        "programs": [
          {
            "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
            "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
            "orderableCategoryDisplayName": "Vaccines",
            "orderableCategoryDisplayOrder": 5,
            "fullSupply": true,
            "displayOrder": 5,
            "pricePerPack": 6
          }
        ],
        "dispensable": {
          "dispensingUnit": null,
          "displayUnit": "10 dose,injection"
        },
        "identifiers": {
          "commodityType": "ac5e9d62-dc0a-4a0e-bead-e66a2eb81b07"
        },
        "id": "e217910c-3364-46b3-92cd-8dd8acf0c557",
        "productCode": "tetanus10",
        "fullProductName": "VAT",
        "netContent": 10,
        "packRoundingThreshold": 1,
        "roundToZero": true,
        "commodityTypeIdentifier": "ac5e9d62-dc0a-4a0e-bead-e66a2eb81b07"
      },
      "beginningBalance": 100,
      "totalReceivedQuantity": 50,
      "totalLossesAndAdjustments": 0,
      "stockOnHand": 50,
      "requestedQuantity": 100,
      "totalConsumedQuantity": 100,
      "requestedQuantityExplanation": "Need more",
      "approvedQuantity": 100,
      "totalStockoutDays": 0,
      "total": 150,
      "packsToShip": 50,
      "pricePerPack": 6,
      "totalCost": 60,
      "skipped": false,
      "adjustedConsumption": 100,
      "previousAdjustedConsumptions": [],
      "averageConsumption": 100,
      "maxPeriodsOfStock": 3,
      "calculatedOrderQuantity": 250,
      "stockAdjustments": []
    }
  ],
  "draftStatusMessage": null,
  "facility": {
    "id": "7fc9bda8-ad8a-468d-8244-38e1918527d5",
    "code": "N003",
    "name": "Cuamba, Cuamba",
    "active": true,
    "geographicZone": {
      "id": "9b8cfb5a-217a-4261-a64f-16ca06ae79fa",
      "code": "cuamba",
      "name": "Cuamba",
      "level": {
        "id": "93c05138-4550-4461-9e8a-79d5f050c223",
        "code": "District",
        "name": null,
        "levelNumber": 3
      },
      "parent": {
        "id": "0d4eb5ee-ae7f-42e7-89e1-d0f276090755",
        "code": "niassa",
        "name": "Niassa",
        "level": {
          "id": "9b497d87-cdd9-400e-bb04-fae0bf6a9491",
          "code": "Region",
          "name": null,
          "levelNumber": 2
        },
        "parent": {
          "id": "d22d86fb-9123-437a-9eae-da2b31b77e34",
          "code": "moz",
          "name": "Mozambique",
          "level": {
            "id": "6b78e6c6-292e-4733-bb9c-3d802ad61206",
            "code": "Country",
            "name": null,
            "levelNumber": 1
          },
          "parent": null
        }
      }
    },
    "type": {
      "id": "ac1d268b-ce10-455f-bf87-9c667da8f060",
      "code": "health_center",
      "name": "Health Center",
      "description": null,
      "displayOrder": 3,
      "active": true
    },
    "goLiveDate": "2010-09-01",
    "enabled": true,
    "openLmisAccessible": true,
    "operator": {
      "id": "9456c3e9-c4a6-4a28-9e08-47ceb16a4121",
      "code": "moh",
      "name": "Ministry of Health"
    }
  },
  "program": {
    "id": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
    "code": "PRG004",
    "name": "EPI",
    "description": null,
    "active": true,
    "periodsSkippable": false,
    "showNonFullSupplyTab": false,
    "skipAuthorization": true,
    "enableDatePhysicalStockCountCompleted": false
  },
  "processingPeriod": {
    "id": "516ac930-0d28-49f5-a178-64764e22b236",
    "name": "Jan2017",
    "startDate": "2017-01-01",
    "endDate": "2017-01-31",
    "processingSchedule": {
      "id": "9c15bd6e-3f6b-4b91-b53a-36c199d35eac",
      "code": "SCH001",
      "description": null,
      "modifiedDate": null,
      "name": "Monthly"
    },
    "description": null,
    "durationInMonths": 1,
    "extraData": {}
  },
  "status": "APPROVED",
  "emergency": false,
  "reportOnly": null,
  "supplyingFacility": null,
  "supervisoryNode": "6f5eae49-bc62-4664-a679-1ef0bfd5b21d",
  "template": {
    "id": "6c4b004b-b7c9-46f0-bb1b-bd128d36729f",
    "createdDate": "2016-06-14T12:00:00Z",
    "numberOfPeriodsToAverage": 3,
    "populateStockOnHandFromStockCards": true,
    "name": "EPI",
    "columnsMap": {
      "numberOfNewPatientsAdded": {
        "name": "numberOfNewPatientsAdded",
        "label": "Number of new patients added",
        "indicator": "F",
        "displayOrder": 20,
        "isDisplayed": true,
        "source": "USER_INPUT",
        "option": {
          "id": "34b8e763-71a0-41f1-86b4-1829963f0704",
          "optionName": "newPatientCount",
          "optionLabel": "requisitionConstants.newPatientCount"
        },
        "definition": "New patients data.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "adjustedConsumption": {
        "name": "adjustedConsumption",
        "label": "Adjusted consumption",
        "indicator": "N",
        "displayOrder": 22,
        "isDisplayed": true,
        "source": "CALCULATED",
        "option": null,
        "definition": "Total consumed quantity after adjusting for stockout days. Quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "totalLossesAndAdjustments": {
        "name": "totalLossesAndAdjustments",
        "label": "Total losses and adjustments",
        "indicator": "D",
        "displayOrder": 7,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "All kind of losses/adjustments made at the facility.",
        "tag": "adjustment",
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "totalStockoutDays": {
        "name": "totalStockoutDays",
        "label": "Total stockout days",
        "indicator": "X",
        "displayOrder": 8,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "Total number of days facility was out of stock.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "packsToShip": {
        "name": "packsToShip",
        "label": "Packs to ship",
        "indicator": "V",
        "displayOrder": 14,
        "isDisplayed": false,
        "source": "CALCULATED",
        "option": {
          "id": "dcf41f06-3000-4af6-acf5-5de4fffc966f",
          "optionName": "showPackToShipInAllPages",
          "optionLabel": "requisitionConstants.showPackToShipInAllPages"
        },
        "definition": "Total packs to be shipped based on pack size and applying rounding rules.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "skipped": {
        "name": "skipped",
        "label": "Skip",
        "indicator": "S",
        "displayOrder": 1,
        "isDisplayed": true,
        "source": "USER_INPUT",
        "option": {
          "id": "17d6e860-a746-4500-a0fa-afc84d799dca",
          "optionName": "disableSkippedLineItems",
          "optionLabel": "requisitionConstants.disableSkippedLineItems"
        },
        "definition": "Select the check box below to skip a single product. Remove all data from the row prior to selection.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": false,
          "columnType": "BOOLEAN"
        }
      },
      "orderable.productCode": {
        "name": "orderable.productCode",
        "label": "Product code",
        "indicator": "O",
        "displayOrder": 2,
        "isDisplayed": true,
        "source": "REFERENCE_DATA",
        "option": null,
        "definition": "Unique identifier for each commodity/product.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": false,
          "columnType": "TEXT"
        }
      },
      "idealStockAmount": {
        "name": "idealStockAmount",
        "label": "Ideal Stock Amount",
        "indicator": "G",
        "displayOrder": 10,
        "isDisplayed": true,
        "source": "REFERENCE_DATA",
        "option": null,
        "definition": "The Ideal Stock Amount is the target quantity for a specific commodity type, facility, and period.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "total": {
        "name": "total",
        "label": "Total",
        "indicator": "Y",
        "displayOrder": 19,
        "isDisplayed": true,
        "source": "CALCULATED",
        "option": null,
        "definition": "Total of beginning balance and quantity received.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "totalConsumedQuantity": {
        "name": "totalConsumedQuantity",
        "label": "Total consumed quantity",
        "indicator": "C",
        "displayOrder": 6,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "Quantity dispensed/consumed in the reporting period. This is quantified in dispensing units.",
        "tag": "consumed",
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "stockOnHand": {
        "name": "stockOnHand",
        "label": "Stock on hand",
        "indicator": "E",
        "displayOrder": 9,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "Current physical count of stock on hand. This is quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "requestedQuantity": {
        "name": "requestedQuantity",
        "label": "Requested quantity",
        "indicator": "J",
        "displayOrder": 15,
        "isDisplayed": true,
        "source": "USER_INPUT",
        "option": null,
        "definition": "Requested override of calculated quantity. This is quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "beginningBalance": {
        "name": "beginningBalance",
        "label": "Beginning balance",
        "indicator": "A",
        "displayOrder": 4,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "Based on the Stock On Hand from the previous period. This is quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "orderable.dispensable.displayUnit": {
        "name": "orderable.dispensable.displayUnit",
        "label": "Display Unit",
        "indicator": "U",
        "displayOrder": 24,
        "isDisplayed": true,
        "source": "REFERENCE_DATA",
        "option": null,
        "definition": "Display unit for this product.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "TEXT"
        }
      },
      "totalReceivedQuantity": {
        "name": "totalReceivedQuantity",
        "label": "Total received quantity",
        "indicator": "B",
        "displayOrder": 5,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "Total quantity received in the reporting period. This is quantified in dispensing units.",
        "tag": "received",
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "approvedQuantity": {
        "name": "approvedQuantity",
        "label": "Approved quantity",
        "indicator": "K",
        "displayOrder": 17,
        "isDisplayed": true,
        "source": "USER_INPUT",
        "option": null,
        "definition": "Final approved quantity. This is quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "orderable.fullProductName": {
        "name": "orderable.fullProductName",
        "label": "Product",
        "indicator": "R",
        "displayOrder": 3,
        "isDisplayed": true,
        "source": "REFERENCE_DATA",
        "option": null,
        "definition": "Primary name of the product.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": false,
          "columnType": "TEXT"
        }
      },
      "pricePerPack": {
        "name": "pricePerPack",
        "label": "Price per pack",
        "indicator": "T",
        "displayOrder": 23,
        "isDisplayed": true,
        "source": "REFERENCE_DATA",
        "option": null,
        "definition": "Price per pack. Will be blank if price is not defined.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "CURRENCY"
        }
      },
      "calculatedOrderQuantityIsa": {
        "name": "calculatedOrderQuantityIsa",
        "label": "Calc Order Qty ISA",
        "indicator": "S",
        "displayOrder": 11,
        "isDisplayed": true,
        "source": "CALCULATED",
        "option": null,
        "definition": "Calculated Order Quantity ISA is based on an ISA configured by commodity type, and several trade items may fill for one commodity type.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "averageConsumption": {
        "name": "averageConsumption",
        "label": "Average consumption",
        "indicator": "P",
        "displayOrder": 12,
        "isDisplayed": true,
        "source": "STOCK_CARDS",
        "option": null,
        "definition": "Average consumption over a specified number of periods/months. Quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "requestedQuantityExplanation": {
        "name": "requestedQuantityExplanation",
        "label": "Requested quantity explanation",
        "indicator": "W",
        "displayOrder": 16,
        "isDisplayed": true,
        "source": "USER_INPUT",
        "option": null,
        "definition": "Explanation of request for a quantity other than calculated order quantity.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "TEXT"
        }
      },
      "calculatedOrderQuantity": {
        "name": "calculatedOrderQuantity",
        "label": "Calculated order quantity",
        "indicator": "I",
        "displayOrder": 13,
        "isDisplayed": true,
        "source": "CALCULATED",
        "option": null,
        "definition": "Actual quantity needed after deducting stock in hand. This is quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "remarks": {
        "name": "remarks",
        "label": "Remarks",
        "indicator": "L",
        "displayOrder": 18,
        "isDisplayed": true,
        "source": "USER_INPUT",
        "option": null,
        "definition": "Any additional remarks.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "TEXT"
        }
      },
      "totalCost": {
        "name": "totalCost",
        "label": "Total cost",
        "indicator": "Q",
        "displayOrder": 25,
        "isDisplayed": true,
        "source": "CALCULATED",
        "option": null,
        "definition": "Total cost of the product based on quantity requested. Will be blank if price is not defined.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "CURRENCY"
        }
      },
      "maximumStockQuantity": {
        "name": "maximumStockQuantity",
        "label": "Maximum stock quantity",
        "indicator": "H",
        "displayOrder": 21,
        "isDisplayed": true,
        "source": "CALCULATED",
        "option": {
          "id": "ff2b350c-37f2-4801-b21e-27ca12c12b3c",
          "optionName": "default",
          "optionLabel": "requisitionConstants.default"
        },
        "definition": "Maximum stock calculated based on consumption and max stock amounts. Quantified in dispensing units.",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      },
      "additionalQuantityRequired": {
        "name": "additionalQuantityRequired",
        "label": "Additional quantity required",
        "indicator": "Z",
        "displayOrder": 24,
        "isDisplayed": false,
        "source": "USER_INPUT",
        "option": null,
        "definition": "Additional quantity required for new patients",
        "tag": null,
        "columnDefinition": {
          "canChangeOrder": true,
          "columnType": "NUMERIC"
        }
      }
    }
  },
  "availableFullSupplyProducts": [
    {
      "programs": [
        {
          "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
          "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
          "orderableCategoryDisplayName": "Vaccines",
          "orderableCategoryDisplayOrder": 5,
          "fullSupply": true,
          "displayOrder": 5,
          "pricePerPack": 8.5
        }
      ],
      "dispensable": {
        "dispensingUnit": null,
        "displayUnit": "1 dose,injection"
      },
      "identifiers": {
        "commodityType": "08561b98-2f90-4c4a-a1e3-f180a83abd66"
      },
      "id": "b61c652d-2259-41d7-8bb6-fc5fcdd95626",
      "productCode": "rota1",
      "fullProductName": "Rotavirus",
      "netContent": 1,
      "packRoundingThreshold": 1,
      "roundToZero": true,
      "commodityTypeIdentifier": "08561b98-2f90-4c4a-a1e3-f180a83abd66"
    },
    {
      "programs": [
        {
          "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
          "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
          "orderableCategoryDisplayName": "Vaccines",
          "orderableCategoryDisplayOrder": 5,
          "fullSupply": true,
          "displayOrder": 5,
          "pricePerPack": 12
        }
      ],
      "dispensable": {
        "dispensingUnit": null,
        "displayUnit": "20 dose,injection"
      },
      "identifiers": {
        "commodityType": "99ccf663-3304-44ae-b2e0-a67fd5511e2a"
      },
      "id": "8ef9d4da-b6e5-401c-b433-765a5fd8a0cc",
      "productCode": "bcg20",
      "fullProductName": "BCG",
      "netContent": 20,
      "packRoundingThreshold": 1,
      "roundToZero": true,
      "commodityTypeIdentifier": "99ccf663-3304-44ae-b2e0-a67fd5511e2a"
    },
    {
      "programs": [
        {
          "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
          "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
          "orderableCategoryDisplayName": "Vaccines",
          "orderableCategoryDisplayOrder": 5,
          "fullSupply": true,
          "displayOrder": 5,
          "pricePerPack": 6
        }
      ],
      "dispensable": {
        "dispensingUnit": null,
        "displayUnit": "10 dose,injection"
      },
      "identifiers": {
        "commodityType": "ac5e9d62-dc0a-4a0e-bead-e66a2eb81b07"
      },
      "id": "e217910c-3364-46b3-92cd-8dd8acf0c557",
      "productCode": "tetanus10",
      "fullProductName": "VAT",
      "netContent": 10,
      "packRoundingThreshold": 1,
      "roundToZero": true,
      "commodityTypeIdentifier": "ac5e9d62-dc0a-4a0e-bead-e66a2eb81b07"
    },
    {
      "programs": [
        {
          "programId": "418bdc1d-c303-4bd0-b2d3-d8901150a983",
          "orderableDisplayCategoryId": "16173fd0-f439-4222-931e-91c413a495c3",
          "orderableCategoryDisplayName": "Vaccines",
          "orderableCategoryDisplayOrder": 5,
          "fullSupply": true,
          "displayOrder": 5,
          "pricePerPack": 2
        }
      ],
      "dispensable": {
        "dispensingUnit": null,
        "displayUnit": "1 dose,injection"
      },
      "identifiers": {
        "commodityType": "43a1cd06-42f1-4e7c-b8ed-594416985381"
      },
      "id": "5f0dd194-aae1-490c-8b51-3cf0c87af983",
      "productCode": "penta1",
      "fullProductName": "Pentavalent (1 dose)",
      "netContent": 1,
      "packRoundingThreshold": 1,
      "roundToZero": true,
      "commodityTypeIdentifier": "43a1cd06-42f1-4e7c-b8ed-594416985381"
    }
  ],
  "availableNonFullSupplyProducts": [],
  "statusChanges": {
    "IN_APPROVAL": {
      "authorId": "560be32a-ea2e-4d12-ae00-1f69376ad535",
      "changeDate": "2018-01-16T17:34:57.915007Z"
    },
    "INITIATED": {
      "authorId": "211a6b4d-3c59-4fb2-8075-eedb79a18103",
      "changeDate": "2018-01-16T14:34:57.915007Z"
    },
    "SUBMITTED": {
      "authorId": "211a6b4d-3c59-4fb2-8075-eedb79a18103",
      "changeDate": "2018-01-16T15:34:57.915007Z"
    },
    "APPROVED": {
      "authorId": "1e3b03a5-1d48-4de1-bb4a-389beece2277",
      "changeDate": "2018-01-16T18:34:57.915007Z"
    },
    "AUTHORIZED": {
      "authorId": "211a6b4d-3c59-4fb2-8075-eedb79a18103",
      "changeDate": "2018-01-16T16:34:57.915007Z"
    }
  },
  "statusHistory": [
    {
      "status": "AUTHORIZED",
      "statusMessageDto": null,
      "authorId": "211a6b4d-3c59-4fb2-8075-eedb79a18103",
      "createdDate": "2018-01-16T16:34:57.915007Z"
    },
    {
      "status": "APPROVED",
      "statusMessageDto": null,
      "authorId": "1e3b03a5-1d48-4de1-bb4a-389beece2277",
      "createdDate": "2018-01-16T18:34:57.915007Z"
    },
    {
      "status": "INITIATED",
      "statusMessageDto": null,
      "authorId": "211a6b4d-3c59-4fb2-8075-eedb79a18103",
      "createdDate": "2018-01-16T14:34:57.915007Z"
    },
    {
      "status": "SUBMITTED",
      "statusMessageDto": null,
      "authorId": "211a6b4d-3c59-4fb2-8075-eedb79a18103",
      "createdDate": "2018-01-16T15:34:57.915007Z"
    },
    {
      "status": "IN_APPROVAL",
      "statusMessageDto": null,
      "authorId": "560be32a-ea2e-4d12-ae00-1f69376ad535",
      "createdDate": "2018-01-16T17:34:57.915007Z"
    }
  ],
  "datePhysicalStockCountCompleted": null,
  "stockAdjustmentReasons": [],
  "extraData": {}
}

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 FieldMeasure Resource Field NameNotes
Programname & titleThe 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.identifierEach product in the requisition will be mapped to a group
Productgroup.nameThe name of the product
Beginning Balancegroup.stratifier.codevalue=beginning_balance
Total Received Quantitygroup.stratifier.codevalue=total_received_quantity
Total Consumed Quantitygroup.stratifier.codevalue=total_consumed_quantity
Total Losses and Adjustmentsgroup.stratifier.codevalue=total_losses_and_adjustments
Total Stock Out Daysgroup.stratifier.codevalue=total_stock_out_days
Stock On Handgroup.stratifier.codevalue=stock_on_hand
Stock Status (metric)group.stratifier.codeThis 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 FieldFHIR Resource (Maturity Level)HAPI FHIRNotes
ProgramNot 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
FacilityLocation (3)Available
Reporting Period

Period Complex Data Type

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)AvailableWe would have to load in all of the medications to provide the item field in the supplyRequest resource
Beginning BalanceNot Available
This would have to be an extension for each item
Total Received QuantityNot Available
This would have to be an extension for each item
Total Consumed QuantityNot Available
This would have to be an extension for each item
Total Losses and AdjustmentsNot Available
This would have to be an extension for each item
Total Stock Out DaysNot Available
This would have to be an extension for each item
Stock On HandNot 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:

  1. We need to post the locations to the FHIR server to make sure the location exists
  2. We need to post a questionnaire, which should match the Requisition Template
  3. We need to post a measure that references that questionnaire
  4. 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"
            }
          ]
        }
      ]
    }
  ]
}


 Archived Items from a previous version of this page

Approach

  • Define meta-data (reference data) alignment (OpenLMIS concept & DHIS2 equivalent)
    • Locations & Org Units (through FHIR Location and a Facility Registry)
    • Products & Data Elements (through swivel chair integration)
    • Programs & Programs (through swivel chair)
    • Processing Periods & Periods (through swivel chair)
  • Publishing metrics in OpenLMIS for DHIS2 (or other) systems to retrieve.
  • Potentially building deployment architecture (the rest of the messaging infrastructure in pub-sub to take published metrics and make them available in DHIS2)

Decision Points:

  • Which data format (FHIR or ADX)?
  • Are we satisfied with the definition of each of the 4 metrics?  (are they formal enough)
  • What role is the reporting stack, if any, playing in the data pipeline to DHIS2?
  • Which of the 4 metrics are we building first?
  • When complete, will we have a full working demo of OpenLMIS → DHIS2?  Or would we only show the metrics being published?
    • Are we setting up our own DHIS2 instance? Or using one available to us (e.g. play.dhis2.org)?
    • How would we align the reference data between OpenLMIS and DHIS2?
    • Would we write a reference deployment architecture (message delivery) in OpenHIM?  Would we build an OpenHIM Reference Mediator?  Not all country implementations use the same interoperability layer technology meaning anything we build here will likely need to be a reference.

Architecture Diagram

This is currently in a draft state and is created in LucidCharts.

OpenLMIS: the global initiative for powerful LMIS software