canvas 0.42.0__py3-none-any.whl → 0.44.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.42.0.dist-info → canvas-0.44.0.dist-info}/METADATA +1 -2
- {canvas-0.42.0.dist-info → canvas-0.44.0.dist-info}/RECORD +47 -33
- canvas_cli/apps/auth/storage.py +65 -0
- canvas_cli/apps/auth/utils.py +6 -42
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +12 -0
- canvas_sdk/effects/note/appointment.py +135 -50
- canvas_sdk/effects/note/base.py +108 -10
- canvas_sdk/effects/note/note.py +81 -22
- canvas_sdk/effects/patient/__init__.py +10 -2
- canvas_sdk/effects/patient/base.py +20 -1
- canvas_sdk/effects/patient/create_patient_external_identifier.py +40 -0
- canvas_sdk/effects/protocol_card/__init__.py +4 -1
- canvas_sdk/effects/protocol_card/protocol_card.py +2 -0
- canvas_sdk/utils/http.py +46 -2
- canvas_sdk/v1/data/__init__.py +45 -0
- canvas_sdk/v1/data/allergy_intolerance.py +6 -2
- canvas_sdk/v1/data/assessment.py +7 -3
- canvas_sdk/v1/data/billing.py +1 -1
- canvas_sdk/v1/data/business_line.py +35 -0
- canvas_sdk/v1/data/claim.py +321 -0
- canvas_sdk/v1/data/claim_line_item.py +140 -0
- canvas_sdk/v1/data/command.py +12 -3
- canvas_sdk/v1/data/condition.py +7 -2
- canvas_sdk/v1/data/detected_issue.py +9 -3
- canvas_sdk/v1/data/device.py +9 -3
- canvas_sdk/v1/data/discount.py +21 -0
- canvas_sdk/v1/data/fields.py +37 -0
- canvas_sdk/v1/data/imaging.py +18 -6
- canvas_sdk/v1/data/invoice.py +62 -0
- canvas_sdk/v1/data/lab.py +36 -12
- canvas_sdk/v1/data/line_item_transaction.py +83 -0
- canvas_sdk/v1/data/medication.py +6 -2
- canvas_sdk/v1/data/observation.py +9 -3
- canvas_sdk/v1/data/organization.py +3 -1
- canvas_sdk/v1/data/patient.py +17 -1
- canvas_sdk/v1/data/patient_consent.py +92 -0
- canvas_sdk/v1/data/payment_collection.py +35 -0
- canvas_sdk/v1/data/payor_specific_charge.py +28 -0
- canvas_sdk/v1/data/posting.py +225 -0
- canvas_sdk/v1/data/protocol_override.py +6 -2
- canvas_sdk/v1/data/questionnaire.py +6 -2
- canvas_sdk/v1/data/user.py +2 -0
- canvas_sdk/v1/data/utils.py +10 -0
- protobufs/canvas_generated/messages/effects.proto +7 -0
- {canvas-0.42.0.dist-info → canvas-0.44.0.dist-info}/WHEEL +0 -0
- {canvas-0.42.0.dist-info → canvas-0.44.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from typing import Any
|
|
2
3
|
from uuid import UUID
|
|
3
4
|
|
|
4
5
|
from pydantic_core import InitErrorDetails
|
|
5
6
|
|
|
7
|
+
from canvas_sdk.effects import Effect
|
|
6
8
|
from canvas_sdk.effects.note.base import AppointmentABC
|
|
7
9
|
from canvas_sdk.v1.data import NoteType, Patient
|
|
8
10
|
from canvas_sdk.v1.data.note import NoteTypeCategories
|
|
@@ -13,7 +15,7 @@ class ScheduleEvent(AppointmentABC):
|
|
|
13
15
|
Effect to create a schedule event.
|
|
14
16
|
|
|
15
17
|
Attributes:
|
|
16
|
-
note_type_id (UUID | str): The ID of the note type.
|
|
18
|
+
note_type_id (UUID | str | None): The ID of the note type.
|
|
17
19
|
patient_id (str | None): The ID of the patient, if applicable.
|
|
18
20
|
description (str | None): The description of the schedule event, if applicable.
|
|
19
21
|
"""
|
|
@@ -21,7 +23,7 @@ class ScheduleEvent(AppointmentABC):
|
|
|
21
23
|
class Meta:
|
|
22
24
|
effect_type = "SCHEDULE_EVENT"
|
|
23
25
|
|
|
24
|
-
note_type_id: UUID | str
|
|
26
|
+
note_type_id: UUID | str | None = None
|
|
25
27
|
patient_id: str | None = None
|
|
26
28
|
description: str | None = None
|
|
27
29
|
|
|
@@ -37,68 +39,102 @@ class ScheduleEvent(AppointmentABC):
|
|
|
37
39
|
"""
|
|
38
40
|
errors = super()._get_error_details(method)
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.filter(id=self.note_type_id)
|
|
43
|
-
.first()
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if not note_type:
|
|
42
|
+
# note_type_id is required for create
|
|
43
|
+
if method == "create" and not self.note_type_id:
|
|
47
44
|
errors.append(
|
|
48
45
|
self._create_error_detail(
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
"missing",
|
|
47
|
+
"Field 'note_type_id' is required to create a schedule event.",
|
|
48
|
+
None,
|
|
52
49
|
)
|
|
53
50
|
)
|
|
54
51
|
return errors
|
|
55
52
|
|
|
56
|
-
if
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
self.note_type_id,
|
|
62
|
-
)
|
|
53
|
+
if self.note_type_id:
|
|
54
|
+
note_type = (
|
|
55
|
+
NoteType.objects.values("category", "is_patient_required", "allow_custom_title")
|
|
56
|
+
.filter(id=self.note_type_id)
|
|
57
|
+
.first()
|
|
63
58
|
)
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
if not note_type:
|
|
61
|
+
errors.append(
|
|
62
|
+
self._create_error_detail(
|
|
63
|
+
"value",
|
|
64
|
+
f"Note type with ID {self.note_type_id} does not exist.",
|
|
65
|
+
self.note_type_id,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
return errors
|
|
69
|
+
|
|
70
|
+
if note_type["category"] != NoteTypeCategories.SCHEDULE_EVENT:
|
|
71
|
+
errors.append(
|
|
72
|
+
self._create_error_detail(
|
|
73
|
+
"value",
|
|
74
|
+
f"Schedule event note type must be of type: {NoteTypeCategories.SCHEDULE_EVENT}.",
|
|
75
|
+
self.note_type_id,
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if note_type["is_patient_required"] and not self.patient_id:
|
|
80
|
+
errors.append(
|
|
81
|
+
self._create_error_detail(
|
|
82
|
+
"value",
|
|
83
|
+
"Patient ID is required for this note type.",
|
|
84
|
+
self.note_type_id,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if not note_type["allow_custom_title"] and self.description:
|
|
89
|
+
errors.append(
|
|
90
|
+
self._create_error_detail(
|
|
91
|
+
"value",
|
|
92
|
+
"Description is not allowed for this note type.",
|
|
93
|
+
self.note_type_id,
|
|
94
|
+
)
|
|
71
95
|
)
|
|
72
|
-
)
|
|
73
96
|
|
|
74
|
-
|
|
97
|
+
# Validate patient exists if provided
|
|
98
|
+
if self.patient_id and not Patient.objects.filter(id=self.patient_id).exists():
|
|
75
99
|
errors.append(
|
|
76
100
|
self._create_error_detail(
|
|
77
101
|
"value",
|
|
78
|
-
"
|
|
79
|
-
self.
|
|
102
|
+
f"Patient with ID {self.patient_id} does not exist.",
|
|
103
|
+
self.patient_id,
|
|
80
104
|
)
|
|
81
105
|
)
|
|
82
106
|
|
|
83
107
|
return errors
|
|
84
108
|
|
|
109
|
+
def delete(self) -> Effect:
|
|
110
|
+
"""Send a DELETE effect for the schedule event."""
|
|
111
|
+
self._validate_before_effect("delete")
|
|
112
|
+
return Effect(
|
|
113
|
+
type=f"DELETE_{self.Meta.effect_type}",
|
|
114
|
+
payload=json.dumps(
|
|
115
|
+
{
|
|
116
|
+
"data": self.values,
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
|
|
85
121
|
|
|
86
122
|
class Appointment(AppointmentABC):
|
|
87
123
|
"""
|
|
88
|
-
Effect to create an appointment.
|
|
124
|
+
Effect to create or update an appointment.
|
|
89
125
|
|
|
90
126
|
Attributes:
|
|
91
|
-
appointment_note_type_id (UUID | str): The ID of the appointment note type.
|
|
127
|
+
appointment_note_type_id (UUID | str | None): The ID of the appointment note type.
|
|
92
128
|
meeting_link (str | None): The meeting link for the appointment, if any.
|
|
93
|
-
patient_id (str): The ID of the patient.
|
|
129
|
+
patient_id (str | None): The ID of the patient.
|
|
94
130
|
"""
|
|
95
131
|
|
|
96
132
|
class Meta:
|
|
97
133
|
effect_type = "APPOINTMENT"
|
|
98
134
|
|
|
99
|
-
appointment_note_type_id: UUID | str
|
|
135
|
+
appointment_note_type_id: UUID | str | None = None
|
|
100
136
|
meeting_link: str | None = None
|
|
101
|
-
patient_id: str
|
|
137
|
+
patient_id: str | None = None
|
|
102
138
|
|
|
103
139
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
104
140
|
"""
|
|
@@ -112,37 +148,86 @@ class Appointment(AppointmentABC):
|
|
|
112
148
|
"""
|
|
113
149
|
errors = super()._get_error_details(method)
|
|
114
150
|
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
151
|
+
if method == "create":
|
|
152
|
+
if not self.appointment_note_type_id:
|
|
153
|
+
errors.append(
|
|
154
|
+
self._create_error_detail(
|
|
155
|
+
"missing",
|
|
156
|
+
"Field 'appointment_note_type_id' is required to create an appointment.",
|
|
157
|
+
None,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
if not self.patient_id:
|
|
161
|
+
errors.append(
|
|
162
|
+
self._create_error_detail(
|
|
163
|
+
"missing",
|
|
164
|
+
"Field 'patient_id' is required to create an appointment.",
|
|
165
|
+
None,
|
|
166
|
+
)
|
|
121
167
|
)
|
|
122
|
-
)
|
|
123
168
|
|
|
124
|
-
|
|
125
|
-
id=self.appointment_note_type_id
|
|
126
|
-
)
|
|
127
|
-
if category != NoteTypeCategories.ENCOUNTER:
|
|
169
|
+
if method == "update" and self.patient_id:
|
|
128
170
|
errors.append(
|
|
129
171
|
self._create_error_detail(
|
|
130
172
|
"value",
|
|
131
|
-
|
|
132
|
-
|
|
173
|
+
"Field 'patient_id' cannot be updated for an existing appointment.",
|
|
174
|
+
None,
|
|
133
175
|
)
|
|
134
176
|
)
|
|
135
177
|
|
|
136
|
-
if not
|
|
178
|
+
if self.patient_id and not Patient.objects.filter(id=self.patient_id).exists():
|
|
137
179
|
errors.append(
|
|
138
180
|
self._create_error_detail(
|
|
139
181
|
"value",
|
|
140
|
-
"
|
|
141
|
-
self.
|
|
182
|
+
"Patient with ID {self.patient_id} does not exist.",
|
|
183
|
+
self.patient_id,
|
|
142
184
|
)
|
|
143
185
|
)
|
|
144
186
|
|
|
187
|
+
if self.appointment_note_type_id:
|
|
188
|
+
try:
|
|
189
|
+
category, is_scheduleable = NoteType.objects.values_list(
|
|
190
|
+
"category", "is_scheduleable"
|
|
191
|
+
).get(id=self.appointment_note_type_id)
|
|
192
|
+
if category != NoteTypeCategories.ENCOUNTER:
|
|
193
|
+
errors.append(
|
|
194
|
+
self._create_error_detail(
|
|
195
|
+
"value",
|
|
196
|
+
f"Appointment note type must be of type, {NoteTypeCategories.ENCOUNTER} but got: {category}.",
|
|
197
|
+
self.appointment_note_type_id,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if not is_scheduleable:
|
|
202
|
+
errors.append(
|
|
203
|
+
self._create_error_detail(
|
|
204
|
+
"value",
|
|
205
|
+
"Appointment note type must be scheduleable.",
|
|
206
|
+
self.appointment_note_type_id,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
except NoteType.DoesNotExist:
|
|
210
|
+
errors.append(
|
|
211
|
+
self._create_error_detail(
|
|
212
|
+
"value",
|
|
213
|
+
f"Note type with ID {self.appointment_note_type_id} does not exist.",
|
|
214
|
+
self.appointment_note_type_id,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
145
218
|
return errors
|
|
146
219
|
|
|
220
|
+
def cancel(self) -> Effect:
|
|
221
|
+
"""Send a CANCEL effect for the appointment."""
|
|
222
|
+
self._validate_before_effect("cancel")
|
|
223
|
+
return Effect(
|
|
224
|
+
type=f"CANCEL_{self.Meta.effect_type}",
|
|
225
|
+
payload=json.dumps(
|
|
226
|
+
{
|
|
227
|
+
"data": self.values,
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
|
|
147
232
|
|
|
148
233
|
__exports__ = ("ScheduleEvent", "Appointment")
|
canvas_sdk/effects/note/base.py
CHANGED
|
@@ -10,7 +10,7 @@ from pydantic_core import InitErrorDetails
|
|
|
10
10
|
from canvas_generated.messages.effects_pb2 import Effect
|
|
11
11
|
from canvas_sdk.base import TrackableFieldsModel
|
|
12
12
|
from canvas_sdk.v1.data import PracticeLocation, Staff
|
|
13
|
-
from canvas_sdk.v1.data.appointment import AppointmentProgressStatus
|
|
13
|
+
from canvas_sdk.v1.data.appointment import Appointment, AppointmentProgressStatus
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass
|
|
@@ -32,6 +32,7 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
32
32
|
Base class for all note effects.
|
|
33
33
|
|
|
34
34
|
Attributes:
|
|
35
|
+
instance_id (UUID | str): The unique identifier for the note or appointment instance.
|
|
35
36
|
practice_location_id (UUID | str): The ID of the practice location.
|
|
36
37
|
provider_id (str): The ID of the provider.
|
|
37
38
|
"""
|
|
@@ -39,8 +40,9 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
39
40
|
class Meta:
|
|
40
41
|
effect_type = None
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
instance_id: UUID | str | None = None
|
|
44
|
+
practice_location_id: UUID | str | None = None
|
|
45
|
+
provider_id: str | None = None
|
|
44
46
|
|
|
45
47
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
46
48
|
"""
|
|
@@ -54,7 +56,46 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
54
56
|
"""
|
|
55
57
|
errors = super()._get_error_details(method)
|
|
56
58
|
|
|
57
|
-
if
|
|
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
|
+
if not self.practice_location_id:
|
|
69
|
+
errors.append(
|
|
70
|
+
self._create_error_detail(
|
|
71
|
+
"value",
|
|
72
|
+
"Practice location ID is required.",
|
|
73
|
+
self.practice_location_id,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
if not self.provider_id:
|
|
77
|
+
errors.append(
|
|
78
|
+
self._create_error_detail(
|
|
79
|
+
"value",
|
|
80
|
+
"Provider ID is required.",
|
|
81
|
+
self.provider_id,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
if not self.instance_id:
|
|
87
|
+
errors.append(
|
|
88
|
+
self._create_error_detail(
|
|
89
|
+
"missing",
|
|
90
|
+
"Field 'instance_id' is required to update or cancel/delete a note or appointment.",
|
|
91
|
+
None,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
self.practice_location_id
|
|
97
|
+
and not PracticeLocation.objects.filter(id=self.practice_location_id).exists()
|
|
98
|
+
):
|
|
58
99
|
errors.append(
|
|
59
100
|
self._create_error_detail(
|
|
60
101
|
"value",
|
|
@@ -63,7 +104,7 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
63
104
|
)
|
|
64
105
|
)
|
|
65
106
|
|
|
66
|
-
if not Staff.objects.filter(id=self.provider_id).exists():
|
|
107
|
+
if self.provider_id and not Staff.objects.filter(id=self.provider_id).exists():
|
|
67
108
|
errors.append(
|
|
68
109
|
self._create_error_detail(
|
|
69
110
|
"value",
|
|
@@ -75,7 +116,7 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
75
116
|
return errors
|
|
76
117
|
|
|
77
118
|
def create(self) -> Effect:
|
|
78
|
-
"""
|
|
119
|
+
"""Send a CREATE effect for the note or appointment."""
|
|
79
120
|
self._validate_before_effect("create")
|
|
80
121
|
return Effect(
|
|
81
122
|
type=f"CREATE_{self.Meta.effect_type}",
|
|
@@ -86,10 +127,27 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
|
|
|
86
127
|
),
|
|
87
128
|
)
|
|
88
129
|
|
|
130
|
+
def update(self) -> Effect:
|
|
131
|
+
"""Send an UPDATE effect for the note or appointment."""
|
|
132
|
+
self._validate_before_effect("update")
|
|
133
|
+
|
|
134
|
+
# Check if any fields were actually modified
|
|
135
|
+
if self._dirty_keys == {"instance_id"}:
|
|
136
|
+
raise ValueError("No fields have been modified. Nothing to update.")
|
|
137
|
+
|
|
138
|
+
return Effect(
|
|
139
|
+
type=f"UPDATE_{self.Meta.effect_type}",
|
|
140
|
+
payload=json.dumps(
|
|
141
|
+
{
|
|
142
|
+
"data": self.values,
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
89
147
|
|
|
90
148
|
class AppointmentABC(NoteOrAppointmentABC, ABC):
|
|
91
149
|
"""
|
|
92
|
-
Base class for appointment
|
|
150
|
+
Base class for appointment create/update effects.
|
|
93
151
|
|
|
94
152
|
Attributes:
|
|
95
153
|
start_time (datetime.datetime): The start time of the appointment.
|
|
@@ -105,11 +163,48 @@ class AppointmentABC(NoteOrAppointmentABC, ABC):
|
|
|
105
163
|
"external_identifiers",
|
|
106
164
|
]
|
|
107
165
|
|
|
108
|
-
start_time: datetime.datetime
|
|
109
|
-
duration_minutes: int
|
|
166
|
+
start_time: datetime.datetime | None = None
|
|
167
|
+
duration_minutes: int | None = None
|
|
110
168
|
status: AppointmentProgressStatus | None = None
|
|
111
169
|
external_identifiers: list[AppointmentIdentifier] | None = None
|
|
112
170
|
|
|
171
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
172
|
+
"""Validates the appointment instance and returns a list of error details if validation fails."""
|
|
173
|
+
errors = super()._get_error_details(method)
|
|
174
|
+
|
|
175
|
+
if method == "create":
|
|
176
|
+
# Additional required fields for appointment creation
|
|
177
|
+
if not self.start_time:
|
|
178
|
+
errors.append(
|
|
179
|
+
self._create_error_detail(
|
|
180
|
+
"missing",
|
|
181
|
+
"Field 'start_time' is required to create an appointment.",
|
|
182
|
+
None,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if not self.duration_minutes:
|
|
187
|
+
errors.append(
|
|
188
|
+
self._create_error_detail(
|
|
189
|
+
"missing",
|
|
190
|
+
"Field 'duration_minutes' is required to create an appointment.",
|
|
191
|
+
None,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Validate appointment exists for update/delete
|
|
196
|
+
else:
|
|
197
|
+
if self.instance_id and not Appointment.objects.filter(id=self.instance_id).exists():
|
|
198
|
+
errors.append(
|
|
199
|
+
self._create_error_detail(
|
|
200
|
+
"value",
|
|
201
|
+
f"Appointment with ID {self.instance_id} does not exist.",
|
|
202
|
+
self.instance_id,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return errors
|
|
207
|
+
|
|
113
208
|
@property
|
|
114
209
|
def values(self) -> dict:
|
|
115
210
|
"""
|
|
@@ -117,7 +212,10 @@ class AppointmentABC(NoteOrAppointmentABC, ABC):
|
|
|
117
212
|
"""
|
|
118
213
|
values = super().values
|
|
119
214
|
|
|
120
|
-
|
|
215
|
+
# Handle external_identifiers separately since it's excluded from dirty tracking
|
|
216
|
+
# because it can be a complex type
|
|
217
|
+
# Only include if explicitly set (not None)
|
|
218
|
+
if self.external_identifiers is not None:
|
|
121
219
|
values["external_identifiers"] = [
|
|
122
220
|
{"system": identifier.system, "value": identifier.value}
|
|
123
221
|
for identifier in self.external_identifiers
|
canvas_sdk/effects/note/note.py
CHANGED
|
@@ -5,6 +5,7 @@ from uuid import UUID
|
|
|
5
5
|
from pydantic_core import InitErrorDetails
|
|
6
6
|
|
|
7
7
|
from canvas_sdk.effects.note.base import NoteOrAppointmentABC
|
|
8
|
+
from canvas_sdk.v1.data import Note as NoteModel
|
|
8
9
|
from canvas_sdk.v1.data import NoteType, Patient
|
|
9
10
|
from canvas_sdk.v1.data.note import NoteTypeCategories
|
|
10
11
|
|
|
@@ -22,9 +23,10 @@ class Note(NoteOrAppointmentABC):
|
|
|
22
23
|
class Meta:
|
|
23
24
|
effect_type = "NOTE"
|
|
24
25
|
|
|
25
|
-
note_type_id: UUID | str
|
|
26
|
-
datetime_of_service: datetime.datetime
|
|
27
|
-
patient_id: str
|
|
26
|
+
note_type_id: UUID | str | None = None
|
|
27
|
+
datetime_of_service: datetime.datetime | None = None
|
|
28
|
+
patient_id: str | None = None
|
|
29
|
+
title: str | None = None
|
|
28
30
|
|
|
29
31
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
30
32
|
"""
|
|
@@ -38,33 +40,90 @@ class Note(NoteOrAppointmentABC):
|
|
|
38
40
|
"""
|
|
39
41
|
errors = super()._get_error_details(method)
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
if method == "create":
|
|
44
|
+
if not self.note_type_id:
|
|
45
|
+
errors.append(
|
|
46
|
+
self._create_error_detail(
|
|
47
|
+
"missing",
|
|
48
|
+
"Field 'note_type_id' is required to create a note.",
|
|
49
|
+
None,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
if not self.datetime_of_service:
|
|
54
|
+
errors.append(
|
|
55
|
+
self._create_error_detail(
|
|
56
|
+
"missing",
|
|
57
|
+
"Field 'datetime_of_service' is required to create a note.",
|
|
58
|
+
None,
|
|
59
|
+
)
|
|
51
60
|
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
|
|
62
|
+
if not self.patient_id:
|
|
63
|
+
errors.append(
|
|
64
|
+
self._create_error_detail(
|
|
65
|
+
"missing",
|
|
66
|
+
"Field 'patient_id' is required to create a note.",
|
|
67
|
+
None,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
elif method == "update":
|
|
71
|
+
if self.note_type_id:
|
|
72
|
+
errors.append(
|
|
73
|
+
self._create_error_detail(
|
|
74
|
+
"invalid",
|
|
75
|
+
"Field 'note_type_id' cannot be updated for a note.",
|
|
76
|
+
self.note_type_id,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
if self.patient_id:
|
|
80
|
+
errors.append(
|
|
81
|
+
self._create_error_detail(
|
|
82
|
+
"invalid",
|
|
83
|
+
"Field 'patient_id' cannot be updated for a note.",
|
|
84
|
+
self.patient_id,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if self.instance_id and not NoteModel.objects.filter(id=self.instance_id).exists():
|
|
59
89
|
errors.append(
|
|
60
90
|
self._create_error_detail(
|
|
61
91
|
"value",
|
|
62
|
-
f"
|
|
63
|
-
self.
|
|
92
|
+
f"Note with ID {self.instance_id} does not exist.",
|
|
93
|
+
self.instance_id,
|
|
64
94
|
)
|
|
65
95
|
)
|
|
66
96
|
|
|
67
|
-
if
|
|
97
|
+
if self.note_type_id:
|
|
98
|
+
note_type_category = (
|
|
99
|
+
NoteType.objects.values_list("category", flat=True)
|
|
100
|
+
.filter(id=self.note_type_id)
|
|
101
|
+
.first()
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if not note_type_category:
|
|
105
|
+
errors.append(
|
|
106
|
+
self._create_error_detail(
|
|
107
|
+
"value",
|
|
108
|
+
f"Note type with ID {self.note_type_id} does not exist.",
|
|
109
|
+
self.note_type_id,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
elif note_type_category in (
|
|
113
|
+
NoteTypeCategories.APPOINTMENT,
|
|
114
|
+
NoteTypeCategories.SCHEDULE_EVENT,
|
|
115
|
+
NoteTypeCategories.MESSAGE,
|
|
116
|
+
NoteTypeCategories.LETTER,
|
|
117
|
+
):
|
|
118
|
+
errors.append(
|
|
119
|
+
self._create_error_detail(
|
|
120
|
+
"value",
|
|
121
|
+
f"Visit note type cannot be of type: {note_type_category}.",
|
|
122
|
+
self.note_type_id,
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if self.patient_id and not Patient.objects.filter(id=self.patient_id).exists():
|
|
68
127
|
errors.append(
|
|
69
128
|
self._create_error_detail(
|
|
70
129
|
"value",
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
from canvas_sdk.effects.patient.base import Patient, PatientContactPoint
|
|
1
|
+
from canvas_sdk.effects.patient.base import Patient, PatientContactPoint, PatientExternalIdentifier
|
|
2
|
+
from canvas_sdk.effects.patient.create_patient_external_identifier import (
|
|
3
|
+
CreatePatientExternalIdentifier,
|
|
4
|
+
)
|
|
2
5
|
|
|
3
|
-
__all__ = __exports__ = (
|
|
6
|
+
__all__ = __exports__ = (
|
|
7
|
+
"Patient",
|
|
8
|
+
"PatientContactPoint",
|
|
9
|
+
"PatientExternalIdentifier",
|
|
10
|
+
"CreatePatientExternalIdentifier",
|
|
11
|
+
)
|
|
@@ -32,6 +32,21 @@ class PatientContactPoint:
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class PatientExternalIdentifier:
|
|
37
|
+
"""A class representing a patient external identifier."""
|
|
38
|
+
|
|
39
|
+
value: str
|
|
40
|
+
system: str | None = None
|
|
41
|
+
|
|
42
|
+
def to_dict(self) -> dict[str, Any]:
|
|
43
|
+
"""Convert the external identifier to a dictionary."""
|
|
44
|
+
return {
|
|
45
|
+
"system": self.system,
|
|
46
|
+
"value": self.value,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
35
50
|
class Patient(TrackableFieldsModel):
|
|
36
51
|
"""Effect to create a Patient record."""
|
|
37
52
|
|
|
@@ -53,6 +68,7 @@ class Patient(TrackableFieldsModel):
|
|
|
53
68
|
default_provider_id: str | None = None
|
|
54
69
|
previous_names: list[str] | None = None
|
|
55
70
|
contact_points: list[PatientContactPoint] | None = None
|
|
71
|
+
external_identifiers: list[PatientExternalIdentifier] | None = None
|
|
56
72
|
|
|
57
73
|
@property
|
|
58
74
|
def values(self) -> dict[str, Any]:
|
|
@@ -75,6 +91,9 @@ class Patient(TrackableFieldsModel):
|
|
|
75
91
|
"contact_points": [cp.to_dict() for cp in self.contact_points]
|
|
76
92
|
if self.contact_points
|
|
77
93
|
else None,
|
|
94
|
+
"external_identifiers": [ids.to_dict() for ids in self.external_identifiers]
|
|
95
|
+
if self.external_identifiers
|
|
96
|
+
else None,
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
@@ -120,4 +139,4 @@ class Patient(TrackableFieldsModel):
|
|
|
120
139
|
)
|
|
121
140
|
|
|
122
141
|
|
|
123
|
-
__exports__ = ("Patient", "PatientContactPoint")
|
|
142
|
+
__exports__ = ("Patient", "PatientContactPoint", "PatientExternalIdentifier")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from canvas_generated.messages.effects_pb2 import Effect
|
|
4
|
+
from canvas_sdk.base import TrackableFieldsModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CreatePatientExternalIdentifier(TrackableFieldsModel):
|
|
8
|
+
"""Effect to create a Patient External Identifier record."""
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
effect_type = "PATIENT_EXTERNAL_IDENTIFIER"
|
|
12
|
+
|
|
13
|
+
value: str
|
|
14
|
+
system: str | None = None
|
|
15
|
+
patient_id: str | None = None
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def values(self) -> dict[str, str | None]:
|
|
19
|
+
"""Return the values of the external identifier."""
|
|
20
|
+
return {
|
|
21
|
+
"value": self.value,
|
|
22
|
+
"system": self.system,
|
|
23
|
+
"patient_id": self.patient_id,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def create(self) -> Effect:
|
|
27
|
+
"""Create a new Patient External Identifier."""
|
|
28
|
+
self._validate_before_effect("create")
|
|
29
|
+
|
|
30
|
+
return Effect(
|
|
31
|
+
type=f"CREATE_{self.Meta.effect_type}",
|
|
32
|
+
payload=json.dumps(
|
|
33
|
+
{
|
|
34
|
+
"data": self.values,
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__exports__ = ("CreatePatientExternalIdentifier",)
|