# Alarms

Notify the district of heat, freeze, door, power and disconnection alarms, once per alarm rather than once per record.

This page follows [Temperature readings](https://zato.io/docs/dev/healthcare/dhis2/supply-chain/readings.html) in the [Supply chain](https://zato.io/docs/dev/healthcare/dhis2/supply-chain/index.html) series. A record of a PQS E006 report carries an alarm code in `ALRM` while the appliance is in an alarm condition:

| Code | Alarm |
| --- | --- |
| `HEAT` | The temperature has been too high for too long |
| `FRZE` | The temperature has been below freezing for too long |
| `DOOR` | The door or lid has been open for too long |
| `POWR` | The appliance has had no power for more than a day |
| `DCNT` | The monitoring device has been disconnected from the appliance |

An alarm is recorded in every record for as long as it lasts, and once someone at the facility acknowledges it, for example by muting it, the code gains an `ACK` suffix, as in `HEATACK`. The service below notifies the district of a new alarm, and of an alarm that is still on after a set time, rather than of every record.

## The configuration {#the-configuration}

The names of the alarms in messages, the time after which an alarm still on is notified again and the sender of the emails go to `cold_chain.ini`:

```ini
[alarms]
repeat_after_seconds = 21600
sender = cold-chain@example.com

[[names]]
HEAT = Heat
FRZE = Freeze
DOOR = Door open
POWR = Power outage
DCNT = Monitoring device disconnected
```

## The notification {#the-notification}

The message names the alarm and the appliance, and goes to the district of the appliance by email and SMS:

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

# Zato
from zato.common import SMTPMessage
from zato.server.service import Service

class CheckAlarms(Service):
    name = 'dhis2.cold-chain.check-alarms'

    def notify(self, serial:'str', code:'str') -> 'None':

        cold_chain = self.config.cold_chain
        appliance = cold_chain.appliances[serial]
        district_id = appliance.district
        district = self.config.districts[district_id]
        name = cold_chain.alarms.names[code]
        text = f'{name} alarm: {appliance.label}'

        message = SMTPMessage()
        message.subject = text
        message.to = district.email
        message.from_ = cold_chain.alarms.sender
        message.body = text

        smtp = self.email.smtp['District Alerts'].conn
        smtp.send(message)

        sms = {'to': district.phone, 'text': text}
        conn = self.rest['SMS Gateway']
        conn.post(sms)
```

A heat alarm of the first refrigerator in the setup reads `Heat alarm: Health Centre 12 - Vaccine refrigerator 1`.

## Once per alarm {#once-per-alarm}

The cache key of an alarm is made of the appliance and the code, and it expires after `repeat_after_seconds`. Setting it with `nx=True` succeeds only when the key is not there, which is the case for a new alarm and for one still on after the key expired:

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

        report = self.request.payload
        serial = report['ASER']
        records = report['records']
        repeat_after = self.config.cold_chain.alarms.repeat_after_seconds

        for record in records:

            # Most records carry no alarm ..
            code = record.get('ALRM')
            if code is None:
                continue

            # .. one that someone at the facility acknowledged needs no notification ..
            if code.endswith('ACK'):
                continue

            # .. and an alarm still on is notified once per repeat_after_seconds.
            key = f'cold-chain.alarm.{serial}.{code}'
            is_new = self.cache.set(key, '1', ex=repeat_after, nx=True)
            if is_new:
                self.notify(serial, code)
```

The cache is shared by all servers, so an alarm is notified once even when the reports of one appliance reach different servers.

## Next {#next}

[Consignment receipts](https://zato.io/docs/dev/healthcare/dhis2/supply-chain/consignments.html) confirms the consignments facilities acknowledge in DHIS2 to the central store.

## See also {#see-also}

- [Cache](https://zato.io/docs/dev/examples/cache.html) - Set only if absent, with an expiry
- [District notifications](https://zato.io/docs/dev/healthcare/dhis2/outbreak-alerts/notify.html) - The same email and SMS for outbreak alerts

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