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 #
| Attribute | Type | Description | Required |
|---|---|---|---|
| errors | list[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:
| Field | Type | Description |
|---|---|---|
| message | string | The 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 #
| Event | Behavior |
|---|---|
NOTE_STATE_CHANGE_EVENT_PRE_CREATE | Blocks the note state change (e.g. lock, push charges) and displays errors in the UI. |
APPOINTMENT__FORM__UPDATED | Disables the Book button on the appointment scheduling modal and displays errors as a tooltip. |
Implementation Details #
- If an
EventValidationErroris 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
EventValidationErrorare still applied, even though the event itself is blocked. In the example above, theAddBannerAlerteffect is added and persists even when the note state change is rejected.