# Weekly counts

Report the number of results per test and outcome to a DHIS2 data set each week.

This page concludes the [Lab results](https://zato.io/docs/dev/healthcare/dhis2/lab-results/index.html) series. Individual results are written to tracker cases on the preceding pages. Aggregate reporting in DHIS2 is based on data sets, so a weekly job counts the results of the previous week per test and outcome and sends the counts to a data set.

## The data set {#the-data-set}

The data set, its data elements and the organisation unit are identified by code rather than UID, which keeps the service independent of the DHIS2 instance it runs against. The data set and organisation unit codes come from the `weekly` section of `dhis2.ini`. Each data element code is the test and result option codes joined with an underscore and prefixed with `LAB_`, for instance `LAB_COVID19_PCR_POSITIVE`.

## The reporting week {#the-reporting-week}

The job runs on Mondays and reports on the week that has just ended. The service computes the Monday and Sunday of that week and its DHIS2 period identifier:

```python
# -*- coding: utf-8 -*-

# stdlib
from collections import Counter
from datetime import date, timedelta

# Zato
from zato.server.service import Service

class SendWeeklyLabCounts(Service):
    name = 'dhis2.lab.send-weekly-counts'

    def previous_week(self) -> 'tuple[date, date, str]':

        # Any day of the previous week, then its Monday and Sunday
        last_week = date.today() - timedelta(days=7)
        start = last_week - timedelta(days=last_week.weekday())
        end = start + timedelta(days=6)

        year, week, _ = start.isocalendar()
        period = f'{year}W{week}'

        return start, end, period
```

`period` is the DHIS2 weekly period identifier, such as `2026W40` for the week starting on 28 September 2026.

## Read the events {#read-the-events}

The lab result events of the week are read from the tracker, with paging turned off so that one request returns all of them:

```python
    def read_events(self, start:'date', end:'date') -> 'list':

        program = self.config.dhis2.program
        occurred_after = start.isoformat()
        occurred_before = end.isoformat()

        params = {
            'program': program.id,
            'programStage': program.stage_lab_result,
            'occurredAfter': occurred_after,
            'occurredBefore': occurred_before,
            'orgUnitMode': 'ACCESSIBLE',
            'fields': 'dataValues',
            'paging': 'false',
        }

        conn = self.rest['DHIS2 Events']
        response = conn.get(params=params)

        return response.data['events']
```

## Count the events {#count-the-events}

Each event is counted under the data element code of its test and result:

```python
    def count_events(self, events:'list') -> 'Counter':

        element = self.config.dhis2.element
        counts = Counter()

        for event in events:

            # Index the event's data values by data element ..
            values = {}
            for item in event['dataValues']:
                data_element = item['dataElement']
                values[data_element] = item['value']

            # .. and count the event under its test and result.
            test = values[element.test]
            result = values[element.result]
            counts[f'LAB_{test}_{result}'] += 1

        return counts
```

## Send the counts {#send-the-counts}

The counts are posted as one data value set for the period:

```python
    def handle(self) -> 'None':

        weekly = self.config.dhis2.weekly
        start, end, period = self.previous_week()

        events = self.read_events(start, end)
        counts = self.count_events(events)

        # DHIS2 takes every value as a string, counts included.
        data_values = []
        for code, count in counts.items():
            value = str(count)
            data_values.append({'dataElement': code, 'value': value})

        payload = {
            'dataSet': weekly.data_set,
            'period': period,
            'orgUnit': weekly.org_unit,
            'dataValues': data_values,
        }

        params = {
            'dataSetIdScheme': 'code',
            'dataElementIdScheme': 'code',
            'orgUnitIdScheme': 'code',
        }

        conn = self.rest['DHIS2 Data Values']
        conn.post(payload, params)
```

The `IdScheme` parameters tell DHIS2 that the data set, data elements and organisation unit are given by code. Posting the same period again replaces the values of that period, so the job can be re-run after a correction.

## The import summary {#the-import-summary}

DHIS2 returns the counts of imported, updated and ignored values under `response.importCount`. A value DHIS2 cannot accept is ignored and listed under `response.conflicts` with the reason, while the other values are imported. A data element code that is not part of the data set makes DHIS2 reject the whole set with HTTP 409:

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

## The schedule {#the-schedule}

The service runs from a [scheduler](https://zato.io/docs/dev/examples/scheduler.html) job with `dhis2.lab.send-weekly-counts` as the service and an interval of one week, starting on a Monday.

![Weekly lab counts job](https://zatosource-production.b-cdn.net/docs/gfx/dhis2/lab-results-weekly-job-create.webp?v=1791212092)

Dashboard menu: Scheduler > Config

## See also {#see-also}

- [Setup](https://zato.io/docs/dev/healthcare/dhis2/lab-results/setup.html) - The DHIS2 Data Values connection and the weekly section of dhis2.ini
- [Write a result](https://zato.io/docs/dev/healthcare/dhis2/lab-results/write.html) - The events this service counts
- [Scheduler](https://zato.io/docs/dev/examples/scheduler.html) - Interval-based jobs

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
