canvas 0.34.0__py3-none-any.whl → 0.35.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.34.0.dist-info → canvas-0.35.0.dist-info}/METADATA +1 -1
- {canvas-0.34.0.dist-info → canvas-0.35.0.dist-info}/RECORD +58 -50
- canvas_generated/messages/effects_pb2.py +4 -4
- canvas_generated/messages/effects_pb2.pyi +22 -2
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +30 -0
- canvas_sdk/base.py +56 -0
- canvas_sdk/commands/base.py +22 -46
- canvas_sdk/commands/commands/adjust_prescription.py +0 -10
- canvas_sdk/commands/commands/allergy.py +0 -1
- canvas_sdk/commands/commands/assess.py +2 -2
- canvas_sdk/commands/commands/change_medication.py +58 -0
- canvas_sdk/commands/commands/close_goal.py +0 -1
- canvas_sdk/commands/commands/diagnose.py +0 -1
- canvas_sdk/commands/commands/exam.py +0 -1
- canvas_sdk/commands/commands/family_history.py +0 -1
- canvas_sdk/commands/commands/follow_up.py +4 -2
- canvas_sdk/commands/commands/goal.py +8 -7
- canvas_sdk/commands/commands/history_present_illness.py +0 -1
- canvas_sdk/commands/commands/imaging_order.py +9 -8
- canvas_sdk/commands/commands/instruct.py +2 -2
- canvas_sdk/commands/commands/lab_order.py +10 -9
- canvas_sdk/commands/commands/medical_history.py +0 -1
- canvas_sdk/commands/commands/medication_statement.py +0 -1
- canvas_sdk/commands/commands/past_surgical_history.py +0 -1
- canvas_sdk/commands/commands/perform.py +3 -2
- canvas_sdk/commands/commands/plan.py +0 -1
- canvas_sdk/commands/commands/prescribe.py +0 -9
- canvas_sdk/commands/commands/refer.py +10 -10
- canvas_sdk/commands/commands/refill.py +0 -9
- canvas_sdk/commands/commands/remove_allergy.py +0 -1
- canvas_sdk/commands/commands/resolve_condition.py +3 -2
- canvas_sdk/commands/commands/review_of_systems.py +0 -1
- canvas_sdk/commands/commands/stop_medication.py +0 -1
- canvas_sdk/commands/commands/structured_assessment.py +0 -1
- canvas_sdk/commands/commands/task.py +0 -4
- canvas_sdk/commands/commands/update_diagnosis.py +8 -6
- canvas_sdk/commands/commands/update_goal.py +0 -1
- canvas_sdk/commands/commands/vitals.py +0 -1
- canvas_sdk/effects/note/__init__.py +10 -0
- canvas_sdk/effects/note/appointment.py +148 -0
- canvas_sdk/effects/note/base.py +129 -0
- canvas_sdk/effects/note/note.py +79 -0
- canvas_sdk/effects/patient/__init__.py +3 -0
- canvas_sdk/effects/patient/base.py +123 -0
- canvas_sdk/utils/http.py +7 -26
- canvas_sdk/utils/metrics.py +192 -0
- canvas_sdk/utils/plugins.py +24 -0
- canvas_sdk/v1/data/__init__.py +4 -0
- canvas_sdk/v1/data/message.py +82 -0
- plugin_runner/load_all_plugins.py +0 -3
- plugin_runner/plugin_runner.py +107 -114
- plugin_runner/sandbox.py +102 -8
- protobufs/canvas_generated/messages/effects.proto +13 -0
- protobufs/canvas_generated/messages/events.proto +16 -0
- settings.py +4 -0
- canvas_sdk/utils/stats.py +0 -74
- {canvas-0.34.0.dist-info → canvas-0.35.0.dist-info}/WHEEL +0 -0
- {canvas-0.34.0.dist-info → canvas-0.35.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
1
3
|
from canvas_sdk.commands.base import _BaseCommand as BaseCommand
|
|
2
4
|
|
|
3
5
|
|
|
@@ -6,13 +8,13 @@ class UpdateDiagnosisCommand(BaseCommand):
|
|
|
6
8
|
|
|
7
9
|
class Meta:
|
|
8
10
|
key = "updateDiagnosis"
|
|
9
|
-
commit_required_fields = (
|
|
10
|
-
"condition_code",
|
|
11
|
-
"new_condition_code",
|
|
12
|
-
)
|
|
13
11
|
|
|
14
|
-
condition_code: str | None =
|
|
15
|
-
|
|
12
|
+
condition_code: str | None = Field(
|
|
13
|
+
default=None, json_schema_extra={"commands_api_name": "condition"}
|
|
14
|
+
)
|
|
15
|
+
new_condition_code: str | None = Field(
|
|
16
|
+
default=None, json_schema_extra={"commands_api_name": "new_condition"}
|
|
17
|
+
)
|
|
16
18
|
background: str | None = None
|
|
17
19
|
narrative: str | None = None
|
|
18
20
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from canvas_sdk.effects.note.appointment import Appointment, ScheduleEvent
|
|
2
|
+
from canvas_sdk.effects.note.base import AppointmentIdentifier
|
|
3
|
+
from canvas_sdk.effects.note.note import Note
|
|
4
|
+
|
|
5
|
+
__all__ = __exports__ = (
|
|
6
|
+
"AppointmentIdentifier",
|
|
7
|
+
"Note",
|
|
8
|
+
"Appointment",
|
|
9
|
+
"ScheduleEvent",
|
|
10
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
|
|
4
|
+
from pydantic_core import InitErrorDetails
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.effects.note.base import AppointmentABC
|
|
7
|
+
from canvas_sdk.v1.data import NoteType, Patient
|
|
8
|
+
from canvas_sdk.v1.data.note import NoteTypeCategories
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ScheduleEvent(AppointmentABC):
|
|
12
|
+
"""
|
|
13
|
+
Effect to create a schedule event.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
note_type_id (UUID | str): The ID of the note type.
|
|
17
|
+
patient_id (str | None): The ID of the patient, if applicable.
|
|
18
|
+
description (str | None): The description of the schedule event, if applicable.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
effect_type = "SCHEDULE_EVENT"
|
|
23
|
+
|
|
24
|
+
note_type_id: UUID | str
|
|
25
|
+
patient_id: str | None = None
|
|
26
|
+
description: str | None = None
|
|
27
|
+
|
|
28
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
29
|
+
"""
|
|
30
|
+
Validates the schedule event note type and returns a list of error details if validation fails.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
method (Any): The method being validated.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list[InitErrorDetails]: A list of error details for invalid schedule event note types.
|
|
37
|
+
"""
|
|
38
|
+
errors = super()._get_error_details(method)
|
|
39
|
+
|
|
40
|
+
note_type = (
|
|
41
|
+
NoteType.objects.values("category", "is_patient_required", "allow_custom_title")
|
|
42
|
+
.filter(id=self.note_type_id)
|
|
43
|
+
.first()
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if not note_type:
|
|
47
|
+
errors.append(
|
|
48
|
+
self._create_error_detail(
|
|
49
|
+
"value",
|
|
50
|
+
f"Note type with ID {self.note_type_id} does not exist.",
|
|
51
|
+
self.note_type_id,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return errors
|
|
55
|
+
|
|
56
|
+
if note_type["category"] != NoteTypeCategories.SCHEDULE_EVENT:
|
|
57
|
+
errors.append(
|
|
58
|
+
self._create_error_detail(
|
|
59
|
+
"value",
|
|
60
|
+
f"Schedule event note type must be of type: {NoteTypeCategories.SCHEDULE_EVENT}.",
|
|
61
|
+
self.note_type_id,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if note_type["is_patient_required"] and not self.patient_id:
|
|
66
|
+
errors.append(
|
|
67
|
+
self._create_error_detail(
|
|
68
|
+
"value",
|
|
69
|
+
"Patient ID is required for this note type.",
|
|
70
|
+
self.note_type_id,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if not note_type["allow_custom_title"] and self.description:
|
|
75
|
+
errors.append(
|
|
76
|
+
self._create_error_detail(
|
|
77
|
+
"value",
|
|
78
|
+
"Description is not allowed for this note type.",
|
|
79
|
+
self.note_type_id,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return errors
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Appointment(AppointmentABC):
|
|
87
|
+
"""
|
|
88
|
+
Effect to create an appointment.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
appointment_note_type_id (UUID | str): The ID of the appointment note type.
|
|
92
|
+
meeting_link (str | None): The meeting link for the appointment, if any.
|
|
93
|
+
patient_id (str): The ID of the patient.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
class Meta:
|
|
97
|
+
effect_type = "APPOINTMENT"
|
|
98
|
+
|
|
99
|
+
appointment_note_type_id: UUID | str
|
|
100
|
+
meeting_link: str | None = None
|
|
101
|
+
patient_id: str
|
|
102
|
+
|
|
103
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
104
|
+
"""
|
|
105
|
+
Validates the appointment note type and returns a list of error details if validation fails.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
method (Any): The method being validated.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
list[InitErrorDetails]: A list of error details for invalid appointment note types or missing patient IDs.
|
|
112
|
+
"""
|
|
113
|
+
errors = super()._get_error_details(method)
|
|
114
|
+
|
|
115
|
+
if not Patient.objects.filter(id=self.patient_id).exists():
|
|
116
|
+
errors.append(
|
|
117
|
+
self._create_error_detail(
|
|
118
|
+
"value",
|
|
119
|
+
"Patient with ID {self.patient_id} does not exist.",
|
|
120
|
+
self.patient_id,
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
category, is_scheduleable = NoteType.objects.values_list("category", "is_scheduleable").get(
|
|
125
|
+
id=self.appointment_note_type_id
|
|
126
|
+
)
|
|
127
|
+
if category != NoteTypeCategories.ENCOUNTER:
|
|
128
|
+
errors.append(
|
|
129
|
+
self._create_error_detail(
|
|
130
|
+
"value",
|
|
131
|
+
f"Appointment note type must be of type, {NoteTypeCategories.ENCOUNTER} but got: {category}.",
|
|
132
|
+
self.appointment_note_type_id,
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if not is_scheduleable:
|
|
137
|
+
errors.append(
|
|
138
|
+
self._create_error_detail(
|
|
139
|
+
"value",
|
|
140
|
+
"Appointment note type must be scheduleable.",
|
|
141
|
+
self.appointment_note_type_id,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return errors
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
__exports__ = ("ScheduleEvent", "Appointment")
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic_core import InitErrorDetails
|
|
9
|
+
|
|
10
|
+
from canvas_generated.messages.effects_pb2 import Effect
|
|
11
|
+
from canvas_sdk.base import TrackableFieldsModel
|
|
12
|
+
from canvas_sdk.v1.data import PracticeLocation, Staff
|
|
13
|
+
from canvas_sdk.v1.data.appointment import AppointmentProgressStatus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AppointmentIdentifier:
|
|
18
|
+
"""
|
|
19
|
+
Dataclass for appointment identifiers.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
system (str): The system identifier for the appointment.
|
|
23
|
+
value (str): The value associated with the system identifier.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
system: str
|
|
27
|
+
value: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
31
|
+
"""
|
|
32
|
+
Base class for all note effects.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
practice_location_id (UUID | str): The ID of the practice location.
|
|
36
|
+
provider_id (str): The ID of the provider.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
class Meta:
|
|
40
|
+
effect_type = None
|
|
41
|
+
|
|
42
|
+
practice_location_id: UUID | str
|
|
43
|
+
provider_id: str
|
|
44
|
+
|
|
45
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
46
|
+
"""
|
|
47
|
+
Validates the practice location and provider IDs and returns a list of error details if validation fails.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
method (Any): The method being validated.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
list[InitErrorDetails]: A list of error details for invalid practice location or provider IDs.
|
|
54
|
+
"""
|
|
55
|
+
errors = super()._get_error_details(method)
|
|
56
|
+
|
|
57
|
+
if not PracticeLocation.objects.filter(id=self.practice_location_id).exists():
|
|
58
|
+
errors.append(
|
|
59
|
+
self._create_error_detail(
|
|
60
|
+
"value",
|
|
61
|
+
f"Practice location with ID {self.practice_location_id} does not exist.",
|
|
62
|
+
self.practice_location_id,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not Staff.objects.filter(id=self.provider_id).exists():
|
|
67
|
+
errors.append(
|
|
68
|
+
self._create_error_detail(
|
|
69
|
+
"value",
|
|
70
|
+
"Provider with ID {self.provider_id} does not exist.",
|
|
71
|
+
self.provider_id,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return errors
|
|
76
|
+
|
|
77
|
+
def create(self) -> Effect:
|
|
78
|
+
"""Originate a new command in the note body."""
|
|
79
|
+
self._validate_before_effect("create")
|
|
80
|
+
return Effect(
|
|
81
|
+
type=f"CREATE_{self.Meta.effect_type}",
|
|
82
|
+
payload=json.dumps(
|
|
83
|
+
{
|
|
84
|
+
"data": self.values,
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AppointmentABC(NoteOrAppointmentABC, ABC):
|
|
91
|
+
"""
|
|
92
|
+
Base class for appointment creation effects.
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
start_time (datetime.datetime): The start time of the appointment.
|
|
96
|
+
duration_minutes (int): The duration of the appointment in minutes.
|
|
97
|
+
status (AppointmentProgressStatus | None): The status of the appointment.
|
|
98
|
+
external_identifiers (list[AppointmentIdentifier] | None): List of external identifiers for the appointment.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
class Meta:
|
|
102
|
+
effect_type = "APPOINTMENT"
|
|
103
|
+
|
|
104
|
+
_dirty_excluded_keys = [
|
|
105
|
+
"external_identifiers",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
start_time: datetime.datetime
|
|
109
|
+
duration_minutes: int
|
|
110
|
+
status: AppointmentProgressStatus | None = None
|
|
111
|
+
external_identifiers: list[AppointmentIdentifier] | None = None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def values(self) -> dict:
|
|
115
|
+
"""
|
|
116
|
+
Returns a dictionary of modified attributes with type-specific transformations.
|
|
117
|
+
"""
|
|
118
|
+
values = super().values
|
|
119
|
+
|
|
120
|
+
if self.external_identifiers:
|
|
121
|
+
values["external_identifiers"] = [
|
|
122
|
+
{"system": identifier.system, "value": identifier.value}
|
|
123
|
+
for identifier in self.external_identifiers
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
return values
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__exports__ = ("AppointmentIdentifier", "NoteOrAppointmentABC", "AppointmentABC")
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from pydantic_core import InitErrorDetails
|
|
6
|
+
|
|
7
|
+
from canvas_sdk.effects.note.base import NoteOrAppointmentABC
|
|
8
|
+
from canvas_sdk.v1.data import NoteType, Patient
|
|
9
|
+
from canvas_sdk.v1.data.note import NoteTypeCategories
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Note(NoteOrAppointmentABC):
|
|
13
|
+
"""
|
|
14
|
+
Effect to create a visit note.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
note_type_id (UUID | str): The ID of the note type.
|
|
18
|
+
datetime_of_service (datetime.datetime): The date and time of the service.
|
|
19
|
+
patient_id (str): The ID of the patient.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
class Meta:
|
|
23
|
+
effect_type = "NOTE"
|
|
24
|
+
|
|
25
|
+
note_type_id: UUID | str
|
|
26
|
+
datetime_of_service: datetime.datetime
|
|
27
|
+
patient_id: str
|
|
28
|
+
|
|
29
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
30
|
+
"""
|
|
31
|
+
Validates the note type category and returns a list of error details if validation fails.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
method (Any): The method being validated.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
list[InitErrorDetails]: A list of error details for invalid note type categories.
|
|
38
|
+
"""
|
|
39
|
+
errors = super()._get_error_details(method)
|
|
40
|
+
|
|
41
|
+
note_type_category = (
|
|
42
|
+
NoteType.objects.values_list("category", flat=True).filter(id=self.note_type_id).first()
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if not note_type_category:
|
|
46
|
+
errors.append(
|
|
47
|
+
self._create_error_detail(
|
|
48
|
+
"value",
|
|
49
|
+
f"Note type with ID {self.note_type_id} does not exist.",
|
|
50
|
+
self.note_type_id,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
elif note_type_category in (
|
|
54
|
+
NoteTypeCategories.APPOINTMENT,
|
|
55
|
+
NoteTypeCategories.SCHEDULE_EVENT,
|
|
56
|
+
NoteTypeCategories.MESSAGE,
|
|
57
|
+
NoteTypeCategories.LETTER,
|
|
58
|
+
):
|
|
59
|
+
errors.append(
|
|
60
|
+
self._create_error_detail(
|
|
61
|
+
"value",
|
|
62
|
+
f"Visit note type cannot be of type: {note_type_category}.",
|
|
63
|
+
self.note_type_id,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if not Patient.objects.filter(id=self.patient_id).exists():
|
|
68
|
+
errors.append(
|
|
69
|
+
self._create_error_detail(
|
|
70
|
+
"value",
|
|
71
|
+
f"Patient with ID {self.patient_id} does not exist.",
|
|
72
|
+
self.patient_id,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return errors
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__exports__ = ("Note",)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic_core import InitErrorDetails
|
|
7
|
+
|
|
8
|
+
from canvas_generated.messages.effects_pb2 import Effect
|
|
9
|
+
from canvas_sdk.base import TrackableFieldsModel
|
|
10
|
+
from canvas_sdk.v1.data import PracticeLocation, Staff
|
|
11
|
+
from canvas_sdk.v1.data.common import ContactPointSystem, ContactPointUse, PersonSex
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class PatientContactPoint:
|
|
16
|
+
"""A class representing a patient contact point."""
|
|
17
|
+
|
|
18
|
+
system: ContactPointSystem
|
|
19
|
+
value: str
|
|
20
|
+
use: ContactPointUse
|
|
21
|
+
rank: int
|
|
22
|
+
has_consent: bool | None = None
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, Any]:
|
|
25
|
+
"""Convert the contact point to a dictionary."""
|
|
26
|
+
return {
|
|
27
|
+
"system": self.system.value,
|
|
28
|
+
"value": self.value,
|
|
29
|
+
"use": self.use.value,
|
|
30
|
+
"rank": self.rank,
|
|
31
|
+
"has_consent": self.has_consent,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Patient(TrackableFieldsModel):
|
|
36
|
+
"""Effect to create a Patient record."""
|
|
37
|
+
|
|
38
|
+
class Meta:
|
|
39
|
+
effect_type = "PATIENT"
|
|
40
|
+
|
|
41
|
+
first_name: str
|
|
42
|
+
last_name: str
|
|
43
|
+
middle_name: str | None = None
|
|
44
|
+
birthdate: datetime.date | None = None
|
|
45
|
+
prefix: str | None = None
|
|
46
|
+
suffix: str | None = None
|
|
47
|
+
sex_at_birth: PersonSex | None = None
|
|
48
|
+
nickname: str | None = None
|
|
49
|
+
social_security_number: str | None = None
|
|
50
|
+
administrative_note: str | None = None
|
|
51
|
+
clinical_note: str | None = None
|
|
52
|
+
default_location_id: str | None = None
|
|
53
|
+
default_provider_id: str | None = None
|
|
54
|
+
previous_names: list[str] | None = None
|
|
55
|
+
contact_points: list[PatientContactPoint] | None = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def values(self) -> dict[str, Any]:
|
|
59
|
+
"""Return the values of the patient as a dictionary."""
|
|
60
|
+
return {
|
|
61
|
+
"first_name": self.first_name,
|
|
62
|
+
"last_name": self.last_name,
|
|
63
|
+
"middle_name": self.middle_name,
|
|
64
|
+
"previous_names": self.previous_names or [],
|
|
65
|
+
"birthdate": self.birthdate.isoformat() if self.birthdate else None,
|
|
66
|
+
"prefix": self.prefix,
|
|
67
|
+
"suffix": self.suffix,
|
|
68
|
+
"sex_at_birth": self.sex_at_birth.value if self.sex_at_birth else None,
|
|
69
|
+
"nickname": self.nickname,
|
|
70
|
+
"social_security_number": self.social_security_number,
|
|
71
|
+
"administrative_note": self.administrative_note,
|
|
72
|
+
"clinical_note": self.clinical_note,
|
|
73
|
+
"default_location": self.default_location_id,
|
|
74
|
+
"default_provider": self.default_provider_id,
|
|
75
|
+
"contact_points": [cp.to_dict() for cp in self.contact_points]
|
|
76
|
+
if self.contact_points
|
|
77
|
+
else None,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
81
|
+
errors = super()._get_error_details(method)
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
self.default_location_id
|
|
85
|
+
and not PracticeLocation.objects.filter(id=self.default_location_id).exists()
|
|
86
|
+
):
|
|
87
|
+
errors.append(
|
|
88
|
+
self._create_error_detail(
|
|
89
|
+
"value",
|
|
90
|
+
f"Practice location with ID {self.default_location_id} does not exist.",
|
|
91
|
+
self.default_location_id,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
self.default_provider_id
|
|
97
|
+
and not Staff.objects.filter(id=self.default_provider_id).exists()
|
|
98
|
+
):
|
|
99
|
+
errors.append(
|
|
100
|
+
self._create_error_detail(
|
|
101
|
+
"value",
|
|
102
|
+
f"Provider with ID {self.default_provider_id} does not exist.",
|
|
103
|
+
self.default_provider_id,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return errors
|
|
108
|
+
|
|
109
|
+
def create(self) -> Effect:
|
|
110
|
+
"""Create a new Patient."""
|
|
111
|
+
self._validate_before_effect("create")
|
|
112
|
+
|
|
113
|
+
return Effect(
|
|
114
|
+
type=f"CREATE_{self.Meta.effect_type}",
|
|
115
|
+
payload=json.dumps(
|
|
116
|
+
{
|
|
117
|
+
"data": self.values,
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__exports__ = ("Patient", "PatientContactPoint")
|
canvas_sdk/utils/http.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import concurrent
|
|
2
2
|
import functools
|
|
3
3
|
import os
|
|
4
|
-
import time
|
|
5
4
|
import urllib.parse
|
|
6
5
|
from collections.abc import Callable, Iterable, Mapping
|
|
7
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
-
from
|
|
9
|
-
from typing import Any, Literal, Protocol, TypeVar, cast
|
|
7
|
+
from typing import Any, Literal, Protocol, TypeVar
|
|
10
8
|
|
|
11
9
|
import requests
|
|
12
10
|
|
|
13
|
-
from canvas_sdk.utils.
|
|
11
|
+
from canvas_sdk.utils.metrics import measured
|
|
14
12
|
|
|
15
13
|
F = TypeVar("F", bound=Callable)
|
|
16
14
|
|
|
@@ -117,24 +115,7 @@ class Http:
|
|
|
117
115
|
self._base_url = base_url
|
|
118
116
|
self._session = requests.Session()
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@staticmethod
|
|
123
|
-
def measure_time(fn: F) -> F:
|
|
124
|
-
"""A decorator to store timing of HTTP calls."""
|
|
125
|
-
|
|
126
|
-
@wraps(fn)
|
|
127
|
-
def wrapper(self: "Http", *args: Any, **kwargs: Any) -> Any:
|
|
128
|
-
start_time = time.time()
|
|
129
|
-
result = fn(self, *args, **kwargs)
|
|
130
|
-
end_time = time.time()
|
|
131
|
-
timing = int((end_time - start_time) * 1000)
|
|
132
|
-
self.statsd_client.timing(f"plugins.http_{fn.__name__}", timing, tags={})
|
|
133
|
-
return result
|
|
134
|
-
|
|
135
|
-
return cast(F, wrapper)
|
|
136
|
-
|
|
137
|
-
@measure_time
|
|
118
|
+
@measured(track_plugins_usage=True)
|
|
138
119
|
def get(
|
|
139
120
|
self, url: str, headers: Mapping[str, str | bytes | None] | None = None
|
|
140
121
|
) -> requests.Response:
|
|
@@ -147,7 +128,7 @@ class Http:
|
|
|
147
128
|
timeout=self._MAX_REQUEST_TIMEOUT_SECONDS,
|
|
148
129
|
)
|
|
149
130
|
|
|
150
|
-
@
|
|
131
|
+
@measured(track_plugins_usage=True)
|
|
151
132
|
def post(
|
|
152
133
|
self,
|
|
153
134
|
url: str,
|
|
@@ -164,7 +145,7 @@ class Http:
|
|
|
164
145
|
timeout=self._MAX_REQUEST_TIMEOUT_SECONDS,
|
|
165
146
|
)
|
|
166
147
|
|
|
167
|
-
@
|
|
148
|
+
@measured(track_plugins_usage=True)
|
|
168
149
|
def put(
|
|
169
150
|
self,
|
|
170
151
|
url: str,
|
|
@@ -181,7 +162,7 @@ class Http:
|
|
|
181
162
|
timeout=self._MAX_REQUEST_TIMEOUT_SECONDS,
|
|
182
163
|
)
|
|
183
164
|
|
|
184
|
-
@
|
|
165
|
+
@measured(track_plugins_usage=True)
|
|
185
166
|
def patch(
|
|
186
167
|
self,
|
|
187
168
|
url: str,
|
|
@@ -198,7 +179,7 @@ class Http:
|
|
|
198
179
|
timeout=self._MAX_REQUEST_TIMEOUT_SECONDS,
|
|
199
180
|
)
|
|
200
181
|
|
|
201
|
-
@
|
|
182
|
+
@measured(track_plugins_usage=True)
|
|
202
183
|
def batch_requests(
|
|
203
184
|
self,
|
|
204
185
|
batch_requests: Iterable[BatchableRequest],
|