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
|
@@ -560,6 +560,8 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
|
560
560
|
PATIENT_CHART__CONDITIONS: _ClassVar[EventType]
|
|
561
561
|
PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION: _ClassVar[EventType]
|
|
562
562
|
CLAIM__CONDITIONS: _ClassVar[EventType]
|
|
563
|
+
PLUGIN_CREATED: _ClassVar[EventType]
|
|
564
|
+
PLUGIN_UPDATED: _ClassVar[EventType]
|
|
563
565
|
UNKNOWN: EventType
|
|
564
566
|
ALLERGY_INTOLERANCE_CREATED: EventType
|
|
565
567
|
ALLERGY_INTOLERANCE_UPDATED: EventType
|
|
@@ -1111,6 +1113,8 @@ VITALS_COMMAND__POST_EXECUTE_ACTION: EventType
|
|
|
1111
1113
|
PATIENT_CHART__CONDITIONS: EventType
|
|
1112
1114
|
PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION: EventType
|
|
1113
1115
|
CLAIM__CONDITIONS: EventType
|
|
1116
|
+
PLUGIN_CREATED: EventType
|
|
1117
|
+
PLUGIN_UPDATED: EventType
|
|
1114
1118
|
|
|
1115
1119
|
class Event(_message.Message):
|
|
1116
1120
|
__slots__ = ("type", "target", "context")
|
canvas_sdk/base.py
CHANGED
|
@@ -31,7 +31,8 @@ class Model(BaseModel):
|
|
|
31
31
|
|
|
32
32
|
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
33
33
|
required_fields = self._get_effect_method_required_fields(method)
|
|
34
|
-
class_name = self.__repr_name__()
|
|
34
|
+
class_name = self.__repr_name__() # type: ignore[misc]
|
|
35
|
+
|
|
35
36
|
class_name_article = "an" if class_name.startswith(("A", "E", "I", "O", "U")) else "a"
|
|
36
37
|
return [
|
|
37
38
|
self._create_error_detail(
|
canvas_sdk/commands/base.py
CHANGED
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import re
|
|
3
3
|
from enum import EnumType
|
|
4
4
|
from types import NoneType, UnionType
|
|
5
|
-
from typing import Any, Literal, Tuple, Union, get_args, get_origin
|
|
5
|
+
from typing import Any, Literal, Tuple, Union, cast, get_args, get_origin
|
|
6
6
|
|
|
7
7
|
from canvas_sdk.base import Model
|
|
8
8
|
from canvas_sdk.commands.constants import Coding
|
|
@@ -25,9 +25,7 @@ class _BaseCommand(Model):
|
|
|
25
25
|
note_uuid: str | None = None
|
|
26
26
|
command_uuid: str | None = None
|
|
27
27
|
|
|
28
|
-
def _get_effect_method_required_fields(
|
|
29
|
-
self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error"]
|
|
30
|
-
) -> tuple:
|
|
28
|
+
def _get_effect_method_required_fields(self, method: str) -> tuple:
|
|
31
29
|
base_required_fields: tuple = getattr(
|
|
32
30
|
_BaseCommand.Meta, f"{method}_required_fields", tuple()
|
|
33
31
|
)
|
|
@@ -52,7 +50,7 @@ class _BaseCommand(Model):
|
|
|
52
50
|
return schema.get("$defs", {}).get(choice_key, {}).get("enum")
|
|
53
51
|
|
|
54
52
|
@classmethod
|
|
55
|
-
def _get_property_type(cls, name: str) -> type:
|
|
53
|
+
def _get_property_type(cls, name: str) -> type | None:
|
|
56
54
|
annotation = cls.model_fields[name].annotation
|
|
57
55
|
origin = get_origin(annotation)
|
|
58
56
|
|
|
@@ -89,7 +87,7 @@ class _BaseCommand(Model):
|
|
|
89
87
|
"""Originate a new command in the note body."""
|
|
90
88
|
self._validate_before_effect("originate")
|
|
91
89
|
return Effect(
|
|
92
|
-
type=
|
|
90
|
+
type=f"ORIGINATE_{self.constantized_key()}_COMMAND",
|
|
93
91
|
payload=json.dumps(
|
|
94
92
|
{
|
|
95
93
|
"note": self.note_uuid,
|
|
@@ -101,37 +99,39 @@ class _BaseCommand(Model):
|
|
|
101
99
|
def edit(self) -> Effect:
|
|
102
100
|
"""Edit the command."""
|
|
103
101
|
self._validate_before_effect("edit")
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
return Effect(
|
|
103
|
+
type=f"EDIT_{self.constantized_key()}_COMMAND",
|
|
104
|
+
payload=json.dumps(
|
|
105
|
+
{
|
|
106
|
+
"command": self.command_uuid,
|
|
107
|
+
"data": self.values,
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
111
|
|
|
112
112
|
def delete(self) -> Effect:
|
|
113
113
|
"""Delete the command."""
|
|
114
114
|
self._validate_before_effect("delete")
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
return Effect(
|
|
116
|
+
type=f"DELETE_{self.constantized_key()}_COMMAND",
|
|
117
|
+
payload=json.dumps({"command": self.command_uuid}),
|
|
118
|
+
)
|
|
119
119
|
|
|
120
120
|
def commit(self) -> Effect:
|
|
121
121
|
"""Commit the command."""
|
|
122
122
|
self._validate_before_effect("commit")
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
return Effect(
|
|
124
|
+
type=f"COMMIT_{self.constantized_key()}_COMMAND",
|
|
125
|
+
payload=json.dumps({"command": self.command_uuid}),
|
|
126
|
+
)
|
|
127
127
|
|
|
128
128
|
def enter_in_error(self) -> Effect:
|
|
129
129
|
"""Mark the command as entered-in-error."""
|
|
130
130
|
self._validate_before_effect("enter_in_error")
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
return Effect(
|
|
132
|
+
type=f"ENTER_IN_ERROR_{self.constantized_key()}_COMMAND",
|
|
133
|
+
payload=json.dumps({"command": self.command_uuid}),
|
|
134
|
+
)
|
|
135
135
|
|
|
136
136
|
def recommend(self, title: str = "", button: str | None = None) -> Recommendation:
|
|
137
137
|
"""Returns a command recommendation to be inserted via Protocol Card."""
|
|
@@ -31,10 +31,10 @@ def plugin_name() -> str:
|
|
|
31
31
|
return f"commands{datetime.now().timestamp()}".replace(".", "")
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
@pytest.fixture(
|
|
34
|
+
@pytest.fixture(scope="session")
|
|
35
35
|
def write_and_install_protocol_and_clean_up(
|
|
36
36
|
plugin_name: str, token: MaskedValue, new_note: dict
|
|
37
|
-
) -> Generator[
|
|
37
|
+
) -> Generator[None, None, None]:
|
|
38
38
|
write_protocol_code(new_note["externallyExposableId"], plugin_name, COMMANDS)
|
|
39
39
|
install_plugin(plugin_name, token)
|
|
40
40
|
|
|
@@ -44,7 +44,9 @@ def write_and_install_protocol_and_clean_up(
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
@pytest.mark.integtest
|
|
47
|
-
def test_protocol_that_inserts_every_command(
|
|
47
|
+
def test_protocol_that_inserts_every_command(
|
|
48
|
+
write_and_install_protocol_and_clean_up: None, token: MaskedValue, new_note: dict
|
|
49
|
+
) -> None:
|
|
48
50
|
trigger_plugin_event(token)
|
|
49
51
|
|
|
50
52
|
commands_in_body = get_original_note_body_commands(new_note["id"], token)
|
|
@@ -5,7 +5,7 @@ from contextlib import chdir
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from decimal import Decimal
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
import requests
|
|
@@ -63,21 +63,7 @@ def random_string() -> str:
|
|
|
63
63
|
return "".join(random.choices(string.ascii_uppercase + string.digits, k=7))
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def fake(
|
|
67
|
-
field_props: dict,
|
|
68
|
-
Command: (
|
|
69
|
-
AssessCommand
|
|
70
|
-
| DiagnoseCommand
|
|
71
|
-
| GoalCommand
|
|
72
|
-
| HistoryOfPresentIllnessCommand
|
|
73
|
-
| MedicationStatementCommand
|
|
74
|
-
| PlanCommand
|
|
75
|
-
| PrescribeCommand
|
|
76
|
-
| QuestionnaireCommand
|
|
77
|
-
| ReasonForVisitCommand
|
|
78
|
-
| StopMedicationCommand
|
|
79
|
-
),
|
|
80
|
-
) -> Any:
|
|
66
|
+
def fake(field_props: dict, Command: type[_BaseCommand]) -> Any:
|
|
81
67
|
t = get_field_type(field_props)
|
|
82
68
|
match t:
|
|
83
69
|
case "string":
|
|
@@ -107,18 +93,7 @@ def fake(
|
|
|
107
93
|
|
|
108
94
|
|
|
109
95
|
def raises_wrong_type_error(
|
|
110
|
-
Command:
|
|
111
|
-
AssessCommand
|
|
112
|
-
| DiagnoseCommand
|
|
113
|
-
| GoalCommand
|
|
114
|
-
| HistoryOfPresentIllnessCommand
|
|
115
|
-
| MedicationStatementCommand
|
|
116
|
-
| PlanCommand
|
|
117
|
-
| PrescribeCommand
|
|
118
|
-
| QuestionnaireCommand
|
|
119
|
-
| ReasonForVisitCommand
|
|
120
|
-
| StopMedicationCommand
|
|
121
|
-
),
|
|
96
|
+
Command: type[_BaseCommand],
|
|
122
97
|
field: str,
|
|
123
98
|
) -> None:
|
|
124
99
|
field_props = Command.model_json_schema()["properties"][field]
|
|
@@ -155,18 +130,7 @@ def raises_wrong_type_error(
|
|
|
155
130
|
|
|
156
131
|
|
|
157
132
|
def raises_none_error_for_effect_method(
|
|
158
|
-
Command:
|
|
159
|
-
AssessCommand
|
|
160
|
-
| DiagnoseCommand
|
|
161
|
-
| GoalCommand
|
|
162
|
-
| HistoryOfPresentIllnessCommand
|
|
163
|
-
| MedicationStatementCommand
|
|
164
|
-
| PlanCommand
|
|
165
|
-
| PrescribeCommand
|
|
166
|
-
| QuestionnaireCommand
|
|
167
|
-
| ReasonForVisitCommand
|
|
168
|
-
| StopMedicationCommand
|
|
169
|
-
),
|
|
133
|
+
Command: type[_BaseCommand],
|
|
170
134
|
method: str,
|
|
171
135
|
) -> None:
|
|
172
136
|
cmd_name = Command.__name__
|
|
@@ -214,7 +178,7 @@ class Protocol(BaseProtocol):
|
|
|
214
178
|
|
|
215
179
|
def install_plugin(plugin_name: str, token: MaskedValue) -> None:
|
|
216
180
|
requests.post(
|
|
217
|
-
plugin_url(settings.INTEGRATION_TEST_URL),
|
|
181
|
+
plugin_url(cast(str, settings.INTEGRATION_TEST_URL)),
|
|
218
182
|
data={"is_enabled": True},
|
|
219
183
|
files={"package": open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb")},
|
|
220
184
|
headers={"Authorization": f"Bearer {token.value}"},
|
|
@@ -267,7 +231,7 @@ def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None:
|
|
|
267
231
|
|
|
268
232
|
# disable
|
|
269
233
|
requests.patch(
|
|
270
|
-
plugin_url(settings.INTEGRATION_TEST_URL, plugin_name),
|
|
234
|
+
plugin_url(cast(str, settings.INTEGRATION_TEST_URL), plugin_name),
|
|
271
235
|
data={"is_enabled": False},
|
|
272
236
|
headers={
|
|
273
237
|
"Authorization": f"Bearer {token.value}",
|
|
@@ -275,13 +239,13 @@ def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None:
|
|
|
275
239
|
)
|
|
276
240
|
# delete
|
|
277
241
|
requests.delete(
|
|
278
|
-
plugin_url(settings.INTEGRATION_TEST_URL, plugin_name),
|
|
242
|
+
plugin_url(cast(str, settings.INTEGRATION_TEST_URL), plugin_name),
|
|
279
243
|
headers={"Authorization": f"Bearer {token.value}"},
|
|
280
244
|
)
|
|
281
245
|
|
|
282
246
|
|
|
283
247
|
# For reuse with the protocol code
|
|
284
|
-
COMMANDS = [
|
|
248
|
+
COMMANDS: list[type[_BaseCommand]] = [
|
|
285
249
|
AssessCommand,
|
|
286
250
|
DiagnoseCommand,
|
|
287
251
|
GoalCommand,
|
|
@@ -79,7 +79,7 @@ runner = CliRunner()
|
|
|
79
79
|
],
|
|
80
80
|
)
|
|
81
81
|
def test_command_raises_generic_error_when_kwarg_given_incorrect_type(
|
|
82
|
-
Command: _BaseCommand,
|
|
82
|
+
Command: type[_BaseCommand],
|
|
83
83
|
fields_to_test: tuple[str],
|
|
84
84
|
) -> None:
|
|
85
85
|
for field in fields_to_test:
|
|
@@ -179,7 +179,7 @@ def test_command_raises_generic_error_when_kwarg_given_incorrect_type(
|
|
|
179
179
|
],
|
|
180
180
|
)
|
|
181
181
|
def test_command_raises_specific_error_when_kwarg_given_incorrect_type(
|
|
182
|
-
Command: PlanCommand | ReasonForVisitCommand,
|
|
182
|
+
Command: type[PlanCommand] | type[ReasonForVisitCommand],
|
|
183
183
|
err_kwargs: dict,
|
|
184
184
|
err_msg: str,
|
|
185
185
|
valid_kwargs: dict,
|
|
@@ -255,7 +255,7 @@ def test_command_raises_specific_error_when_kwarg_given_incorrect_type(
|
|
|
255
255
|
],
|
|
256
256
|
)
|
|
257
257
|
def test_command_allows_kwarg_with_correct_type(
|
|
258
|
-
Command: _BaseCommand,
|
|
258
|
+
Command: type[_BaseCommand],
|
|
259
259
|
fields_to_test: tuple[str],
|
|
260
260
|
) -> None:
|
|
261
261
|
schema = Command.model_json_schema()
|
canvas_sdk/data/client.py
CHANGED
|
@@ -54,7 +54,7 @@ class _CanvasGQLClient:
|
|
|
54
54
|
|
|
55
55
|
def __init__(self) -> None:
|
|
56
56
|
self.client = Client(
|
|
57
|
-
transport=AIOHTTPTransport(url=
|
|
57
|
+
transport=AIOHTTPTransport(url=GRAPHQL_ENDPOINT),
|
|
58
58
|
# TODO: follow the documentation in the link below to specify a
|
|
59
59
|
# cached copy of the schema
|
|
60
60
|
# https://gql.readthedocs.io/en/stable/usage/validation.html#using-a-provided-schema
|
|
@@ -6,6 +6,7 @@ from typing import Any, Generator
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
import requests
|
|
9
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
9
10
|
from pydantic import ValidationError
|
|
10
11
|
from typer.testing import CliRunner
|
|
11
12
|
|
|
@@ -49,10 +50,14 @@ def plugin_name() -> str:
|
|
|
49
50
|
return f"addbanneralert{datetime.now().timestamp()}".replace(".", "")
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
@pytest.fixture(
|
|
53
|
+
@pytest.fixture(scope="session")
|
|
53
54
|
def write_and_install_protocol_and_clean_up(
|
|
54
55
|
first_patient_id: str, plugin_name: str, token: MaskedValue
|
|
55
56
|
) -> Generator[Any, Any, Any]:
|
|
57
|
+
|
|
58
|
+
if not settings.INTEGRATION_TEST_URL:
|
|
59
|
+
raise ImproperlyConfigured("INTEGRATION_TEST_URL is not set")
|
|
60
|
+
|
|
56
61
|
# write the protocol
|
|
57
62
|
with chdir(Path("./custom-plugins")):
|
|
58
63
|
runner.invoke(app, "init", input=plugin_name)
|
|
@@ -122,7 +127,10 @@ class Protocol(BaseProtocol):
|
|
|
122
127
|
|
|
123
128
|
@pytest.mark.integtest
|
|
124
129
|
def test_protocol_that_adds_banner_alert(
|
|
125
|
-
|
|
130
|
+
write_and_install_protocol_and_clean_up: None,
|
|
131
|
+
token: MaskedValue,
|
|
132
|
+
plugin_name: str,
|
|
133
|
+
first_patient_id: str,
|
|
126
134
|
) -> None:
|
|
127
135
|
# trigger the event
|
|
128
136
|
requests.post(
|
|
@@ -180,7 +188,7 @@ def test_protocol_that_adds_banner_alert(
|
|
|
180
188
|
],
|
|
181
189
|
)
|
|
182
190
|
def test_banner_alert_apply_method_succeeds_with_all_required_fields(
|
|
183
|
-
Effect: AddBannerAlert | RemoveBannerAlert, params: dict, expected_payload: str
|
|
191
|
+
Effect: type[AddBannerAlert] | type[RemoveBannerAlert], params: dict, expected_payload: str
|
|
184
192
|
) -> None:
|
|
185
193
|
b = Effect()
|
|
186
194
|
for k, v in params.items():
|
|
@@ -214,7 +222,7 @@ def test_banner_alert_apply_method_succeeds_with_all_required_fields(
|
|
|
214
222
|
],
|
|
215
223
|
)
|
|
216
224
|
def test_banner_alert_apply_method_raises_error_without_required_fields(
|
|
217
|
-
Effect: AddBannerAlert | RemoveBannerAlert, expected_err_msgs: str
|
|
225
|
+
Effect: type[AddBannerAlert] | type[RemoveBannerAlert], expected_err_msgs: str
|
|
218
226
|
) -> None:
|
|
219
227
|
b = Effect()
|
|
220
228
|
with pytest.raises(ValidationError) as e:
|
|
@@ -107,7 +107,7 @@ def test_apply_method_raises_error_without_patient_id_and_key() -> None:
|
|
|
107
107
|
],
|
|
108
108
|
)
|
|
109
109
|
def test_add_recommendations(
|
|
110
|
-
init_params: dict[
|
|
110
|
+
init_params: dict[Any, Any], rec1_params: dict[Any, Any], rec2_params: dict[Any, Any]
|
|
111
111
|
) -> None:
|
|
112
112
|
p = ProtocolCard(**init_params)
|
|
113
113
|
p.add_recommendation(**rec1_params)
|
|
@@ -156,7 +156,7 @@ def test_add_recommendations(
|
|
|
156
156
|
],
|
|
157
157
|
)
|
|
158
158
|
def test_add_recommendations_from_commands(
|
|
159
|
-
Command: _BaseCommand, init_params: dict[str, str]
|
|
159
|
+
Command: type[_BaseCommand], init_params: dict[str, str]
|
|
160
160
|
) -> None:
|
|
161
161
|
cmd = Command(**init_params)
|
|
162
162
|
p = ProtocolCard(patient_id="uuid", key="commands")
|
canvas_sdk/utils/http.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from typing import Any, Callable, Mapping, TypeVar
|
|
3
|
+
from typing import Any, Callable, Mapping, TypeVar, cast
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
import statsd
|
|
@@ -28,7 +28,7 @@ class Http:
|
|
|
28
28
|
self.statsd_client.timing(f"http_{fn.__name__}", timing)
|
|
29
29
|
return result
|
|
30
30
|
|
|
31
|
-
return wrapper
|
|
31
|
+
return cast(F, wrapper)
|
|
32
32
|
|
|
33
33
|
@measure_time
|
|
34
34
|
def get(
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DocumentReviewMode(models.TextChoices):
|
|
5
|
+
"""Choices for document reviews."""
|
|
6
|
+
|
|
7
|
+
REVIEW_REQUIRED = "RR", "Review required"
|
|
8
|
+
ALREADY_REVIEWED_OFFLINE = "AR", "Already reviewed offline"
|
|
9
|
+
REVIEW_NOT_REQUIRED = "RN", "Review not required"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OrderStatus(models.TextChoices):
|
|
13
|
+
"""Choices for Order statuses."""
|
|
14
|
+
|
|
15
|
+
PROPOSED = "proposed", "Proposed"
|
|
16
|
+
DRAFT = "draft", "Draft"
|
|
17
|
+
PLANNED = "planned", "Planned"
|
|
18
|
+
REQUESTED = "requested", "Requested"
|
|
19
|
+
RECEIVED = "received", "Received"
|
|
20
|
+
ACCEPTED = "accepted", "Accepted"
|
|
21
|
+
IN_PROGRESS = "in-progress", "In-progress"
|
|
22
|
+
REVIEW = "review", "Review"
|
|
23
|
+
COMPLETED = "completed", "Completed"
|
|
24
|
+
CANCELLED = "cancelled", "Cancelled"
|
|
25
|
+
SUSPENDED = "suspended", "Suspended"
|
|
26
|
+
REJECTED = "rejected", "Rejected"
|
|
27
|
+
FAILED = "failed", "Failed"
|
|
28
|
+
ENTERED_IN_ERROR = "EIE", "Entered in Error"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ReviewPatientCommunicationMethod(models.TextChoices):
|
|
32
|
+
"""Choices for patient communication regarding reviews."""
|
|
33
|
+
|
|
34
|
+
DELEGATED_CALL_CAN_LEAVE_MESSAGE = "DM", "delegate call, can leave message"
|
|
35
|
+
DELEGATED_CALL_NEED_ANSWER = "DA", "delegate call, need patient to answer"
|
|
36
|
+
DELEGATED_LETTER = "DL", "delegate letter"
|
|
37
|
+
DO_NOT_COMMUNICATE = "DC", "do not communicate"
|
|
38
|
+
ALREADY_LEFT_MESSAGE = "AM", "already left message"
|
|
39
|
+
ALREADY_REVIEWED_WITH_PATIENT = "AR", "already reviewed with patient"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ReviewStatus(models.TextChoices):
|
|
43
|
+
"""Status choices for reviews."""
|
|
44
|
+
|
|
45
|
+
STATUS_REVIEWING = "reviewing", "reviewing"
|
|
46
|
+
STATUS_REVIEWED = "reviewed", "reviewed"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.v1.data.common import (
|
|
4
|
+
DocumentReviewMode,
|
|
5
|
+
OrderStatus,
|
|
6
|
+
ReviewPatientCommunicationMethod,
|
|
7
|
+
ReviewStatus,
|
|
8
|
+
)
|
|
9
|
+
from canvas_sdk.v1.data.patient import Patient
|
|
10
|
+
from canvas_sdk.v1.data.user import CanvasUser
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ImagingOrder(models.Model):
|
|
14
|
+
"""Model to read ImagingOrder data."""
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
managed = False
|
|
18
|
+
app_label = "canvas_sdk"
|
|
19
|
+
db_table = "canvas_sdk_data_api_imagingorder_001"
|
|
20
|
+
|
|
21
|
+
id = models.UUIDField()
|
|
22
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
23
|
+
created = models.DateTimeField()
|
|
24
|
+
modified = models.DateTimeField()
|
|
25
|
+
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
26
|
+
deleted = models.BooleanField()
|
|
27
|
+
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
28
|
+
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
29
|
+
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="imaging_orders")
|
|
30
|
+
# TODO - uncomment when Note model is complete
|
|
31
|
+
# note = models.ForeigneKey(Note, on_delete=models.DO_NOTHING, related_name="imaging_orders")
|
|
32
|
+
imaging = models.CharField()
|
|
33
|
+
# TODO - uncomment when ServiceProvider model is complete
|
|
34
|
+
# imaging_center = models.ForeignKey(ServiceProvider, related_name="imaging_orders", null=True, on_delete=models.DO_NOTHING)
|
|
35
|
+
note_to_radiologist = models.CharField()
|
|
36
|
+
internal_comment = models.CharField()
|
|
37
|
+
status = models.CharField(choices=OrderStatus)
|
|
38
|
+
date_time_ordered = models.DateTimeField()
|
|
39
|
+
priority = models.CharField()
|
|
40
|
+
# TODO - uncomment when Staff model is complete
|
|
41
|
+
# ordering_provider = models.ForeignKey(Staff, on_delete=models.CASCADE, related_name="imaging_orders", null=True)
|
|
42
|
+
delegated = models.BooleanField(default=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ImagingReview(models.Model):
|
|
46
|
+
"""Model to read ImagingReview data."""
|
|
47
|
+
|
|
48
|
+
class Meta:
|
|
49
|
+
managed = False
|
|
50
|
+
app_label = "canvas_sdk"
|
|
51
|
+
db_table = "canvas_sdk_data_api_imagingreview_001"
|
|
52
|
+
|
|
53
|
+
id = models.UUIDField()
|
|
54
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
55
|
+
created = models.DateTimeField()
|
|
56
|
+
modified = models.DateTimeField()
|
|
57
|
+
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
58
|
+
deleted = models.BooleanField()
|
|
59
|
+
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
60
|
+
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
61
|
+
patient_communication_method = models.CharField(choices=ReviewPatientCommunicationMethod)
|
|
62
|
+
# TODO - uncomment when Note model is complete
|
|
63
|
+
# note = models.ForeignKey(Note, on_delete=models.DO_NOTHING, related_name="imaging_reviews")
|
|
64
|
+
internal_comment = models.CharField()
|
|
65
|
+
message_to_patient = models.CharField()
|
|
66
|
+
is_released_to_patient = models.BooleanField()
|
|
67
|
+
status = models.CharField(choices=ReviewStatus)
|
|
68
|
+
patient = models.ForeignKey(
|
|
69
|
+
Patient, on_delete=models.DO_NOTHING, related_name="imaging_reviews"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ImagingReport(models.Model):
|
|
74
|
+
"""Model to read ImagingReport data."""
|
|
75
|
+
|
|
76
|
+
class ImagingReportSource(models.TextChoices):
|
|
77
|
+
RADIOLOGY_FROM_PATIENT = "RADIOLOGY_PATIENT", "Radiology Report From Patient"
|
|
78
|
+
VERBAL_FROM_PATIENT = "VERBAL_PATIENT", "Verbal Report From Patient"
|
|
79
|
+
DIRECTLY_REPORT = "DIRECTLY_RADIOLOGY", "Directly Radiology Report"
|
|
80
|
+
|
|
81
|
+
class Meta:
|
|
82
|
+
managed = False
|
|
83
|
+
app_label = "canvas_sdk"
|
|
84
|
+
db_table = "canvas_sdk_data_api_imagingreport_001"
|
|
85
|
+
|
|
86
|
+
id = models.UUIDField()
|
|
87
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
88
|
+
created = models.DateTimeField()
|
|
89
|
+
modified = models.DateTimeField()
|
|
90
|
+
review_mode = models.CharField(choices=DocumentReviewMode)
|
|
91
|
+
junked = models.BooleanField()
|
|
92
|
+
requires_signature = models.BooleanField()
|
|
93
|
+
assigned_date = models.DateTimeField()
|
|
94
|
+
patient = models.ForeignKey(
|
|
95
|
+
Patient, on_delete=models.DO_NOTHING, related_name="imaging_results"
|
|
96
|
+
)
|
|
97
|
+
order = models.ForeignKey(ImagingOrder, on_delete=models.DO_NOTHING, null=True)
|
|
98
|
+
source = models.CharField(choices=ImagingReportSource)
|
|
99
|
+
name = models.CharField()
|
|
100
|
+
result_date = models.DateField()
|
|
101
|
+
original_date = models.DateField()
|
|
102
|
+
review = models.ForeignKey(ImagingReview, null=True, on_delete=models.DO_NOTHING)
|