diff --git a/canvas_sdk/base.py b/canvas_sdk/base.py index a97152d9..35ceb73f 100644 --- a/canvas_sdk/base.py +++ b/canvas_sdk/base.py @@ -34,15 +34,18 @@ def _get_error_details(self, method: Any) -> list[InitErrorDetails]: class_name = self.__repr_name__() # type: ignore[misc] class_name_article = "an" if class_name.startswith(("A", "E", "I", "O", "U")) else "a" - return [ - self._create_error_detail( - "missing", - f"Field '{field}' is required to {method.replace('_', ' ')} {class_name_article} {class_name}", - v, - ) - for field in required_fields - if (v := getattr(self, field)) is None - ] + + error_details = [] + for field in required_fields: + fields = field.split("|") + if not all(getattr(self, f) is None for f in fields): + continue + field_description = " or ".join([f"'{f}'" for f in fields]) + message = f"Field {field_description} is required to {method.replace('_', ' ')} {class_name_article} {class_name}" + error = self._create_error_detail("missing", message, None) + error_details.append(error) + + return error_details def _validate_before_effect(self, method: str) -> None: self.model_validate(self) diff --git a/canvas_sdk/effects/banner_alert/add_banner_alert.py b/canvas_sdk/effects/banner_alert/add_banner_alert.py index 95502321..949bd95d 100644 --- a/canvas_sdk/effects/banner_alert/add_banner_alert.py +++ b/canvas_sdk/effects/banner_alert/add_banner_alert.py @@ -13,7 +13,13 @@ class AddBannerAlert(_BaseEffect): class Meta: effect_type = EffectType.ADD_BANNER_ALERT - apply_required_fields = ("patient_id", "key", "narrative", "placement", "intent") + apply_required_fields = ( + "patient_id|patient_filter", + "key", + "narrative", + "placement", + "intent", + ) class Placement(Enum): CHART = "chart" @@ -47,4 +53,9 @@ def values(self) -> dict[str, Any]: @property def effect_payload(self) -> dict[str, Any]: """The payload of the effect.""" - return {"patient": self.patient_id, "key": self.key, "data": self.values} + return { + "patient": self.patient_id, + "patient_filter": self.patient_filter, + "key": self.key, + "data": self.values, + } diff --git a/canvas_sdk/effects/banner_alert/remove_banner_alert.py b/canvas_sdk/effects/banner_alert/remove_banner_alert.py index 1331c145..22b0fb02 100644 --- a/canvas_sdk/effects/banner_alert/remove_banner_alert.py +++ b/canvas_sdk/effects/banner_alert/remove_banner_alert.py @@ -10,7 +10,7 @@ class RemoveBannerAlert(_BaseEffect): class Meta: effect_type = EffectType.REMOVE_BANNER_ALERT - apply_required_fields = ("patient_id", "key") + apply_required_fields = ("patient_id|patient_filter", "key") patient_id: str | None = None key: str | None = None @@ -18,4 +18,4 @@ class Meta: @property def effect_payload(self) -> dict[str, Any]: """The payload of the effect.""" - return {"patient": self.patient_id, "key": self.key} + return {"patient": self.patient_id, "patient_filter": self.patient_filter, "key": self.key} diff --git a/canvas_sdk/effects/banner_alert/tests.py b/canvas_sdk/effects/banner_alert/tests.py index d4f84b03..966da14f 100644 --- a/canvas_sdk/effects/banner_alert/tests.py +++ b/canvas_sdk/effects/banner_alert/tests.py @@ -178,12 +178,28 @@ def test_protocol_that_adds_banner_alert( "placement": [AddBannerAlert.Placement.APPOINTMENT_CARD], "intent": AddBannerAlert.Intent.INFO, }, - '{"patient": "uuid", "key": "test-key", "data": {"narrative": "hellooo", "placement": ["appointment_card"], "intent": "info", "href": null}}', + '{"patient": "uuid", "patient_filter": null, "key": "test-key", "data": {"narrative": "hellooo", "placement": ["appointment_card"], "intent": "info", "href": null}}', + ), + ( + AddBannerAlert, + { + "patient_filter": {"active": True}, + "key": "test-key", + "narrative": "hellooo", + "placement": [AddBannerAlert.Placement.APPOINTMENT_CARD], + "intent": AddBannerAlert.Intent.INFO, + }, + '{"patient": null, "patient_filter": {"active": true}, "key": "test-key", "data": {"narrative": "hellooo", "placement": ["appointment_card"], "intent": "info", "href": null}}', ), ( RemoveBannerAlert, {"patient_id": "uuid", "key": "testeroo"}, - '{"patient": "uuid", "key": "testeroo"}', + '{"patient": "uuid", "patient_filter": null, "key": "testeroo"}', + ), + ( + RemoveBannerAlert, + {"patient_filter": {"active": True}, "key": "testeroo"}, + '{"patient": null, "patient_filter": {"active": true}, "key": "testeroo"}', ), ], ) @@ -204,7 +220,7 @@ def test_banner_alert_apply_method_succeeds_with_all_required_fields( AddBannerAlert, [ "5 validation errors for AddBannerAlert", - "Field 'patient_id' is required to apply an AddBannerAlert [type=missing", + "Field 'patient_id' or 'patient_filter' is required to apply an AddBannerAlert [type=missing", "Field 'key' is required to apply an AddBannerAlert [type=missing", "Field 'narrative' is required to apply an AddBannerAlert [type=missing", "Field 'placement' is required to apply an AddBannerAlert [type=missing", @@ -215,7 +231,7 @@ def test_banner_alert_apply_method_succeeds_with_all_required_fields( RemoveBannerAlert, [ "2 validation errors for RemoveBannerAlert", - "Field 'patient_id' is required to apply a RemoveBannerAlert [type=missing", + "Field 'patient_id' or 'patient_filter' is required to apply a RemoveBannerAlert [type=missing", "Field 'key' is required to apply a RemoveBannerAlert [type=missing", ], ), diff --git a/canvas_sdk/effects/base.py b/canvas_sdk/effects/base.py index e67d96bd..30d96f8d 100644 --- a/canvas_sdk/effects/base.py +++ b/canvas_sdk/effects/base.py @@ -10,6 +10,8 @@ class _BaseEffect(Model): A Canvas Effect that changes user behavior or autonomously performs activities on behalf of users. """ + patient_filter: dict | None = None + class Meta: effect_type = EffectType.UNKNOWN_EFFECT diff --git a/canvas_sdk/effects/protocol_card/protocol_card.py b/canvas_sdk/effects/protocol_card/protocol_card.py index 31b0c1ce..7d519b46 100644 --- a/canvas_sdk/effects/protocol_card/protocol_card.py +++ b/canvas_sdk/effects/protocol_card/protocol_card.py @@ -42,7 +42,7 @@ class Status(Enum): class Meta: effect_type = EffectType.ADD_OR_UPDATE_PROTOCOL_CARD - apply_required_fields = ("patient_id", "key") + apply_required_fields = ("patient_id|patient_filter", "key") patient_id: str | None = None key: str | None = None @@ -68,7 +68,12 @@ def values(self) -> dict[str, Any]: @property def effect_payload(self) -> dict[str, Any]: """The payload of the effect.""" - return {"patient": self.patient_id, "key": self.key, "data": self.values} + return { + "patient": self.patient_id, + "patient_filter": self.patient_filter, + "key": self.key, + "data": self.values, + } def add_recommendation( self, diff --git a/canvas_sdk/effects/protocol_card/tests.py b/canvas_sdk/effects/protocol_card/tests.py index 2728abb0..16ca27c4 100644 --- a/canvas_sdk/effects/protocol_card/tests.py +++ b/canvas_sdk/effects/protocol_card/tests.py @@ -25,7 +25,7 @@ def test_apply_method_succeeds_with_patient_id_and_key() -> None: applied = p.apply() assert ( applied.payload - == '{"patient": "uuid", "key": "something-unique", "data": {"title": "", "narrative": "", "recommendations": [], "status": "due", "feedback_enabled": false}}' + == '{"patient": "uuid", "patient_filter": null, "key": "something-unique", "data": {"title": "", "narrative": "", "recommendations": [], "status": "due", "feedback_enabled": false}}' ) @@ -38,7 +38,7 @@ def test_apply_method_raises_error_without_patient_id_and_key() -> None: assert "2 validation errors for ProtocolCard" in err_msg assert ( - "Field 'patient_id' is required to apply a ProtocolCard [type=missing, input_value=None, input_type=NoneType]" + "Field 'patient_id' or 'patient_filter' is required to apply a ProtocolCard [type=missing, input_value=None, input_type=NoneType]" in err_msg ) assert (