canvas 0.3.1__py3-none-any.whl → 0.4.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.3.1.dist-info → canvas-0.4.0.dist-info}/METADATA +2 -1
- {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/RECORD +36 -34
- canvas_cli/apps/emit/emit.py +1 -1
- canvas_cli/apps/logs/logs.py +6 -6
- canvas_cli/apps/plugin/plugin.py +11 -7
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +1 -1
- canvas_cli/tests.py +12 -5
- canvas_cli/utils/context/context.py +2 -2
- canvas_cli/utils/context/tests.py +5 -4
- canvas_cli/utils/print/print.py +1 -1
- canvas_cli/utils/print/tests.py +2 -3
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +4 -0
- canvas_sdk/base.py +2 -1
- canvas_sdk/commands/base.py +25 -25
- canvas_sdk/commands/tests/protocol/tests.py +5 -3
- canvas_sdk/commands/tests/test_utils.py +8 -44
- canvas_sdk/commands/tests/unit/tests.py +3 -3
- canvas_sdk/data/client.py +1 -1
- canvas_sdk/effects/banner_alert/tests.py +12 -4
- canvas_sdk/effects/protocol_card/protocol_card.py +1 -1
- canvas_sdk/effects/protocol_card/tests.py +2 -2
- canvas_sdk/protocols/clinical_quality_measure.py +1 -0
- canvas_sdk/utils/http.py +2 -2
- canvas_sdk/v1/data/common.py +46 -0
- canvas_sdk/v1/data/imaging.py +102 -0
- canvas_sdk/v1/data/lab.py +182 -10
- canvas_sdk/v1/data/patient.py +4 -1
- canvas_sdk/v1/data/questionnaire.py +4 -2
- canvas_sdk/value_set/tests/test_value_sets.py +9 -6
- canvas_sdk/value_set/v2022/intervention.py +0 -24
- canvas_sdk/value_set/value_set.py +24 -21
- plugin_runner/plugin_runner.py +59 -8
- plugin_runner/sandbox.py +1 -1
- {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/WHEEL +0 -0
- {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/entry_points.txt +0 -0
canvas_sdk/v1/data/lab.py
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
from django.db import models
|
|
2
2
|
|
|
3
3
|
from canvas_sdk.v1.data.base import CommittableModelManager
|
|
4
|
+
from canvas_sdk.v1.data.condition import Condition
|
|
4
5
|
from canvas_sdk.v1.data.patient import Patient
|
|
6
|
+
|
|
7
|
+
# from canvas_sdk.v1.data.staff import Staff
|
|
5
8
|
from canvas_sdk.v1.data.user import CanvasUser
|
|
6
9
|
|
|
7
10
|
|
|
11
|
+
class TransmissionType(models.TextChoices):
|
|
12
|
+
"""Choices for transmission types."""
|
|
13
|
+
|
|
14
|
+
FAX = "F", "fax"
|
|
15
|
+
HL7 = "H", "hl7"
|
|
16
|
+
MANUAL = "M", "manual"
|
|
17
|
+
|
|
18
|
+
|
|
8
19
|
class LabReport(models.Model):
|
|
9
20
|
"""A class representing a lab report."""
|
|
10
21
|
|
|
@@ -23,19 +34,23 @@ class LabReport(models.Model):
|
|
|
23
34
|
junked = models.BooleanField()
|
|
24
35
|
requires_signature = models.BooleanField()
|
|
25
36
|
assigned_date = models.DateTimeField()
|
|
26
|
-
patient = models.ForeignKey(
|
|
27
|
-
|
|
37
|
+
patient = models.ForeignKey(
|
|
38
|
+
Patient, on_delete=models.DO_NOTHING, related_name="lab_reports", null=True
|
|
39
|
+
)
|
|
40
|
+
transmission_type = models.CharField(choices=TransmissionType)
|
|
28
41
|
for_test_only = models.BooleanField()
|
|
29
42
|
external_id = models.CharField()
|
|
30
43
|
version = models.IntegerField()
|
|
31
44
|
requisition_number = models.CharField()
|
|
32
|
-
review = models.ForeignKey(
|
|
45
|
+
review = models.ForeignKey(
|
|
46
|
+
"LabReview", related_name="reports", on_delete=models.DO_NOTHING, null=True
|
|
47
|
+
)
|
|
33
48
|
original_date = models.DateTimeField()
|
|
34
49
|
date_performed = models.DateTimeField()
|
|
35
50
|
custom_document_name = models.CharField()
|
|
36
51
|
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
37
|
-
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
38
|
-
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
52
|
+
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
53
|
+
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
39
54
|
deleted = models.BooleanField()
|
|
40
55
|
|
|
41
56
|
|
|
@@ -55,12 +70,12 @@ class LabReview(models.Model):
|
|
|
55
70
|
modified = models.DateTimeField()
|
|
56
71
|
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
57
72
|
deleted = models.BooleanField()
|
|
58
|
-
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
59
|
-
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
73
|
+
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
74
|
+
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
60
75
|
internal_comment = models.TextField()
|
|
61
76
|
message_to_patient = models.CharField()
|
|
62
77
|
status = models.CharField()
|
|
63
|
-
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING)
|
|
78
|
+
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_reviews")
|
|
64
79
|
patient_communication_method = models.CharField()
|
|
65
80
|
|
|
66
81
|
|
|
@@ -76,7 +91,9 @@ class LabValue(models.Model):
|
|
|
76
91
|
dbid = models.BigIntegerField(primary_key=True)
|
|
77
92
|
created = models.DateTimeField()
|
|
78
93
|
modified = models.DateTimeField()
|
|
79
|
-
report = models.ForeignKey(
|
|
94
|
+
report = models.ForeignKey(
|
|
95
|
+
"LabReport", related_name="values", on_delete=models.DO_NOTHING, null=True
|
|
96
|
+
)
|
|
80
97
|
value = models.TextField()
|
|
81
98
|
units = models.CharField()
|
|
82
99
|
abnormal_flag = models.CharField()
|
|
@@ -98,7 +115,162 @@ class LabValueCoding(models.Model):
|
|
|
98
115
|
dbid = models.BigIntegerField(primary_key=True)
|
|
99
116
|
created = models.DateTimeField()
|
|
100
117
|
modified = models.DateTimeField()
|
|
101
|
-
value = models.ForeignKey(
|
|
118
|
+
value = models.ForeignKey(
|
|
119
|
+
LabValue, on_delete=models.DO_NOTHING, related_name="codings", null=True
|
|
120
|
+
)
|
|
102
121
|
code = models.CharField()
|
|
103
122
|
name = models.CharField()
|
|
104
123
|
system = models.CharField()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class LabOrder(models.Model):
|
|
127
|
+
"""A class representing a lab order."""
|
|
128
|
+
|
|
129
|
+
class SpecimenCollectionType(models.TextChoices):
|
|
130
|
+
"""Choices for specimen collection types."""
|
|
131
|
+
|
|
132
|
+
ON_LOCATION = "L", "on location"
|
|
133
|
+
PATIENT_SERVICE_CENTER = "P", "patient service center"
|
|
134
|
+
OTHER = "O", "other"
|
|
135
|
+
|
|
136
|
+
class CourtesyCopyType(models.TextChoices):
|
|
137
|
+
"""Choices for courtesy copy types."""
|
|
138
|
+
|
|
139
|
+
ACCOUNT = "A", "account"
|
|
140
|
+
FAX = "F", "fax"
|
|
141
|
+
PATIENT = "P", "patient"
|
|
142
|
+
|
|
143
|
+
class ManualProcessingStatus(models.TextChoices):
|
|
144
|
+
"""Choices for manual processing statuses."""
|
|
145
|
+
|
|
146
|
+
MANUAL_PROCESSING_STATUS_NEEDS_REVIEW = "NEEDS_REVIEW", "Needs Review"
|
|
147
|
+
MANUAL_PROCESSING_STATUS_IN_PROGRESS = "IN_PROGRESS", "In Progress"
|
|
148
|
+
MANUAL_PROCESSING_STATUS_PROCESSED = "PROCESSED", "Processed"
|
|
149
|
+
MANUAL_PROCESSING_STATUS_FLAGGED = "FLAGGED", "Flagged"
|
|
150
|
+
|
|
151
|
+
class Meta:
|
|
152
|
+
managed = False
|
|
153
|
+
app_label = "canvas_sdk"
|
|
154
|
+
db_table = "canvas_sdk_data_api_laborder_001"
|
|
155
|
+
|
|
156
|
+
id = models.UUIDField()
|
|
157
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
158
|
+
created = models.DateTimeField()
|
|
159
|
+
modified = models.DateTimeField()
|
|
160
|
+
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
161
|
+
deleted = models.BooleanField()
|
|
162
|
+
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
163
|
+
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
164
|
+
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_orders")
|
|
165
|
+
ontology_lab_partner = models.CharField()
|
|
166
|
+
# TODO - uncomment when the Note model is finished
|
|
167
|
+
# note = models.ForeignKey("Note", on_delete=models.DO_NOTHING)
|
|
168
|
+
comment = models.CharField()
|
|
169
|
+
requisition_number = models.CharField()
|
|
170
|
+
is_patient_bill = models.BooleanField(null=True)
|
|
171
|
+
date_ordered = models.DateTimeField()
|
|
172
|
+
fasting_status = models.BooleanField(null=True)
|
|
173
|
+
specimen_collection_type = models.CharField(choices=SpecimenCollectionType, null=True)
|
|
174
|
+
transmission_type = models.CharField(choices=TransmissionType, null=True)
|
|
175
|
+
courtesy_copy_type = models.CharField(choices=CourtesyCopyType, null=True)
|
|
176
|
+
courtesy_copy_number = models.CharField()
|
|
177
|
+
courtesy_copy_text = models.CharField()
|
|
178
|
+
# TODO - uncomment when Staff model is added
|
|
179
|
+
# ordering_provider = models.ForeignKey(Staff, on_delete=models.DO_NOTHING, null=True)
|
|
180
|
+
parent_order = models.ForeignKey("LabOrder", on_delete=models.DO_NOTHING, null=True)
|
|
181
|
+
healthgorilla_id = models.CharField()
|
|
182
|
+
manual_processing_status = models.CharField(choices=ManualProcessingStatus)
|
|
183
|
+
manual_processing_comment = models.CharField()
|
|
184
|
+
labcorp_abn_url = models.URLField()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class LabOrderReason(models.Model):
|
|
188
|
+
"""A class representing a lab order reason."""
|
|
189
|
+
|
|
190
|
+
class LabReasonMode(models.TextChoices):
|
|
191
|
+
"""Choices for lab order reasons."""
|
|
192
|
+
|
|
193
|
+
MONITOR = "MO", "monitor"
|
|
194
|
+
INVESTIGATE = "IN", "investigate"
|
|
195
|
+
SCREEN_FOR = "SF", "screen for"
|
|
196
|
+
UNKNOWN = "UNK", "unknown"
|
|
197
|
+
|
|
198
|
+
class Meta:
|
|
199
|
+
managed = False
|
|
200
|
+
app_label = "canvas_sdk"
|
|
201
|
+
db_table = "canvas_sdk_data_api_laborderreason_001"
|
|
202
|
+
|
|
203
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
204
|
+
created = models.DateTimeField()
|
|
205
|
+
modified = models.DateTimeField()
|
|
206
|
+
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
207
|
+
deleted = models.BooleanField()
|
|
208
|
+
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
209
|
+
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
210
|
+
order = models.ForeignKey(LabOrder, on_delete=models.DO_NOTHING, related_name="reasons")
|
|
211
|
+
mode = models.CharField(max_length=30, choices=LabReasonMode)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class LabOrderReasonCondition(models.Model):
|
|
215
|
+
"""A class representing a lab order reason's condition."""
|
|
216
|
+
|
|
217
|
+
class Meta:
|
|
218
|
+
managed = False
|
|
219
|
+
app_label = "canvas_sdk"
|
|
220
|
+
db_table = "canvas_sdk_data_api_laborderreasoncondition_001"
|
|
221
|
+
|
|
222
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
223
|
+
created = models.DateTimeField()
|
|
224
|
+
modified = models.DateTimeField()
|
|
225
|
+
reason = models.ForeignKey(
|
|
226
|
+
LabOrderReason, on_delete=models.DO_NOTHING, related_name="reason_conditions", null=True
|
|
227
|
+
)
|
|
228
|
+
condition = models.ForeignKey(
|
|
229
|
+
Condition,
|
|
230
|
+
on_delete=models.DO_NOTHING,
|
|
231
|
+
related_name="lab_order_reason_conditions",
|
|
232
|
+
null=True,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class LabTest(models.Model):
|
|
237
|
+
"""A class representing a lab test."""
|
|
238
|
+
|
|
239
|
+
class LabTestOrderStatus(models.TextChoices):
|
|
240
|
+
"""Choicees for lab test order statuses."""
|
|
241
|
+
|
|
242
|
+
NEW = "NE", "new"
|
|
243
|
+
STAGED_FOR_REQUISITION = "SR", "staged for requisition"
|
|
244
|
+
SENDING = "SE", "sending"
|
|
245
|
+
SENDING_FAILED = "SF", "sending failed"
|
|
246
|
+
PROCESSING = "PR", "processing"
|
|
247
|
+
PROCESSING_FAILED = "PF", "processing failed"
|
|
248
|
+
RECEIVED = "RE", "received"
|
|
249
|
+
REVIEWED = "RV", "reviewed"
|
|
250
|
+
INACTIVE = "IN", "inactive"
|
|
251
|
+
|
|
252
|
+
class Meta:
|
|
253
|
+
managed = False
|
|
254
|
+
app_label = "canvas_sdk"
|
|
255
|
+
db_table = "canvas_sdk_data_api_labtest_001"
|
|
256
|
+
|
|
257
|
+
id = models.UUIDField()
|
|
258
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
259
|
+
ontology_test_name = models.CharField(max_length=512, blank=True, default="")
|
|
260
|
+
ontology_test_code = models.CharField(max_length=512, blank=True, default="")
|
|
261
|
+
status = models.CharField(max_length=30, choices=LabTestOrderStatus)
|
|
262
|
+
report = models.ForeignKey(
|
|
263
|
+
LabReport, on_delete=models.DO_NOTHING, null=True, related_name="tests"
|
|
264
|
+
)
|
|
265
|
+
aoe_code = models.CharField(max_length=10, default="")
|
|
266
|
+
procedure_class = models.CharField(max_length=10, default="")
|
|
267
|
+
specimen_type = models.CharField()
|
|
268
|
+
specimen_source_code = models.CharField()
|
|
269
|
+
specimen_source_description = models.CharField()
|
|
270
|
+
specimen_source_coding_system = models.CharField()
|
|
271
|
+
order = models.ForeignKey(
|
|
272
|
+
LabOrder, on_delete=models.DO_NOTHING, related_name="tests", null=True
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def __str__(self) -> str:
|
|
276
|
+
return f"{self.ontology_test_name}: f{self.ontology_test_code}"
|
canvas_sdk/v1/data/patient.py
CHANGED
|
@@ -49,4 +49,7 @@ class Patient(models.Model):
|
|
|
49
49
|
@classmethod
|
|
50
50
|
def find(cls, id: str) -> Self:
|
|
51
51
|
"""Find a patient by id."""
|
|
52
|
-
return cls.
|
|
52
|
+
return cls._default_manager.get(id=id)
|
|
53
|
+
|
|
54
|
+
def __str__(self) -> str:
|
|
55
|
+
return f"{self.first_name} {self.last_name}"
|
|
@@ -109,7 +109,7 @@ class Questionnaire(models.Model):
|
|
|
109
109
|
code_system = models.CharField()
|
|
110
110
|
code = models.CharField()
|
|
111
111
|
search_tags = models.CharField()
|
|
112
|
-
questions = models.ManyToManyField(Question, through="QuestionnaireQuestionMap")
|
|
112
|
+
questions = models.ManyToManyField(Question, through="canvas_sdk.QuestionnaireQuestionMap") # type: ignore[misc, var-annotated]
|
|
113
113
|
use_in_shx = models.BooleanField()
|
|
114
114
|
carry_forward = models.TextField()
|
|
115
115
|
|
|
@@ -152,7 +152,9 @@ class Interview(models.Model):
|
|
|
152
152
|
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="interviews")
|
|
153
153
|
note_id = models.BigIntegerField()
|
|
154
154
|
appointment_id = models.BigIntegerField()
|
|
155
|
-
questionnaires = models.ManyToManyField(
|
|
155
|
+
questionnaires = models.ManyToManyField( # type: ignore[var-annotated]
|
|
156
|
+
Questionnaire, through="canvas_sdk.InterviewQuestionnaireMap" # type: ignore[misc]
|
|
157
|
+
)
|
|
156
158
|
progress_status = models.CharField()
|
|
157
159
|
created = models.DateTimeField()
|
|
158
160
|
modified = models.DateTimeField()
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
1
|
from canvas_sdk.value_set.v2022.condition import (
|
|
4
2
|
DisordersOfTheImmuneSystem,
|
|
5
3
|
EncephalopathyDueToChildhoodVaccination,
|
|
6
4
|
Rhabdomyolysis,
|
|
7
5
|
StableAndUnstableAngina,
|
|
8
6
|
)
|
|
7
|
+
from canvas_sdk.value_set.value_set import CombinedValueSet
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def test_value_set_class_values_property() -> None:
|
|
@@ -15,7 +14,9 @@ def test_value_set_class_values_property() -> None:
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
def test_value_set_class_pipe_operator_with_two_value_sets() -> None:
|
|
18
|
-
combined_value_set =
|
|
17
|
+
combined_value_set: CombinedValueSet = (
|
|
18
|
+
DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination
|
|
19
|
+
)
|
|
19
20
|
|
|
20
21
|
both_classes_icd_10_codes = DisordersOfTheImmuneSystem.ICD10CM.union(
|
|
21
22
|
EncephalopathyDueToChildhoodVaccination.ICD10CM
|
|
@@ -29,7 +30,7 @@ def test_value_set_class_pipe_operator_with_two_value_sets() -> None:
|
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def test_value_set_class_pipe_operator_with_three_value_sets() -> None:
|
|
32
|
-
combined_value_set = (
|
|
33
|
+
combined_value_set: CombinedValueSet = (
|
|
33
34
|
DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination | Rhabdomyolysis
|
|
34
35
|
)
|
|
35
36
|
|
|
@@ -45,8 +46,10 @@ def test_value_set_class_pipe_operator_with_three_value_sets() -> None:
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def test_value_set_class_pipe_operator_with_two_combined_value_sets() -> None:
|
|
48
|
-
combined_value_set_1 =
|
|
49
|
-
|
|
49
|
+
combined_value_set_1: CombinedValueSet = (
|
|
50
|
+
DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination
|
|
51
|
+
)
|
|
52
|
+
combined_value_set_2: CombinedValueSet = Rhabdomyolysis | StableAndUnstableAngina
|
|
50
53
|
|
|
51
54
|
combined_value_set = combined_value_set_1 | combined_value_set_2
|
|
52
55
|
|
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
from ..value_set import ValueSet
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class HospiceCareAmbulatory(ValueSet):
|
|
5
|
-
"""
|
|
6
|
-
**Clinical Focus:** The purpose of this value set is to represent concepts of interventions to identify patients receiving hospice care outside of a hospital or long term care facility.
|
|
7
|
-
|
|
8
|
-
**Data Element Scope:** This value set may use a model element related to Procedure or Intervention.
|
|
9
|
-
|
|
10
|
-
**Inclusion Criteria:** Includes concepts that represent a procedure or intervention for hospice care.
|
|
11
|
-
|
|
12
|
-
**Exclusion Criteria:** Excludes concepts that represent palliative care or comfort measures.
|
|
13
|
-
|
|
14
|
-
** Used in:** CMS90v11, CMS134v10, CMS165v10, CMS146v10, CMS124v10, CMS139v10, CMS154v10, CMS56v10, CMS74v11, CMS75v10, CMS137v10, CMS136v11, CMS128v10, CMS122v10, CMS153v10, CMS66v10, CMS130v10, CMS155v10, CMS127v10, CMS117v10, CMS131v10, CMS156v10, CMS125v10
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
VALUE_SET_NAME = "Hospice care ambulatory"
|
|
18
|
-
OID = "2.16.840.1.113762.1.4.1108.15"
|
|
19
|
-
DEFINITION_VERSION = "20170504"
|
|
20
|
-
EXPANSION_VERSION = "eCQM Update 2021-05-06"
|
|
21
|
-
|
|
22
|
-
SNOMEDCT = {
|
|
23
|
-
"385763009", # Hospice care (regime/therapy)
|
|
24
|
-
"385765002", # Hospice care management (procedure)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
4
|
class PalliativeCareIntervention(ValueSet):
|
|
29
5
|
"""
|
|
30
6
|
**Clinical Focus:** The purpose of this value set is to represent concepts for palliative care interventions.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Union, cast
|
|
3
|
+
|
|
4
|
+
from django.utils.functional import classproperty
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class CodeConstants:
|
|
@@ -33,7 +35,7 @@ class CodeConstants:
|
|
|
33
35
|
URL_NDC = "http://hl7.org/fhir/sid/ndc"
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
class
|
|
38
|
+
class CodeConstantsURLMappingMixin:
|
|
37
39
|
"""A class that maps code systems to their URLs."""
|
|
38
40
|
|
|
39
41
|
CODE_SYSTEM_MAPPING = {
|
|
@@ -53,21 +55,21 @@ class CodeConstantsURLMapping:
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
class CombinedValueSet(
|
|
58
|
+
class CombinedValueSet(CodeConstantsURLMappingMixin):
|
|
57
59
|
"""A class representing a combination of two value sets."""
|
|
58
60
|
|
|
59
61
|
def __init__(
|
|
60
62
|
self,
|
|
61
|
-
value_set_1: Union["ValueSet", "CombinedValueSet"],
|
|
62
|
-
value_set_2: Union["ValueSet", "CombinedValueSet"],
|
|
63
|
+
value_set_1: Union[type["ValueSet"], "CombinedValueSet"],
|
|
64
|
+
value_set_2: Union[type["ValueSet"], "CombinedValueSet"],
|
|
63
65
|
) -> None:
|
|
64
66
|
self.value_set_1 = value_set_1
|
|
65
67
|
self.value_set_2 = value_set_2
|
|
66
68
|
|
|
67
69
|
@property
|
|
68
|
-
def values(self) ->
|
|
70
|
+
def values(self) -> dict[str, set]:
|
|
69
71
|
"""A property that returns the combined values from both value sets."""
|
|
70
|
-
values:
|
|
72
|
+
values: dict[str, set] = defaultdict(set)
|
|
71
73
|
|
|
72
74
|
for vs in [self.value_set_1, self.value_set_2]:
|
|
73
75
|
sub_values = vs.values
|
|
@@ -77,7 +79,7 @@ class CombinedValueSet(CodeConstantsURLMapping):
|
|
|
77
79
|
|
|
78
80
|
return values
|
|
79
81
|
|
|
80
|
-
def __or__(self, value_set: Union["ValueSet", "CombinedValueSet"]) -> "CombinedValueSet":
|
|
82
|
+
def __or__(self, value_set: Union[type["ValueSet"], "CombinedValueSet"]) -> "CombinedValueSet":
|
|
81
83
|
"""Implements the `|` (or) operator to combine value sets."""
|
|
82
84
|
return CombinedValueSet(self, value_set)
|
|
83
85
|
|
|
@@ -85,22 +87,23 @@ class CombinedValueSet(CodeConstantsURLMapping):
|
|
|
85
87
|
class ValueSystems(type):
|
|
86
88
|
"""A metaclass for defining a ValueSet."""
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
""
|
|
91
|
-
return {
|
|
92
|
-
system: getattr(cls, system)
|
|
93
|
-
for system in cast(ValueSet, cls).CODE_SYSTEM_MAPPING.keys()
|
|
94
|
-
if hasattr(cls, system)
|
|
95
|
-
}
|
|
90
|
+
def __or__(self, value_set: Union[type["ValueSet"], "CombinedValueSet"]) -> "CombinedValueSet": # type: ignore[override]
|
|
91
|
+
"""Implements the `|` (or) operator."""
|
|
92
|
+
return CombinedValueSet(cast(type["ValueSet"], self), value_set)
|
|
96
93
|
|
|
97
|
-
def
|
|
94
|
+
def __ror__(self, value_set: Union[type["ValueSet"], "CombinedValueSet"]) -> "CombinedValueSet": # type: ignore[override]
|
|
98
95
|
"""Implements the `|` (or) operator."""
|
|
99
|
-
return
|
|
96
|
+
return self.__or__(value_set)
|
|
100
97
|
|
|
101
98
|
|
|
102
|
-
class ValueSet(
|
|
99
|
+
class ValueSet(CodeConstantsURLMappingMixin, metaclass=ValueSystems):
|
|
103
100
|
"""The Base class for a ValueSet."""
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
@classproperty
|
|
103
|
+
def values(cls) -> dict[str, set]:
|
|
104
|
+
"""A property that returns a dictionary of code systems and their associated values."""
|
|
105
|
+
return {
|
|
106
|
+
system: getattr(cls, system)
|
|
107
|
+
for system in cls.CODE_SYSTEM_MAPPING.keys()
|
|
108
|
+
if hasattr(cls, system)
|
|
109
|
+
}
|
plugin_runner/plugin_runner.py
CHANGED
|
@@ -9,7 +9,7 @@ import time
|
|
|
9
9
|
import traceback
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from types import FrameType
|
|
12
|
-
from typing import Any, Optional
|
|
12
|
+
from typing import Any, AsyncGenerator, Optional, TypedDict, cast
|
|
13
13
|
|
|
14
14
|
import grpc
|
|
15
15
|
import statsd
|
|
@@ -54,6 +54,49 @@ LOADED_PLUGINS: dict = {}
|
|
|
54
54
|
EVENT_PROTOCOL_MAP: dict = {}
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
class DataAccess(TypedDict):
|
|
58
|
+
"""DataAccess."""
|
|
59
|
+
|
|
60
|
+
event: str
|
|
61
|
+
read: list[str]
|
|
62
|
+
write: list[str]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Protocol = TypedDict(
|
|
66
|
+
"Protocol",
|
|
67
|
+
{
|
|
68
|
+
"class": str,
|
|
69
|
+
"data_access": DataAccess,
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Components(TypedDict):
|
|
75
|
+
"""Components."""
|
|
76
|
+
|
|
77
|
+
protocols: list[Protocol]
|
|
78
|
+
commands: list[dict]
|
|
79
|
+
content: list[dict]
|
|
80
|
+
effects: list[dict]
|
|
81
|
+
views: list[dict]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class PluginManifest(TypedDict):
|
|
85
|
+
"""PluginManifest."""
|
|
86
|
+
|
|
87
|
+
sdk_version: str
|
|
88
|
+
plugin_version: str
|
|
89
|
+
name: str
|
|
90
|
+
description: str
|
|
91
|
+
components: Components
|
|
92
|
+
secrets: list[dict]
|
|
93
|
+
tags: dict[str, str]
|
|
94
|
+
references: list[str]
|
|
95
|
+
license: str
|
|
96
|
+
diagram: bool
|
|
97
|
+
readme: str
|
|
98
|
+
|
|
99
|
+
|
|
57
100
|
class PluginRunner(PluginRunnerServicer):
|
|
58
101
|
"""This process runs provided plugins that register interest in incoming events."""
|
|
59
102
|
|
|
@@ -63,12 +106,20 @@ class PluginRunner(PluginRunnerServicer):
|
|
|
63
106
|
|
|
64
107
|
sandbox: Sandbox
|
|
65
108
|
|
|
66
|
-
async def HandleEvent(
|
|
109
|
+
async def HandleEvent(
|
|
110
|
+
self, request: Event, context: Any
|
|
111
|
+
) -> AsyncGenerator[EventResponse, None]:
|
|
67
112
|
"""This is invoked when an event comes in."""
|
|
68
113
|
event_start_time = time.time()
|
|
69
|
-
|
|
114
|
+
event_type = request.type
|
|
115
|
+
event_name = EventType.Name(event_type)
|
|
70
116
|
relevant_plugins = EVENT_PROTOCOL_MAP.get(event_name, [])
|
|
71
117
|
|
|
118
|
+
if event_type in [EventType.PLUGIN_CREATED, EventType.PLUGIN_UPDATED]:
|
|
119
|
+
plugin_name = request.target
|
|
120
|
+
# filter only for the plugin(s) that were created/updated
|
|
121
|
+
relevant_plugins = [p for p in relevant_plugins if p.startswith(f"{plugin_name}:")]
|
|
122
|
+
|
|
72
123
|
effect_list = []
|
|
73
124
|
|
|
74
125
|
for plugin_name in relevant_plugins:
|
|
@@ -129,7 +180,7 @@ class PluginRunner(PluginRunnerServicer):
|
|
|
129
180
|
|
|
130
181
|
async def ReloadPlugins(
|
|
131
182
|
self, request: ReloadPluginsRequest, context: Any
|
|
132
|
-
) -> ReloadPluginsResponse:
|
|
183
|
+
) -> AsyncGenerator[ReloadPluginsResponse, None]:
|
|
133
184
|
"""This is invoked when we need to reload plugins."""
|
|
134
185
|
try:
|
|
135
186
|
load_plugins()
|
|
@@ -166,13 +217,13 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
|
|
|
166
217
|
log.info(f"Loading {path}")
|
|
167
218
|
|
|
168
219
|
manifest_file = path / MANIFEST_FILE_NAME
|
|
169
|
-
|
|
220
|
+
manifest_json_str = manifest_file.read_text()
|
|
170
221
|
|
|
171
222
|
# the name is the folder name underneath the plugins directory
|
|
172
223
|
name = path.name
|
|
173
224
|
|
|
174
225
|
try:
|
|
175
|
-
manifest_json = json.loads(
|
|
226
|
+
manifest_json: PluginManifest = json.loads(manifest_json_str)
|
|
176
227
|
except Exception as e:
|
|
177
228
|
log.error(f'Unable to load plugin "{name}": {e}')
|
|
178
229
|
return
|
|
@@ -264,8 +315,8 @@ def load_plugins(specified_plugin_paths: list[str] | None = None) -> None:
|
|
|
264
315
|
for plugin_path in plugin_paths:
|
|
265
316
|
# when we import plugins we'll use the module name directly so we need to add the plugin
|
|
266
317
|
# directory to the path
|
|
267
|
-
path_to_append =
|
|
268
|
-
sys.path.append(path_to_append)
|
|
318
|
+
path_to_append = pathlib.Path(".") / plugin_path.parent
|
|
319
|
+
sys.path.append(path_to_append.as_posix())
|
|
269
320
|
else:
|
|
270
321
|
candidates = os.listdir(PLUGIN_DIRECTORY)
|
|
271
322
|
|
plugin_runner/sandbox.py
CHANGED
|
@@ -113,7 +113,7 @@ class Sandbox:
|
|
|
113
113
|
=> 'from _a import x' is ok, because '_a' is not added to the scope.
|
|
114
114
|
"""
|
|
115
115
|
for name in node.names:
|
|
116
|
-
if "*" in name.name and not _is_known_module(node.module):
|
|
116
|
+
if "*" in name.name and node.module and not _is_known_module(node.module):
|
|
117
117
|
self.error(node, '"*" imports are not allowed.')
|
|
118
118
|
self.check_name(node, name.name)
|
|
119
119
|
if name.asname:
|
|
File without changes
|
|
File without changes
|