# Thresholds

Check the week just ended against an alert rule per disease and raise an alert for each district that crosses it.

This page follows [Weekly data](https://zato.io/docs/dev/healthcare/dhis2/outbreak-alerts/weekly-data.html) in the [Outbreak alerts](https://zato.io/docs/dev/healthcare/dhis2/outbreak-alerts/index.html) series. Each disease in `alerts.ini` names the rule it is checked with. A rule is a Python function that takes the twelve weekly counts of one district and the disease's configuration, and says whether the week just ended is an alert.

## The rules {#the-rules}

A disease for which a single case is an alert, such as cholera, is checked with the simplest rule:

```python
# stdlib
from datetime import date
from statistics import mean, stdev

# Zato
from zato.common.typing_ import any_

def is_single_case(counts:'list', disease:'any_') -> 'bool':

    # One case in the week just ended is enough.
    this_week = counts[-1]

    out = this_week > 0
    return out
```

A disease that is always present, such as malaria, alerts when the week is well above the weeks before it. The threshold is the mean of the earlier weeks plus a number of standard deviations, and a minimum count keeps a district with very few cases from alerting on small changes:

```python
def is_above_baseline(counts:'list', disease:'any_') -> 'bool':

    # The weeks before the one just ended are the baseline ..
    this_week = counts[-1]
    start = -1 - disease.baseline_weeks
    baseline = counts[start:-1]

    # .. the threshold is their mean plus a number of standard deviations ..
    baseline_mean = mean(baseline)
    baseline_deviation = stdev(baseline)
    spread = disease.deviations * baseline_deviation
    threshold = baseline_mean + spread

    # .. and the week is an alert when it is above both the threshold and the minimum.
    if this_week < disease.minimum:
        out = False
    else:
        out = this_week > threshold

    return out

_rules = {
    'single_case': is_single_case,
    'baseline': is_above_baseline,
}
```

`baseline_weeks` can be up to eleven, which is how many weeks the request returns before the one just ended. A new rule is a function of the same shape, an entry in `_rules`, and its name in the section of each disease that uses it.

## Check every district {#check-every-district}

Each series is keyed by its data element, and the diseases in `alerts.ini` say which disease each data element reports:

```python
    def diseases_by_element(self) -> 'dict':

        diseases = self.config.alerts.diseases
        out = {}

        for name, disease in diseases.items():
            data_element = disease.data_element
            out[data_element] = name

        return out
```

An alert names the disease, the district and the week, which is the last of the twelve, with the number of cases and the date on which the alert was detected:

```python
    def new_alert(self, name:'str', district:'str', counts:'list', week:'str') -> 'dict':

        district_config = self.config.districts[district]
        this_week = counts[-1]
        cases = int(this_week)

        today = date.today()
        detected = today.isoformat()

        alert = {
            'disease': name,
            'district': district,
            'district_name': district_config.name,
            'week': week,
            'cases': cases,
            'detected': detected,
        }

        return alert
```

Each series is checked with the rule of its disease:

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

        diseases = self.config.alerts.diseases
        by_element = self.diseases_by_element()
        weeks, series = self.read_series()
        week = weeks[-1]

        for (data_element, district_id), counts in series.items():

            # Check the series with the rule of its disease ..
            name = by_element[data_element]
            disease = diseases[name]
            rule = _rules[disease.rule]

            # .. and raise an alert for its district if the rule says so.
            if rule(counts, disease):
                alert = self.new_alert(name, district_id, counts, week)
                self.raise_alert(alert)
```

## Raise the alert {#raise-the-alert}

Each alert goes to three services. The district is notified first, because the date of the notification is one of the dates the 7-1-7 register keeps. The service that records the alert is the one `alerts.ini` names, which is how the same code serves DHIS2 and an EMS:

```python
    def raise_alert(self, alert:'dict') -> 'None':

        # Notify the district ..
        response = self.invoke('dhis2.alerts.notify-district', alert)
        alert['notified'] = response['notified']

        # .. record the alert where the configuration says ..
        record_service = self.config.alerts.record.service
        self.invoke(record_service, alert)

        # .. and keep its dates in the 7-1-7 register.
        self.invoke('dhis2.alerts.update-register', alert)
```

## Next {#next}

[District notifications](https://zato.io/docs/dev/healthcare/dhis2/outbreak-alerts/notify.html) sends the alert to the district by email and SMS.

## See also {#see-also}

- [Weekly data](https://zato.io/docs/dev/healthcare/dhis2/outbreak-alerts/weekly-data.html) - The series the rules check
- [Config files](https://zato.io/docs/dev/examples/config.html) - Nested sections and values read through self.config

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