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.
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/METADATA +4 -1
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/RECORD +184 -98
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/WHEEL +1 -1
- canvas_cli/apps/emit/event_fixtures/UNKNOWN.ndjson +1 -0
- canvas_cli/apps/logs/logs.py +386 -22
- canvas_cli/main.py +3 -1
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py +46 -4
- canvas_cli/utils/context/context.py +13 -13
- canvas_cli/utils/validators/manifest_schema.py +26 -1
- canvas_generated/messages/effects_pb2.py +5 -5
- canvas_generated/messages/effects_pb2.pyi +108 -2
- canvas_generated/messages/events_pb2.py +6 -6
- canvas_generated/messages/events_pb2.pyi +282 -2
- canvas_sdk/clients/__init__.py +1 -0
- canvas_sdk/clients/llms/__init__.py +17 -0
- canvas_sdk/clients/llms/libraries/__init__.py +11 -0
- canvas_sdk/clients/llms/libraries/llm_anthropic.py +87 -0
- canvas_sdk/clients/llms/libraries/llm_api.py +143 -0
- canvas_sdk/clients/llms/libraries/llm_google.py +92 -0
- canvas_sdk/clients/llms/libraries/llm_openai.py +98 -0
- canvas_sdk/clients/llms/structures/__init__.py +9 -0
- canvas_sdk/clients/llms/structures/llm_response.py +33 -0
- canvas_sdk/clients/llms/structures/llm_tokens.py +53 -0
- canvas_sdk/clients/llms/structures/llm_turn.py +47 -0
- canvas_sdk/clients/llms/structures/settings/__init__.py +13 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings.py +27 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_anthropic.py +43 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gemini.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt4.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt5.py +48 -0
- canvas_sdk/clients/third_party.py +3 -0
- canvas_sdk/commands/__init__.py +12 -0
- canvas_sdk/commands/base.py +33 -2
- canvas_sdk/commands/commands/adjust_prescription.py +4 -0
- canvas_sdk/commands/commands/custom_command.py +86 -0
- canvas_sdk/commands/commands/family_history.py +17 -1
- canvas_sdk/commands/commands/immunization_statement.py +42 -2
- canvas_sdk/commands/commands/medication_statement.py +16 -1
- canvas_sdk/commands/commands/past_surgical_history.py +16 -1
- canvas_sdk/commands/commands/perform.py +18 -1
- canvas_sdk/commands/commands/prescribe.py +8 -9
- canvas_sdk/commands/commands/refill.py +5 -5
- canvas_sdk/commands/commands/resolve_condition.py +5 -5
- canvas_sdk/commands/commands/review/__init__.py +3 -0
- canvas_sdk/commands/commands/review/base.py +72 -0
- canvas_sdk/commands/commands/review/imaging.py +13 -0
- canvas_sdk/commands/commands/review/lab.py +13 -0
- canvas_sdk/commands/commands/review/referral.py +13 -0
- canvas_sdk/commands/commands/review/uncategorized_document.py +13 -0
- canvas_sdk/commands/validation.py +43 -0
- canvas_sdk/effects/batch_originate.py +22 -0
- canvas_sdk/effects/calendar/__init__.py +13 -3
- canvas_sdk/effects/calendar/{create_calendar.py → calendar.py} +19 -5
- canvas_sdk/effects/calendar/event.py +172 -0
- canvas_sdk/effects/claim_label.py +93 -0
- canvas_sdk/effects/claim_line_item.py +47 -0
- canvas_sdk/effects/claim_queue.py +49 -0
- canvas_sdk/effects/fax/__init__.py +3 -0
- canvas_sdk/effects/fax/base.py +77 -0
- canvas_sdk/effects/fax/note.py +42 -0
- canvas_sdk/effects/metadata.py +15 -1
- canvas_sdk/effects/note/__init__.py +8 -1
- canvas_sdk/effects/note/appointment.py +135 -7
- canvas_sdk/effects/note/base.py +17 -0
- canvas_sdk/effects/note/message.py +22 -14
- canvas_sdk/effects/note/note.py +150 -1
- canvas_sdk/effects/observation/__init__.py +11 -0
- canvas_sdk/effects/observation/base.py +206 -0
- canvas_sdk/effects/patient/__init__.py +2 -0
- canvas_sdk/effects/patient/base.py +8 -0
- canvas_sdk/effects/payment/__init__.py +11 -0
- canvas_sdk/effects/payment/base.py +355 -0
- canvas_sdk/effects/payment/post_claim_payment.py +49 -0
- canvas_sdk/effects/send_contact_verification.py +42 -0
- canvas_sdk/effects/task/__init__.py +2 -1
- canvas_sdk/effects/task/task.py +30 -0
- canvas_sdk/effects/validation/__init__.py +3 -0
- canvas_sdk/effects/validation/base.py +92 -0
- canvas_sdk/events/base.py +15 -0
- canvas_sdk/handlers/application.py +7 -7
- canvas_sdk/handlers/simple_api/api.py +1 -4
- canvas_sdk/handlers/simple_api/websocket.py +1 -4
- canvas_sdk/handlers/utils.py +14 -0
- canvas_sdk/questionnaires/utils.py +1 -0
- canvas_sdk/templates/utils.py +17 -4
- canvas_sdk/test_utils/factories/FACTORY_GUIDE.md +362 -0
- canvas_sdk/test_utils/factories/__init__.py +115 -0
- canvas_sdk/test_utils/factories/calendar.py +24 -0
- canvas_sdk/test_utils/factories/claim.py +81 -0
- canvas_sdk/test_utils/factories/claim_diagnosis_code.py +16 -0
- canvas_sdk/test_utils/factories/coverage.py +17 -0
- canvas_sdk/test_utils/factories/imaging.py +74 -0
- canvas_sdk/test_utils/factories/lab.py +192 -0
- canvas_sdk/test_utils/factories/medication_history.py +75 -0
- canvas_sdk/test_utils/factories/note.py +52 -0
- canvas_sdk/test_utils/factories/organization.py +50 -0
- canvas_sdk/test_utils/factories/practicelocation.py +88 -0
- canvas_sdk/test_utils/factories/referral.py +81 -0
- canvas_sdk/test_utils/factories/staff.py +111 -0
- canvas_sdk/test_utils/factories/task.py +66 -0
- canvas_sdk/test_utils/factories/uncategorized_clinical_document.py +48 -0
- canvas_sdk/utils/metrics.py +4 -1
- canvas_sdk/v1/data/__init__.py +66 -7
- canvas_sdk/v1/data/allergy_intolerance.py +5 -11
- canvas_sdk/v1/data/appointment.py +18 -4
- canvas_sdk/v1/data/assessment.py +2 -12
- canvas_sdk/v1/data/banner_alert.py +2 -4
- canvas_sdk/v1/data/base.py +53 -14
- canvas_sdk/v1/data/billing.py +8 -11
- canvas_sdk/v1/data/calendar.py +64 -0
- canvas_sdk/v1/data/care_team.py +4 -10
- canvas_sdk/v1/data/claim.py +172 -66
- canvas_sdk/v1/data/claim_diagnosis_code.py +19 -0
- canvas_sdk/v1/data/claim_line_item.py +2 -5
- canvas_sdk/v1/data/coding.py +19 -0
- canvas_sdk/v1/data/command.py +2 -4
- canvas_sdk/v1/data/common.py +10 -0
- canvas_sdk/v1/data/compound_medication.py +3 -4
- canvas_sdk/v1/data/condition.py +4 -9
- canvas_sdk/v1/data/coverage.py +66 -26
- canvas_sdk/v1/data/detected_issue.py +20 -20
- canvas_sdk/v1/data/device.py +2 -14
- canvas_sdk/v1/data/discount.py +2 -5
- canvas_sdk/v1/data/encounter.py +44 -0
- canvas_sdk/v1/data/facility.py +1 -0
- canvas_sdk/v1/data/goal.py +2 -14
- canvas_sdk/v1/data/imaging.py +4 -30
- canvas_sdk/v1/data/immunization.py +7 -15
- canvas_sdk/v1/data/lab.py +12 -65
- canvas_sdk/v1/data/line_item_transaction.py +2 -5
- canvas_sdk/v1/data/medication.py +3 -8
- canvas_sdk/v1/data/medication_history.py +142 -0
- canvas_sdk/v1/data/medication_statement.py +41 -0
- canvas_sdk/v1/data/message.py +4 -8
- canvas_sdk/v1/data/note.py +37 -38
- canvas_sdk/v1/data/observation.py +9 -36
- canvas_sdk/v1/data/organization.py +70 -9
- canvas_sdk/v1/data/patient.py +8 -12
- canvas_sdk/v1/data/patient_consent.py +4 -14
- canvas_sdk/v1/data/payment_collection.py +2 -5
- canvas_sdk/v1/data/posting.py +3 -9
- canvas_sdk/v1/data/practicelocation.py +66 -7
- canvas_sdk/v1/data/protocol_override.py +3 -4
- canvas_sdk/v1/data/protocol_result.py +3 -3
- canvas_sdk/v1/data/questionnaire.py +10 -26
- canvas_sdk/v1/data/reason_for_visit.py +2 -6
- canvas_sdk/v1/data/referral.py +41 -17
- canvas_sdk/v1/data/staff.py +34 -26
- canvas_sdk/v1/data/stop_medication_event.py +27 -0
- canvas_sdk/v1/data/task.py +30 -11
- canvas_sdk/v1/data/team.py +2 -4
- canvas_sdk/v1/data/uncategorized_clinical_document.py +84 -0
- canvas_sdk/v1/data/user.py +14 -0
- canvas_sdk/v1/data/utils.py +5 -0
- canvas_sdk/value_set/v2026/__init__.py +1 -0
- canvas_sdk/value_set/v2026/adverse_event.py +157 -0
- canvas_sdk/value_set/v2026/allergy.py +116 -0
- canvas_sdk/value_set/v2026/assessment.py +466 -0
- canvas_sdk/value_set/v2026/communication.py +496 -0
- canvas_sdk/value_set/v2026/condition.py +52934 -0
- canvas_sdk/value_set/v2026/device.py +315 -0
- canvas_sdk/value_set/v2026/diagnostic_study.py +5243 -0
- canvas_sdk/value_set/v2026/encounter.py +2714 -0
- canvas_sdk/value_set/v2026/immunization.py +297 -0
- canvas_sdk/value_set/v2026/individual_characteristic.py +339 -0
- canvas_sdk/value_set/v2026/intervention.py +1703 -0
- canvas_sdk/value_set/v2026/laboratory_test.py +1831 -0
- canvas_sdk/value_set/v2026/medication.py +8218 -0
- canvas_sdk/value_set/v2026/no_qdm_category_assigned.py +26493 -0
- canvas_sdk/value_set/v2026/physical_exam.py +342 -0
- canvas_sdk/value_set/v2026/procedure.py +27869 -0
- canvas_sdk/value_set/v2026/symptom.py +625 -0
- logger/logger.py +30 -31
- logger/logstash.py +282 -0
- logger/pubsub.py +26 -0
- plugin_runner/allowed-module-imports.json +940 -9
- plugin_runner/generate_allowed_imports.py +1 -0
- plugin_runner/installation.py +2 -2
- plugin_runner/plugin_runner.py +21 -24
- plugin_runner/sandbox.py +34 -0
- protobufs/canvas_generated/messages/effects.proto +65 -0
- protobufs/canvas_generated/messages/events.proto +150 -51
- settings.py +27 -11
- canvas_sdk/effects/calendar/create_event.py +0 -43
- {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.
|
|
2
|
-
from canvas_sdk.effects.calendar.
|
|
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__ = (
|
|
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
|
|
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
|
-
"
|
|
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",)
|