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.

Files changed (47) hide show
  1. {canvas-0.42.0.dist-info → canvas-0.44.0.dist-info}/METADATA +1 -2
  2. {canvas-0.42.0.dist-info → canvas-0.44.0.dist-info}/RECORD +47 -33
  3. canvas_cli/apps/auth/storage.py +65 -0
  4. canvas_cli/apps/auth/utils.py +6 -42
  5. canvas_generated/messages/effects_pb2.py +2 -2
  6. canvas_generated/messages/effects_pb2.pyi +12 -0
  7. canvas_sdk/effects/note/appointment.py +135 -50
  8. canvas_sdk/effects/note/base.py +108 -10
  9. canvas_sdk/effects/note/note.py +81 -22
  10. canvas_sdk/effects/patient/__init__.py +10 -2
  11. canvas_sdk/effects/patient/base.py +20 -1
  12. canvas_sdk/effects/patient/create_patient_external_identifier.py +40 -0
  13. canvas_sdk/effects/protocol_card/__init__.py +4 -1
  14. canvas_sdk/effects/protocol_card/protocol_card.py +2 -0
  15. canvas_sdk/utils/http.py +46 -2
  16. canvas_sdk/v1/data/__init__.py +45 -0
  17. canvas_sdk/v1/data/allergy_intolerance.py +6 -2
  18. canvas_sdk/v1/data/assessment.py +7 -3
  19. canvas_sdk/v1/data/billing.py +1 -1
  20. canvas_sdk/v1/data/business_line.py +35 -0
  21. canvas_sdk/v1/data/claim.py +321 -0
  22. canvas_sdk/v1/data/claim_line_item.py +140 -0
  23. canvas_sdk/v1/data/command.py +12 -3
  24. canvas_sdk/v1/data/condition.py +7 -2
  25. canvas_sdk/v1/data/detected_issue.py +9 -3
  26. canvas_sdk/v1/data/device.py +9 -3
  27. canvas_sdk/v1/data/discount.py +21 -0
  28. canvas_sdk/v1/data/fields.py +37 -0
  29. canvas_sdk/v1/data/imaging.py +18 -6
  30. canvas_sdk/v1/data/invoice.py +62 -0
  31. canvas_sdk/v1/data/lab.py +36 -12
  32. canvas_sdk/v1/data/line_item_transaction.py +83 -0
  33. canvas_sdk/v1/data/medication.py +6 -2
  34. canvas_sdk/v1/data/observation.py +9 -3
  35. canvas_sdk/v1/data/organization.py +3 -1
  36. canvas_sdk/v1/data/patient.py +17 -1
  37. canvas_sdk/v1/data/patient_consent.py +92 -0
  38. canvas_sdk/v1/data/payment_collection.py +35 -0
  39. canvas_sdk/v1/data/payor_specific_charge.py +28 -0
  40. canvas_sdk/v1/data/posting.py +225 -0
  41. canvas_sdk/v1/data/protocol_override.py +6 -2
  42. canvas_sdk/v1/data/questionnaire.py +6 -2
  43. canvas_sdk/v1/data/user.py +2 -0
  44. canvas_sdk/v1/data/utils.py +10 -0
  45. protobufs/canvas_generated/messages/effects.proto +7 -0
  46. {canvas-0.42.0.dist-info → canvas-0.44.0.dist-info}/WHEEL +0 -0
  47. {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
- 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:
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
- "value",
50
- f"Note type with ID {self.note_type_id} does not exist.",
51
- self.note_type_id,
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 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
- )
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
- 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,
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
- if not note_type["allow_custom_title"] and self.description:
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
- "Description is not allowed for this note type.",
79
- self.note_type_id,
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 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,
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
- category, is_scheduleable = NoteType.objects.values_list("category", "is_scheduleable").get(
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
- f"Appointment note type must be of type, {NoteTypeCategories.ENCOUNTER} but got: {category}.",
132
- self.appointment_note_type_id,
173
+ "Field 'patient_id' cannot be updated for an existing appointment.",
174
+ None,
133
175
  )
134
176
  )
135
177
 
136
- if not is_scheduleable:
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
- "Appointment note type must be scheduleable.",
141
- self.appointment_note_type_id,
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")
@@ -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
- practice_location_id: UUID | str
43
- provider_id: str
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 not PracticeLocation.objects.filter(id=self.practice_location_id).exists():
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
- """Originate a new command in the note body."""
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 creation effects.
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
- if self.external_identifiers:
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
@@ -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
- note_type_category = (
42
- NoteType.objects.values_list("category", flat=True).filter(id=self.note_type_id).first()
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
- 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,
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
- elif note_type_category in (
54
- NoteTypeCategories.APPOINTMENT,
55
- NoteTypeCategories.SCHEDULE_EVENT,
56
- NoteTypeCategories.MESSAGE,
57
- NoteTypeCategories.LETTER,
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"Visit note type cannot be of type: {note_type_category}.",
63
- self.note_type_id,
92
+ f"Note with ID {self.instance_id} does not exist.",
93
+ self.instance_id,
64
94
  )
65
95
  )
66
96
 
67
- if not Patient.objects.filter(id=self.patient_id).exists():
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__ = ("Patient", "PatientContactPoint")
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",)