canvas 0.63.0__py3-none-any.whl → 0.89.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/METADATA +4 -1
  2. {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/RECORD +184 -98
  3. {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/WHEEL +1 -1
  4. canvas_cli/apps/emit/event_fixtures/UNKNOWN.ndjson +1 -0
  5. canvas_cli/apps/logs/logs.py +386 -22
  6. canvas_cli/main.py +3 -1
  7. canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py +46 -4
  8. canvas_cli/utils/context/context.py +13 -13
  9. canvas_cli/utils/validators/manifest_schema.py +26 -1
  10. canvas_generated/messages/effects_pb2.py +5 -5
  11. canvas_generated/messages/effects_pb2.pyi +108 -2
  12. canvas_generated/messages/events_pb2.py +6 -6
  13. canvas_generated/messages/events_pb2.pyi +282 -2
  14. canvas_sdk/clients/__init__.py +1 -0
  15. canvas_sdk/clients/llms/__init__.py +17 -0
  16. canvas_sdk/clients/llms/libraries/__init__.py +11 -0
  17. canvas_sdk/clients/llms/libraries/llm_anthropic.py +87 -0
  18. canvas_sdk/clients/llms/libraries/llm_api.py +143 -0
  19. canvas_sdk/clients/llms/libraries/llm_google.py +92 -0
  20. canvas_sdk/clients/llms/libraries/llm_openai.py +98 -0
  21. canvas_sdk/clients/llms/structures/__init__.py +9 -0
  22. canvas_sdk/clients/llms/structures/llm_response.py +33 -0
  23. canvas_sdk/clients/llms/structures/llm_tokens.py +53 -0
  24. canvas_sdk/clients/llms/structures/llm_turn.py +47 -0
  25. canvas_sdk/clients/llms/structures/settings/__init__.py +13 -0
  26. canvas_sdk/clients/llms/structures/settings/llm_settings.py +27 -0
  27. canvas_sdk/clients/llms/structures/settings/llm_settings_anthropic.py +43 -0
  28. canvas_sdk/clients/llms/structures/settings/llm_settings_gemini.py +40 -0
  29. canvas_sdk/clients/llms/structures/settings/llm_settings_gpt4.py +40 -0
  30. canvas_sdk/clients/llms/structures/settings/llm_settings_gpt5.py +48 -0
  31. canvas_sdk/clients/third_party.py +3 -0
  32. canvas_sdk/commands/__init__.py +12 -0
  33. canvas_sdk/commands/base.py +33 -2
  34. canvas_sdk/commands/commands/adjust_prescription.py +4 -0
  35. canvas_sdk/commands/commands/custom_command.py +86 -0
  36. canvas_sdk/commands/commands/family_history.py +17 -1
  37. canvas_sdk/commands/commands/immunization_statement.py +42 -2
  38. canvas_sdk/commands/commands/medication_statement.py +16 -1
  39. canvas_sdk/commands/commands/past_surgical_history.py +16 -1
  40. canvas_sdk/commands/commands/perform.py +18 -1
  41. canvas_sdk/commands/commands/prescribe.py +8 -9
  42. canvas_sdk/commands/commands/refill.py +5 -5
  43. canvas_sdk/commands/commands/resolve_condition.py +5 -5
  44. canvas_sdk/commands/commands/review/__init__.py +3 -0
  45. canvas_sdk/commands/commands/review/base.py +72 -0
  46. canvas_sdk/commands/commands/review/imaging.py +13 -0
  47. canvas_sdk/commands/commands/review/lab.py +13 -0
  48. canvas_sdk/commands/commands/review/referral.py +13 -0
  49. canvas_sdk/commands/commands/review/uncategorized_document.py +13 -0
  50. canvas_sdk/commands/validation.py +43 -0
  51. canvas_sdk/effects/batch_originate.py +22 -0
  52. canvas_sdk/effects/calendar/__init__.py +13 -3
  53. canvas_sdk/effects/calendar/{create_calendar.py → calendar.py} +19 -5
  54. canvas_sdk/effects/calendar/event.py +172 -0
  55. canvas_sdk/effects/claim_label.py +93 -0
  56. canvas_sdk/effects/claim_line_item.py +47 -0
  57. canvas_sdk/effects/claim_queue.py +49 -0
  58. canvas_sdk/effects/fax/__init__.py +3 -0
  59. canvas_sdk/effects/fax/base.py +77 -0
  60. canvas_sdk/effects/fax/note.py +42 -0
  61. canvas_sdk/effects/metadata.py +15 -1
  62. canvas_sdk/effects/note/__init__.py +8 -1
  63. canvas_sdk/effects/note/appointment.py +135 -7
  64. canvas_sdk/effects/note/base.py +17 -0
  65. canvas_sdk/effects/note/message.py +22 -14
  66. canvas_sdk/effects/note/note.py +150 -1
  67. canvas_sdk/effects/observation/__init__.py +11 -0
  68. canvas_sdk/effects/observation/base.py +206 -0
  69. canvas_sdk/effects/patient/__init__.py +2 -0
  70. canvas_sdk/effects/patient/base.py +8 -0
  71. canvas_sdk/effects/payment/__init__.py +11 -0
  72. canvas_sdk/effects/payment/base.py +355 -0
  73. canvas_sdk/effects/payment/post_claim_payment.py +49 -0
  74. canvas_sdk/effects/send_contact_verification.py +42 -0
  75. canvas_sdk/effects/task/__init__.py +2 -1
  76. canvas_sdk/effects/task/task.py +30 -0
  77. canvas_sdk/effects/validation/__init__.py +3 -0
  78. canvas_sdk/effects/validation/base.py +92 -0
  79. canvas_sdk/events/base.py +15 -0
  80. canvas_sdk/handlers/application.py +7 -7
  81. canvas_sdk/handlers/simple_api/api.py +1 -4
  82. canvas_sdk/handlers/simple_api/websocket.py +1 -4
  83. canvas_sdk/handlers/utils.py +14 -0
  84. canvas_sdk/questionnaires/utils.py +1 -0
  85. canvas_sdk/templates/utils.py +17 -4
  86. canvas_sdk/test_utils/factories/FACTORY_GUIDE.md +362 -0
  87. canvas_sdk/test_utils/factories/__init__.py +115 -0
  88. canvas_sdk/test_utils/factories/calendar.py +24 -0
  89. canvas_sdk/test_utils/factories/claim.py +81 -0
  90. canvas_sdk/test_utils/factories/claim_diagnosis_code.py +16 -0
  91. canvas_sdk/test_utils/factories/coverage.py +17 -0
  92. canvas_sdk/test_utils/factories/imaging.py +74 -0
  93. canvas_sdk/test_utils/factories/lab.py +192 -0
  94. canvas_sdk/test_utils/factories/medication_history.py +75 -0
  95. canvas_sdk/test_utils/factories/note.py +52 -0
  96. canvas_sdk/test_utils/factories/organization.py +50 -0
  97. canvas_sdk/test_utils/factories/practicelocation.py +88 -0
  98. canvas_sdk/test_utils/factories/referral.py +81 -0
  99. canvas_sdk/test_utils/factories/staff.py +111 -0
  100. canvas_sdk/test_utils/factories/task.py +66 -0
  101. canvas_sdk/test_utils/factories/uncategorized_clinical_document.py +48 -0
  102. canvas_sdk/utils/metrics.py +4 -1
  103. canvas_sdk/v1/data/__init__.py +66 -7
  104. canvas_sdk/v1/data/allergy_intolerance.py +5 -11
  105. canvas_sdk/v1/data/appointment.py +18 -4
  106. canvas_sdk/v1/data/assessment.py +2 -12
  107. canvas_sdk/v1/data/banner_alert.py +2 -4
  108. canvas_sdk/v1/data/base.py +53 -14
  109. canvas_sdk/v1/data/billing.py +8 -11
  110. canvas_sdk/v1/data/calendar.py +64 -0
  111. canvas_sdk/v1/data/care_team.py +4 -10
  112. canvas_sdk/v1/data/claim.py +172 -66
  113. canvas_sdk/v1/data/claim_diagnosis_code.py +19 -0
  114. canvas_sdk/v1/data/claim_line_item.py +2 -5
  115. canvas_sdk/v1/data/coding.py +19 -0
  116. canvas_sdk/v1/data/command.py +2 -4
  117. canvas_sdk/v1/data/common.py +10 -0
  118. canvas_sdk/v1/data/compound_medication.py +3 -4
  119. canvas_sdk/v1/data/condition.py +4 -9
  120. canvas_sdk/v1/data/coverage.py +66 -26
  121. canvas_sdk/v1/data/detected_issue.py +20 -20
  122. canvas_sdk/v1/data/device.py +2 -14
  123. canvas_sdk/v1/data/discount.py +2 -5
  124. canvas_sdk/v1/data/encounter.py +44 -0
  125. canvas_sdk/v1/data/facility.py +1 -0
  126. canvas_sdk/v1/data/goal.py +2 -14
  127. canvas_sdk/v1/data/imaging.py +4 -30
  128. canvas_sdk/v1/data/immunization.py +7 -15
  129. canvas_sdk/v1/data/lab.py +12 -65
  130. canvas_sdk/v1/data/line_item_transaction.py +2 -5
  131. canvas_sdk/v1/data/medication.py +3 -8
  132. canvas_sdk/v1/data/medication_history.py +142 -0
  133. canvas_sdk/v1/data/medication_statement.py +41 -0
  134. canvas_sdk/v1/data/message.py +4 -8
  135. canvas_sdk/v1/data/note.py +37 -38
  136. canvas_sdk/v1/data/observation.py +9 -36
  137. canvas_sdk/v1/data/organization.py +70 -9
  138. canvas_sdk/v1/data/patient.py +8 -12
  139. canvas_sdk/v1/data/patient_consent.py +4 -14
  140. canvas_sdk/v1/data/payment_collection.py +2 -5
  141. canvas_sdk/v1/data/posting.py +3 -9
  142. canvas_sdk/v1/data/practicelocation.py +66 -7
  143. canvas_sdk/v1/data/protocol_override.py +3 -4
  144. canvas_sdk/v1/data/protocol_result.py +3 -3
  145. canvas_sdk/v1/data/questionnaire.py +10 -26
  146. canvas_sdk/v1/data/reason_for_visit.py +2 -6
  147. canvas_sdk/v1/data/referral.py +41 -17
  148. canvas_sdk/v1/data/staff.py +34 -26
  149. canvas_sdk/v1/data/stop_medication_event.py +27 -0
  150. canvas_sdk/v1/data/task.py +30 -11
  151. canvas_sdk/v1/data/team.py +2 -4
  152. canvas_sdk/v1/data/uncategorized_clinical_document.py +84 -0
  153. canvas_sdk/v1/data/user.py +14 -0
  154. canvas_sdk/v1/data/utils.py +5 -0
  155. canvas_sdk/value_set/v2026/__init__.py +1 -0
  156. canvas_sdk/value_set/v2026/adverse_event.py +157 -0
  157. canvas_sdk/value_set/v2026/allergy.py +116 -0
  158. canvas_sdk/value_set/v2026/assessment.py +466 -0
  159. canvas_sdk/value_set/v2026/communication.py +496 -0
  160. canvas_sdk/value_set/v2026/condition.py +52934 -0
  161. canvas_sdk/value_set/v2026/device.py +315 -0
  162. canvas_sdk/value_set/v2026/diagnostic_study.py +5243 -0
  163. canvas_sdk/value_set/v2026/encounter.py +2714 -0
  164. canvas_sdk/value_set/v2026/immunization.py +297 -0
  165. canvas_sdk/value_set/v2026/individual_characteristic.py +339 -0
  166. canvas_sdk/value_set/v2026/intervention.py +1703 -0
  167. canvas_sdk/value_set/v2026/laboratory_test.py +1831 -0
  168. canvas_sdk/value_set/v2026/medication.py +8218 -0
  169. canvas_sdk/value_set/v2026/no_qdm_category_assigned.py +26493 -0
  170. canvas_sdk/value_set/v2026/physical_exam.py +342 -0
  171. canvas_sdk/value_set/v2026/procedure.py +27869 -0
  172. canvas_sdk/value_set/v2026/symptom.py +625 -0
  173. logger/logger.py +30 -31
  174. logger/logstash.py +282 -0
  175. logger/pubsub.py +26 -0
  176. plugin_runner/allowed-module-imports.json +940 -9
  177. plugin_runner/generate_allowed_imports.py +1 -0
  178. plugin_runner/installation.py +2 -2
  179. plugin_runner/plugin_runner.py +21 -24
  180. plugin_runner/sandbox.py +34 -0
  181. protobufs/canvas_generated/messages/effects.proto +65 -0
  182. protobufs/canvas_generated/messages/events.proto +150 -51
  183. settings.py +27 -11
  184. canvas_sdk/effects/calendar/create_event.py +0 -43
  185. {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,72 @@
1
+ from enum import StrEnum
2
+ from typing import Any
3
+ from uuid import UUID
4
+
5
+ from django.core.exceptions import ImproperlyConfigured
6
+ from django.db.models import Q
7
+ from pydantic_core import InitErrorDetails
8
+
9
+ from canvas_sdk.commands.base import _BaseCommand
10
+
11
+
12
+ class ReportReviewCommunicationMethod(StrEnum):
13
+ """Communication methods for a report review."""
14
+
15
+ DELEGATED_CALL_CAN_LEAVE_MESSAGE = "DM"
16
+ DELEGATED_CALL_NEED_ANSWER = "DA"
17
+ DELEGATED_LETTER = "DL"
18
+ ALREADY_LEFT_MESSAGE = "AM"
19
+ ALREADY_REVIEWED_WITH_PATIENT = "AR"
20
+
21
+
22
+ class ReviewMode(StrEnum):
23
+ """Review modes for a report review."""
24
+
25
+ REVIEW_REQUIRED = "RR"
26
+ ALREADY_REVIEWED_OFFLINE = "AR"
27
+ REVIEW_NOT_REQUIRED = "RN"
28
+
29
+
30
+ class _BaseReview(_BaseCommand):
31
+ """A base class for review commands."""
32
+
33
+ class Meta:
34
+ abstract = True
35
+ model = None
36
+
37
+ def __init_subclass__(cls, **kwargs: Any) -> None:
38
+ """Validate that concrete review commands have a unique key and model."""
39
+ super().__init_subclass__(**kwargs)
40
+
41
+ if not hasattr(cls.Meta, "model") or cls.Meta.model is None:
42
+ raise ImproperlyConfigured(f"Review command {cls.__name__!r} must specify Meta.model.")
43
+
44
+ report_ids: list[str | UUID] | None = None
45
+ message_to_patient: str | None = None
46
+ communication_method: ReportReviewCommunicationMethod | None = None
47
+ linked_items_urns: list[str] | None = None
48
+ comment: str | None = None
49
+
50
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
51
+ errors = super()._get_error_details(method)
52
+
53
+ if self.report_ids:
54
+ for report_id in self.report_ids:
55
+ can_be_reviewed = self.Meta.model.objects.filter( # type: ignore[attr-defined]
56
+ Q(id=report_id),
57
+ Q(review_mode=ReviewMode.REVIEW_REQUIRED),
58
+ (Q(review__committer__isnull=True) | Q(review__entered_in_error__isnull=False)),
59
+ ).exists()
60
+ if not can_be_reviewed:
61
+ errors.append(
62
+ self._create_error_detail(
63
+ "value",
64
+ f"{self.Meta.model.__class__.__name__} with ID {report_id} cannot be reviewed.",
65
+ self.report_ids,
66
+ )
67
+ )
68
+
69
+ return errors
70
+
71
+
72
+ __exports__ = ()
@@ -0,0 +1,13 @@
1
+ from canvas_sdk.commands.commands.review.base import _BaseReview
2
+ from canvas_sdk.v1.data import ImagingReport
3
+
4
+
5
+ class ImagingReviewCommand(_BaseReview):
6
+ """A class for managing an Imaging Review command within a specific note."""
7
+
8
+ class Meta:
9
+ key = "imagingReview"
10
+ model = ImagingReport
11
+
12
+
13
+ __exports__ = ("ImagingReviewCommand",)
@@ -0,0 +1,13 @@
1
+ from canvas_sdk.commands.commands.review.base import _BaseReview
2
+ from canvas_sdk.v1.data import LabReport
3
+
4
+
5
+ class LabReviewCommand(_BaseReview):
6
+ """A class for managing a Lab Review command within a specific note."""
7
+
8
+ class Meta:
9
+ key = "labReview"
10
+ model = LabReport
11
+
12
+
13
+ __exports__ = ("LabReviewCommand",)
@@ -0,0 +1,13 @@
1
+ from canvas_sdk.commands.commands.review.base import _BaseReview
2
+ from canvas_sdk.v1.data import ReferralReport
3
+
4
+
5
+ class ReferralReviewCommand(_BaseReview):
6
+ """A class for managing a Referral Review command within a specific note."""
7
+
8
+ class Meta:
9
+ key = "referralReview"
10
+ model = ReferralReport
11
+
12
+
13
+ __exports__ = ("ReferralReviewCommand",)
@@ -0,0 +1,13 @@
1
+ from canvas_sdk.commands.commands.review.base import _BaseReview
2
+ from canvas_sdk.v1.data import UncategorizedClinicalDocument
3
+
4
+
5
+ class UncategorizedDocumentReviewCommand(_BaseReview):
6
+ """A class for managing an Uncategorized Document Review command within a specific note."""
7
+
8
+ class Meta:
9
+ key = "uncategorizedDocumentReview"
10
+ model = UncategorizedClinicalDocument
11
+
12
+
13
+ __exports__ = ("UncategorizedDocumentReviewCommand",)
@@ -0,0 +1,43 @@
1
+ from canvas_sdk.effects import EffectType
2
+ from canvas_sdk.effects.validation.base import (
3
+ ValidationError,
4
+ _BaseValidationErrorEffect,
5
+ )
6
+
7
+
8
+ class CommandValidationErrorEffect(_BaseValidationErrorEffect):
9
+ """
10
+ Effect to represent command validation errors.
11
+
12
+ This effect allows plugins to return validation errors for commands,
13
+ which will be displayed to users in the Canvas UI.
14
+
15
+ Example:
16
+ # Add validation errors
17
+ effect = CommandValidationErrorEffect()
18
+ effect.add_error("Narrative is required")
19
+ effect.add_error("Dosage must be a positive number")
20
+ return [effect.apply()]
21
+
22
+ # Or initialize with errors
23
+ errors = [
24
+ ValidationError("Narrative is required"),
25
+ ValidationError("This command cannot be submitted yet")
26
+ ]
27
+ effect = CommandValidationErrorEffect(errors=errors)
28
+ return [effect.apply()]
29
+
30
+ # Method chaining
31
+ effect = CommandValidationErrorEffect()
32
+ effect.add_error("Error 1").add_error("Error 2")
33
+ return [effect.apply()]
34
+ """
35
+
36
+ class Meta:
37
+ effect_type = EffectType.COMMAND_VALIDATION_ERRORS
38
+
39
+
40
+ __all__ = __exports__ = (
41
+ "CommandValidationErrorEffect",
42
+ "ValidationError",
43
+ )
@@ -0,0 +1,22 @@
1
+ from typing import Any
2
+
3
+ from pydantic import Field
4
+
5
+ from canvas_sdk.effects import EffectType, _BaseEffect
6
+
7
+
8
+ class BatchOriginateCommandEffect(_BaseEffect):
9
+ """An Effect that will originate multiple commands in a batch operation."""
10
+
11
+ class Meta:
12
+ effect_type = EffectType.BATCH_ORIGINATE_COMMANDS
13
+
14
+ commands: list = Field(min_length=1)
15
+
16
+ @property
17
+ def values(self) -> dict[str, Any]:
18
+ """The BatchOriginateCommandEffect's values."""
19
+ return {"commands": [command._origination_payload_for_batch() for command in self.commands]}
20
+
21
+
22
+ __exports__ = ("BatchOriginateCommandEffect",)
@@ -1,4 +1,14 @@
1
- from canvas_sdk.effects.calendar.create_calendar import CalendarType, CreateCalendar
2
- from canvas_sdk.effects.calendar.create_event import CreateEvent, EventRecurrence
1
+ from canvas_sdk.effects.calendar.calendar import Calendar, CalendarType
2
+ from canvas_sdk.effects.calendar.event import (
3
+ DaysOfWeek,
4
+ Event,
5
+ EventRecurrence,
6
+ )
3
7
 
4
- __all__ = __exports__ = ("CreateCalendar", "CalendarType", "CreateEvent", "EventRecurrence")
8
+ __all__ = __exports__ = (
9
+ "Calendar",
10
+ "CalendarType",
11
+ "Event",
12
+ "EventRecurrence",
13
+ "DaysOfWeek",
14
+ )
@@ -1,7 +1,9 @@
1
+ import json
1
2
  from enum import StrEnum
2
3
  from typing import Any
3
4
  from uuid import UUID
4
5
 
6
+ from canvas_generated.messages.effects_pb2 import Effect
5
7
  from canvas_sdk.effects import EffectType, _BaseEffect
6
8
 
7
9
 
@@ -12,15 +14,13 @@ class CalendarType(StrEnum):
12
14
  Administrative = "Admin"
13
15
 
14
16
 
15
- class CreateCalendar(_BaseEffect):
17
+ class Calendar(_BaseEffect):
16
18
  """Effect to create a Calendar."""
17
19
 
18
- class Meta:
19
- effect_type = EffectType.CALENDAR__CREATE
20
-
21
20
  id: str | UUID | None = None
22
21
  provider: str | UUID
23
22
  type: CalendarType
23
+ location: str | UUID | None = None
24
24
  description: str | None = None
25
25
 
26
26
  @property
@@ -30,11 +30,25 @@ class CreateCalendar(_BaseEffect):
30
30
  "id": self.id,
31
31
  "provider": self.provider,
32
32
  "type": self.type,
33
+ "location": self.location,
33
34
  "description": self.description,
34
35
  }
35
36
 
37
+ def create(self) -> Effect:
38
+ """Send a CREATE effect for the calendar."""
39
+ self._validate_before_effect("create")
40
+
41
+ return Effect(
42
+ type=EffectType.CALENDAR__CREATE,
43
+ payload=json.dumps(
44
+ {
45
+ "data": self.values,
46
+ }
47
+ ),
48
+ )
49
+
36
50
 
37
51
  __exports__ = (
38
- "CreateCalendar",
52
+ "Calendar",
39
53
  "CalendarType",
40
54
  )
@@ -0,0 +1,172 @@
1
+ import json
2
+ from datetime import datetime
3
+ from enum import StrEnum
4
+ from typing import Any
5
+ from uuid import UUID
6
+
7
+ from pydantic_core import InitErrorDetails
8
+
9
+ from canvas_generated.messages.effects_pb2 import Effect
10
+ from canvas_sdk.effects import EffectType, _BaseEffect
11
+
12
+
13
+ class EventRecurrence(StrEnum):
14
+ """Calendar event recurrence."""
15
+
16
+ Daily = "DAILY"
17
+ Weekly = "WEEKLY"
18
+
19
+
20
+ class DaysOfWeek(StrEnum):
21
+ """Days of the week."""
22
+
23
+ Monday = "MO"
24
+ Tuesday = "TU"
25
+ Wednesday = "WE"
26
+ Thursday = "TH"
27
+ Friday = "FR"
28
+ Saturday = "SA"
29
+ Sunday = "SU"
30
+
31
+
32
+ def get_recurrence_string(
33
+ frequency: EventRecurrence | None, interval: int | None, days_of_week: list[DaysOfWeek] | None
34
+ ) -> str:
35
+ """Generate the recurrence string for an event."""
36
+ parts = [
37
+ f"FREQ={frequency}" if frequency else "",
38
+ f"INTERVAL={interval}" if interval else "",
39
+ f"BYDAY={','.join(list(map(str, days_of_week)))}" if days_of_week else "",
40
+ ]
41
+
42
+ return ";".join(filter(bool, parts))
43
+
44
+
45
+ class Event(_BaseEffect):
46
+ """Effect to create a Calendar event."""
47
+
48
+ calendar_id: str | UUID | None = None
49
+ event_id: str | UUID | None = None
50
+ title: str | None = None
51
+ starts_at: datetime | None = None
52
+ ends_at: datetime | None = None
53
+ recurrence_frequency: EventRecurrence | None = None
54
+ recurrence_interval: int | None = None
55
+ recurrence_days: list[DaysOfWeek] | None = None
56
+ recurrence_ends_at: datetime | None = None
57
+ allowed_note_types: list[str] | None = None
58
+
59
+ @property
60
+ def values(self) -> dict[str, Any]:
61
+ """The event's values."""
62
+ return {
63
+ "event_id": self.event_id,
64
+ "calendar_id": self.calendar_id,
65
+ "title": self.title,
66
+ "starts_at": self.starts_at.isoformat() if self.starts_at else None,
67
+ "ends_at": self.ends_at.isoformat() if self.ends_at else None,
68
+ "recurrence": get_recurrence_string(
69
+ self.recurrence_frequency, self.recurrence_interval, self.recurrence_days
70
+ ),
71
+ "recurrence_ends_at": self.recurrence_ends_at.isoformat()
72
+ if self.recurrence_ends_at
73
+ else None,
74
+ "allowed_note_types": self.allowed_note_types,
75
+ }
76
+
77
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
78
+ errors = super()._get_error_details(method)
79
+
80
+ # calendar_id is required for create
81
+ if method == "create" and not self.calendar_id:
82
+ errors.append(
83
+ self._create_error_detail(
84
+ "missing",
85
+ "Field 'calendar_id' is required to create an event.",
86
+ None,
87
+ )
88
+ )
89
+
90
+ if method in ("update", "delete") and not self.event_id:
91
+ errors.append(
92
+ self._create_error_detail(
93
+ "missing",
94
+ f"Field 'event_id' is required to {method} an event.",
95
+ None,
96
+ )
97
+ )
98
+
99
+ if method in (
100
+ "create",
101
+ "update",
102
+ ):
103
+ if not self.title:
104
+ errors.append(
105
+ self._create_error_detail(
106
+ "missing",
107
+ f"Field 'title' is required to {method} an event.",
108
+ None,
109
+ )
110
+ )
111
+
112
+ if not self.starts_at:
113
+ errors.append(
114
+ self._create_error_detail(
115
+ "missing",
116
+ f"Field 'starts_at' is required to {method} an event.",
117
+ None,
118
+ )
119
+ )
120
+
121
+ if not self.ends_at:
122
+ errors.append(
123
+ self._create_error_detail(
124
+ "missing",
125
+ f"Field 'ends_at' is required to {method} an event.",
126
+ None,
127
+ )
128
+ )
129
+
130
+ return errors
131
+
132
+ def create(self) -> Effect:
133
+ """Send a CREATE effect for the calendar event."""
134
+ self._validate_before_effect("create")
135
+
136
+ return Effect(
137
+ type=EffectType.CALENDAR__EVENT__CREATE,
138
+ payload=json.dumps(
139
+ {
140
+ "data": self.values,
141
+ }
142
+ ),
143
+ )
144
+
145
+ def update(self) -> Effect:
146
+ """Send an UPDATE effect for the calendar event."""
147
+ self._validate_before_effect("update")
148
+
149
+ return Effect(
150
+ type=EffectType.CALENDAR__EVENT__UPDATE,
151
+ payload=json.dumps(
152
+ {
153
+ "data": self.values,
154
+ }
155
+ ),
156
+ )
157
+
158
+ def delete(self) -> Effect:
159
+ """Send a DELETE effect for the calendar event."""
160
+ self._validate_before_effect("delete")
161
+
162
+ return Effect(
163
+ type=EffectType.CALENDAR__EVENT__DELETE,
164
+ payload=json.dumps(
165
+ {
166
+ "data": self.values,
167
+ }
168
+ ),
169
+ )
170
+
171
+
172
+ __exports__ = ("Event", "EventRecurrence", "DaysOfWeek")
@@ -0,0 +1,93 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+ from uuid import UUID
4
+
5
+ from pydantic import conlist
6
+ from pydantic_core import InitErrorDetails
7
+
8
+ from canvas_sdk.effects.base import EffectType, _BaseEffect
9
+ from canvas_sdk.v1.data import Claim
10
+ from canvas_sdk.v1.data.common import ColorEnum
11
+
12
+
13
+ @dataclass
14
+ class Label:
15
+ """A class representing a label."""
16
+
17
+ color: ColorEnum
18
+ name: str
19
+
20
+ def to_dict(self) -> dict[str, Any]:
21
+ """Convert the label to a dictionary."""
22
+ return {"color": self.color.value, "name": self.name}
23
+
24
+
25
+ class _ClaimLabelBase(_BaseEffect):
26
+ """Base class for managing ClaimLabels."""
27
+
28
+ claim_id: UUID | str
29
+ labels: conlist(str | Label, min_length=1) # type: ignore
30
+
31
+ def _check_if_claim_exists(self) -> list[InitErrorDetails]:
32
+ if Claim.objects.filter(id=self.claim_id).exists():
33
+ return []
34
+ return [
35
+ (
36
+ self._create_error_detail(
37
+ "value",
38
+ f"Claim with id {self.claim_id} does not exist.",
39
+ self.claim_id,
40
+ )
41
+ )
42
+ ]
43
+
44
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
45
+ errors = super()._get_error_details(method)
46
+ if not Claim.objects.filter(id=self.claim_id).exists():
47
+ errors.append(
48
+ self._create_error_detail(
49
+ "value",
50
+ f"Claim with id {self.claim_id} does not exist.",
51
+ self.claim_id,
52
+ )
53
+ )
54
+ return errors
55
+
56
+
57
+ class AddClaimLabel(_ClaimLabelBase):
58
+ """Effect to add a label to a Claim."""
59
+
60
+ class Meta:
61
+ effect_type = EffectType.ADD_CLAIM_LABEL
62
+
63
+ @property
64
+ def values(self) -> dict[str, Any]:
65
+ """The values for adding a claim label."""
66
+ return {
67
+ "claim_id": str(self.claim_id),
68
+ "labels": [
69
+ label.to_dict() if isinstance(label, Label) else {"name": label}
70
+ for label in self.labels
71
+ ],
72
+ }
73
+
74
+
75
+ class RemoveClaimLabel(_ClaimLabelBase):
76
+ """Effect to remove a label from a Claim."""
77
+
78
+ class Meta:
79
+ effect_type = EffectType.REMOVE_CLAIM_LABEL
80
+
81
+ labels: conlist(str, min_length=1) # type: ignore
82
+
83
+ @property
84
+ def values(self) -> dict[str, Any]:
85
+ """The values for removing a claim label."""
86
+ return {"claim_id": str(self.claim_id), "labels": list(self.labels)}
87
+
88
+
89
+ __exports__ = (
90
+ "Label",
91
+ "AddClaimLabel",
92
+ "RemoveClaimLabel",
93
+ )
@@ -0,0 +1,47 @@
1
+ from typing import Any
2
+ from uuid import UUID
3
+
4
+ from pydantic_core import InitErrorDetails
5
+
6
+ from canvas_sdk.effects.base import EffectType, _BaseEffect
7
+ from canvas_sdk.v1.data import ClaimLineItem
8
+
9
+
10
+ class UpdateClaimLineItem(_BaseEffect):
11
+ """
12
+ An Effect that updates a Claim Line Item.
13
+ """
14
+
15
+ class Meta:
16
+ effect_type = EffectType.UPDATE_CLAIM_LINE_ITEM
17
+
18
+ claim_line_item_id: str | UUID
19
+ charge: float | None = None
20
+
21
+ @property
22
+ def values(self) -> dict[str, Any]:
23
+ """The values for the payload."""
24
+ if self.charge is None:
25
+ return {}
26
+ return {"charge": str(self.charge)}
27
+
28
+ @property
29
+ def effect_payload(self) -> dict[str, Any]:
30
+ """The payload of the effect."""
31
+ return {"data": self.values, "claim_line_item_id": str(self.claim_line_item_id)}
32
+
33
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
34
+ errors = super()._get_error_details(method)
35
+
36
+ if not ClaimLineItem.objects.filter(id=self.claim_line_item_id).exists():
37
+ errors.append(
38
+ self._create_error_detail(
39
+ "value",
40
+ "Claim Line Item does not exist",
41
+ self.claim_line_item_id,
42
+ )
43
+ )
44
+ return errors
45
+
46
+
47
+ __exports__ = ("UpdateClaimLineItem",)
@@ -0,0 +1,49 @@
1
+ from typing import Any
2
+ from uuid import UUID
3
+
4
+ from pydantic import constr
5
+ from pydantic_core import InitErrorDetails
6
+
7
+ from canvas_sdk.effects.base import EffectType, _BaseEffect
8
+ from canvas_sdk.v1.data import Claim, ClaimQueue
9
+
10
+
11
+ class MoveClaimToQueue(_BaseEffect):
12
+ """
13
+ An Effect that moves a Claim to a Queue.
14
+ """
15
+
16
+ class Meta:
17
+ effect_type = EffectType.MOVE_CLAIM_TO_QUEUE
18
+
19
+ claim_id: UUID | str
20
+ queue: constr(min_length=1, strip_whitespace=True) # type: ignore[valid-type]
21
+
22
+ @property
23
+ def values(self) -> dict[str, Any]:
24
+ """The claim_id and queue_id."""
25
+ return {"claim_id": str(self.claim_id), "queue": self.queue}
26
+
27
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
28
+ errors = super()._get_error_details(method)
29
+
30
+ if not Claim.objects.filter(id=self.claim_id).exists():
31
+ errors.append(
32
+ self._create_error_detail(
33
+ "value",
34
+ "Claim does not exist",
35
+ self.claim_id,
36
+ )
37
+ )
38
+ if not ClaimQueue.objects.filter(name=self.queue).exists():
39
+ errors.append(
40
+ self._create_error_detail(
41
+ "value",
42
+ "Queue does not exist",
43
+ self.queue,
44
+ )
45
+ )
46
+ return errors
47
+
48
+
49
+ __exports__ = ("MoveClaimToQueue",)
@@ -0,0 +1,3 @@
1
+ from canvas_sdk.effects.fax.note import FaxNoteEffect
2
+
3
+ __all__ = __exports__ = ("FaxNoteEffect",)