canvas 0.13.3__py3-none-any.whl → 0.15.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.13.3.dist-info → canvas-0.15.0.dist-info}/METADATA +1 -3
- {canvas-0.13.3.dist-info → canvas-0.15.0.dist-info}/RECORD +69 -46
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +1 -1
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +6 -0
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +8 -0
- canvas_sdk/commands/tests/protocol/tests.py +3 -1
- canvas_sdk/commands/tests/test_utils.py +76 -18
- canvas_sdk/effects/banner_alert/tests.py +41 -20
- canvas_sdk/effects/questionnaire_result.py +29 -0
- canvas_sdk/events/base.py +1 -3
- canvas_sdk/handlers/action_button.py +5 -2
- canvas_sdk/handlers/application.py +1 -1
- canvas_sdk/handlers/cron_task.py +1 -1
- canvas_sdk/protocols/clinical_quality_measure.py +1 -1
- canvas_sdk/v1/apps.py +7 -0
- canvas_sdk/v1/data/__init__.py +75 -4
- canvas_sdk/v1/data/allergy_intolerance.py +3 -7
- canvas_sdk/v1/data/billing.py +2 -5
- canvas_sdk/v1/data/command.py +19 -8
- canvas_sdk/v1/data/condition.py +3 -7
- canvas_sdk/v1/data/detected_issue.py +4 -9
- canvas_sdk/v1/data/device.py +4 -8
- canvas_sdk/v1/data/imaging.py +12 -17
- canvas_sdk/v1/data/lab.py +16 -29
- canvas_sdk/v1/data/medication.py +3 -7
- canvas_sdk/v1/data/note.py +7 -14
- canvas_sdk/v1/data/observation.py +4 -11
- canvas_sdk/v1/data/organization.py +1 -2
- canvas_sdk/v1/data/patient.py +0 -1
- canvas_sdk/v1/data/practicelocation.py +2 -4
- canvas_sdk/v1/data/protocol_override.py +3 -6
- canvas_sdk/v1/data/questionnaire.py +5 -15
- canvas_sdk/v1/data/staff.py +5 -7
- canvas_sdk/v1/data/task.py +5 -11
- canvas_sdk/v1/data/user.py +0 -1
- canvas_sdk/v1/models.py +4 -0
- plugin_runner/aws_headers.py +77 -0
- plugin_runner/plugin_installer.py +26 -8
- plugin_runner/plugin_runner.py +5 -25
- plugin_runner/sandbox.py +105 -9
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +38 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +11 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +33 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +3 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +6 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +5 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +4 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +29 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +12 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +10 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +18 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +29 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +12 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +10 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +18 -0
- plugin_runner/tests/test_application.py +9 -9
- plugin_runner/tests/test_plugin_installer.py +22 -13
- plugin_runner/tests/test_plugin_runner.py +171 -32
- plugin_runner/tests/test_sandbox.py +18 -14
- settings.py +7 -1
- canvas_sdk/models/__init__.py +0 -7
- {canvas-0.13.3.dist-info → canvas-0.15.0.dist-info}/WHEEL +0 -0
- {canvas-0.13.3.dist-info → canvas-0.15.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import random
|
|
2
2
|
import shutil
|
|
3
3
|
import string
|
|
4
|
+
import threading
|
|
4
5
|
from contextlib import chdir
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from decimal import Decimal
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, cast
|
|
10
|
+
from urllib.parse import urlparse
|
|
9
11
|
|
|
10
12
|
import pytest
|
|
11
13
|
import requests
|
|
14
|
+
import websocket
|
|
12
15
|
from pydantic import ValidationError
|
|
13
16
|
from typer.testing import CliRunner
|
|
14
17
|
|
|
@@ -187,17 +190,25 @@ class Protocol(BaseProtocol):
|
|
|
187
190
|
def install_plugin(plugin_name: str, token: MaskedValue) -> None:
|
|
188
191
|
"""Install a plugin."""
|
|
189
192
|
with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package:
|
|
190
|
-
|
|
193
|
+
message_received_event = wait_for_log(
|
|
194
|
+
cast(str, settings.INTEGRATION_TEST_URL),
|
|
195
|
+
token.value,
|
|
196
|
+
f"Loading plugin '{plugin_name}",
|
|
197
|
+
)
|
|
198
|
+
response = requests.post(
|
|
191
199
|
plugin_url(cast(str, settings.INTEGRATION_TEST_URL)),
|
|
192
200
|
data={"is_enabled": True},
|
|
193
201
|
files={"package": package},
|
|
194
202
|
headers={"Authorization": f"Bearer {token.value}"},
|
|
195
203
|
)
|
|
204
|
+
response.raise_for_status()
|
|
205
|
+
|
|
206
|
+
message_received_event.wait(timeout=5.0)
|
|
196
207
|
|
|
197
208
|
|
|
198
209
|
def trigger_plugin_event(token: MaskedValue) -> None:
|
|
199
210
|
"""Trigger a plugin event."""
|
|
200
|
-
requests.post(
|
|
211
|
+
response = requests.post(
|
|
201
212
|
f"{settings.INTEGRATION_TEST_URL}/api/Note/",
|
|
202
213
|
headers={
|
|
203
214
|
"Authorization": f"Bearer {token.value}",
|
|
@@ -212,18 +223,22 @@ def trigger_plugin_event(token: MaskedValue) -> None:
|
|
|
212
223
|
"lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
|
|
213
224
|
},
|
|
214
225
|
)
|
|
226
|
+
response.raise_for_status()
|
|
215
227
|
|
|
216
228
|
|
|
217
229
|
def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> list[str]:
|
|
218
230
|
"""Get the commands from the original note body."""
|
|
219
|
-
|
|
231
|
+
response = requests.get(
|
|
220
232
|
f"{settings.INTEGRATION_TEST_URL}/api/Note/{new_note_id}",
|
|
221
233
|
headers={
|
|
222
234
|
"Authorization": f"Bearer {token.value}",
|
|
223
235
|
"Content-Type": "application/json",
|
|
224
236
|
"Accept": "application/json",
|
|
225
237
|
},
|
|
226
|
-
)
|
|
238
|
+
)
|
|
239
|
+
response.raise_for_status()
|
|
240
|
+
|
|
241
|
+
original_note = response.json()
|
|
227
242
|
|
|
228
243
|
body = original_note["body"]
|
|
229
244
|
return [
|
|
@@ -243,18 +258,21 @@ def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None:
|
|
|
243
258
|
shutil.rmtree(Path(f"./custom-plugins/{plugin_name}"))
|
|
244
259
|
|
|
245
260
|
# disable
|
|
246
|
-
requests.patch(
|
|
261
|
+
response = requests.patch(
|
|
247
262
|
plugin_url(cast(str, settings.INTEGRATION_TEST_URL), plugin_name),
|
|
248
263
|
data={"is_enabled": False},
|
|
249
264
|
headers={
|
|
250
265
|
"Authorization": f"Bearer {token.value}",
|
|
251
266
|
},
|
|
252
267
|
)
|
|
268
|
+
response.raise_for_status()
|
|
269
|
+
|
|
253
270
|
# delete
|
|
254
|
-
requests.delete(
|
|
271
|
+
response = requests.delete(
|
|
255
272
|
plugin_url(cast(str, settings.INTEGRATION_TEST_URL), plugin_name),
|
|
256
273
|
headers={"Authorization": f"Bearer {token.value}"},
|
|
257
274
|
)
|
|
275
|
+
response.raise_for_status()
|
|
258
276
|
|
|
259
277
|
|
|
260
278
|
# For reuse with the protocol code
|
|
@@ -287,21 +305,61 @@ def create_new_note(token: MaskedValue) -> dict:
|
|
|
287
305
|
"note_type_version": 1,
|
|
288
306
|
"lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
|
|
289
307
|
}
|
|
290
|
-
|
|
308
|
+
response = requests.post(
|
|
291
309
|
f"{settings.INTEGRATION_TEST_URL}/api/Note/", headers=headers, json=data
|
|
292
|
-
)
|
|
310
|
+
)
|
|
311
|
+
response.raise_for_status()
|
|
312
|
+
return response.json()
|
|
293
313
|
|
|
294
314
|
|
|
295
315
|
def get_token() -> MaskedValue:
|
|
296
316
|
"""Get a valid token."""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
},
|
|
306
|
-
).json()["access_token"]
|
|
317
|
+
response = requests.post(
|
|
318
|
+
f"{settings.INTEGRATION_TEST_URL}/auth/token/",
|
|
319
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
320
|
+
data={
|
|
321
|
+
"grant_type": "client_credentials",
|
|
322
|
+
"client_id": settings.INTEGRATION_TEST_CLIENT_ID,
|
|
323
|
+
"client_secret": settings.INTEGRATION_TEST_CLIENT_SECRET,
|
|
324
|
+
},
|
|
307
325
|
)
|
|
326
|
+
response.raise_for_status()
|
|
327
|
+
|
|
328
|
+
return MaskedValue(response.json()["access_token"])
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def wait_for_log(host: str, token: str, message: str) -> threading.Event:
|
|
332
|
+
"""Wait for a specific log message."""
|
|
333
|
+
hostname = cast(str, urlparse(host).hostname)
|
|
334
|
+
instance = hostname.removesuffix(".canvasmedical.com")
|
|
335
|
+
|
|
336
|
+
websocket_uri = f"wss://logs.console.canvasmedical.com/{instance}?token={token}"
|
|
337
|
+
|
|
338
|
+
connected_event = threading.Event()
|
|
339
|
+
message_received_event = threading.Event()
|
|
340
|
+
|
|
341
|
+
def _on_message(ws: websocket.WebSocket, received_message: str) -> None:
|
|
342
|
+
try:
|
|
343
|
+
if "Log stream connected" in received_message:
|
|
344
|
+
connected_event.set()
|
|
345
|
+
if message.lower() in received_message.lower():
|
|
346
|
+
message_received_event.set()
|
|
347
|
+
ws.close()
|
|
348
|
+
except Exception as ex:
|
|
349
|
+
print(f"Error processing message: {ex}")
|
|
350
|
+
|
|
351
|
+
def _on_error(ws: websocket.WebSocket, error: str) -> None:
|
|
352
|
+
print(f"WebSocket error: {error}")
|
|
353
|
+
|
|
354
|
+
ws = websocket.WebSocketApp(
|
|
355
|
+
websocket_uri,
|
|
356
|
+
on_message=_on_message,
|
|
357
|
+
on_error=_on_error,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
thread = threading.Thread(target=ws.run_forever)
|
|
361
|
+
thread.start()
|
|
362
|
+
|
|
363
|
+
connected_event.wait(timeout=5.0)
|
|
364
|
+
|
|
365
|
+
return message_received_event
|
|
@@ -14,7 +14,7 @@ from typer.testing import CliRunner
|
|
|
14
14
|
import settings
|
|
15
15
|
from canvas_cli.apps.plugin.plugin import _build_package, plugin_url
|
|
16
16
|
from canvas_cli.main import app
|
|
17
|
-
from canvas_sdk.commands.tests.test_utils import MaskedValue
|
|
17
|
+
from canvas_sdk.commands.tests.test_utils import MaskedValue, wait_for_log
|
|
18
18
|
from canvas_sdk.effects.banner_alert import AddBannerAlert, RemoveBannerAlert
|
|
19
19
|
|
|
20
20
|
runner = CliRunner()
|
|
@@ -23,17 +23,18 @@ runner = CliRunner()
|
|
|
23
23
|
@pytest.fixture(scope="session")
|
|
24
24
|
def token() -> MaskedValue:
|
|
25
25
|
"""Get a valid token."""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
).json()["access_token"]
|
|
26
|
+
response = requests.post(
|
|
27
|
+
f"{settings.INTEGRATION_TEST_URL}/auth/token/",
|
|
28
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
29
|
+
data={
|
|
30
|
+
"grant_type": "client_credentials",
|
|
31
|
+
"client_id": settings.INTEGRATION_TEST_CLIENT_ID,
|
|
32
|
+
"client_secret": settings.INTEGRATION_TEST_CLIENT_SECRET,
|
|
33
|
+
},
|
|
36
34
|
)
|
|
35
|
+
response.raise_for_status()
|
|
36
|
+
|
|
37
|
+
return MaskedValue(response.json()["access_token"])
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@pytest.fixture(scope="session")
|
|
@@ -44,7 +45,9 @@ def first_patient_id(token: MaskedValue) -> dict:
|
|
|
44
45
|
"Content-Type": "application/json",
|
|
45
46
|
"Accept": "application/json",
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
+
response = requests.get(f"{settings.INTEGRATION_TEST_URL}/api/Patient", headers=headers)
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
patients = response.json()
|
|
48
51
|
return patients["entry"][0]["resource"]["key"]
|
|
49
52
|
|
|
50
53
|
|
|
@@ -89,13 +92,22 @@ class Protocol(BaseProtocol):
|
|
|
89
92
|
protocol.write(protocol_code)
|
|
90
93
|
|
|
91
94
|
with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package:
|
|
95
|
+
message_received_event = wait_for_log(
|
|
96
|
+
settings.INTEGRATION_TEST_URL,
|
|
97
|
+
token.value,
|
|
98
|
+
f"Loading plugin '{plugin_name}:{plugin_name}.protocols.my_protocol:Protocol'",
|
|
99
|
+
)
|
|
100
|
+
|
|
92
101
|
# install the plugin
|
|
93
|
-
requests.post(
|
|
102
|
+
response = requests.post(
|
|
94
103
|
plugin_url(settings.INTEGRATION_TEST_URL),
|
|
95
104
|
data={"is_enabled": True},
|
|
96
105
|
files={"package": package},
|
|
97
106
|
headers={"Authorization": f"Bearer {token.value}"},
|
|
98
107
|
)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
|
|
110
|
+
message_received_event.wait(timeout=5.0)
|
|
99
111
|
|
|
100
112
|
yield
|
|
101
113
|
|
|
@@ -104,26 +116,31 @@ class Protocol(BaseProtocol):
|
|
|
104
116
|
shutil.rmtree(Path(f"./custom-plugins/{plugin_name}"))
|
|
105
117
|
|
|
106
118
|
# disable
|
|
107
|
-
requests.patch(
|
|
119
|
+
response = requests.patch(
|
|
108
120
|
plugin_url(settings.INTEGRATION_TEST_URL, plugin_name),
|
|
109
121
|
data={"is_enabled": False},
|
|
110
122
|
headers={
|
|
111
123
|
"Authorization": f"Bearer {token.value}",
|
|
112
124
|
},
|
|
113
125
|
)
|
|
126
|
+
response.raise_for_status()
|
|
127
|
+
|
|
114
128
|
# delete
|
|
115
|
-
requests.delete(
|
|
129
|
+
response = requests.delete(
|
|
116
130
|
plugin_url(settings.INTEGRATION_TEST_URL, plugin_name),
|
|
117
131
|
headers={"Authorization": f"Bearer {token.value}"},
|
|
118
132
|
)
|
|
133
|
+
response.raise_for_status()
|
|
119
134
|
|
|
120
135
|
# confirm no more banner
|
|
121
|
-
|
|
136
|
+
response = requests.get(
|
|
122
137
|
f"{settings.INTEGRATION_TEST_URL}/api/BannerAlert/?patient__key={first_patient_id}",
|
|
123
138
|
headers={
|
|
124
139
|
"Authorization": f"Bearer {token.value}",
|
|
125
140
|
},
|
|
126
|
-
)
|
|
141
|
+
)
|
|
142
|
+
response.raise_for_status()
|
|
143
|
+
patient_banners_none = response.json()
|
|
127
144
|
patient_banner = next(
|
|
128
145
|
(b for b in patient_banners_none["results"] if b["key"] == plugin_name), None
|
|
129
146
|
)
|
|
@@ -139,7 +156,7 @@ def test_protocol_that_adds_banner_alert(
|
|
|
139
156
|
) -> None:
|
|
140
157
|
"""Test that the protocol adds a banner alert."""
|
|
141
158
|
# trigger the event
|
|
142
|
-
requests.post(
|
|
159
|
+
response = requests.post(
|
|
143
160
|
f"{settings.INTEGRATION_TEST_URL}/api/Note/",
|
|
144
161
|
headers={
|
|
145
162
|
"Authorization": f"Bearer {token.value}",
|
|
@@ -154,13 +171,17 @@ def test_protocol_that_adds_banner_alert(
|
|
|
154
171
|
"lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
|
|
155
172
|
},
|
|
156
173
|
)
|
|
174
|
+
response.raise_for_status()
|
|
157
175
|
|
|
158
|
-
|
|
176
|
+
response = requests.get(
|
|
159
177
|
f"{settings.INTEGRATION_TEST_URL}/api/BannerAlert/?patient__key={first_patient_id}",
|
|
160
178
|
headers={
|
|
161
179
|
"Authorization": f"Bearer {token.value}",
|
|
162
180
|
},
|
|
163
|
-
)
|
|
181
|
+
)
|
|
182
|
+
response.raise_for_status()
|
|
183
|
+
|
|
184
|
+
patient_banners = response.json()
|
|
164
185
|
assert patient_banners["count"] > 0
|
|
165
186
|
|
|
166
187
|
patient_banner = next(b for b in patient_banners["results"] if b["key"] == plugin_name)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.effects.base import EffectType, _BaseEffect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreateQuestionnaireResult(_BaseEffect):
|
|
7
|
+
"""CreateQuestionnaireResult effect."""
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
effect_type = EffectType.CREATE_QUESTIONNAIRE_RESULT
|
|
11
|
+
|
|
12
|
+
interview_id: str | None = None
|
|
13
|
+
score: float | None = None
|
|
14
|
+
abnormal: bool | None = None
|
|
15
|
+
narrative: str | None = None
|
|
16
|
+
code_system: str | None = None
|
|
17
|
+
code: str | None = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def values(self) -> dict[str, Any]:
|
|
21
|
+
"""Make the payload."""
|
|
22
|
+
return {
|
|
23
|
+
"interview_id": self.interview_id,
|
|
24
|
+
"score": self.score,
|
|
25
|
+
"abnormal": self.abnormal or False,
|
|
26
|
+
"narrative": self.narrative or "",
|
|
27
|
+
"code_system": self.code_system,
|
|
28
|
+
"code": self.code,
|
|
29
|
+
}
|
canvas_sdk/events/base.py
CHANGED
|
@@ -33,9 +33,7 @@ class Event:
|
|
|
33
33
|
|
|
34
34
|
def __init__(self, event_request: EventRequest) -> None:
|
|
35
35
|
try:
|
|
36
|
-
target_model = apps.get_model(
|
|
37
|
-
app_label="canvas_sdk", model_name=event_request.target_type
|
|
38
|
-
)
|
|
36
|
+
target_model = apps.get_model(app_label="v1", model_name=event_request.target_type)
|
|
39
37
|
except LookupError:
|
|
40
38
|
target_model = None
|
|
41
39
|
|
|
@@ -42,13 +42,16 @@ class ActionButton(BaseHandler):
|
|
|
42
42
|
EventType.SHOW_NOTE_HEADER_BUTTON,
|
|
43
43
|
EventType.SHOW_NOTE_FOOTER_BUTTON,
|
|
44
44
|
):
|
|
45
|
-
if
|
|
45
|
+
if (
|
|
46
|
+
self.event.context["location"].lower() == self.BUTTON_LOCATION.value
|
|
47
|
+
and self.visible()
|
|
48
|
+
):
|
|
46
49
|
return [ShowButtonEffect(key=self.BUTTON_KEY, title=self.BUTTON_TITLE).apply()]
|
|
47
50
|
else:
|
|
48
51
|
return []
|
|
49
52
|
elif (
|
|
50
53
|
self.event.type == EventType.ACTION_BUTTON_CLICKED
|
|
51
|
-
and self.context["key"] == self.BUTTON_KEY
|
|
54
|
+
and self.event.context["key"] == self.BUTTON_KEY
|
|
52
55
|
):
|
|
53
56
|
return self.handle()
|
|
54
57
|
|
|
@@ -14,7 +14,7 @@ class Application(BaseHandler, ABC):
|
|
|
14
14
|
"""Handle the application events."""
|
|
15
15
|
match self.event.type:
|
|
16
16
|
case EventType.APPLICATION__ON_OPEN:
|
|
17
|
-
return [self.on_open()] if self.target == self.identifier else []
|
|
17
|
+
return [self.on_open()] if self.event.target.id == self.identifier else []
|
|
18
18
|
case _:
|
|
19
19
|
return []
|
|
20
20
|
|
canvas_sdk/handlers/cron_task.py
CHANGED
|
@@ -29,7 +29,7 @@ class CronTask(BaseHandler):
|
|
|
29
29
|
"""
|
|
30
30
|
if not self.SCHEDULE:
|
|
31
31
|
raise ValueError("You must set a SCHEDULE.")
|
|
32
|
-
datetime = arrow.get(self.target).datetime
|
|
32
|
+
datetime = arrow.get(self.event.target.id).datetime
|
|
33
33
|
if datetime in Cron(self.SCHEDULE):
|
|
34
34
|
return self.execute()
|
|
35
35
|
return []
|
canvas_sdk/v1/apps.py
ADDED
canvas_sdk/v1/data/__init__.py
CHANGED
|
@@ -1,23 +1,94 @@
|
|
|
1
|
+
from .allergy_intolerance import AllergyIntolerance, AllergyIntoleranceCoding
|
|
1
2
|
from .billing import BillingLineItem
|
|
3
|
+
from .command import Command
|
|
2
4
|
from .condition import Condition, ConditionCoding
|
|
5
|
+
from .detected_issue import DetectedIssue, DetectedIssueEvidence
|
|
6
|
+
from .device import Device
|
|
7
|
+
from .imaging import ImagingOrder, ImagingReport, ImagingReview
|
|
8
|
+
from .lab import (
|
|
9
|
+
LabOrder,
|
|
10
|
+
LabOrderReason,
|
|
11
|
+
LabOrderReasonCondition,
|
|
12
|
+
LabReport,
|
|
13
|
+
LabReview,
|
|
14
|
+
LabTest,
|
|
15
|
+
LabValue,
|
|
16
|
+
LabValueCoding,
|
|
17
|
+
)
|
|
3
18
|
from .medication import Medication, MedicationCoding
|
|
19
|
+
from .note import Note, NoteType
|
|
20
|
+
from .observation import (
|
|
21
|
+
Observation,
|
|
22
|
+
ObservationCoding,
|
|
23
|
+
ObservationComponent,
|
|
24
|
+
ObservationComponentCoding,
|
|
25
|
+
ObservationValueCoding,
|
|
26
|
+
)
|
|
4
27
|
from .organization import Organization
|
|
5
28
|
from .patient import Patient
|
|
6
|
-
from .practicelocation import PracticeLocation
|
|
29
|
+
from .practicelocation import PracticeLocation, PracticeLocationSetting
|
|
30
|
+
from .protocol_override import ProtocolOverride
|
|
31
|
+
from .questionnaire import (
|
|
32
|
+
Interview,
|
|
33
|
+
InterviewQuestionnaireMap,
|
|
34
|
+
InterviewQuestionResponse,
|
|
35
|
+
Question,
|
|
36
|
+
Questionnaire,
|
|
37
|
+
QuestionnaireQuestionMap,
|
|
38
|
+
ResponseOption,
|
|
39
|
+
ResponseOptionSet,
|
|
40
|
+
)
|
|
7
41
|
from .staff import Staff
|
|
8
|
-
from .task import Task, TaskComment, TaskLabel
|
|
42
|
+
from .task import Task, TaskComment, TaskLabel, TaskTaskLabel
|
|
43
|
+
from .user import CanvasUser
|
|
9
44
|
|
|
10
|
-
__all__ =
|
|
45
|
+
__all__ = [
|
|
46
|
+
"AllergyIntolerance",
|
|
47
|
+
"AllergyIntoleranceCoding",
|
|
11
48
|
"BillingLineItem",
|
|
49
|
+
"CanvasUser",
|
|
50
|
+
"Command",
|
|
12
51
|
"Condition",
|
|
13
52
|
"ConditionCoding",
|
|
53
|
+
"DetectedIssue",
|
|
54
|
+
"DetectedIssueEvidence",
|
|
55
|
+
"Device",
|
|
56
|
+
"ImagingOrder",
|
|
57
|
+
"ImagingReport",
|
|
58
|
+
"ImagingReview",
|
|
59
|
+
"Interview",
|
|
60
|
+
"InterviewQuestionnaireMap",
|
|
61
|
+
"InterviewQuestionResponse",
|
|
62
|
+
"LabOrder",
|
|
63
|
+
"LabOrderReason",
|
|
64
|
+
"LabOrderReasonCondition",
|
|
65
|
+
"LabReport",
|
|
66
|
+
"LabReview",
|
|
67
|
+
"LabTest",
|
|
68
|
+
"LabValue",
|
|
69
|
+
"LabValueCoding",
|
|
14
70
|
"Medication",
|
|
15
71
|
"MedicationCoding",
|
|
72
|
+
"Note",
|
|
73
|
+
"NoteType",
|
|
74
|
+
"Observation",
|
|
75
|
+
"ObservationCoding",
|
|
76
|
+
"ObservationComponent",
|
|
77
|
+
"ObservationComponentCoding",
|
|
78
|
+
"ObservationValueCoding",
|
|
16
79
|
"Organization",
|
|
17
80
|
"Patient",
|
|
18
81
|
"PracticeLocation",
|
|
82
|
+
"PracticeLocationSetting",
|
|
83
|
+
"ProtocolOverride",
|
|
84
|
+
"Question",
|
|
85
|
+
"Questionnaire",
|
|
86
|
+
"QuestionnaireQuestionMap",
|
|
87
|
+
"ResponseOption",
|
|
88
|
+
"ResponseOptionSet",
|
|
19
89
|
"Staff",
|
|
20
90
|
"Task",
|
|
21
91
|
"TaskComment",
|
|
22
92
|
"TaskLabel",
|
|
23
|
-
|
|
93
|
+
"TaskTaskLabel",
|
|
94
|
+
]
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from django.db import models
|
|
2
2
|
|
|
3
3
|
from canvas_sdk.v1.data.base import CommittableModelManager, ValueSetLookupQuerySet
|
|
4
|
-
from canvas_sdk.v1.data.patient import Patient
|
|
5
|
-
from canvas_sdk.v1.data.user import CanvasUser
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
class AllergyIntolerance(models.Model):
|
|
@@ -10,7 +8,6 @@ class AllergyIntolerance(models.Model):
|
|
|
10
8
|
|
|
11
9
|
class Meta:
|
|
12
10
|
managed = False
|
|
13
|
-
app_label = "canvas_sdk"
|
|
14
11
|
db_table = "canvas_sdk_data_api_allergyintolerance_001"
|
|
15
12
|
|
|
16
13
|
objects = CommittableModelManager().from_queryset(ValueSetLookupQuerySet)()
|
|
@@ -20,10 +17,10 @@ class AllergyIntolerance(models.Model):
|
|
|
20
17
|
created = models.DateTimeField()
|
|
21
18
|
modified = models.DateTimeField()
|
|
22
19
|
deleted = models.BooleanField()
|
|
23
|
-
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
24
|
-
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
20
|
+
committer = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
21
|
+
entered_in_error = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
25
22
|
patient = models.ForeignKey(
|
|
26
|
-
Patient,
|
|
23
|
+
"v1.Patient",
|
|
27
24
|
on_delete=models.DO_NOTHING,
|
|
28
25
|
related_name="allergy_intolerances",
|
|
29
26
|
null=True,
|
|
@@ -46,7 +43,6 @@ class AllergyIntoleranceCoding(models.Model):
|
|
|
46
43
|
|
|
47
44
|
class Meta:
|
|
48
45
|
managed = False
|
|
49
|
-
app_label = "canvas_sdk"
|
|
50
46
|
db_table = "canvas_sdk_data_api_allergyintolerancecoding_001"
|
|
51
47
|
|
|
52
48
|
dbid = models.BigIntegerField(primary_key=True)
|
canvas_sdk/v1/data/billing.py
CHANGED
|
@@ -3,8 +3,6 @@ from typing import TYPE_CHECKING
|
|
|
3
3
|
from django.db import models
|
|
4
4
|
|
|
5
5
|
from canvas_sdk.v1.data.base import ValueSetTimeframeLookupQuerySet
|
|
6
|
-
from canvas_sdk.v1.data.note import Note
|
|
7
|
-
from canvas_sdk.v1.data.patient import Patient
|
|
8
6
|
from canvas_sdk.value_set.value_set import CodeConstants
|
|
9
7
|
|
|
10
8
|
if TYPE_CHECKING:
|
|
@@ -36,7 +34,6 @@ class BillingLineItem(models.Model):
|
|
|
36
34
|
|
|
37
35
|
class Meta:
|
|
38
36
|
managed = False
|
|
39
|
-
app_label = "canvas_sdk"
|
|
40
37
|
db_table = "canvas_sdk_data_api_billinglineitem_001"
|
|
41
38
|
|
|
42
39
|
# objects = BillingLineItemQuerySet.as_manager()
|
|
@@ -47,10 +44,10 @@ class BillingLineItem(models.Model):
|
|
|
47
44
|
created = models.DateTimeField()
|
|
48
45
|
modified = models.DateTimeField()
|
|
49
46
|
note = models.ForeignKey(
|
|
50
|
-
Note, on_delete=models.DO_NOTHING, related_name="billing_line_items", null=True
|
|
47
|
+
"v1.Note", on_delete=models.DO_NOTHING, related_name="billing_line_items", null=True
|
|
51
48
|
)
|
|
52
49
|
patient = models.ForeignKey(
|
|
53
|
-
Patient, on_delete=models.DO_NOTHING, related_name="billing_line_items", null=True
|
|
50
|
+
"v1.Patient", on_delete=models.DO_NOTHING, related_name="billing_line_items", null=True
|
|
54
51
|
)
|
|
55
52
|
cpt = models.CharField()
|
|
56
53
|
charge = models.DecimalField()
|
canvas_sdk/v1/data/command.py
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
|
+
from django.apps import apps
|
|
1
2
|
from django.db import models
|
|
2
3
|
|
|
3
|
-
from canvas_sdk.v1.data.patient import Patient
|
|
4
|
-
from canvas_sdk.v1.data.user import CanvasUser
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
class Command(models.Model):
|
|
8
6
|
"""Command."""
|
|
9
7
|
|
|
10
8
|
class Meta:
|
|
11
9
|
managed = False
|
|
12
|
-
app_label = "canvas_sdk"
|
|
13
10
|
db_table = "canvas_sdk_data_commands_command_001"
|
|
14
11
|
|
|
15
12
|
id = models.UUIDField()
|
|
16
13
|
dbid = models.BigIntegerField(primary_key=True)
|
|
17
14
|
created = models.DateTimeField()
|
|
18
15
|
modified = models.DateTimeField()
|
|
19
|
-
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
20
|
-
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
21
|
-
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
16
|
+
originator = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
17
|
+
committer = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
18
|
+
entered_in_error = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
22
19
|
state = models.CharField()
|
|
23
|
-
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, null=True)
|
|
20
|
+
patient = models.ForeignKey("v1.Patient", on_delete=models.DO_NOTHING, null=True)
|
|
24
21
|
note_id = models.BigIntegerField()
|
|
25
22
|
schema_key = models.TextField()
|
|
26
23
|
data = models.JSONField()
|
|
27
24
|
origination_source = models.CharField()
|
|
25
|
+
anchor_object_type = models.CharField()
|
|
26
|
+
anchor_object_dbid = models.BigIntegerField()
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def anchor_object(self) -> models.Model | None:
|
|
30
|
+
"""
|
|
31
|
+
Use the anchor_object_type and anchor_object_dbid to get the anchor object for the command.
|
|
32
|
+
"""
|
|
33
|
+
# TODO: Is the anchor object type enough here, or do we need a mapping? The home-app model
|
|
34
|
+
# names might not exactly match the plugins model names.
|
|
35
|
+
anchor_model = apps.get_model(
|
|
36
|
+
app_label=self._meta.app_label, model_name=self.anchor_object_type
|
|
37
|
+
)
|
|
38
|
+
return anchor_model.objects.get(dbid=self.anchor_object_dbid)
|
canvas_sdk/v1/data/condition.py
CHANGED
|
@@ -2,8 +2,6 @@ from django.db import models
|
|
|
2
2
|
from django.db.models import TextChoices
|
|
3
3
|
|
|
4
4
|
from canvas_sdk.v1.data.base import ValueSetLookupQuerySet
|
|
5
|
-
from canvas_sdk.v1.data.patient import Patient
|
|
6
|
-
from canvas_sdk.v1.data.user import CanvasUser
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
class ClinicalStatus(TextChoices):
|
|
@@ -27,7 +25,6 @@ class Condition(models.Model):
|
|
|
27
25
|
|
|
28
26
|
class Meta:
|
|
29
27
|
managed = False
|
|
30
|
-
app_label = "canvas_sdk"
|
|
31
28
|
db_table = "canvas_sdk_data_api_condition_001"
|
|
32
29
|
|
|
33
30
|
objects = ConditionQuerySet.as_manager()
|
|
@@ -35,10 +32,10 @@ class Condition(models.Model):
|
|
|
35
32
|
id = models.UUIDField()
|
|
36
33
|
dbid = models.BigIntegerField(primary_key=True)
|
|
37
34
|
deleted = models.BooleanField()
|
|
38
|
-
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
39
|
-
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
|
|
35
|
+
entered_in_error = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
36
|
+
committer = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
40
37
|
patient = models.ForeignKey(
|
|
41
|
-
Patient, on_delete=models.DO_NOTHING, related_name="conditions", null=True
|
|
38
|
+
"v1.Patient", on_delete=models.DO_NOTHING, related_name="conditions", null=True
|
|
42
39
|
)
|
|
43
40
|
onset_date = models.DateField()
|
|
44
41
|
resolution_date = models.DateField()
|
|
@@ -50,7 +47,6 @@ class ConditionCoding(models.Model):
|
|
|
50
47
|
|
|
51
48
|
class Meta:
|
|
52
49
|
managed = False
|
|
53
|
-
app_label = "canvas_sdk"
|
|
54
50
|
db_table = "canvas_sdk_data_api_conditioncoding_001"
|
|
55
51
|
|
|
56
52
|
dbid = models.BigIntegerField(primary_key=True)
|