# 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](https://zato.io/docs/dev/healthcare/dhis2/one-health/index.html) 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-source}

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

```json
{
  "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](https://zato.io/docs/dev/examples/config-tables.html) with the org unit of each station, and `environment.ini` has the data element each measurement is recorded under:

```ini
# config/user-conf/stations.ini

[MET_SERVICE]
ST-0412 = O6uvpzGd5pu
ST-0415 = fdc6uOvgoji
```

```ini
# config/user-conf/environment.ini

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

## Build the data values {#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:

```python
# -*- 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 {#post-the-day}

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

```python
    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-schedule}

The service runs from a daily [scheduler](https://zato.io/docs/dev/examples/scheduler.html) job, timed after the source has published the previous day's readings:

![Daily environmental data job](https://zatosource-production.b-cdn.net/docs/gfx/dhis2/one-health-environment-job-create.webp?v=1791212104)

Dashboard menu: Scheduler > Config

## See also {#see-also}

- [Calling REST APIs](https://zato.io/docs/dev/rest/calling-apis.html) - Query parameters and response objects
- [Config tables](https://zato.io/docs/dev/examples/config-tables.html) - Translate the codes of one party into another's

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