canvas 0.62.0__py3-none-any.whl → 0.64.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.
Potentially problematic release.
This version of canvas might be problematic. Click here for more details.
- {canvas-0.62.0.dist-info → canvas-0.64.0.dist-info}/METADATA +1 -1
- {canvas-0.62.0.dist-info → canvas-0.64.0.dist-info}/RECORD +36 -21
- 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 -0
- 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/questionnaires/utils.py +1 -0
- canvas_sdk/test_utils/factories/__init__.py +2 -0
- canvas_sdk/test_utils/factories/claim_diagnosis_code.py +15 -0
- canvas_sdk/v1/data/__init__.py +20 -1
- canvas_sdk/v1/data/appointment.py +19 -0
- canvas_sdk/v1/data/claim_diagnosis_code.py +22 -0
- canvas_sdk/v1/data/encounter.py +46 -0
- canvas_sdk/v1/data/immunization.py +159 -0
- canvas_sdk/v1/data/medication_statement.py +50 -0
- canvas_sdk/v1/data/patient.py +6 -3
- canvas_sdk/v1/data/stop_medication_event.py +36 -0
- plugin_runner/allowed-module-imports.json +64 -0
- protobufs/canvas_generated/messages/effects.proto +20 -0
- protobufs/canvas_generated/messages/events.proto +18 -0
- settings.py +15 -1
- {canvas-0.62.0.dist-info → canvas-0.64.0.dist-info}/WHEEL +0 -0
- {canvas-0.62.0.dist-info → canvas-0.64.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
|
@@ -159,11 +159,35 @@ class AppointmentABC(NoteOrAppointmentABC, ABC):
|
|
|
159
159
|
duration_minutes: int | None = None
|
|
160
160
|
status: AppointmentProgressStatus | None = None
|
|
161
161
|
external_identifiers: list[AppointmentIdentifier] | None = None
|
|
162
|
+
parent_appointment_id: str | UUID | None = None
|
|
162
163
|
|
|
163
164
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
164
165
|
"""Validates the appointment instance and returns a list of error details if validation fails."""
|
|
165
166
|
errors = super()._get_error_details(method)
|
|
166
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
|
+
|
|
167
191
|
if method == "create":
|
|
168
192
|
# Additional required fields for appointment creation
|
|
169
193
|
if not self.start_time:
|
|
@@ -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."""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from .claim_diagnosis_code import ClaimDiagnosisCodeFactory
|
|
1
2
|
from .facility import FacilityFactory
|
|
2
3
|
from .patient import PatientAddressFactory, PatientFacilityAddressFactory, PatientFactory
|
|
3
4
|
from .protocol_current import ProtocolCurrentFactory
|
|
@@ -5,6 +6,7 @@ from .user import CanvasUserFactory
|
|
|
5
6
|
|
|
6
7
|
__all__ = (
|
|
7
8
|
"CanvasUserFactory",
|
|
9
|
+
"ClaimDiagnosisCodeFactory",
|
|
8
10
|
"FacilityFactory",
|
|
9
11
|
"PatientAddressFactory",
|
|
10
12
|
"PatientFacilityAddressFactory",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from factory.fuzzy import FuzzyInteger
|
|
3
|
+
|
|
4
|
+
from canvas_sdk.v1.data import ClaimDiagnosisCode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ClaimDiagnosisCodeFactory(factory.django.DjangoModelFactory[ClaimDiagnosisCode]):
|
|
8
|
+
"""Factory for creating a ClaimDiagnosisCode."""
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
model = ClaimDiagnosisCode
|
|
12
|
+
|
|
13
|
+
rank = FuzzyInteger(1, 12) # ICD-10 allows up to 12 diagnosis codes on a claim
|
|
14
|
+
code = factory.Faker("bothify", text="?##.##") # ICD-10 format like A01.23
|
|
15
|
+
display = factory.Faker("sentence", nb_words=4) # Human readable diagnosis description
|
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
|
|
@@ -7,6 +7,7 @@ from .business_line import BusinessLine
|
|
|
7
7
|
from .care_team import CareTeamMembership, CareTeamRole
|
|
8
8
|
from .charge_description_master import ChargeDescriptionMaster
|
|
9
9
|
from .claim import Claim, ClaimCoverage, ClaimPatient, ClaimQueue, InstallmentPlan
|
|
10
|
+
from .claim_diagnosis_code import ClaimDiagnosisCode
|
|
10
11
|
from .claim_line_item import ClaimLineItem
|
|
11
12
|
from .command import Command
|
|
12
13
|
from .compound_medication import CompoundMedication
|
|
@@ -15,9 +16,16 @@ from .coverage import Coverage, Transactor, TransactorAddress, TransactorPhone
|
|
|
15
16
|
from .detected_issue import DetectedIssue, DetectedIssueEvidence
|
|
16
17
|
from .device import Device
|
|
17
18
|
from .discount import Discount
|
|
19
|
+
from .encounter import Encounter
|
|
18
20
|
from .facility import Facility
|
|
19
21
|
from .goal import Goal
|
|
20
22
|
from .imaging import ImagingOrder, ImagingReport, ImagingReview
|
|
23
|
+
from .immunization import (
|
|
24
|
+
Immunization,
|
|
25
|
+
ImmunizationCoding,
|
|
26
|
+
ImmunizationStatement,
|
|
27
|
+
ImmunizationStatementCoding,
|
|
28
|
+
)
|
|
21
29
|
from .invoice import Invoice
|
|
22
30
|
from .lab import (
|
|
23
31
|
LabOrder,
|
|
@@ -37,6 +45,7 @@ from .line_item_transaction import (
|
|
|
37
45
|
NewLineItemPayment,
|
|
38
46
|
)
|
|
39
47
|
from .medication import Medication, MedicationCoding
|
|
48
|
+
from .medication_statement import MedicationStatement
|
|
40
49
|
from .message import Message, MessageAttachment, MessageTransmission
|
|
41
50
|
from .note import CurrentNoteStateEvent, Note, NoteStateChangeEvent, NoteType
|
|
42
51
|
from .observation import (
|
|
@@ -87,12 +96,14 @@ from .reason_for_visit import ReasonForVisitSettingCoding
|
|
|
87
96
|
from .referral import Referral, ReferralReport
|
|
88
97
|
from .service_provider import ServiceProvider
|
|
89
98
|
from .staff import Staff, StaffAddress, StaffContactPoint, StaffLicense, StaffPhoto, StaffRole
|
|
99
|
+
from .stop_medication_event import StopMedicationEvent
|
|
90
100
|
from .task import Task, TaskComment, TaskLabel, TaskTaskLabel
|
|
91
101
|
from .team import Team, TeamContactPoint
|
|
92
102
|
from .user import CanvasUser
|
|
93
103
|
|
|
94
104
|
__all__ = __exports__ = (
|
|
95
105
|
"Appointment",
|
|
106
|
+
"AppointmentMetadata",
|
|
96
107
|
"AppointmentExternalIdentifier",
|
|
97
108
|
"AllergyIntolerance",
|
|
98
109
|
"AllergyIntoleranceCoding",
|
|
@@ -110,6 +121,7 @@ __all__ = __exports__ = (
|
|
|
110
121
|
"ChargeDescriptionMaster",
|
|
111
122
|
"Claim",
|
|
112
123
|
"ClaimCoverage",
|
|
124
|
+
"ClaimDiagnosisCode",
|
|
113
125
|
"ClaimLineItem",
|
|
114
126
|
"ClaimPatient",
|
|
115
127
|
"ClaimQueue",
|
|
@@ -124,11 +136,16 @@ __all__ = __exports__ = (
|
|
|
124
136
|
"DetectedIssueEvidence",
|
|
125
137
|
"Device",
|
|
126
138
|
"Discount",
|
|
139
|
+
"Encounter",
|
|
127
140
|
"Facility",
|
|
128
141
|
"Goal",
|
|
129
142
|
"ImagingOrder",
|
|
130
143
|
"ImagingReport",
|
|
131
144
|
"ImagingReview",
|
|
145
|
+
"Immunization",
|
|
146
|
+
"ImmunizationCoding",
|
|
147
|
+
"ImmunizationStatement",
|
|
148
|
+
"ImmunizationStatementCoding",
|
|
132
149
|
"InstallmentPlan",
|
|
133
150
|
"Interview",
|
|
134
151
|
"InterviewQuestionnaireMap",
|
|
@@ -147,6 +164,7 @@ __all__ = __exports__ = (
|
|
|
147
164
|
"LineItemTransfer",
|
|
148
165
|
"Medication",
|
|
149
166
|
"MedicationCoding",
|
|
167
|
+
"MedicationStatement",
|
|
150
168
|
"Message",
|
|
151
169
|
"MessageAttachment",
|
|
152
170
|
"MessageTransmission",
|
|
@@ -193,6 +211,7 @@ __all__ = __exports__ = (
|
|
|
193
211
|
"StaffPhoto",
|
|
194
212
|
"StaffRole",
|
|
195
213
|
"StaffContactPoint",
|
|
214
|
+
"StopMedicationEvent",
|
|
196
215
|
"Task",
|
|
197
216
|
"TaskComment",
|
|
198
217
|
"TaskLabel",
|