Banner Alerts

The workflow kit allows the user to place Banners on the canvas UI.

You can customize two things with banners: Placement and Intent

AlertPlacement #

from canvas_workflow_kit.constants import AlertPlacement

The placement of an alert on the Canvas Interface.

Alert PlacementDescription
AlertPlacement.ALERT_PLACEMENT_CHARTThis will place the banner under the patient’s name on their chart
AlertPlacement.ALERT_PLACEMENT_TIMELINEThis will place the banner on the top of the patient’s timeline of notes in their chart
AlertPlacement.ALERT_PLACEMENT_APPOINTMENT_CARDThis will appear when you click an appointment on the calendar view
AlertPlacement.ALERT_PLACEMENT_SCHEDULING_CARDThis will appear when you select a patient during the scheduling of an appointment on the calendar view
AlertPlacement.ALERT_PLACEMENT_PROFILEThis will place the banner under the patient’s name on their patient registration page

AlertIntent #

from canvas_workflow_kit.constants import AlertIntent

The type or severity of an alert. This will change how the Banner alert looks

ALERT_INTENT_INFO
ALERT_INTENT_WARNING
ALERT_INTENT_ALERT

Example #

This protocol displays banners in several locations throughout Canvas. So if the patient is over 70 and has a certain number of contacts listed as emergency, authorized for release of information, and power of attorney. The following scenarios will be followed for patients > 70 years old

  • exactly 1 contact designated as emergency, authorized for release, or power of attorney: a banner with info styling is displayed above the timeline and on appointment cards with the contact’s name.
  • no contact designated as emergency, authorized for release, or power of attorney: a banner with alert styling is displayed in the profile and chart, as well as in appointment and scheduling cards (with links to take the user right to patient registration to update the patient’s contacts).
  • more than one contact designated as emergency, authorized for release, or power of attorney: a banner with warning styling is displayed in the profile indicating the number of contacts, and asking the user to reduce to just 1 contact

Here is the code:

import math

from canvas_workflow_kit import events
from canvas_workflow_kit.constants import CHANGE_TYPE
from canvas_workflow_kit.intervention import BannerAlertIntervention
from canvas_workflow_kit.protocol import STATUS_DUE, ClinicalQualityMeasure, ProtocolResult


