Environmental data

Load the daily readings of a national meteorological service into DHIS2, under the org units of its stations.

This page concludes the One Health series. The DHIS2 Climate app imports global datasets on its own, and what is left are national sources - the stations of a meteorological service, or the sampling sites of environmental surveillance. The service below reads yesterday's readings of every station once a day and posts them to a daily data set in public health, under the org unit each station is in.

The source

The examples use a REST outgoing connection named Met Service, pointing to an API that returns one observation per station and day:

{
  "observations": [
    {
      "station": "ST-0412",
      "rainfall_mm": 18.6,
      "temperature_max": 31.4,
      "temperature_min": null
    },
    {
      "station": "ST-0415",
      "rainfall_mm": 0.0,
      "temperature_max": 33.1,
      "temperature_min": 21.7
    }
  ]
}

stations.ini is a config table with the org unit of each station, and environment.ini has the data element each measurement is recorded under:

# config/user-conf/stations.ini

[MET_SERVICE]
ST-0412 = O6uvpzGd5pu
ST-0415 = fdc6uOvgoji
# config/user-conf/environment.ini

[elements]
rainfall_mm = Rk2vT8nWq4L
temperature_max = Hs6bX1cMp9D
temperature_min = Zf4jA7yEu3K

Build the data values

Each measurement a station reported becomes one data value, as a string. A measurement the station did not report is null in the response and is left out:

# -*- coding: utf-8 -*-

# stdlib
from datetime import date, timedelta

# Zato
from zato.server.service import Service

class LoadEnvironment(Service):
    name = 'dhis2.one-health.load-environment'

    def build_values(self, reading:'dict', org_unit:'str', period:'str') -> 'list':

        elements = self.config.environment.elements
        out = []

        for name, data_element in elements.items():
            measured = reading[name]
            if measured is None:
                continue
            value = str(measured)
            data_value = {
                'dataElement': data_element,
                'period': period,
                'orgUnit': org_unit,
                'value': value,
            }
            out.append(data_value)

        return out

Post the day

The period of a daily data set is the date written as YYYYMMDD:

    def handle(self) -> 'None':

        stations = self.config.stations
        today = date.today()
        yesterday = today - timedelta(days=1)
        period = yesterday.strftime('%Y%m%d')
        day = yesterday.isoformat()

        params = {'date': day}
        conn = self.rest['Met Service']
        response = conn.get(params=params)
        observations = response.data['observations']

        data_values = []
        for reading in observations:

            # A station no org unit is mapped to is logged and left out ..
            station = reading['station']
            org_unit = stations.translate(source='MET_SERVICE', code=station)
            if org_unit is None:
                self.logger.warning('No org unit for station %s', station)
                continue

            # .. and every other one adds its measurements.
            values = self.build_values(reading, org_unit, period)
            data_values.extend(values)

        payload = {'dataValues': data_values}
        conn = self.rest['Public Health Data Values']
        conn.post(payload)

A station added to the network is a line in stations.ini, and a measurement added to the API is a line in environment.ini and a data element in the data set.

The schedule

The service runs from a daily scheduler job, timed after the source has published the previous day's readings:

Daily environmental data job

See also

FeatureWhat it does
Calling REST APIsQuery parameters and response objects
Config tablesTranslate the codes of one party into another's

Learn more