EventValidationError Effect

Overview #

The EventValidationError effect is used to block the creation of an event (such as a NoteStateChangeEvent create) when custom validation fails. If this effect is returned by a protocol in response to an event (e.g., NOTE_STATE_CHANGE_EVENT_PRE_CREATE), the event is aborted and the provided error message is surfaced to the user.

Attributes #

AttributeTypeDescriptionRequired
errorslist[ValidationError]List of validation errors to display to the user.Yes

ValidationError dataclass #

Each item in the errors list is a ValidationError dataclass with the following fields:

FieldTypeDescription
messagestringThe error message to display to the user.

Example Usage #

Return an EventValidationError from your protocol’s compute method to block the event and show a message to the user. You can also return other effects alongside EventValidationError.

from canvas_sdk.effects import Effect
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from canvas_sdk.v1.data import Note
from canvas_sdk.v1.data.coverage import CoverageStack
from canvas_sdk.effects.validation import EventValidationError, ValidationError
from canvas_sdk.effects.banner_alert import AddBannerAlert, RemoveBannerAlert


class MyHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_PRE_CREATE)

    def handle_no_coverage(self, patient_id: str, note: Note) -> list[Effect]:
        """If the patient has no coverage, add banner alert and do not allow notes to be locked or charges pushed."""
        if note.patient.coverages.filter(stack=CoverageStack.IN_USE).count() == 0:
            return [
                EventValidationError(
                    errors=[
                        ValidationError(
                            message="Patient has no coverage. Do not send claim to billing department until coverage(s) have been added."
                        )
                    ]
                ).apply(),
                AddBannerAlert(
                    patient_id=patient_id,
                    key="no_coverage",
                    narrative="Patient has no documented coverages.",
                    placement=[
                        AddBannerAlert.Placement.CHART,
                        AddBannerAlert.Placement.APPOINTMENT_CARD,
                    ],
                    intent=AddBannerAlert.Intent.ALERT,
                ).apply(),
            ]
        return [RemoveBannerAlert(patient_id=patient_id, key="no_coverage").apply()]

    def handle_no_billing_line_items(self, note: Note) -> Effect | None:
        """If the note has no billing line items, do not allow notes to be locked or charges pushed."""
        if note.billing_line_items.filter(status="active").count() > 0:
            return None

        val_effect = EventValidationError()
        val_effect.add_error(
            "Cannot lock or push charges for a note with no billing line items."
        )
        return val_effect.apply()

    def compute(self) -> list[Effect]:
        state = self.event.context["state"]
        if state not in ["PSH", "LKD"]:
            return []
        note = Note.objects.get(id=self.event.context["note_id"])
        patient_id = str(note.patient.id)
        effects = self.handle_no_coverage(patient_id=patient_id, note=note)
        if v := self.handle_no_billing_line_items(note=note):
            effects.append(v)

        return effects

Supported Events #

EventBehavior
NOTE_STATE_CHANGE_EVENT_PRE_CREATEBlocks the note state change (e.g. lock, push charges) and displays errors in the UI.
APPOINTMENT__FORM__UPDATEDDisables the Book button on the appointment scheduling modal and displays errors as a tooltip.

Implementation Details #

  • If an EventValidationError is returned, the event is aborted and the error message is shown in the UI (if initiated from the UI).
  • This effect is typically used for pre-create validation of events, such as note state changes or appointment scheduling.
  • Any other effects returned alongside an EventValidationError are still applied, even though the event itself is blocked. In the example above, the AddBannerAlert effect is added and persists even when the note state change is rejected.