# Results from FHIR

Poll a FHIR server for new Observations and write each one to DHIS2.

This page follows [Results over HL7 v2](https://zato.io/docs/dev/healthcare/dhis2/lab-results/hl7.html) in the [Lab results](https://zato.io/docs/dev/healthcare/dhis2/lab-results/index.html) series. Laboratories that expose a FHIR server publish each result as an Observation resource. Zato reads new Observations at an interval, resolves the Specimen each one refers to, and invokes `dhis2.lab.write-result` for each result.

## The connection {#the-connection}

The laboratory's FHIR server is a [FHIR outgoing connection](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) named `Lab FHIR`. The service reaches it as `self.fhir['Lab FHIR']`.

[Video: Laboratory FHIR server](https://zatosource-production.b-cdn.net/docs/gfx/dhis2/lab-results-fhir-create.webm?v=1791212154)

Dashboard menu: Connections > Outgoing > HL7 > FHIR

## The marker {#the-marker}

Each run reads the Observations updated since the previous run. The time of the last Observation processed is kept in the [cache](https://zato.io/docs/dev/examples/cache.html) under a key from `dhis2.ini`:

```ini
[fhir]
marker_key = dhis2.lab.fhir.since
```

The key is created once, before the first run, with the date from which results are to be read.

## Build the request {#build-the-request}

The test code is the first coding of the Observation's `code`, the result code is the first coding of its `valueCodeableConcept`, and the specimen ID is the `accessionIdentifier` of the Specimen the Observation refers to. Nested values are read with [path access](https://zato.io/docs/dev/healthcare/hl7/fhir/path-access/index.html):

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

# Zato
from zato.server.service import Service

class ReadLabResultsFHIR(Service):
    name = 'dhis2.lab.from-fhir'

    def build_request(self, observation:'dict') -> 'dict':

        loinc_code = observation.get_by_path('code.coding.0.code')
        snomed_code = observation.get_by_path('valueCodeableConcept.coding.0.code')

        test = self.config.lab_tests.translate(source='LOINC', code=loinc_code)
        result = self.config.lab_results.translate(source='SNOMED', code=snomed_code)

        # The specimen ID is the accession identifier of the referenced Specimen
        specimen_reference = observation.get_by_path('specimen.reference')
        client = self.fhir['Lab FHIR']
        specimen = client.get(specimen_reference)
        specimen_id = specimen.get_by_path('accessionIdentifier.value')

        request = {
            'specimen_id': specimen_id,
            'test': test,
            'result': result,
            'result_date': observation['effectiveDateTime'],
        }

        return request
```

## Read the results {#read-the-results}

The service searches for final Observations updated after the marker, writes each one to its case and advances the marker after each result:

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

        client = self.fhir['Lab FHIR']
        marker_key = self.config.dhis2.fhir.marker_key

        # Read the Observations finalised since the previous run ..
        since = self.cache.get(marker_key)
        since = since.decode()

        observations = client.resources('Observation')
        observations = observations.search(_lastUpdated__gt=since, status='final')
        observations = observations.fetch_all()

        # .. write each one to its case and move the marker past it.
        for observation in observations:

            request = self.build_request(observation)
            self.invoke('dhis2.lab.write-result', request)

            last_updated = observation.get_by_path('meta.lastUpdated')
            self.cache.set(marker_key, last_updated)
```

`fetch_all` follows the `next` links of the search Bundle, so a run that finds more results than one page holds processes all of them. An Observation whose codes have no mapping in the config tables reaches DHIS2 with empty values and is rejected by the tracker importer, which the import summary reports as described in [Write a result](https://zato.io/docs/dev/healthcare/dhis2/lab-results/write.html#the-import-summary).

## The schedule {#the-schedule}

The service runs from a [scheduler](https://zato.io/docs/dev/examples/scheduler.html) job, created in the Dashboard under **Scheduler** with `dhis2.lab.from-fhir` as the service and an interval of five minutes.

![Job reading FHIR results](https://zatosource-production.b-cdn.net/docs/gfx/dhis2/lab-results-fhir-job-create.webp?v=1791212096)

Dashboard menu: Scheduler > Config

## Next {#next}

[Held results](https://zato.io/docs/dev/healthcare/dhis2/lab-results/held.html) covers results that arrive before the case they belong to has been registered in DHIS2.

## See also {#see-also}

- [FHIR connections](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) - Outgoing connections to FHIR servers
- [Path access](https://zato.io/docs/dev/healthcare/hl7/fhir/path-access/index.html) - Reading nested values with get\_by\_path
- [Scheduler](https://zato.io/docs/dev/examples/scheduler.html) - Interval-based jobs
- [Cache](https://zato.io/docs/dev/examples/cache.html) - Keeping state between runs

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