canvas 0.61.0__py3-none-any.whl → 0.63.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.61.0.dist-info → canvas-0.63.0.dist-info}/METADATA +1 -1
- {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/RECORD +29 -19
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +30 -0
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +28 -0
- canvas_sdk/effects/appointments_metadata/__init__.py +13 -0
- canvas_sdk/effects/appointments_metadata/appointments_metadata_create_form.py +12 -0
- canvas_sdk/effects/appointments_metadata/base.py +30 -0
- canvas_sdk/effects/calendar/__init__.py +4 -0
- canvas_sdk/effects/calendar/create_calendar.py +40 -0
- canvas_sdk/effects/calendar/create_event.py +43 -0
- canvas_sdk/effects/form.py +69 -0
- canvas_sdk/effects/generate_full_chart_pdf.py +56 -0
- canvas_sdk/effects/metadata.py +26 -0
- canvas_sdk/effects/note/base.py +24 -8
- canvas_sdk/effects/note/note.py +17 -7
- canvas_sdk/effects/patient_metadata/base.py +2 -18
- canvas_sdk/effects/patient_metadata/patient_metadata_create_form.py +3 -61
- canvas_sdk/handlers/application.py +13 -1
- canvas_sdk/v1/data/__init__.py +12 -1
- canvas_sdk/v1/data/appointment.py +19 -0
- canvas_sdk/v1/data/immunization.py +159 -0
- canvas_sdk/v1/data/patient.py +3 -3
- plugin_runner/allowed-module-imports.json +45 -0
- protobufs/canvas_generated/messages/effects.proto +20 -0
- protobufs/canvas_generated/messages/events.proto +18 -0
- {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/WHEEL +0 -0
- {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/entry_points.txt +0 -0
|
@@ -863,6 +863,7 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
|
863
863
|
PLUGIN_CREATED: _ClassVar[EventType]
|
|
864
864
|
PLUGIN_UPDATED: _ClassVar[EventType]
|
|
865
865
|
APPLICATION__ON_OPEN: _ClassVar[EventType]
|
|
866
|
+
APPLICATION__ON_CONTEXT_CHANGE: _ClassVar[EventType]
|
|
866
867
|
PATIENT_PORTAL__GET_FORMS: _ClassVar[EventType]
|
|
867
868
|
PATIENT_PORTAL__APPOINTMENT_CANCELED: _ClassVar[EventType]
|
|
868
869
|
PATIENT_PORTAL__APPOINTMENT_RESCHEDULED: _ClassVar[EventType]
|
|
@@ -899,6 +900,19 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
|
899
900
|
PATIENT_EXTERNAL_IDENTIFIER_DELETED: _ClassVar[EventType]
|
|
900
901
|
PATIENT_METADATA_CREATED: _ClassVar[EventType]
|
|
901
902
|
PATIENT_METADATA_UPDATED: _ClassVar[EventType]
|
|
903
|
+
APPOINTMENT__FORM__PROVIDERS__PRE_SEARCH: _ClassVar[EventType]
|
|
904
|
+
APPOINTMENT__FORM__LOCATIONS__PRE_SEARCH: _ClassVar[EventType]
|
|
905
|
+
APPOINTMENT__FORM__VISIT_TYPES__PRE_SEARCH: _ClassVar[EventType]
|
|
906
|
+
APPOINTMENT__FORM__DURATIONS__PRE_SEARCH: _ClassVar[EventType]
|
|
907
|
+
APPOINTMENT__FORM__REASON_FOR_VISIT__PRE_SEARCH: _ClassVar[EventType]
|
|
908
|
+
APPOINTMENT__FORM__PROVIDERS__POST_SEARCH: _ClassVar[EventType]
|
|
909
|
+
APPOINTMENT__FORM__LOCATIONS__POST_SEARCH: _ClassVar[EventType]
|
|
910
|
+
APPOINTMENT__FORM__VISIT_TYPES__POST_SEARCH: _ClassVar[EventType]
|
|
911
|
+
APPOINTMENT__FORM__DURATIONS__POST_SEARCH: _ClassVar[EventType]
|
|
912
|
+
APPOINTMENT__FORM__REASON_FOR_VISIT__POST_SEARCH: _ClassVar[EventType]
|
|
913
|
+
APPOINTMENT__FORM__GET_ADDITIONAL_FIELDS: _ClassVar[EventType]
|
|
914
|
+
APPOINTMENT_METADATA_CREATED: _ClassVar[EventType]
|
|
915
|
+
APPOINTMENT_METADATA_UPDATED: _ClassVar[EventType]
|
|
902
916
|
DOCUMENT_REFERENCE_CREATED: _ClassVar[EventType]
|
|
903
917
|
DOCUMENT_REFERENCE_UPDATED: _ClassVar[EventType]
|
|
904
918
|
DOCUMENT_REFERENCE_DELETED: _ClassVar[EventType]
|
|
@@ -1763,6 +1777,7 @@ CLAIM__CONDITIONS: EventType
|
|
|
1763
1777
|
PLUGIN_CREATED: EventType
|
|
1764
1778
|
PLUGIN_UPDATED: EventType
|
|
1765
1779
|
APPLICATION__ON_OPEN: EventType
|
|
1780
|
+
APPLICATION__ON_CONTEXT_CHANGE: EventType
|
|
1766
1781
|
PATIENT_PORTAL__GET_FORMS: EventType
|
|
1767
1782
|
PATIENT_PORTAL__APPOINTMENT_CANCELED: EventType
|
|
1768
1783
|
PATIENT_PORTAL__APPOINTMENT_RESCHEDULED: EventType
|
|
@@ -1799,6 +1814,19 @@ PATIENT_EXTERNAL_IDENTIFIER_UPDATED: EventType
|
|
|
1799
1814
|
PATIENT_EXTERNAL_IDENTIFIER_DELETED: EventType
|
|
1800
1815
|
PATIENT_METADATA_CREATED: EventType
|
|
1801
1816
|
PATIENT_METADATA_UPDATED: EventType
|
|
1817
|
+
APPOINTMENT__FORM__PROVIDERS__PRE_SEARCH: EventType
|
|
1818
|
+
APPOINTMENT__FORM__LOCATIONS__PRE_SEARCH: EventType
|
|
1819
|
+
APPOINTMENT__FORM__VISIT_TYPES__PRE_SEARCH: EventType
|
|
1820
|
+
APPOINTMENT__FORM__DURATIONS__PRE_SEARCH: EventType
|
|
1821
|
+
APPOINTMENT__FORM__REASON_FOR_VISIT__PRE_SEARCH: EventType
|
|
1822
|
+
APPOINTMENT__FORM__PROVIDERS__POST_SEARCH: EventType
|
|
1823
|
+
APPOINTMENT__FORM__LOCATIONS__POST_SEARCH: EventType
|
|
1824
|
+
APPOINTMENT__FORM__VISIT_TYPES__POST_SEARCH: EventType
|
|
1825
|
+
APPOINTMENT__FORM__DURATIONS__POST_SEARCH: EventType
|
|
1826
|
+
APPOINTMENT__FORM__REASON_FOR_VISIT__POST_SEARCH: EventType
|
|
1827
|
+
APPOINTMENT__FORM__GET_ADDITIONAL_FIELDS: EventType
|
|
1828
|
+
APPOINTMENT_METADATA_CREATED: EventType
|
|
1829
|
+
APPOINTMENT_METADATA_UPDATED: EventType
|
|
1802
1830
|
DOCUMENT_REFERENCE_CREATED: EventType
|
|
1803
1831
|
DOCUMENT_REFERENCE_UPDATED: EventType
|
|
1804
1832
|
DOCUMENT_REFERENCE_DELETED: EventType
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from canvas_sdk.effects.appointments_metadata.appointments_metadata_create_form import (
|
|
2
|
+
AppointmentsMetadataCreateFormEffect,
|
|
3
|
+
FormField,
|
|
4
|
+
InputType,
|
|
5
|
+
)
|
|
6
|
+
from canvas_sdk.effects.appointments_metadata.base import AppointmentsMetadata
|
|
7
|
+
|
|
8
|
+
__all__ = __exports__ = (
|
|
9
|
+
"FormField",
|
|
10
|
+
"InputType",
|
|
11
|
+
"AppointmentsMetadata",
|
|
12
|
+
"AppointmentsMetadataCreateFormEffect",
|
|
13
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from canvas_sdk.effects import EffectType
|
|
2
|
+
from canvas_sdk.effects.form import BaseCreateFormEffect, FormField, InputType # noqa
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AppointmentsMetadataCreateFormEffect(BaseCreateFormEffect):
|
|
6
|
+
"""An Effect that will create a form."""
|
|
7
|
+
|
|
8
|
+
class Meta:
|
|
9
|
+
effect_type = EffectType.APPOINTMENT__FORM__CREATE_ADDITIONAL_FIELDS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__exports__ = ("AppointmentsMetadataCreateFormEffect", "FormField", "InputType")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.effects.metadata import BaseMetadata
|
|
4
|
+
from canvas_sdk.v1.data import Appointment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AppointmentsMetadata(BaseMetadata):
|
|
8
|
+
"""Effect to upsert an Appointment Metadata record."""
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
effect_type = "APPOINTMENT_METADATA"
|
|
12
|
+
|
|
13
|
+
appointment_id: str
|
|
14
|
+
|
|
15
|
+
def _get_error_details(self, method: Any) -> list:
|
|
16
|
+
errors = super()._get_error_details(method)
|
|
17
|
+
|
|
18
|
+
if not Appointment.objects.filter(id=self.appointment_id).exists():
|
|
19
|
+
errors.append(
|
|
20
|
+
self._create_error_detail(
|
|
21
|
+
"appointment_id",
|
|
22
|
+
f"Appointment with id: {self.appointment_id} does not exist.",
|
|
23
|
+
self.appointment_id,
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return errors
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__exports__ = ("AppointmentsMetadata",)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Any
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from canvas_sdk.effects import EffectType, _BaseEffect
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CalendarType(StrEnum):
|
|
9
|
+
"""Calendar type."""
|
|
10
|
+
|
|
11
|
+
Clinic = "Clinic"
|
|
12
|
+
Administrative = "Admin"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CreateCalendar(_BaseEffect):
|
|
16
|
+
"""Effect to create a Calendar."""
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
effect_type = EffectType.CALENDAR__CREATE
|
|
20
|
+
|
|
21
|
+
id: str | UUID | None = None
|
|
22
|
+
provider: str | UUID
|
|
23
|
+
type: CalendarType
|
|
24
|
+
description: str | None = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def values(self) -> dict[str, Any]:
|
|
28
|
+
"""The Calendar's values."""
|
|
29
|
+
return {
|
|
30
|
+
"id": self.id,
|
|
31
|
+
"provider": self.provider,
|
|
32
|
+
"type": self.type,
|
|
33
|
+
"description": self.description,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__exports__ = (
|
|
38
|
+
"CreateCalendar",
|
|
39
|
+
"CalendarType",
|
|
40
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Any
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.effects import EffectType, _BaseEffect
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EventRecurrence(StrEnum):
|
|
10
|
+
"""Calendar event recurrence."""
|
|
11
|
+
|
|
12
|
+
Daily = "FREQ=DAILY"
|
|
13
|
+
Weekly = "FREQ=WEEKLY"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CreateEvent(_BaseEffect):
|
|
17
|
+
"""Effect to create a Calendar event."""
|
|
18
|
+
|
|
19
|
+
class Meta:
|
|
20
|
+
effect_type = EffectType.CALENDAR__EVENT__CREATE
|
|
21
|
+
|
|
22
|
+
calendar_id: str | UUID
|
|
23
|
+
title: str
|
|
24
|
+
starts_at: datetime
|
|
25
|
+
ends_at: datetime
|
|
26
|
+
recurrence: EventRecurrence
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def values(self) -> dict[str, Any]:
|
|
30
|
+
"""The event's values."""
|
|
31
|
+
return {
|
|
32
|
+
"calendar_id": self.calendar_id,
|
|
33
|
+
"title": self.title,
|
|
34
|
+
"starts_at": self.starts_at.isoformat(),
|
|
35
|
+
"ends_at": self.ends_at.isoformat(),
|
|
36
|
+
"recurrence": self.recurrence,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__exports__ = (
|
|
41
|
+
"CreateEvent",
|
|
42
|
+
"EventRecurrence",
|
|
43
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic_core import InitErrorDetails
|
|
6
|
+
|
|
7
|
+
from canvas_sdk.effects import _BaseEffect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InputType(StrEnum):
|
|
11
|
+
"""Type of input for a form field."""
|
|
12
|
+
|
|
13
|
+
TEXT = "text"
|
|
14
|
+
SELECT = "select"
|
|
15
|
+
DATE = "date"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class FormField:
|
|
20
|
+
"""A class representing a Field."""
|
|
21
|
+
|
|
22
|
+
key: str | None = None
|
|
23
|
+
label: str | None = None
|
|
24
|
+
required: bool = False
|
|
25
|
+
editable: bool = True
|
|
26
|
+
type: InputType = InputType.TEXT
|
|
27
|
+
options: list[str] | None = None
|
|
28
|
+
value: str | None = None
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> dict[str, Any]:
|
|
31
|
+
"""Convert the Field to a dictionary."""
|
|
32
|
+
return {
|
|
33
|
+
"key": self.key,
|
|
34
|
+
"label": self.label,
|
|
35
|
+
"required": self.required,
|
|
36
|
+
"editable": self.editable,
|
|
37
|
+
"type": self.type.value,
|
|
38
|
+
"options": self.options,
|
|
39
|
+
"value": self.value,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BaseCreateFormEffect(_BaseEffect):
|
|
44
|
+
"""Base class for form creation effects."""
|
|
45
|
+
|
|
46
|
+
form_fields: list[FormField]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def values(self) -> dict[str, Any]:
|
|
50
|
+
"""Return the values of the form as a dictionary."""
|
|
51
|
+
return {"form": [field.to_dict() for field in self.form_fields]}
|
|
52
|
+
|
|
53
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
54
|
+
errors = super()._get_error_details(method)
|
|
55
|
+
|
|
56
|
+
for field in self.form_fields:
|
|
57
|
+
if field.type != InputType.SELECT and field.options:
|
|
58
|
+
errors.append(
|
|
59
|
+
self._create_error_detail(
|
|
60
|
+
"value",
|
|
61
|
+
"The options attribute is only used for fields of type select",
|
|
62
|
+
field.key,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return errors
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__exports__ = ()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic_core import InitErrorDetails
|
|
5
|
+
|
|
6
|
+
from canvas_generated.messages.effects_pb2 import EffectType
|
|
7
|
+
from canvas_sdk.effects.base import _BaseEffect
|
|
8
|
+
from canvas_sdk.v1.data import Patient, Staff
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GenerateFullChartPDFEffect(_BaseEffect):
|
|
12
|
+
"""
|
|
13
|
+
An Effect that will generate a full chart PDF for a patient.
|
|
14
|
+
|
|
15
|
+
Generated PDF will appear in task list assigned to requestor with "chart pdf" label within 10 minutes.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
effect_type = EffectType.GENERATE_FULL_CHART_PDF
|
|
20
|
+
|
|
21
|
+
patient_id: str = Field(min_length=1)
|
|
22
|
+
requestor_staff_id: str = Field(min_length=1)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def values(self) -> dict[str, Any]:
|
|
26
|
+
"""The GenerateFullChartPDFEffect's values."""
|
|
27
|
+
return {
|
|
28
|
+
"patient_id": self.patient_id,
|
|
29
|
+
"requestor_staff_id": self.requestor_staff_id,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
33
|
+
errors = super()._get_error_details(method)
|
|
34
|
+
|
|
35
|
+
if not Patient.objects.filter(id=self.patient_id).exists():
|
|
36
|
+
errors.append(
|
|
37
|
+
self._create_error_detail(
|
|
38
|
+
"value",
|
|
39
|
+
f"Patient with ID {self.patient_id} does not exist.",
|
|
40
|
+
self.patient_id,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if not Staff.objects.filter(id=self.requestor_staff_id).exists():
|
|
45
|
+
errors.append(
|
|
46
|
+
self._create_error_detail(
|
|
47
|
+
"value",
|
|
48
|
+
f"Requestor staff with ID {self.requestor_staff_id} does not exist.",
|
|
49
|
+
self.requestor_staff_id,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return errors
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__exports__ = ("GenerateFullChartPDFEffect",)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from canvas_generated.messages.effects_pb2 import Effect
|
|
4
|
+
from canvas_sdk.base import TrackableFieldsModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseMetadata(TrackableFieldsModel):
|
|
8
|
+
"""Base class for metadata effects."""
|
|
9
|
+
|
|
10
|
+
key: str
|
|
11
|
+
|
|
12
|
+
def upsert(self, value: str) -> Effect:
|
|
13
|
+
"""Upsert the metadata."""
|
|
14
|
+
self._validate_before_effect("upsert")
|
|
15
|
+
|
|
16
|
+
return Effect(
|
|
17
|
+
type=f"UPSERT_{self.Meta.effect_type}", # type: ignore[attr-defined]
|
|
18
|
+
payload=json.dumps(
|
|
19
|
+
{
|
|
20
|
+
"data": {**self.values, "value": value},
|
|
21
|
+
}
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__exports__ = ()
|
canvas_sdk/effects/note/base.py
CHANGED
|
@@ -57,14 +57,6 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
57
57
|
errors = super()._get_error_details(method)
|
|
58
58
|
|
|
59
59
|
if method == "create":
|
|
60
|
-
if self.instance_id:
|
|
61
|
-
errors.append(
|
|
62
|
-
self._create_error_detail(
|
|
63
|
-
"value",
|
|
64
|
-
"Instance ID should not be provided for create effects.",
|
|
65
|
-
self.instance_id,
|
|
66
|
-
)
|
|
67
|
-
)
|
|
68
60
|
if not self.practice_location_id:
|
|
69
61
|
errors.append(
|
|
70
62
|
self._create_error_detail(
|
|
@@ -167,11 +159,35 @@ class AppointmentABC(NoteOrAppointmentABC, ABC):
|
|
|
167
159
|
duration_minutes: int | None = None
|
|
168
160
|
status: AppointmentProgressStatus | None = None
|
|
169
161
|
external_identifiers: list[AppointmentIdentifier] | None = None
|
|
162
|
+
parent_appointment_id: str | UUID | None = None
|
|
170
163
|
|
|
171
164
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
172
165
|
"""Validates the appointment instance and returns a list of error details if validation fails."""
|
|
173
166
|
errors = super()._get_error_details(method)
|
|
174
167
|
|
|
168
|
+
if method != "create" and self.parent_appointment_id:
|
|
169
|
+
errors.append(
|
|
170
|
+
self._create_error_detail(
|
|
171
|
+
"value",
|
|
172
|
+
"parent_appointment_id can only be set when creating an appointment.",
|
|
173
|
+
self.parent_appointment_id,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# parent_appointment_id needs to be a different appointment
|
|
178
|
+
if (
|
|
179
|
+
self.parent_appointment_id
|
|
180
|
+
and self.instance_id
|
|
181
|
+
and self.parent_appointment_id == self.instance_id
|
|
182
|
+
):
|
|
183
|
+
errors.append(
|
|
184
|
+
self._create_error_detail(
|
|
185
|
+
"value",
|
|
186
|
+
"parent_appointment_id cannot be the same as instance_id",
|
|
187
|
+
self.parent_appointment_id,
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
175
191
|
if method == "create":
|
|
176
192
|
# Additional required fields for appointment creation
|
|
177
193
|
if not self.start_time:
|
canvas_sdk/effects/note/note.py
CHANGED
|
@@ -67,6 +67,16 @@ class Note(NoteOrAppointmentABC):
|
|
|
67
67
|
None,
|
|
68
68
|
)
|
|
69
69
|
)
|
|
70
|
+
|
|
71
|
+
if self.instance_id and NoteModel.objects.filter(id=self.instance_id).exists():
|
|
72
|
+
errors.append(
|
|
73
|
+
self._create_error_detail(
|
|
74
|
+
"value",
|
|
75
|
+
f"Note with ID {self.instance_id} already exists.",
|
|
76
|
+
self.instance_id,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
70
80
|
elif method == "update":
|
|
71
81
|
if self.note_type_id:
|
|
72
82
|
errors.append(
|
|
@@ -85,14 +95,14 @@ class Note(NoteOrAppointmentABC):
|
|
|
85
95
|
)
|
|
86
96
|
)
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
if self.instance_id and not NoteModel.objects.filter(id=self.instance_id).exists():
|
|
99
|
+
errors.append(
|
|
100
|
+
self._create_error_detail(
|
|
101
|
+
"value",
|
|
102
|
+
f"Note with ID {self.instance_id} does not exist.",
|
|
103
|
+
self.instance_id,
|
|
104
|
+
)
|
|
94
105
|
)
|
|
95
|
-
)
|
|
96
106
|
|
|
97
107
|
if self.note_type_id:
|
|
98
108
|
note_type_category = (
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
3
|
from pydantic_core import InitErrorDetails
|
|
5
4
|
|
|
6
|
-
from
|
|
7
|
-
from canvas_sdk.base import TrackableFieldsModel
|
|
5
|
+
from canvas_sdk.effects.metadata import BaseMetadata
|
|
8
6
|
from canvas_sdk.v1.data import Patient
|
|
9
7
|
|
|
10
8
|
|
|
11
|
-
class PatientMetadata(
|
|
9
|
+
class PatientMetadata(BaseMetadata):
|
|
12
10
|
"""Effect to upsert a Patient Metadata record."""
|
|
13
11
|
|
|
14
12
|
class Meta:
|
|
15
13
|
effect_type = "PATIENT_METADATA"
|
|
16
14
|
|
|
17
15
|
patient_id: str
|
|
18
|
-
key: str
|
|
19
16
|
|
|
20
17
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
21
18
|
errors = super()._get_error_details(method)
|
|
@@ -31,18 +28,5 @@ class PatientMetadata(TrackableFieldsModel):
|
|
|
31
28
|
|
|
32
29
|
return errors
|
|
33
30
|
|
|
34
|
-
def upsert(self, value: str) -> Effect:
|
|
35
|
-
"""Upsert the patient metadata."""
|
|
36
|
-
self._validate_before_effect("upsert")
|
|
37
|
-
|
|
38
|
-
return Effect(
|
|
39
|
-
type=f"UPSERT_{self.Meta.effect_type}",
|
|
40
|
-
payload=json.dumps(
|
|
41
|
-
{
|
|
42
|
-
"data": {**self.values, "value": value},
|
|
43
|
-
}
|
|
44
|
-
),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
31
|
|
|
48
32
|
__exports__ = ("PatientMetadata",)
|
|
@@ -1,70 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from typing import Any
|
|
1
|
+
from canvas_sdk.effects import EffectType
|
|
2
|
+
from canvas_sdk.effects.form import BaseCreateFormEffect, FormField, InputType # noqa
|
|
4
3
|
|
|
5
|
-
from pydantic_core import InitErrorDetails
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class InputType(StrEnum):
|
|
11
|
-
"""Type of input for a form field."""
|
|
12
|
-
|
|
13
|
-
TEXT = "text"
|
|
14
|
-
SELECT = "select"
|
|
15
|
-
DATE = "date"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass
|
|
19
|
-
class FormField:
|
|
20
|
-
"""A class representing a Field."""
|
|
21
|
-
|
|
22
|
-
key: str | None = None
|
|
23
|
-
label: str | None = None
|
|
24
|
-
required: bool = False
|
|
25
|
-
editable: bool = True
|
|
26
|
-
type: InputType = InputType.TEXT
|
|
27
|
-
options: list[str] | None = None
|
|
28
|
-
|
|
29
|
-
def to_dict(self) -> dict[str, Any]:
|
|
30
|
-
"""Convert the Field to a dictionary."""
|
|
31
|
-
return {
|
|
32
|
-
"key": self.key,
|
|
33
|
-
"label": self.label,
|
|
34
|
-
"required": self.required,
|
|
35
|
-
"editable": self.editable,
|
|
36
|
-
"type": self.type.value,
|
|
37
|
-
"options": self.options,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class PatientMetadataCreateFormEffect(_BaseEffect):
|
|
5
|
+
class PatientMetadataCreateFormEffect(BaseCreateFormEffect):
|
|
42
6
|
"""An Effect that will create a form."""
|
|
43
7
|
|
|
44
8
|
class Meta:
|
|
45
9
|
effect_type = EffectType.PATIENT_METADATA__CREATE_ADDITIONAL_FIELDS
|
|
46
10
|
|
|
47
|
-
form_fields: list[FormField]
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def values(self) -> dict[str, Any]:
|
|
51
|
-
"""Return the values of the form as a dictionary."""
|
|
52
|
-
return {"form": [field.to_dict() for field in self.form_fields]}
|
|
53
|
-
|
|
54
|
-
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
55
|
-
errors = super()._get_error_details(method)
|
|
56
|
-
|
|
57
|
-
for field in self.form_fields:
|
|
58
|
-
if field.type != InputType.SELECT and field.options:
|
|
59
|
-
errors.append(
|
|
60
|
-
self._create_error_detail(
|
|
61
|
-
"value",
|
|
62
|
-
"The options attribute is only used for fields of type select",
|
|
63
|
-
field.key,
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
return errors
|
|
68
|
-
|
|
69
11
|
|
|
70
12
|
__exports__ = ("PatientMetadataCreateFormEffect", "FormField", "InputType")
|
|
@@ -8,13 +8,21 @@ from canvas_sdk.handlers import BaseHandler
|
|
|
8
8
|
class Application(BaseHandler, ABC):
|
|
9
9
|
"""An embeddable application that can be registered to Canvas."""
|
|
10
10
|
|
|
11
|
-
RESPONDS_TO = [
|
|
11
|
+
RESPONDS_TO = [
|
|
12
|
+
EventType.Name(EventType.APPLICATION__ON_OPEN),
|
|
13
|
+
EventType.Name(EventType.APPLICATION__ON_CONTEXT_CHANGE),
|
|
14
|
+
]
|
|
12
15
|
|
|
13
16
|
def compute(self) -> list[Effect]:
|
|
14
17
|
"""Handle the application events."""
|
|
15
18
|
match self.event.type:
|
|
16
19
|
case EventType.APPLICATION__ON_OPEN:
|
|
17
20
|
return [self.on_open()] if self.event.target.id == self.identifier else []
|
|
21
|
+
case EventType.APPLICATION__ON_CONTEXT_CHANGE:
|
|
22
|
+
if self.event.target.id == self.identifier:
|
|
23
|
+
effect = self.on_context_change()
|
|
24
|
+
return [effect] if effect is not None else []
|
|
25
|
+
return []
|
|
18
26
|
case _:
|
|
19
27
|
return []
|
|
20
28
|
|
|
@@ -23,6 +31,10 @@ class Application(BaseHandler, ABC):
|
|
|
23
31
|
"""Handle the application open event."""
|
|
24
32
|
...
|
|
25
33
|
|
|
34
|
+
def on_context_change(self) -> Effect | None:
|
|
35
|
+
"""Handle the application context change event."""
|
|
36
|
+
return None
|
|
37
|
+
|
|
26
38
|
@property
|
|
27
39
|
def identifier(self) -> str:
|
|
28
40
|
"""The application identifier."""
|
canvas_sdk/v1/data/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .allergy_intolerance import AllergyIntolerance, AllergyIntoleranceCoding
|
|
2
|
-
from .appointment import Appointment, AppointmentExternalIdentifier
|
|
2
|
+
from .appointment import Appointment, AppointmentExternalIdentifier, AppointmentMetadata
|
|
3
3
|
from .assessment import Assessment
|
|
4
4
|
from .banner_alert import BannerAlert
|
|
5
5
|
from .billing import BillingLineItem, BillingLineItemModifier
|
|
@@ -18,6 +18,12 @@ from .discount import Discount
|
|
|
18
18
|
from .facility import Facility
|
|
19
19
|
from .goal import Goal
|
|
20
20
|
from .imaging import ImagingOrder, ImagingReport, ImagingReview
|
|
21
|
+
from .immunization import (
|
|
22
|
+
Immunization,
|
|
23
|
+
ImmunizationCoding,
|
|
24
|
+
ImmunizationStatement,
|
|
25
|
+
ImmunizationStatementCoding,
|
|
26
|
+
)
|
|
21
27
|
from .invoice import Invoice
|
|
22
28
|
from .lab import (
|
|
23
29
|
LabOrder,
|
|
@@ -93,6 +99,7 @@ from .user import CanvasUser
|
|
|
93
99
|
|
|
94
100
|
__all__ = __exports__ = (
|
|
95
101
|
"Appointment",
|
|
102
|
+
"AppointmentMetadata",
|
|
96
103
|
"AppointmentExternalIdentifier",
|
|
97
104
|
"AllergyIntolerance",
|
|
98
105
|
"AllergyIntoleranceCoding",
|
|
@@ -129,6 +136,10 @@ __all__ = __exports__ = (
|
|
|
129
136
|
"ImagingOrder",
|
|
130
137
|
"ImagingReport",
|
|
131
138
|
"ImagingReview",
|
|
139
|
+
"Immunization",
|
|
140
|
+
"ImmunizationCoding",
|
|
141
|
+
"ImmunizationStatement",
|
|
142
|
+
"ImmunizationStatementCoding",
|
|
132
143
|
"InstallmentPlan",
|
|
133
144
|
"Interview",
|
|
134
145
|
"InterviewQuestionnaireMap",
|
|
@@ -29,6 +29,11 @@ class Appointment(IdentifiableModel):
|
|
|
29
29
|
related_name="appointments",
|
|
30
30
|
null=True,
|
|
31
31
|
)
|
|
32
|
+
|
|
33
|
+
parent_appointment = models.ForeignKey(
|
|
34
|
+
"self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
|
|
35
|
+
)
|
|
36
|
+
|
|
32
37
|
appointment_rescheduled_from = models.ForeignKey(
|
|
33
38
|
"self",
|
|
34
39
|
on_delete=models.DO_NOTHING,
|
|
@@ -74,8 +79,22 @@ class AppointmentExternalIdentifier(IdentifiableModel):
|
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
|
|
82
|
+
class AppointmentMetadata(IdentifiableModel):
|
|
83
|
+
"""A class representing Appointment Metadata."""
|
|
84
|
+
|
|
85
|
+
class Meta:
|
|
86
|
+
db_table = "canvas_sdk_data_api_appointmentmetadata_001"
|
|
87
|
+
|
|
88
|
+
appointment = models.ForeignKey(
|
|
89
|
+
"v1.Appointment", on_delete=models.CASCADE, related_name="metadata", null=True
|
|
90
|
+
)
|
|
91
|
+
key = models.CharField(max_length=32)
|
|
92
|
+
value = models.CharField(max_length=256)
|
|
93
|
+
|
|
94
|
+
|
|
77
95
|
__exports__ = (
|
|
78
96
|
"AppointmentProgressStatus",
|
|
79
97
|
"Appointment",
|
|
98
|
+
"AppointmentMetadata",
|
|
80
99
|
"AppointmentExternalIdentifier",
|
|
81
100
|
)
|