class BannerAlertContacts(ClinicalQualityMeasure):
    """
    A protocol that displays banner alerts for patients over the age of 70
    without a single emergency contact, a single contact authorized for release of information,
    and a single Power of Attorney contact
    """

    class Meta:

        title = 'Banner Alert Contacts'

        version = 'v1.0.0'

        description = 'Reminders about patients over the age of 70'

        information = 'https://canvasmedical.com/'

        identifiers = ['BannerAlertContacts']

        types = ['Alerts']

        responds_to_event_types = [
            events.HEALTH_MAINTENANCE,
        ]
        compute_on_change_types = [CHANGE_TYPE.PATIENT]

        authors = ['Canvas Medical']

        references = ['Canvas Medical']

        funding_source = ''

    rounded_patient_age = None

    def get_contact_display(self, contact):
        display = contact['name']
        relationship = contact['relationship']
        if relationship and relationship != '':
            display += f' ({relationship})'
        return display

    def in_denominator(self):
        """
        Patients over the age of 70.

        """
        rounded_patient_age = math.floor(self.patient.age)
        self.rounded_patient_age = rounded_patient_age
        return rounded_patient_age >= 70

    def has_contact_category(self, categories, category):
        return next((cat for cat in categories if cat['category'] == category),
                    None) is not None

    def compute_results(self):
        result = ProtocolResult()
        if self.in_denominator():
            result.status = STATUS_DUE
            result.due_in = -1

            emergency_contacts = []
            release_of_info_contacts = []
            poa_contacts = []
            for contact in self.patient.patient.get('contacts', []):
                categories = contact.get('categories', [])
                if self.has_contact_category(categories, 'EMC'):
                    emergency_contacts.append(contact)
                if self.has_contact_category(categories, 'ARI'):
                    release_of_info_contacts.append(contact)
                if self.has_contact_category(categories, 'POA'):
                    poa_contacts.append(contact)

            num_emergency_contacts = len(emergency_contacts)
            num_release_contacts = len(release_of_info_contacts)
            num_poa_contacts = len(poa_contacts)

            if num_emergency_contacts == 1:
                emergency_contact_display = self.get_contact_display(
                    emergency_contacts[0])
                result.recommendations.append(
                    BannerAlertIntervention(
                        narrative=(
                            f'{self.patient.first_name} has 1 '
                            f'emergency contact: {emergency_contact_display}'),
                        placement=['timeline', 'appointment_card'],
                        intent='info'))
            if num_release_contacts == 1:
                release_contact_display = self.get_contact_display(
                    release_of_info_contacts[0])
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} has 1 '
                        f'contact authorized for release of info: {release_contact_display}'
                    ),
                                            placement=[
                                                'timeline', 'appointment_card'
                                            ],
                                            intent='info'))
            if num_poa_contacts == 1:
                poa_contact_display = self.get_contact_display(poa_contacts[0])
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} has 1 '
                        f'contact with Power of Attorney: {poa_contact_display}'
                    ),
                                            placement=[
                                                'timeline', 'appointment_card'
                                            ],
                                            intent='info'))

            if num_emergency_contacts > 1:
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} has {num_emergency_contacts} '
                        'emergency contacts listed. Please reduce to 1.'),
                                            placement=['profile'],
                                            intent='warning'))
            if num_release_contacts > 1:
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} has {num_release_contacts} '
                        'contacts authorized for release of info. Please reduce to 1.'
                    ),
                                            placement=['profile'],
                                            intent='warning'))
            if num_poa_contacts > 1:
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} has {num_poa_contacts} '
                        'contacts with Power of Attorney. Please reduce to 1.'
                    ),
                                            placement=['profile'],
                                            intent='warning'))

            if num_emergency_contacts == 0:
                result.recommendations.append(
                    BannerAlertIntervention(
                        narrative=
                        (f'{self.patient.first_name} is {self.rounded_patient_age} '
                         'and has no emergency contacts listed'),
                        placement=['appointment_card', 'scheduling_card'],
                        intent='alert',
                        href=
                        f'https://{self.settings.INSTANCE_NAME}.canvasmedical.com/patient/{self.patient.patient_key}/edit#'
                    ))
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} is {self.rounded_patient_age} '
                        'and has no emergency contacts listed'),
                                            placement=['profile', 'chart'],
                                            intent='alert'))
            if num_release_contacts == 0:
                result.recommendations.append(
                    BannerAlertIntervention(
                        narrative=
                        (f'{self.patient.first_name} is {self.rounded_patient_age} '
                         'and has no contacts authorized for release of info'),
                        placement=['appointment_card', 'scheduling_card'],
                        intent='alert',
                        href=
                        f'https://{self.settings.INSTANCE_NAME}.canvasmedical.com/patient/{self.patient.patient_key}/edit#'
                    ))
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} is {self.rounded_patient_age} '
                        'and has no contacts authorized for release of info'),
                                            placement=['profile', 'chart'],
                                            intent='alert'))
            if num_poa_contacts == 0:
                result.recommendations.append(
                    BannerAlertIntervention(
                        narrative=
                        (f'{self.patient.first_name} is {self.rounded_patient_age} '
                         'and has no contacts with Power of Attorney'),
                        placement=['appointment_card', 'scheduling_card'],
                        intent='alert',
                        href=
                        f'https://{self.settings.INSTANCE_NAME}.canvasmedical.com/patient/{self.patient.patient_key}/edit#'
                    ))
                result.recommendations.append(
                    BannerAlertIntervention(narrative=(
                        f'{self.patient.first_name} is {self.rounded_patient_age} '
                        'and has no contacts with Power of Attorney'),
                                            placement=['profile', 'chart'],
                                            intent='alert'))
        return result


After uploading, go to any patient that is >= 70 years old, or find any test patient and update their birth date to make them older than 70. If the patient has no contacts you should see the following banners on the chart (using AlertPlacement.ALERT_PLACEMENT_CHART) :

And the same ones in the profile (using AlertPlacement.ALERT_PLACEMENT_PROFILE):

If you go to the schedule and book an appointment for this patient, you should see the same alerts in the scheduling card (using AlertPlacement.ALERT_PLACEMENT_SCHEDULING_CARD), but with hover styling. Try clicking on the banner, and it should take you to the patient’s registration in another tab.

After booking the appointment, when you click on the appointment slot on the schedule, you should see the same alerts, also with clickable links (using AlertPlacement.ALERT_PLACEMENT_APPOINTMENT_CARD).

Now if you add contact(s) to the patient’s profile and check on Contact in case of emergency and Authorized for release of information each exactly 1 time throughout the contact list, the alert banners should go away, and there should be just be info banners displayed in the appointment cards on the schedule and above the timeline like so:

If you check one of those options on for more than 1 contact, you should see a warning banner in the profile related to the number of contacts exceeding 1.

This example is intended to show all the different placements that banners can be displayed, as well as the different intents they can use. Take note of the placement and intent parameters passed to the recommendation for each scenario to ensure they make sense, and then apply them to your own use case!