canvas 0.63.0__py3-none-any.whl → 0.89.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/METADATA +4 -1
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/RECORD +184 -98
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/WHEEL +1 -1
- canvas_cli/apps/emit/event_fixtures/UNKNOWN.ndjson +1 -0
- canvas_cli/apps/logs/logs.py +386 -22
- canvas_cli/main.py +3 -1
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py +46 -4
- canvas_cli/utils/context/context.py +13 -13
- canvas_cli/utils/validators/manifest_schema.py +26 -1
- canvas_generated/messages/effects_pb2.py +5 -5
- canvas_generated/messages/effects_pb2.pyi +108 -2
- canvas_generated/messages/events_pb2.py +6 -6
- canvas_generated/messages/events_pb2.pyi +282 -2
- canvas_sdk/clients/__init__.py +1 -0
- canvas_sdk/clients/llms/__init__.py +17 -0
- canvas_sdk/clients/llms/libraries/__init__.py +11 -0
- canvas_sdk/clients/llms/libraries/llm_anthropic.py +87 -0
- canvas_sdk/clients/llms/libraries/llm_api.py +143 -0
- canvas_sdk/clients/llms/libraries/llm_google.py +92 -0
- canvas_sdk/clients/llms/libraries/llm_openai.py +98 -0
- canvas_sdk/clients/llms/structures/__init__.py +9 -0
- canvas_sdk/clients/llms/structures/llm_response.py +33 -0
- canvas_sdk/clients/llms/structures/llm_tokens.py +53 -0
- canvas_sdk/clients/llms/structures/llm_turn.py +47 -0
- canvas_sdk/clients/llms/structures/settings/__init__.py +13 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings.py +27 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_anthropic.py +43 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gemini.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt4.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt5.py +48 -0
- canvas_sdk/clients/third_party.py +3 -0
- canvas_sdk/commands/__init__.py +12 -0
- canvas_sdk/commands/base.py +33 -2
- canvas_sdk/commands/commands/adjust_prescription.py +4 -0
- canvas_sdk/commands/commands/custom_command.py +86 -0
- canvas_sdk/commands/commands/family_history.py +17 -1
- canvas_sdk/commands/commands/immunization_statement.py +42 -2
- canvas_sdk/commands/commands/medication_statement.py +16 -1
- canvas_sdk/commands/commands/past_surgical_history.py +16 -1
- canvas_sdk/commands/commands/perform.py +18 -1
- canvas_sdk/commands/commands/prescribe.py +8 -9
- canvas_sdk/commands/commands/refill.py +5 -5
- canvas_sdk/commands/commands/resolve_condition.py +5 -5
- canvas_sdk/commands/commands/review/__init__.py +3 -0
- canvas_sdk/commands/commands/review/base.py +72 -0
- canvas_sdk/commands/commands/review/imaging.py +13 -0
- canvas_sdk/commands/commands/review/lab.py +13 -0
- canvas_sdk/commands/commands/review/referral.py +13 -0
- canvas_sdk/commands/commands/review/uncategorized_document.py +13 -0
- canvas_sdk/commands/validation.py +43 -0
- canvas_sdk/effects/batch_originate.py +22 -0
- canvas_sdk/effects/calendar/__init__.py +13 -3
- canvas_sdk/effects/calendar/{create_calendar.py → calendar.py} +19 -5
- canvas_sdk/effects/calendar/event.py +172 -0
- canvas_sdk/effects/claim_label.py +93 -0
- canvas_sdk/effects/claim_line_item.py +47 -0
- canvas_sdk/effects/claim_queue.py +49 -0
- canvas_sdk/effects/fax/__init__.py +3 -0
- canvas_sdk/effects/fax/base.py +77 -0
- canvas_sdk/effects/fax/note.py +42 -0
- canvas_sdk/effects/metadata.py +15 -1
- canvas_sdk/effects/note/__init__.py +8 -1
- canvas_sdk/effects/note/appointment.py +135 -7
- canvas_sdk/effects/note/base.py +17 -0
- canvas_sdk/effects/note/message.py +22 -14
- canvas_sdk/effects/note/note.py +150 -1
- canvas_sdk/effects/observation/__init__.py +11 -0
- canvas_sdk/effects/observation/base.py +206 -0
- canvas_sdk/effects/patient/__init__.py +2 -0
- canvas_sdk/effects/patient/base.py +8 -0
- canvas_sdk/effects/payment/__init__.py +11 -0
- canvas_sdk/effects/payment/base.py +355 -0
- canvas_sdk/effects/payment/post_claim_payment.py +49 -0
- canvas_sdk/effects/send_contact_verification.py +42 -0
- canvas_sdk/effects/task/__init__.py +2 -1
- canvas_sdk/effects/task/task.py +30 -0
- canvas_sdk/effects/validation/__init__.py +3 -0
- canvas_sdk/effects/validation/base.py +92 -0
- canvas_sdk/events/base.py +15 -0
- canvas_sdk/handlers/application.py +7 -7
- canvas_sdk/handlers/simple_api/api.py +1 -4
- canvas_sdk/handlers/simple_api/websocket.py +1 -4
- canvas_sdk/handlers/utils.py +14 -0
- canvas_sdk/questionnaires/utils.py +1 -0
- canvas_sdk/templates/utils.py +17 -4
- canvas_sdk/test_utils/factories/FACTORY_GUIDE.md +362 -0
- canvas_sdk/test_utils/factories/__init__.py +115 -0
- canvas_sdk/test_utils/factories/calendar.py +24 -0
- canvas_sdk/test_utils/factories/claim.py +81 -0
- canvas_sdk/test_utils/factories/claim_diagnosis_code.py +16 -0
- canvas_sdk/test_utils/factories/coverage.py +17 -0
- canvas_sdk/test_utils/factories/imaging.py +74 -0
- canvas_sdk/test_utils/factories/lab.py +192 -0
- canvas_sdk/test_utils/factories/medication_history.py +75 -0
- canvas_sdk/test_utils/factories/note.py +52 -0
- canvas_sdk/test_utils/factories/organization.py +50 -0
- canvas_sdk/test_utils/factories/practicelocation.py +88 -0
- canvas_sdk/test_utils/factories/referral.py +81 -0
- canvas_sdk/test_utils/factories/staff.py +111 -0
- canvas_sdk/test_utils/factories/task.py +66 -0
- canvas_sdk/test_utils/factories/uncategorized_clinical_document.py +48 -0
- canvas_sdk/utils/metrics.py +4 -1
- canvas_sdk/v1/data/__init__.py +66 -7
- canvas_sdk/v1/data/allergy_intolerance.py +5 -11
- canvas_sdk/v1/data/appointment.py +18 -4
- canvas_sdk/v1/data/assessment.py +2 -12
- canvas_sdk/v1/data/banner_alert.py +2 -4
- canvas_sdk/v1/data/base.py +53 -14
- canvas_sdk/v1/data/billing.py +8 -11
- canvas_sdk/v1/data/calendar.py +64 -0
- canvas_sdk/v1/data/care_team.py +4 -10
- canvas_sdk/v1/data/claim.py +172 -66
- canvas_sdk/v1/data/claim_diagnosis_code.py +19 -0
- canvas_sdk/v1/data/claim_line_item.py +2 -5
- canvas_sdk/v1/data/coding.py +19 -0
- canvas_sdk/v1/data/command.py +2 -4
- canvas_sdk/v1/data/common.py +10 -0
- canvas_sdk/v1/data/compound_medication.py +3 -4
- canvas_sdk/v1/data/condition.py +4 -9
- canvas_sdk/v1/data/coverage.py +66 -26
- canvas_sdk/v1/data/detected_issue.py +20 -20
- canvas_sdk/v1/data/device.py +2 -14
- canvas_sdk/v1/data/discount.py +2 -5
- canvas_sdk/v1/data/encounter.py +44 -0
- canvas_sdk/v1/data/facility.py +1 -0
- canvas_sdk/v1/data/goal.py +2 -14
- canvas_sdk/v1/data/imaging.py +4 -30
- canvas_sdk/v1/data/immunization.py +7 -15
- canvas_sdk/v1/data/lab.py +12 -65
- canvas_sdk/v1/data/line_item_transaction.py +2 -5
- canvas_sdk/v1/data/medication.py +3 -8
- canvas_sdk/v1/data/medication_history.py +142 -0
- canvas_sdk/v1/data/medication_statement.py +41 -0
- canvas_sdk/v1/data/message.py +4 -8
- canvas_sdk/v1/data/note.py +37 -38
- canvas_sdk/v1/data/observation.py +9 -36
- canvas_sdk/v1/data/organization.py +70 -9
- canvas_sdk/v1/data/patient.py +8 -12
- canvas_sdk/v1/data/patient_consent.py +4 -14
- canvas_sdk/v1/data/payment_collection.py +2 -5
- canvas_sdk/v1/data/posting.py +3 -9
- canvas_sdk/v1/data/practicelocation.py +66 -7
- canvas_sdk/v1/data/protocol_override.py +3 -4
- canvas_sdk/v1/data/protocol_result.py +3 -3
- canvas_sdk/v1/data/questionnaire.py +10 -26
- canvas_sdk/v1/data/reason_for_visit.py +2 -6
- canvas_sdk/v1/data/referral.py +41 -17
- canvas_sdk/v1/data/staff.py +34 -26
- canvas_sdk/v1/data/stop_medication_event.py +27 -0
- canvas_sdk/v1/data/task.py +30 -11
- canvas_sdk/v1/data/team.py +2 -4
- canvas_sdk/v1/data/uncategorized_clinical_document.py +84 -0
- canvas_sdk/v1/data/user.py +14 -0
- canvas_sdk/v1/data/utils.py +5 -0
- canvas_sdk/value_set/v2026/__init__.py +1 -0
- canvas_sdk/value_set/v2026/adverse_event.py +157 -0
- canvas_sdk/value_set/v2026/allergy.py +116 -0
- canvas_sdk/value_set/v2026/assessment.py +466 -0
- canvas_sdk/value_set/v2026/communication.py +496 -0
- canvas_sdk/value_set/v2026/condition.py +52934 -0
- canvas_sdk/value_set/v2026/device.py +315 -0
- canvas_sdk/value_set/v2026/diagnostic_study.py +5243 -0
- canvas_sdk/value_set/v2026/encounter.py +2714 -0
- canvas_sdk/value_set/v2026/immunization.py +297 -0
- canvas_sdk/value_set/v2026/individual_characteristic.py +339 -0
- canvas_sdk/value_set/v2026/intervention.py +1703 -0
- canvas_sdk/value_set/v2026/laboratory_test.py +1831 -0
- canvas_sdk/value_set/v2026/medication.py +8218 -0
- canvas_sdk/value_set/v2026/no_qdm_category_assigned.py +26493 -0
- canvas_sdk/value_set/v2026/physical_exam.py +342 -0
- canvas_sdk/value_set/v2026/procedure.py +27869 -0
- canvas_sdk/value_set/v2026/symptom.py +625 -0
- logger/logger.py +30 -31
- logger/logstash.py +282 -0
- logger/pubsub.py +26 -0
- plugin_runner/allowed-module-imports.json +940 -9
- plugin_runner/generate_allowed_imports.py +1 -0
- plugin_runner/installation.py +2 -2
- plugin_runner/plugin_runner.py +21 -24
- plugin_runner/sandbox.py +34 -0
- protobufs/canvas_generated/messages/effects.proto +65 -0
- protobufs/canvas_generated/messages/events.proto +150 -51
- settings.py +27 -11
- canvas_sdk/effects/calendar/create_event.py +0 -43
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Canvas SDK Factory Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to use the existing Canvas SDK test factories and how to create your own custom factories.
|
|
4
|
+
|
|
5
|
+
## Available Factories
|
|
6
|
+
|
|
7
|
+
The Canvas SDK provides the following built-in factories in `canvas_sdk.test_utils.factories`:
|
|
8
|
+
|
|
9
|
+
### Core Entity Factories
|
|
10
|
+
- **PatientFactory** - Creates test patient records
|
|
11
|
+
- **StaffFactory** - Creates staff/provider records
|
|
12
|
+
- **CanvasUserFactory** (UserFactory) - Creates user accounts
|
|
13
|
+
- **OrganizationFactory** - Creates organization entities
|
|
14
|
+
- **FacilityFactory** - Creates healthcare facilities
|
|
15
|
+
- **PracticeLocationFactory** - Creates practice locations
|
|
16
|
+
|
|
17
|
+
### Clinical Data Factories
|
|
18
|
+
- **NoteFactory** - Creates clinical notes (automatically creates initial state)
|
|
19
|
+
- **ClaimFactory** - Creates insurance claims
|
|
20
|
+
- **ClaimDiagnosisCodeFactory** - Creates diagnosis codes for claims
|
|
21
|
+
- **MedicationHistoryMedicationFactory** - Creates medication history records
|
|
22
|
+
- **ProtocolCurrentFactory** - Creates protocol instances
|
|
23
|
+
|
|
24
|
+
### Related Factories
|
|
25
|
+
- **PatientAddressFactory** - Creates patient addresses
|
|
26
|
+
- **StaffAddressFactory** - Creates staff addresses
|
|
27
|
+
- **StaffContactPointFactory** - Creates staff contact info
|
|
28
|
+
- **StaffLicenseFactory** - Creates staff licenses
|
|
29
|
+
- **StaffRoleFactory** - Creates staff role assignments
|
|
30
|
+
- **NoteStateChangeEventFactory** - Creates note state change events
|
|
31
|
+
|
|
32
|
+
## Using Existing Factories
|
|
33
|
+
|
|
34
|
+
### Basic Usage
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from canvas_sdk.test_utils.factories import PatientFactory, StaffFactory, NoteFactory
|
|
38
|
+
|
|
39
|
+
def test_example():
|
|
40
|
+
# Create a patient with random data
|
|
41
|
+
patient = PatientFactory.create()
|
|
42
|
+
|
|
43
|
+
# Access generated fields
|
|
44
|
+
assert patient.first_name is not None
|
|
45
|
+
assert patient.last_name is not None
|
|
46
|
+
assert patient.birth_date is not None
|
|
47
|
+
|
|
48
|
+
# Patient automatically has a user account
|
|
49
|
+
assert patient.user is not None
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Customizing Factory Data
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
def test_with_custom_data():
|
|
56
|
+
# Override specific fields
|
|
57
|
+
patient = PatientFactory.create(
|
|
58
|
+
first_name="John",
|
|
59
|
+
last_name="Doe",
|
|
60
|
+
birth_date=datetime.date(1980, 1, 1)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
assert patient.first_name == "John"
|
|
64
|
+
assert patient.last_name == "Doe"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Creating Multiple Instances
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
def test_multiple_patients():
|
|
71
|
+
# Create 5 patients at once
|
|
72
|
+
patients = PatientFactory.create_batch(5)
|
|
73
|
+
|
|
74
|
+
assert len(patients) == 5
|
|
75
|
+
assert all(p.id is not None for p in patients)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Using Factories with Related Objects
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
def test_note_with_patient():
|
|
82
|
+
# Create a note (automatically creates patient, provider, and location)
|
|
83
|
+
note = NoteFactory.create()
|
|
84
|
+
|
|
85
|
+
assert note.patient is not None
|
|
86
|
+
assert note.provider is not None
|
|
87
|
+
assert note.location is not None
|
|
88
|
+
|
|
89
|
+
# Note automatically has initial state
|
|
90
|
+
assert note.state == NoteStates.NEW
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Creating Related Objects Together
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
def test_note_for_specific_patient():
|
|
97
|
+
# Create a patient first
|
|
98
|
+
patient = PatientFactory.create(first_name="Jane")
|
|
99
|
+
|
|
100
|
+
# Create a note for that specific patient
|
|
101
|
+
note = NoteFactory.create(patient=patient)
|
|
102
|
+
|
|
103
|
+
assert note.patient.first_name == "Jane"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Creating Custom Factories
|
|
107
|
+
|
|
108
|
+
### Basic Custom Factory
|
|
109
|
+
|
|
110
|
+
Here's how to create a custom factory for a model that doesn't have one yet:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import factory
|
|
114
|
+
from factory.fuzzy import FuzzyDate, FuzzyChoice
|
|
115
|
+
import datetime
|
|
116
|
+
|
|
117
|
+
from canvas_sdk.v1.data.appointment import Appointment as AppointmentData
|
|
118
|
+
from canvas_sdk.test_utils.factories import PatientFactory, StaffFactory, PracticeLocationFactory
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class AppointmentFactory(factory.django.DjangoModelFactory):
|
|
122
|
+
"""Factory for creating test Appointments."""
|
|
123
|
+
|
|
124
|
+
class Meta:
|
|
125
|
+
model = AppointmentData
|
|
126
|
+
|
|
127
|
+
# Required relationships
|
|
128
|
+
patient = factory.SubFactory(PatientFactory)
|
|
129
|
+
provider = factory.SubFactory(StaffFactory)
|
|
130
|
+
location = factory.SubFactory(PracticeLocationFactory)
|
|
131
|
+
|
|
132
|
+
# Date/time fields with reasonable defaults
|
|
133
|
+
start_time = factory.LazyFunction(
|
|
134
|
+
lambda: datetime.datetime.now() + datetime.timedelta(days=7)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Use FuzzyChoice for fields with limited options
|
|
138
|
+
duration_minutes = FuzzyChoice([15, 30, 45, 60])
|
|
139
|
+
|
|
140
|
+
# Simple string fields
|
|
141
|
+
comment = factory.Faker("sentence")
|
|
142
|
+
description = factory.Faker("sentence")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Usage of Custom Factory
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
def test_with_custom_appointment_factory():
|
|
149
|
+
"""Example test using the custom AppointmentFactory."""
|
|
150
|
+
# Basic creation
|
|
151
|
+
appointment = AppointmentFactory.create()
|
|
152
|
+
|
|
153
|
+
assert appointment.id is not None
|
|
154
|
+
assert appointment.patient is not None
|
|
155
|
+
assert appointment.provider is not None
|
|
156
|
+
|
|
157
|
+
# With custom values
|
|
158
|
+
specific_patient = PatientFactory.create(first_name="Alice")
|
|
159
|
+
appointment = AppointmentFactory.create(
|
|
160
|
+
patient=specific_patient,
|
|
161
|
+
duration_minutes=60
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
assert appointment.patient.first_name == "Alice"
|
|
165
|
+
assert appointment.duration_minutes == 60
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Advanced Factory Features
|
|
169
|
+
|
|
170
|
+
#### 1. Post-Generation Hooks
|
|
171
|
+
|
|
172
|
+
Use `@factory.post_generation` to perform actions after object creation:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
class TaskFactory(factory.django.DjangoModelFactory):
|
|
176
|
+
class Meta:
|
|
177
|
+
model = Task
|
|
178
|
+
|
|
179
|
+
title = factory.Faker("sentence")
|
|
180
|
+
patient = factory.SubFactory(PatientFactory)
|
|
181
|
+
|
|
182
|
+
@factory.post_generation
|
|
183
|
+
def add_labels(self, create, extracted, **kwargs):
|
|
184
|
+
"""Add labels to the task after creation."""
|
|
185
|
+
if not create:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if extracted:
|
|
189
|
+
# Use provided labels
|
|
190
|
+
for label in extracted:
|
|
191
|
+
self.labels.add(label)
|
|
192
|
+
else:
|
|
193
|
+
# Add default labels
|
|
194
|
+
self.labels.add("test-label")
|
|
195
|
+
|
|
196
|
+
# Usage:
|
|
197
|
+
task = TaskFactory.create(add_labels=["urgent", "review"])
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### 2. Lazy Attributes
|
|
201
|
+
|
|
202
|
+
Use `factory.LazyAttribute` for fields that depend on other fields:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
class StaffFactory(factory.django.DjangoModelFactory):
|
|
206
|
+
class Meta:
|
|
207
|
+
model = Staff
|
|
208
|
+
|
|
209
|
+
first_name = factory.Faker("first_name")
|
|
210
|
+
last_name = factory.Faker("last_name")
|
|
211
|
+
|
|
212
|
+
# Email based on name
|
|
213
|
+
email = factory.LazyAttribute(
|
|
214
|
+
lambda obj: f"{obj.first_name.lower()}.{obj.last_name.lower()}@example.com"
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### 3. Sequences
|
|
219
|
+
|
|
220
|
+
Use `factory.Sequence` for unique values:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
class PatientFactory(factory.django.DjangoModelFactory):
|
|
224
|
+
class Meta:
|
|
225
|
+
model = Patient
|
|
226
|
+
|
|
227
|
+
# Creates patient-1@example.com, patient-2@example.com, etc.
|
|
228
|
+
email = factory.Sequence(lambda n: f"patient-{n}@example.com")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### 4. Conditional SubFactories
|
|
232
|
+
|
|
233
|
+
Use `factory.Maybe` for conditional relationships:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
class NoteFactory(factory.django.DjangoModelFactory):
|
|
237
|
+
class Meta:
|
|
238
|
+
model = Note
|
|
239
|
+
|
|
240
|
+
is_signed = factory.Faker("boolean")
|
|
241
|
+
|
|
242
|
+
# Only create signing provider if note is signed
|
|
243
|
+
signing_provider = factory.Maybe(
|
|
244
|
+
"is_signed",
|
|
245
|
+
yes_declaration=factory.SubFactory(StaffFactory),
|
|
246
|
+
no_declaration=None
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Factory Best Practices
|
|
251
|
+
|
|
252
|
+
### 1. Use Factories for All Test Data
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# Good: Using factories
|
|
256
|
+
def test_patient_lookup():
|
|
257
|
+
patient = PatientFactory.create(first_name="John")
|
|
258
|
+
result = lookup_patient(patient.id)
|
|
259
|
+
assert result.first_name == "John"
|
|
260
|
+
|
|
261
|
+
# Avoid: Manual object creation
|
|
262
|
+
def test_patient_lookup_manual():
|
|
263
|
+
patient = Patient.objects.create(
|
|
264
|
+
first_name="John",
|
|
265
|
+
last_name="Doe",
|
|
266
|
+
birth_date=datetime.date(1980, 1, 1),
|
|
267
|
+
# ... many more required fields
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 2. Create Factories in a Shared Location
|
|
272
|
+
|
|
273
|
+
Place custom factories in a dedicated file:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
tests/
|
|
277
|
+
├── __init__.py
|
|
278
|
+
├── factories.py # Your custom factories
|
|
279
|
+
└── test_my_plugin.py # Your tests
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 3. Override Only What You Need
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
# Good: Only override relevant fields
|
|
286
|
+
patient = PatientFactory.create(first_name="Alice")
|
|
287
|
+
|
|
288
|
+
# Avoid: Overriding everything
|
|
289
|
+
patient = PatientFactory.create(
|
|
290
|
+
first_name="Alice",
|
|
291
|
+
last_name="Smith", # Could use default
|
|
292
|
+
birth_date=datetime.date(1990, 1, 1), # Could use default
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 4. Use build() Instead of create() When Database Isn't Needed
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
# Creates object in database
|
|
300
|
+
patient = PatientFactory.create()
|
|
301
|
+
|
|
302
|
+
# Creates object in memory only (faster for unit tests)
|
|
303
|
+
patient = PatientFactory.build()
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Common Patterns
|
|
307
|
+
|
|
308
|
+
### Testing Protocols with Factories
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from canvas_sdk.test_utils.factories import PatientFactory, NoteFactory
|
|
312
|
+
|
|
313
|
+
def test_my_protocol():
|
|
314
|
+
# Create test data
|
|
315
|
+
patient = PatientFactory.create(
|
|
316
|
+
first_name="Test",
|
|
317
|
+
birth_date=datetime.date(1980, 1, 1)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
note = NoteFactory.create(patient=patient)
|
|
321
|
+
|
|
322
|
+
# Create a mock event
|
|
323
|
+
mock_event = Mock()
|
|
324
|
+
mock_event.context = {"patient": {"id": patient.id}}
|
|
325
|
+
|
|
326
|
+
# Test your protocol
|
|
327
|
+
protocol = MyProtocol(event=mock_event)
|
|
328
|
+
effects = protocol.compute()
|
|
329
|
+
|
|
330
|
+
assert len(effects) > 0
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Testing API Handlers with Factories
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from canvas_sdk.test_utils.factories import PatientFactory, StaffFactory
|
|
337
|
+
|
|
338
|
+
def test_appointment_api():
|
|
339
|
+
# Create test data
|
|
340
|
+
patient = PatientFactory.create()
|
|
341
|
+
provider = StaffFactory.create()
|
|
342
|
+
|
|
343
|
+
# Create API request
|
|
344
|
+
api = AppointmentAPI(event=mock_event)
|
|
345
|
+
api.request = DummyRequest(
|
|
346
|
+
json_body={
|
|
347
|
+
"patient_id": str(patient.id),
|
|
348
|
+
"provider_id": str(provider.id),
|
|
349
|
+
"start_time": "2024-01-01T10:00:00"
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Test the API
|
|
354
|
+
result = api.post()
|
|
355
|
+
assert result[0].status_code == HTTPStatus.CREATED
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## References
|
|
359
|
+
|
|
360
|
+
- [Factory Boy Documentation](https://factoryboy.readthedocs.io/)
|
|
361
|
+
- [Canvas SDK Testing Utils](https://docs.canvasmedical.com/sdk/testing-utils/)
|
|
362
|
+
- [Canvas SDK Factory Source](https://github.com/canvas-medical/canvas-plugins/tree/main/canvas_sdk/test_utils/factories)
|
|
@@ -1,13 +1,128 @@
|
|
|
1
|
+
from .calendar import CalendarFactory, EventFactory
|
|
2
|
+
from .claim import (
|
|
3
|
+
ClaimCommentFactory,
|
|
4
|
+
ClaimCoverageFactory,
|
|
5
|
+
ClaimFactory,
|
|
6
|
+
ClaimLabelFactory,
|
|
7
|
+
ClaimProviderFactory,
|
|
8
|
+
ClaimQueueFactory,
|
|
9
|
+
ClaimSubmissionFactory,
|
|
10
|
+
)
|
|
11
|
+
from .claim_diagnosis_code import ClaimDiagnosisCodeFactory
|
|
12
|
+
from .coverage import CoverageFactory
|
|
1
13
|
from .facility import FacilityFactory
|
|
14
|
+
from .imaging import ImagingOrderFactory, ImagingReportFactory, ImagingReviewFactory
|
|
15
|
+
from .lab import (
|
|
16
|
+
LabOrderFactory,
|
|
17
|
+
LabOrderReasonConditionFactory,
|
|
18
|
+
LabOrderReasonFactory,
|
|
19
|
+
LabPartnerFactory,
|
|
20
|
+
LabPartnerTestFactory,
|
|
21
|
+
LabReportFactory,
|
|
22
|
+
LabReviewFactory,
|
|
23
|
+
LabTestFactory,
|
|
24
|
+
LabValueCodingFactory,
|
|
25
|
+
LabValueFactory,
|
|
26
|
+
)
|
|
27
|
+
from .medication_history import (
|
|
28
|
+
MedicationHistoryMedicationCodingFactory,
|
|
29
|
+
MedicationHistoryMedicationFactory,
|
|
30
|
+
MedicationHistoryResponseFactory,
|
|
31
|
+
)
|
|
32
|
+
from .note import NoteFactory, NoteStateChangeEventFactory, NoteTypeFactory
|
|
33
|
+
from .organization import (
|
|
34
|
+
OrganizationAddressFactory,
|
|
35
|
+
OrganizationContactPointFactory,
|
|
36
|
+
OrganizationFactory,
|
|
37
|
+
)
|
|
2
38
|
from .patient import PatientAddressFactory, PatientFacilityAddressFactory, PatientFactory
|
|
39
|
+
from .practicelocation import (
|
|
40
|
+
PracticeLocationAddressFactory,
|
|
41
|
+
PracticeLocationContactPointFactory,
|
|
42
|
+
PracticeLocationFactory,
|
|
43
|
+
PracticeLocationSettingFactory,
|
|
44
|
+
)
|
|
3
45
|
from .protocol_current import ProtocolCurrentFactory
|
|
46
|
+
from .referral import ReferralFactory, ReferralReportFactory, ReferralReviewFactory
|
|
47
|
+
from .staff import (
|
|
48
|
+
StaffAddressFactory,
|
|
49
|
+
StaffContactPointFactory,
|
|
50
|
+
StaffFactory,
|
|
51
|
+
StaffLicenseFactory,
|
|
52
|
+
StaffPhotoFactory,
|
|
53
|
+
StaffRoleFactory,
|
|
54
|
+
)
|
|
55
|
+
from .task import (
|
|
56
|
+
TaskCommentFactory,
|
|
57
|
+
TaskFactory,
|
|
58
|
+
TaskLabelFactory,
|
|
59
|
+
TaskMetadataFactory,
|
|
60
|
+
TaskTaskLabelFactory,
|
|
61
|
+
)
|
|
62
|
+
from .uncategorized_clinical_document import (
|
|
63
|
+
UncategorizedClinicalDocumentFactory,
|
|
64
|
+
UncategorizedClinicalDocumentReviewFactory,
|
|
65
|
+
)
|
|
4
66
|
from .user import CanvasUserFactory
|
|
5
67
|
|
|
6
68
|
__all__ = (
|
|
69
|
+
"CalendarFactory",
|
|
7
70
|
"CanvasUserFactory",
|
|
71
|
+
"ClaimFactory",
|
|
72
|
+
"ClaimCommentFactory",
|
|
73
|
+
"ClaimCoverageFactory",
|
|
74
|
+
"ClaimDiagnosisCodeFactory",
|
|
75
|
+
"ClaimLabelFactory",
|
|
76
|
+
"ClaimProviderFactory",
|
|
77
|
+
"ClaimQueueFactory",
|
|
78
|
+
"ClaimSubmissionFactory",
|
|
79
|
+
"CoverageFactory",
|
|
80
|
+
"EventFactory",
|
|
8
81
|
"FacilityFactory",
|
|
82
|
+
"ImagingOrderFactory",
|
|
83
|
+
"ImagingReportFactory",
|
|
84
|
+
"ImagingReviewFactory",
|
|
85
|
+
"LabOrderFactory",
|
|
86
|
+
"LabOrderReasonConditionFactory",
|
|
87
|
+
"LabOrderReasonFactory",
|
|
88
|
+
"LabPartnerFactory",
|
|
89
|
+
"LabPartnerTestFactory",
|
|
90
|
+
"LabReportFactory",
|
|
91
|
+
"LabReviewFactory",
|
|
92
|
+
"LabTestFactory",
|
|
93
|
+
"LabValueCodingFactory",
|
|
94
|
+
"LabValueFactory",
|
|
95
|
+
"MedicationHistoryMedicationFactory",
|
|
96
|
+
"MedicationHistoryMedicationCodingFactory",
|
|
97
|
+
"MedicationHistoryResponseFactory",
|
|
98
|
+
"NoteFactory",
|
|
99
|
+
"NoteStateChangeEventFactory",
|
|
100
|
+
"NoteTypeFactory",
|
|
101
|
+
"OrganizationAddressFactory",
|
|
102
|
+
"OrganizationContactPointFactory",
|
|
103
|
+
"OrganizationFactory",
|
|
9
104
|
"PatientAddressFactory",
|
|
10
105
|
"PatientFacilityAddressFactory",
|
|
11
106
|
"PatientFactory",
|
|
107
|
+
"PracticeLocationFactory",
|
|
108
|
+
"PracticeLocationAddressFactory",
|
|
109
|
+
"PracticeLocationContactPointFactory",
|
|
110
|
+
"PracticeLocationSettingFactory",
|
|
12
111
|
"ProtocolCurrentFactory",
|
|
112
|
+
"ReferralFactory",
|
|
113
|
+
"ReferralReportFactory",
|
|
114
|
+
"ReferralReviewFactory",
|
|
115
|
+
"StaffFactory",
|
|
116
|
+
"StaffPhotoFactory",
|
|
117
|
+
"StaffRoleFactory",
|
|
118
|
+
"StaffLicenseFactory",
|
|
119
|
+
"StaffContactPointFactory",
|
|
120
|
+
"StaffAddressFactory",
|
|
121
|
+
"TaskCommentFactory",
|
|
122
|
+
"TaskFactory",
|
|
123
|
+
"TaskLabelFactory",
|
|
124
|
+
"TaskMetadataFactory",
|
|
125
|
+
"TaskTaskLabelFactory",
|
|
126
|
+
"UncategorizedClinicalDocumentFactory",
|
|
127
|
+
"UncategorizedClinicalDocumentReviewFactory",
|
|
13
128
|
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.v1.data import Calendar, Event
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EventFactory(factory.django.DjangoModelFactory[Event]):
|
|
7
|
+
"""Factory for creating an Event."""
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
model = Event
|
|
11
|
+
|
|
12
|
+
title = "Calendar Event"
|
|
13
|
+
calendar = factory.SubFactory("canvas_sdk.test_utils.factories.CalendarFactory")
|
|
14
|
+
starts_at = factory.Faker("date_time")
|
|
15
|
+
ends_at = factory.Faker("date_time")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CalendarFactory(factory.django.DjangoModelFactory[Calendar]):
|
|
19
|
+
"""Factory for creating Calendar."""
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
model = Calendar
|
|
23
|
+
|
|
24
|
+
title = "Calendar"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.v1.data import (
|
|
4
|
+
Claim,
|
|
5
|
+
ClaimComment,
|
|
6
|
+
ClaimCoverage,
|
|
7
|
+
ClaimLabel,
|
|
8
|
+
ClaimProvider,
|
|
9
|
+
ClaimQueue,
|
|
10
|
+
ClaimSubmission,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ClaimCoverageFactory(factory.django.DjangoModelFactory[ClaimCoverage]):
|
|
15
|
+
"""Factory for creating ClaimCoverage."""
|
|
16
|
+
|
|
17
|
+
class Meta:
|
|
18
|
+
model = ClaimCoverage
|
|
19
|
+
|
|
20
|
+
claim = factory.SubFactory("canvas_sdk.test_utils.factories.claim.ClaimFactory")
|
|
21
|
+
coverage = factory.SubFactory("canvas_sdk.test_utils.factories.CoverageFactory")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ClaimQueueFactory(factory.django.DjangoModelFactory[ClaimQueue]):
|
|
25
|
+
"""Factory for creating ClaimQueue."""
|
|
26
|
+
|
|
27
|
+
class Meta:
|
|
28
|
+
model = ClaimQueue
|
|
29
|
+
django_get_or_create = ("queue_sort_ordering",)
|
|
30
|
+
|
|
31
|
+
queue_sort_ordering = 5
|
|
32
|
+
name = "Claim: Filed"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ClaimFactory(factory.django.DjangoModelFactory[Claim]):
|
|
36
|
+
"""Factory for creating Claim."""
|
|
37
|
+
|
|
38
|
+
class Meta:
|
|
39
|
+
model = Claim
|
|
40
|
+
|
|
41
|
+
note = factory.SubFactory("canvas_sdk.test_utils.factories.NoteFactory")
|
|
42
|
+
current_queue = factory.SubFactory(ClaimQueueFactory)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ClaimCommentFactory(factory.django.DjangoModelFactory[ClaimComment]):
|
|
46
|
+
"""Factory for creating ClaimComment."""
|
|
47
|
+
|
|
48
|
+
class Meta:
|
|
49
|
+
model = ClaimComment
|
|
50
|
+
|
|
51
|
+
claim = factory.SubFactory(ClaimFactory)
|
|
52
|
+
comment = "Need to message Dr. House"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ClaimProviderFactory(factory.django.DjangoModelFactory[ClaimProvider]):
|
|
56
|
+
"""Factory for creating ClaimProvider."""
|
|
57
|
+
|
|
58
|
+
class Meta:
|
|
59
|
+
model = ClaimProvider
|
|
60
|
+
|
|
61
|
+
claim = factory.SubFactory(ClaimFactory)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ClaimLabelFactory(factory.django.DjangoModelFactory[ClaimLabel]):
|
|
65
|
+
"""Factory for creating ClaimLabel."""
|
|
66
|
+
|
|
67
|
+
class Meta:
|
|
68
|
+
model = ClaimLabel
|
|
69
|
+
|
|
70
|
+
claim = factory.SubFactory(ClaimFactory)
|
|
71
|
+
label = factory.SubFactory("canvas_sdk.test_utils.factories.TaskLabelFactory")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ClaimSubmissionFactory(factory.django.DjangoModelFactory[ClaimSubmission]):
|
|
75
|
+
"""Factory for creating ClaimSubmission."""
|
|
76
|
+
|
|
77
|
+
class Meta:
|
|
78
|
+
model = ClaimSubmission
|
|
79
|
+
|
|
80
|
+
claim = factory.SubFactory(ClaimFactory)
|
|
81
|
+
clearinghouse_claim_id = "123456"
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
|
16
|
+
claim = factory.SubFactory("canvas_sdk.test_utils.factories.ClaimFactory")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.v1.data import Coverage
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CoverageFactory(factory.django.DjangoModelFactory[Coverage]):
|
|
7
|
+
"""Factory for creating a Coverage."""
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
model = Coverage
|
|
11
|
+
|
|
12
|
+
patient = factory.SubFactory("canvas_sdk.test_utils.factories.PatientFactory")
|
|
13
|
+
plan = "Healthy Families of California"
|
|
14
|
+
group = "San Francisco Mail Carriers"
|
|
15
|
+
coverage_rank = 1
|
|
16
|
+
id_number = "1098659867"
|
|
17
|
+
coverage_start_date = factory.Faker("date")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import factory
|
|
5
|
+
from django.utils import timezone
|
|
6
|
+
from factory.fuzzy import FuzzyDate
|
|
7
|
+
|
|
8
|
+
from canvas_sdk.v1.data import ImagingOrder, ImagingReport, ImagingReview
|
|
9
|
+
from canvas_sdk.v1.data.common import (
|
|
10
|
+
DocumentReviewMode,
|
|
11
|
+
OrderStatus,
|
|
12
|
+
ReviewPatientCommunicationMethod,
|
|
13
|
+
ReviewStatus,
|
|
14
|
+
)
|
|
15
|
+
from canvas_sdk.v1.data.imaging import ImagingReport as ImagingReportModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ImagingOrderFactory(factory.django.DjangoModelFactory[ImagingOrder]):
|
|
19
|
+
"""Factory for creating an ImagingOrder."""
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
model = ImagingOrder
|
|
23
|
+
|
|
24
|
+
patient = factory.SubFactory("canvas_sdk.test_utils.factories.PatientFactory")
|
|
25
|
+
note = factory.SubFactory("canvas_sdk.test_utils.factories.NoteFactory")
|
|
26
|
+
imaging = factory.Faker("text", max_nb_chars=1024)
|
|
27
|
+
imaging_center = None # Optional field
|
|
28
|
+
note_to_radiologist = factory.Faker("text", max_nb_chars=1024)
|
|
29
|
+
internal_comment = factory.Faker("text", max_nb_chars=1024)
|
|
30
|
+
status = OrderStatus.REQUESTED
|
|
31
|
+
date_time_ordered = factory.LazyFunction(lambda: timezone.now())
|
|
32
|
+
priority = factory.Faker("random_element", elements=["routine", "urgent", "stat"])
|
|
33
|
+
ordering_provider = factory.SubFactory("canvas_sdk.test_utils.factories.StaffFactory")
|
|
34
|
+
delegated = False
|
|
35
|
+
task_ids = factory.LazyFunction(lambda: json.dumps([]))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ImagingReviewFactory(factory.django.DjangoModelFactory[ImagingReview]):
|
|
39
|
+
"""Factory for creating an ImagingReview."""
|
|
40
|
+
|
|
41
|
+
class Meta:
|
|
42
|
+
model = ImagingReview
|
|
43
|
+
|
|
44
|
+
patient_communication_method = ReviewPatientCommunicationMethod.DELEGATED_CALL_CAN_LEAVE_MESSAGE
|
|
45
|
+
internal_comment = factory.Faker("text", max_nb_chars=2048)
|
|
46
|
+
message_to_patient = factory.Faker("text", max_nb_chars=2048)
|
|
47
|
+
is_released_to_patient = False
|
|
48
|
+
status = ReviewStatus.STATUS_REVIEWING
|
|
49
|
+
patient = factory.SubFactory("canvas_sdk.test_utils.factories.PatientFactory")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ImagingReportFactory(factory.django.DjangoModelFactory[ImagingReport]):
|
|
53
|
+
"""Factory for creating an ImagingReport."""
|
|
54
|
+
|
|
55
|
+
class Meta:
|
|
56
|
+
model = ImagingReport
|
|
57
|
+
|
|
58
|
+
review_mode = DocumentReviewMode.REVIEW_REQUIRED
|
|
59
|
+
junked = False
|
|
60
|
+
requires_signature = False
|
|
61
|
+
assigned_date = factory.LazyFunction(lambda: timezone.now())
|
|
62
|
+
patient = factory.SubFactory("canvas_sdk.test_utils.factories.PatientFactory")
|
|
63
|
+
order = None # Optional field
|
|
64
|
+
source = ImagingReportModel.ImagingReportSource.DIRECTLY_REPORT
|
|
65
|
+
name = factory.Faker("text", max_nb_chars=255)
|
|
66
|
+
result_date = FuzzyDate(
|
|
67
|
+
start_date=datetime.date.today() - datetime.timedelta(days=365),
|
|
68
|
+
end_date=datetime.date.today(),
|
|
69
|
+
)
|
|
70
|
+
original_date = FuzzyDate(
|
|
71
|
+
start_date=datetime.date.today() - datetime.timedelta(days=365),
|
|
72
|
+
end_date=datetime.date.today(),
|
|
73
|
+
)
|
|
74
|
+
review = None # Optional field
|