# Write a result

Find the case a specimen belongs to and write the result event to it.

This page follows [Setup](https://zato.io/docs/dev/healthcare/dhis2/lab-results/setup.html) in the [Lab results](https://zato.io/docs/dev/healthcare/dhis2/lab-results/index.html) series. It describes the service that writes a result to DHIS2. The services on the [HL7 v2](https://zato.io/docs/dev/healthcare/dhis2/lab-results/hl7.html) and [FHIR](https://zato.io/docs/dev/healthcare/dhis2/lab-results/fhir.html) pages invoke it after translating the laboratory's codes to DHIS2 option codes.

The service accepts four values: the specimen ID, the test and result as DHIS2 option codes, and the result date in ISO 8601 format.

## Find the case {#find-the-case}

The specimen ID is stored on the lab request event, so the lookup queries the lab request stage of the program and filters on the specimen ID data element. The enrollment and organisation unit of the matching event identify the case:

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

# Zato
from zato.server.service import Service

class WriteLabResult(Service):
    name = 'dhis2.lab.write-result'
    input = 'specimen_id', 'test', 'result', 'result_date'

    def find_case(self, specimen_id:'str') -> 'dict | None':
        program = self.config.dhis2.program
        element = self.config.dhis2.element

        # Query the lab request stage for the event that carries this specimen ID ..
        params = {
            'program': program.id,
            'programStage': program.stage_lab_request,
            'filter': f'{element.specimen_id}:eq:{specimen_id}',
            'orgUnitMode': 'ACCESSIBLE',
            'fields': 'enrollment,orgUnit',
        }

        response = self.rest['DHIS2 Events'].get(params=params)
        found = response.data['events']

        # .. there is no case for this specimen if nothing matched.
        if not found:
            return None
        else:
            return found[0]
```

`orgUnitMode=ACCESSIBLE` searches every organisation unit the API user has access to, so the service does not need to know where the case was registered. DHIS2 2.41 and later return the matching events under `events`. DHIS2 2.40 returns them under `instances`.

## Build the event {#build-the-event}

The result is one event on the lab result stage of the enrollment the case belongs to. The specimen ID is written to the result event as well, so that a corrected result can be matched to the event it replaces:

```python
    def build_event(self, case:'dict') -> 'dict':

        request = self.request.input
        program = self.config.dhis2.program
        element = self.config.dhis2.element

        data_values = [
            {'dataElement': element.specimen_id, 'value': request.specimen_id},
            {'dataElement': element.test, 'value': request.test},
            {'dataElement': element.result, 'value': request.result},
        ]

        event = {
            'program': program.id,
            'programStage': program.stage_lab_result,
            'enrollment': case['enrollment'],
            'orgUnit': case['orgUnit'],
            'status': 'COMPLETED',
            'occurredAt': request.result_date,
            'dataValues': data_values,
        }

        return event
```

## Write the event {#write-the-event}

The event is posted to the tracker importer and the import status is returned to the caller:

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

        request = self.request.input
        case = self.find_case(request.specimen_id)

        if case is None:
            self.response.payload = {'is_found': False}
            return

        event = self.build_event(case)
        payload = {'events': [event]}
        params = {'async': 'false', 'importStrategy': 'CREATE_AND_UPDATE'}

        conn = self.rest['DHIS2 Tracker']
        response = conn.post(payload, params)

        status = response.data['status']
        self.response.payload = {'is_found': True, 'status': status}
```

`async=false` makes DHIS2 process the request synchronously and return the import summary in the response. `importStrategy=CREATE_AND_UPDATE` creates the event when it is new and updates it when the payload carries the `event` UID of an existing one.

## Correcting a result {#correcting-a-result}

A corrected result updates the event the original was written to. The lookup in `find_case` is repeated with `program.stage_lab_result` in place of `program.stage_lab_request` and `event` added to `fields`. The returned `event` UID is included in the payload under the `event` key, and `CREATE_AND_UPDATE` updates that event instead of creating a new one.

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

DHIS2 returns the import summary with HTTP 200 when the event was accepted and HTTP 409 when it was rejected. The `status` field is `OK` or `ERROR`, `stats` counts what was created, updated and ignored, and `validationReport.errorReports` lists the reasons for a rejection:

```python
if not response.ok:
    validation_report = response.data['validationReport']
    for report in validation_report['errorReports']:
        self.logger.warning('DHIS2 rejected the result: %s', report['message'])
```

The most frequent rejections are an option code that is not in the option set of the data element, a date in the future, and an enrollment the API user cannot write to.

## Next {#next}

[Results over HL7 v2](https://zato.io/docs/dev/healthcare/dhis2/lab-results/hl7.html) receives ORU messages from a laboratory information system and invokes this service for each one.

## See also {#see-also}

- [Setup](https://zato.io/docs/dev/healthcare/dhis2/lab-results/setup.html) - The connections and configuration files this service reads
- [Calling REST APIs](https://zato.io/docs/dev/rest/calling-apis.html) - Query parameters, payloads and response objects
- [Config files](https://zato.io/docs/dev/examples/config.html) - Reading identifiers from config/user-conf

## 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
