Zoonosis counts

Carry last week's counts of zoonoses from animal health analytics into the public health surveillance data set.

This page follows Zoonotic notifications in the One Health series. Once a week, the service below reads last week's counts of zoonoses per veterinary unit from animal health, adds them up per health district and posts them to the public health surveillance data set.

The data elements

The data elements differ between the instances, and data_elements.ini maps them the way org_units.ini maps the org units. The level of the veterinary units in the animal health hierarchy goes to one_health.ini:

# config/user-conf/data_elements.ini

[ANIMAL_HEALTH]
Fw3kR9pLx2M = RABIES_ANIMAL
Gt8sV1cNz5Q = ANTHRAX_ANIMAL

[PUBLIC_HEALTH]
Jd6yH2bWq7K = RABIES_ANIMAL
Lp4mT9xEr3C = ANTHRAX_ANIMAL
# config/user-conf/one_health.ini

[counts]
unit_level = 3

Read the counts

Analytics returns one row per data element and veterinary unit, as data element, period, org unit and value:

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

# stdlib
from collections import defaultdict

# Zato
from zato.server.service import Service

class CarryCounts(Service):
    name = 'dhis2.one-health.carry-counts'

    def read_counts(self) -> 'list':

        # Every data element the file maps ..
        animal_elements = self.config.data_elements.ANIMAL_HEALTH
        element_ids = animal_elements.keys()
        dx = ';'.join(element_ids)

        # .. for last week and each veterinary unit.
        level = self.config.one_health.counts.unit_level
        dimensions = [f'dx:{dx}', 'pe:LAST_WEEK', f'ou:LEVEL-{level}']
        params = {'dimension': dimensions}

        conn = self.rest['Animal Health Analytics']
        response = conn.get(params=params)

        out = response.data['rows']
        return out

Sum per district

Several veterinary units can belong to one district, so the counts are added up per district before they are sent. Each one sent separately would overwrite the one before it:

    def sum_by_district(self, rows:'list') -> 'dict':

        data_elements = self.config.data_elements
        org_units = self.config.org_units
        out = defaultdict(int)

        for animal_element, period, uid, value in rows:

            element = data_elements.translate(
                source='ANIMAL_HEALTH', code=animal_element, target='PUBLIC_HEALTH')
            org_unit = org_units.translate(
                source='ANIMAL_HEALTH', code=uid, target='PUBLIC_HEALTH')

            # A unit no district is mapped to is logged and left out ..
            if org_unit is None:
                self.logger.warning('No district for unit %s', uid)
                continue

            # .. and the rest add up per data element, week and district.
            key = element, period, org_unit
            count = int(value)
            out[key] += count

        return out

Post the totals

The totals are posted as one data value set, each value as a string:

    def handle(self) -> 'None':

        rows = self.read_counts()
        totals = self.sum_by_district(rows)

        data_values = []
        for (data_element, period, org_unit), total in totals.items():
            value = str(total)
            data_value = {
                'dataElement': data_element,
                'period': period,
                'orgUnit': org_unit,
                'value': value,
            }
            data_values.append(data_value)

        payload = {'dataValues': data_values}

        conn = self.rest['Public Health Data Values']
        response = conn.post(payload)

        import_summary = response.data['response']
        for conflict in import_summary['conflicts']:
            self.logger.warning('DHIS2 conflict: %s', conflict['value'])

The period identifier comes from the analytics rows, so a week posted to public health is the week animal health reported it under. Analytics leaves out veterinary units with no cases, so a district with none has no value posted for the week. Data elements with a category combination other than the default also need a categoryOptionCombo in each data value.

The schedule

The service runs from a weekly scheduler job, timed after the reporting deadline of the week and after the animal health instance has updated its analytics tables:

Weekly zoonosis counts job

Next

Antimicrobial resistance loads the WHONET exports of each laboratory.

See also

FeatureWhat it does
Weekly dataAnalytics requests and DHIS2 periods
Config tablesTranslate the codes of one party into another's

Learn more