canvas 0.2.11__py3-none-any.whl → 0.3.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.2.11.dist-info → canvas-0.3.0.dist-info}/METADATA +3 -3
- {canvas-0.2.11.dist-info → canvas-0.3.0.dist-info}/RECORD +45 -44
- canvas_cli/apps/plugin/plugin.py +56 -23
- canvas_cli/utils/validators/validators.py +1 -1
- canvas_generated/messages/effects_pb2.py +5 -5
- canvas_generated/messages/effects_pb2.pyi +4 -2
- canvas_generated/messages/events_pb2.py +3 -3
- canvas_generated/messages/events_pb2.pyi +62 -0
- canvas_generated/messages/plugins_pb2.py +1 -1
- canvas_generated/services/plugin_runner_pb2.py +1 -1
- canvas_sdk/base.py +2 -2
- canvas_sdk/commands/__init__.py +3 -1
- canvas_sdk/commands/base.py +2 -2
- canvas_sdk/commands/commands/allergy.py +2 -0
- canvas_sdk/commands/commands/close_goal.py +3 -0
- canvas_sdk/commands/commands/prescribe.py +1 -3
- canvas_sdk/commands/commands/refill.py +1 -0
- canvas_sdk/commands/commands/task.py +1 -1
- canvas_sdk/commands/commands/vitals.py +15 -12
- canvas_sdk/commands/tests/schema/tests.py +1 -1
- canvas_sdk/commands/tests/test_utils.py +4 -2
- canvas_sdk/data/base.py +5 -1
- canvas_sdk/data/client.py +1 -1
- canvas_sdk/data/patient.py +2 -0
- canvas_sdk/data/staff.py +2 -0
- canvas_sdk/data/task.py +7 -0
- canvas_sdk/effects/protocol_card/protocol_card.py +2 -0
- canvas_sdk/effects/protocol_card/tests.py +5 -2
- canvas_sdk/protocols/__init__.py +1 -0
- canvas_sdk/protocols/clinical_quality_measure.py +1 -0
- canvas_sdk/utils/http.py +19 -18
- canvas_sdk/utils/stats.py +2 -1
- canvas_sdk/v1/data/allergy_intolerance.py +2 -3
- canvas_sdk/v1/data/base.py +55 -9
- canvas_sdk/v1/data/lab.py +8 -0
- canvas_sdk/v1/data/patient.py +3 -0
- canvas_sdk/v1/data/questionnaire.py +204 -0
- canvas_sdk/v1/data/user.py +2 -0
- canvas_sdk/value_set/v2022/condition.py +2 -2
- canvas_sdk/value_set/v2022/encounter.py +1 -1
- canvas_sdk/value_set/value_set.py +20 -5
- logger/__init__.py +1 -0
- logger/logger.py +17 -6
- {canvas-0.2.11.dist-info → canvas-0.3.0.dist-info}/WHEEL +0 -0
- {canvas-0.2.11.dist-info → canvas-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -62,6 +62,25 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
|
62
62
|
VITAL_SIGN_CREATED: _ClassVar[EventType]
|
|
63
63
|
VITAL_SIGN_UPDATED: _ClassVar[EventType]
|
|
64
64
|
CRON: _ClassVar[EventType]
|
|
65
|
+
CARE_TEAM_MEMBERSHIP_CREATED: _ClassVar[EventType]
|
|
66
|
+
CARE_TEAM_MEMBERSHIP_UPDATED: _ClassVar[EventType]
|
|
67
|
+
CARE_TEAM_MEMBERSHIP_DELETED: _ClassVar[EventType]
|
|
68
|
+
NOTE_STATE_CHANGE_EVENT_CREATED: _ClassVar[EventType]
|
|
69
|
+
NOTE_STATE_CHANGE_EVENT_UPDATED: _ClassVar[EventType]
|
|
70
|
+
PATIENT_ADDRESS_CREATED: _ClassVar[EventType]
|
|
71
|
+
PATIENT_ADDRESS_UPDATED: _ClassVar[EventType]
|
|
72
|
+
PATIENT_ADDRESS_DELETED: _ClassVar[EventType]
|
|
73
|
+
PATIENT_CONTACT_PERSON_CREATED: _ClassVar[EventType]
|
|
74
|
+
PATIENT_CONTACT_PERSON_UPDATED: _ClassVar[EventType]
|
|
75
|
+
PATIENT_CONTACT_PERSON_DELETED: _ClassVar[EventType]
|
|
76
|
+
PATIENT_CONTACT_POINT_CREATED: _ClassVar[EventType]
|
|
77
|
+
PATIENT_CONTACT_POINT_UPDATED: _ClassVar[EventType]
|
|
78
|
+
PATIENT_CONTACT_POINT_DELETED: _ClassVar[EventType]
|
|
79
|
+
PROTOCOL_OVERRIDE_CREATED: _ClassVar[EventType]
|
|
80
|
+
PROTOCOL_OVERRIDE_UPDATED: _ClassVar[EventType]
|
|
81
|
+
PROTOCOL_OVERRIDE_DELETED: _ClassVar[EventType]
|
|
82
|
+
TASK_COMMENT_UPDATED: _ClassVar[EventType]
|
|
83
|
+
TASK_COMMENT_DELETED: _ClassVar[EventType]
|
|
65
84
|
PRE_COMMAND_ORIGINATE: _ClassVar[EventType]
|
|
66
85
|
POST_COMMAND_ORIGINATE: _ClassVar[EventType]
|
|
67
86
|
PRE_COMMAND_UPDATE: _ClassVar[EventType]
|
|
@@ -106,6 +125,18 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
|
106
125
|
ASSESS_COMMAND__POST_EXECUTE_ACTION: _ClassVar[EventType]
|
|
107
126
|
ASSESS__CONDITION__POST_SEARCH: _ClassVar[EventType]
|
|
108
127
|
ASSESS__CONDITION__PRE_SEARCH: _ClassVar[EventType]
|
|
128
|
+
CLIPBOARD_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
|
|
129
|
+
CLIPBOARD_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
|
|
130
|
+
CLIPBOARD_COMMAND__PRE_UPDATE: _ClassVar[EventType]
|
|
131
|
+
CLIPBOARD_COMMAND__POST_UPDATE: _ClassVar[EventType]
|
|
132
|
+
CLIPBOARD_COMMAND__PRE_COMMIT: _ClassVar[EventType]
|
|
133
|
+
CLIPBOARD_COMMAND__POST_COMMIT: _ClassVar[EventType]
|
|
134
|
+
CLIPBOARD_COMMAND__PRE_DELETE: _ClassVar[EventType]
|
|
135
|
+
CLIPBOARD_COMMAND__POST_DELETE: _ClassVar[EventType]
|
|
136
|
+
CLIPBOARD_COMMAND__PRE_ENTER_IN_ERROR: _ClassVar[EventType]
|
|
137
|
+
CLIPBOARD_COMMAND__POST_ENTER_IN_ERROR: _ClassVar[EventType]
|
|
138
|
+
CLIPBOARD_COMMAND__PRE_EXECUTE_ACTION: _ClassVar[EventType]
|
|
139
|
+
CLIPBOARD_COMMAND__POST_EXECUTE_ACTION: _ClassVar[EventType]
|
|
109
140
|
CLOSE_GOAL_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
|
|
110
141
|
CLOSE_GOAL_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
|
|
111
142
|
CLOSE_GOAL_COMMAND__PRE_UPDATE: _ClassVar[EventType]
|
|
@@ -578,6 +609,25 @@ TASK_UPDATED: EventType
|
|
|
578
609
|
VITAL_SIGN_CREATED: EventType
|
|
579
610
|
VITAL_SIGN_UPDATED: EventType
|
|
580
611
|
CRON: EventType
|
|
612
|
+
CARE_TEAM_MEMBERSHIP_CREATED: EventType
|
|
613
|
+
CARE_TEAM_MEMBERSHIP_UPDATED: EventType
|
|
614
|
+
CARE_TEAM_MEMBERSHIP_DELETED: EventType
|
|
615
|
+
NOTE_STATE_CHANGE_EVENT_CREATED: EventType
|
|
616
|
+
NOTE_STATE_CHANGE_EVENT_UPDATED: EventType
|
|
617
|
+
PATIENT_ADDRESS_CREATED: EventType
|
|
618
|
+
PATIENT_ADDRESS_UPDATED: EventType
|
|
619
|
+
PATIENT_ADDRESS_DELETED: EventType
|
|
620
|
+
PATIENT_CONTACT_PERSON_CREATED: EventType
|
|
621
|
+
PATIENT_CONTACT_PERSON_UPDATED: EventType
|
|
622
|
+
PATIENT_CONTACT_PERSON_DELETED: EventType
|
|
623
|
+
PATIENT_CONTACT_POINT_CREATED: EventType
|
|
624
|
+
PATIENT_CONTACT_POINT_UPDATED: EventType
|
|
625
|
+
PATIENT_CONTACT_POINT_DELETED: EventType
|
|
626
|
+
PROTOCOL_OVERRIDE_CREATED: EventType
|
|
627
|
+
PROTOCOL_OVERRIDE_UPDATED: EventType
|
|
628
|
+
PROTOCOL_OVERRIDE_DELETED: EventType
|
|
629
|
+
TASK_COMMENT_UPDATED: EventType
|
|
630
|
+
TASK_COMMENT_DELETED: EventType
|
|
581
631
|
PRE_COMMAND_ORIGINATE: EventType
|
|
582
632
|
POST_COMMAND_ORIGINATE: EventType
|
|
583
633
|
PRE_COMMAND_UPDATE: EventType
|
|
@@ -622,6 +672,18 @@ ASSESS_COMMAND__PRE_EXECUTE_ACTION: EventType
|
|
|
622
672
|
ASSESS_COMMAND__POST_EXECUTE_ACTION: EventType
|
|
623
673
|
ASSESS__CONDITION__POST_SEARCH: EventType
|
|
624
674
|
ASSESS__CONDITION__PRE_SEARCH: EventType
|
|
675
|
+
CLIPBOARD_COMMAND__PRE_ORIGINATE: EventType
|
|
676
|
+
CLIPBOARD_COMMAND__POST_ORIGINATE: EventType
|
|
677
|
+
CLIPBOARD_COMMAND__PRE_UPDATE: EventType
|
|
678
|
+
CLIPBOARD_COMMAND__POST_UPDATE: EventType
|
|
679
|
+
CLIPBOARD_COMMAND__PRE_COMMIT: EventType
|
|
680
|
+
CLIPBOARD_COMMAND__POST_COMMIT: EventType
|
|
681
|
+
CLIPBOARD_COMMAND__PRE_DELETE: EventType
|
|
682
|
+
CLIPBOARD_COMMAND__POST_DELETE: EventType
|
|
683
|
+
CLIPBOARD_COMMAND__PRE_ENTER_IN_ERROR: EventType
|
|
684
|
+
CLIPBOARD_COMMAND__POST_ENTER_IN_ERROR: EventType
|
|
685
|
+
CLIPBOARD_COMMAND__PRE_EXECUTE_ACTION: EventType
|
|
686
|
+
CLIPBOARD_COMMAND__POST_EXECUTE_ACTION: EventType
|
|
625
687
|
CLOSE_GOAL_COMMAND__PRE_ORIGINATE: EventType
|
|
626
688
|
CLOSE_GOAL_COMMAND__POST_ORIGINATE: EventType
|
|
627
689
|
CLOSE_GOAL_COMMAND__PRE_UPDATE: EventType
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# source: canvas_generated/messages/plugins.proto
|
|
4
|
-
# Protobuf Python Version: 4.25.
|
|
4
|
+
# Protobuf Python Version: 4.25.1
|
|
5
5
|
"""Generated protocol buffer code."""
|
|
6
6
|
from google.protobuf import descriptor as _descriptor
|
|
7
7
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# source: canvas_generated/services/plugin_runner.proto
|
|
4
|
-
# Protobuf Python Version: 4.25.
|
|
4
|
+
# Protobuf Python Version: 4.25.1
|
|
5
5
|
"""Generated protocol buffer code."""
|
|
6
6
|
from google.protobuf import descriptor as _descriptor
|
|
7
7
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
canvas_sdk/base.py
CHANGED
|
@@ -23,13 +23,13 @@ class Model(BaseModel):
|
|
|
23
23
|
},
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
def _get_effect_method_required_fields(self, method:
|
|
26
|
+
def _get_effect_method_required_fields(self, method: Any) -> tuple:
|
|
27
27
|
return getattr(self.Meta, f"{method}_required_fields", tuple())
|
|
28
28
|
|
|
29
29
|
def _create_error_detail(self, type: str, message: str, value: Any) -> InitErrorDetails:
|
|
30
30
|
return InitErrorDetails({"type": PydanticCustomError(type, message), "input": value})
|
|
31
31
|
|
|
32
|
-
def _get_error_details(self, method:
|
|
32
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
33
33
|
required_fields = self._get_effect_method_required_fields(method)
|
|
34
34
|
class_name = self.__repr_name__()
|
|
35
35
|
class_name_article = "an" if class_name.startswith(("A", "E", "I", "O", "U")) else "a"
|
canvas_sdk/commands/__init__.py
CHANGED
|
@@ -11,7 +11,9 @@ from canvas_sdk.commands.commands.instruct import InstructCommand
|
|
|
11
11
|
from canvas_sdk.commands.commands.lab_order import LabOrderCommand
|
|
12
12
|
from canvas_sdk.commands.commands.medical_history import MedicalHistoryCommand
|
|
13
13
|
from canvas_sdk.commands.commands.medication_statement import MedicationStatementCommand
|
|
14
|
-
from canvas_sdk.commands.commands.past_surgical_history import
|
|
14
|
+
from canvas_sdk.commands.commands.past_surgical_history import (
|
|
15
|
+
PastSurgicalHistoryCommand,
|
|
16
|
+
)
|
|
15
17
|
from canvas_sdk.commands.commands.perform import PerformCommand
|
|
16
18
|
from canvas_sdk.commands.commands.plan import PlanCommand
|
|
17
19
|
from canvas_sdk.commands.commands.prescribe import PrescribeCommand
|
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 Literal, get_args, get_origin
|
|
5
|
+
from typing import Any, Literal, Tuple, Union, get_args, get_origin
|
|
6
6
|
|
|
7
7
|
from canvas_sdk.base import Model
|
|
8
8
|
from canvas_sdk.commands.constants import Coding
|
|
@@ -27,7 +27,7 @@ class _BaseCommand(Model):
|
|
|
27
27
|
|
|
28
28
|
def _get_effect_method_required_fields(
|
|
29
29
|
self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error"]
|
|
30
|
-
) -> tuple
|
|
30
|
+
) -> tuple:
|
|
31
31
|
base_required_fields: tuple = getattr(
|
|
32
32
|
_BaseCommand.Meta, f"{method}_required_fields", tuple()
|
|
33
33
|
)
|
|
@@ -3,6 +3,8 @@ from canvas_sdk.commands.commands.goal import GoalCommand
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class CloseGoalCommand(BaseCommand):
|
|
6
|
+
"""A class for managing a CloseGoal command within a specific note."""
|
|
7
|
+
|
|
6
8
|
class Meta:
|
|
7
9
|
key = "closeGoal"
|
|
8
10
|
commit_required_fields = ("goal_id",)
|
|
@@ -13,6 +15,7 @@ class CloseGoalCommand(BaseCommand):
|
|
|
13
15
|
|
|
14
16
|
@property
|
|
15
17
|
def values(self) -> dict:
|
|
18
|
+
"""The CloseGoal command's field values."""
|
|
16
19
|
return {
|
|
17
20
|
"goal_id": self.goal_id,
|
|
18
21
|
"achievement_status": (
|
|
@@ -29,9 +29,7 @@ class PrescribeCommand(_BaseCommand):
|
|
|
29
29
|
NOT_ALLOWED = "not_allowed"
|
|
30
30
|
|
|
31
31
|
fdb_code: str | None = Field(default=None, json_schema_extra={"commands_api_name": "prescribe"})
|
|
32
|
-
icd10_codes: conlist(str, max_length=2) = Field(
|
|
33
|
-
[], json_schema_extra={"commands_api_name": "indications"}
|
|
34
|
-
)
|
|
32
|
+
icd10_codes: conlist(str, max_length=2) = Field([], json_schema_extra={"commands_api_name": "indications"}) # type: ignore[valid-type]
|
|
35
33
|
sig: str = ""
|
|
36
34
|
days_supply: int | None = None
|
|
37
35
|
quantity_to_dispense: Decimal | float | int | None = None
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
from pydantic import conint, constr
|
|
2
|
-
from typing import Optional
|
|
3
1
|
from enum import Enum
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import conint, constr
|
|
4
5
|
|
|
5
6
|
from canvas_sdk.commands.base import _BaseCommand as BaseCommand
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class VitalsCommand(BaseCommand):
|
|
10
|
+
"""A class for managing a Vitals command within a specific note."""
|
|
9
11
|
|
|
10
12
|
class Meta:
|
|
11
13
|
key = "vitals"
|
|
@@ -37,23 +39,24 @@ class VitalsCommand(BaseCommand):
|
|
|
37
39
|
IRREGULARLY_IRREGULAR = 1
|
|
38
40
|
REGULARLY_IRREGULAR = 2
|
|
39
41
|
|
|
40
|
-
height: conint(ge=10, le=108) | None = None
|
|
41
|
-
weight_lbs: conint(ge=1, le=1500) | None = None
|
|
42
|
+
height: conint(ge=10, le=108) | None = None # type: ignore[valid-type]
|
|
43
|
+
weight_lbs: conint(ge=1, le=1500) | None = None # type: ignore[valid-type]
|
|
42
44
|
weight_oz: int | None = None
|
|
43
|
-
waist_circumference: conint(ge=20, le=200) | None = None
|
|
44
|
-
body_temperature: conint(ge=85, le=107) | None = None
|
|
45
|
+
waist_circumference: conint(ge=20, le=200) | None = None # type: ignore[valid-type]
|
|
46
|
+
body_temperature: conint(ge=85, le=107) | None = None # type: ignore[valid-type]
|
|
45
47
|
body_temperature_site: BodyTemperatureSite | None = None
|
|
46
|
-
blood_pressure_systole: conint(ge=30, le=305) | None = None
|
|
47
|
-
blood_pressure_diastole: conint(ge=20, le=180) | None = None
|
|
48
|
+
blood_pressure_systole: conint(ge=30, le=305) | None = None # type: ignore[valid-type]
|
|
49
|
+
blood_pressure_diastole: conint(ge=20, le=180) | None = None # type: ignore[valid-type]
|
|
48
50
|
blood_pressure_position_and_site: BloodPressureSite | None = None
|
|
49
|
-
pulse: conint(ge=30, le=250) | None = None
|
|
51
|
+
pulse: conint(ge=30, le=250) | None = None # type: ignore[valid-type]
|
|
50
52
|
pulse_rhythm: PulseRhythm | None = None
|
|
51
|
-
respiration_rate: conint(ge=6, le=60) | None = None
|
|
52
|
-
oxygen_saturation: conint(ge=60, le=100) | None = None
|
|
53
|
-
note: constr(max_length=150) | None = None
|
|
53
|
+
respiration_rate: conint(ge=6, le=60) | None = None # type: ignore[valid-type]
|
|
54
|
+
oxygen_saturation: conint(ge=60, le=100) | None = None # type: ignore[valid-type]
|
|
55
|
+
note: constr(max_length=150) | None = None # type: ignore[valid-type]
|
|
54
56
|
|
|
55
57
|
@property
|
|
56
58
|
def values(self) -> dict:
|
|
59
|
+
"""The Vitals command's field values."""
|
|
57
60
|
return {
|
|
58
61
|
"height": self.height,
|
|
59
62
|
"weight_lbs": self.weight_lbs,
|
|
@@ -7,7 +7,7 @@ import requests
|
|
|
7
7
|
|
|
8
8
|
import settings
|
|
9
9
|
from canvas_sdk.commands.base import _BaseCommand
|
|
10
|
-
from canvas_sdk.commands.constants import
|
|
10
|
+
from canvas_sdk.commands.constants import ClinicalQuantity, Coding
|
|
11
11
|
from canvas_sdk.commands.tests.test_utils import (
|
|
12
12
|
COMMANDS,
|
|
13
13
|
MaskedValue,
|
|
@@ -29,7 +29,7 @@ from canvas_sdk.commands import (
|
|
|
29
29
|
UpdateGoalCommand,
|
|
30
30
|
)
|
|
31
31
|
from canvas_sdk.commands.base import _BaseCommand
|
|
32
|
-
from canvas_sdk.commands.constants import
|
|
32
|
+
from canvas_sdk.commands.constants import ClinicalQuantity, Coding
|
|
33
33
|
|
|
34
34
|
runner = CliRunner()
|
|
35
35
|
|
|
@@ -188,7 +188,9 @@ def raises_none_error_for_effect_method(
|
|
|
188
188
|
)
|
|
189
189
|
|
|
190
190
|
|
|
191
|
-
def write_protocol_code(
|
|
191
|
+
def write_protocol_code(
|
|
192
|
+
note_uuid: str, plugin_name: str, commands: list[type[_BaseCommand]]
|
|
193
|
+
) -> None:
|
|
192
194
|
imports = ", ".join([c.__name__ for c in commands])
|
|
193
195
|
effects = ", ".join([f"{c.__name__}(note_uuid='{note_uuid}').originate()" for c in commands])
|
|
194
196
|
|
canvas_sdk/data/base.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
from pydantic_core import ValidationError
|
|
2
4
|
|
|
3
5
|
from canvas_sdk.base import Model
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class DataModel(Model):
|
|
9
|
+
"""Base class for data models."""
|
|
10
|
+
|
|
7
11
|
class Meta:
|
|
8
12
|
update_required_fields = ("id",)
|
|
9
13
|
|
|
10
|
-
def model_dump_json_nested(self, *args, **kwargs) -> str:
|
|
14
|
+
def model_dump_json_nested(self, *args: Any, **kwargs: Any) -> str:
|
|
11
15
|
"""
|
|
12
16
|
Returns the model's json representation nested in a {"data": {..}} key.
|
|
13
17
|
"""
|
canvas_sdk/data/client.py
CHANGED
canvas_sdk/data/patient.py
CHANGED
canvas_sdk/data/staff.py
CHANGED
canvas_sdk/data/task.py
CHANGED
|
@@ -8,6 +8,8 @@ from canvas_sdk.effects import Effect, EffectType
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Task(DataModel):
|
|
11
|
+
"""Data model for a task."""
|
|
12
|
+
|
|
11
13
|
class Meta(DataModel.Meta):
|
|
12
14
|
create_required_fields = ("title",)
|
|
13
15
|
|
|
@@ -26,15 +28,18 @@ class Task(DataModel):
|
|
|
26
28
|
labels: list[str] | None = None
|
|
27
29
|
|
|
28
30
|
def create(self) -> Effect:
|
|
31
|
+
"""Return an effect to create the task."""
|
|
29
32
|
self._validate_before_effect("create")
|
|
30
33
|
return Effect(type=EffectType.CREATE_TASK, payload=self.model_dump_json_nested())
|
|
31
34
|
|
|
32
35
|
def update(self) -> Effect:
|
|
36
|
+
"""Return an effect to update the task."""
|
|
33
37
|
self._validate_before_effect("update")
|
|
34
38
|
payload = self.model_dump_json_nested(exclude_unset=True)
|
|
35
39
|
return Effect(type=EffectType.UPDATE_TASK, payload=payload)
|
|
36
40
|
|
|
37
41
|
def add_comment(self, comment: str) -> Effect:
|
|
42
|
+
"""Return an effect to add a comment to the task."""
|
|
38
43
|
if not self.id:
|
|
39
44
|
raise ValueError("Cannot add a comment to a Task without an id")
|
|
40
45
|
task_comment = TaskComment(task=self, body=comment)
|
|
@@ -46,6 +51,8 @@ class Task(DataModel):
|
|
|
46
51
|
|
|
47
52
|
|
|
48
53
|
class TaskComment(DataModel):
|
|
54
|
+
"""Data model for a task comment."""
|
|
55
|
+
|
|
49
56
|
class Meta:
|
|
50
57
|
create_required_fields = (
|
|
51
58
|
"body",
|
|
@@ -50,6 +50,7 @@ class ProtocolCard(_BaseEffect):
|
|
|
50
50
|
narrative: str = ""
|
|
51
51
|
recommendations: list[Recommendation] = []
|
|
52
52
|
status: Status = Status.DUE # type: ignore
|
|
53
|
+
feedback_enabled: bool = False
|
|
53
54
|
|
|
54
55
|
@property
|
|
55
56
|
def values(self) -> dict[str, Any]:
|
|
@@ -61,6 +62,7 @@ class ProtocolCard(_BaseEffect):
|
|
|
61
62
|
rec.values | {"key": i} for i, rec in enumerate(self.recommendations)
|
|
62
63
|
],
|
|
63
64
|
"status": self.status.value,
|
|
65
|
+
"feedback_enabled": self.feedback_enabled,
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
@property
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from pydantic import ValidationError
|
|
3
5
|
|
|
@@ -23,7 +25,7 @@ def test_apply_method_succeeds_with_patient_id_and_key() -> None:
|
|
|
23
25
|
applied = p.apply()
|
|
24
26
|
assert (
|
|
25
27
|
applied.payload
|
|
26
|
-
== '{"patient": "uuid", "key": "something-unique", "data": {"title": "", "narrative": "", "recommendations": [], "status": "due"}}'
|
|
28
|
+
== '{"patient": "uuid", "key": "something-unique", "data": {"title": "", "narrative": "", "recommendations": [], "status": "due", "feedback_enabled": false}}'
|
|
27
29
|
)
|
|
28
30
|
|
|
29
31
|
|
|
@@ -105,7 +107,7 @@ def test_apply_method_raises_error_without_patient_id_and_key() -> None:
|
|
|
105
107
|
],
|
|
106
108
|
)
|
|
107
109
|
def test_add_recommendations(
|
|
108
|
-
init_params: dict[str, str], rec1_params: dict[
|
|
110
|
+
init_params: dict[str, str], rec1_params: dict[Any, Any], rec2_params: dict[str, str]
|
|
109
111
|
) -> None:
|
|
110
112
|
p = ProtocolCard(**init_params)
|
|
111
113
|
p.add_recommendation(**rec1_params)
|
|
@@ -133,6 +135,7 @@ def test_add_recommendations(
|
|
|
133
135
|
},
|
|
134
136
|
],
|
|
135
137
|
"status": "due",
|
|
138
|
+
"feedback_enabled": False,
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
|
canvas_sdk/protocols/__init__.py
CHANGED
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, TypeVar
|
|
3
|
+
from typing import Any, Callable, Mapping, TypeVar
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
import statsd
|
|
@@ -16,26 +16,27 @@ class Http:
|
|
|
16
16
|
self.statsd_client = statsd.StatsClient()
|
|
17
17
|
|
|
18
18
|
@staticmethod
|
|
19
|
-
def measure_time(fn: F
|
|
19
|
+
def measure_time(fn: F) -> F:
|
|
20
20
|
"""A decorator to store timing of HTTP calls."""
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return result
|
|
22
|
+
@wraps(fn)
|
|
23
|
+
def wrapper(self: "Http", *args: Any, **kwargs: Any) -> Any:
|
|
24
|
+
start_time = time.time()
|
|
25
|
+
result = fn(self, *args, **kwargs)
|
|
26
|
+
end_time = time.time()
|
|
27
|
+
timing = int((end_time - start_time) * 1000)
|
|
28
|
+
self.statsd_client.timing(f"http_{fn.__name__}", timing)
|
|
29
|
+
return result
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return _decorator(fn) if fn else _decorator
|
|
31
|
+
return wrapper
|
|
35
32
|
|
|
36
33
|
@measure_time
|
|
37
|
-
def get(
|
|
34
|
+
def get(
|
|
35
|
+
self, url: str, headers: Mapping[str, str | bytes | None] | None = None
|
|
36
|
+
) -> requests.Response:
|
|
38
37
|
"""Sends a GET request."""
|
|
38
|
+
if headers is None:
|
|
39
|
+
headers = {}
|
|
39
40
|
return self.session.get(url, headers=headers)
|
|
40
41
|
|
|
41
42
|
@measure_time
|
|
@@ -44,7 +45,7 @@ class Http:
|
|
|
44
45
|
url: str,
|
|
45
46
|
json: dict | None = None,
|
|
46
47
|
data: dict | str | list | bytes | None = None,
|
|
47
|
-
headers:
|
|
48
|
+
headers: Mapping[str, str | bytes | None] | None = None,
|
|
48
49
|
) -> requests.Response:
|
|
49
50
|
"""Sends a POST request."""
|
|
50
51
|
return self.session.post(url, json=json, data=data, headers=headers)
|
|
@@ -55,7 +56,7 @@ class Http:
|
|
|
55
56
|
url: str,
|
|
56
57
|
json: dict | None = None,
|
|
57
58
|
data: dict | str | list | bytes | None = None,
|
|
58
|
-
headers:
|
|
59
|
+
headers: Mapping[str, str | bytes | None] | None = None,
|
|
59
60
|
) -> requests.Response:
|
|
60
61
|
"""Sends a PUT request."""
|
|
61
62
|
return self.session.put(url, json=json, data=data, headers=headers)
|
|
@@ -66,7 +67,7 @@ class Http:
|
|
|
66
67
|
url: str,
|
|
67
68
|
json: dict | None = None,
|
|
68
69
|
data: dict | str | list | bytes | None = None,
|
|
69
|
-
headers:
|
|
70
|
+
headers: Mapping[str, str | bytes | None] | None = None,
|
|
70
71
|
) -> requests.Response:
|
|
71
72
|
"""Sends a PATCH request."""
|
|
72
73
|
return self.session.patch(url, json=json, data=data, headers=headers)
|
canvas_sdk/utils/stats.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from django.contrib.postgres.fields import ArrayField
|
|
2
2
|
from django.db import models
|
|
3
3
|
|
|
4
|
-
from canvas_sdk.v1.data.base import CommittableModelManager
|
|
4
|
+
from canvas_sdk.v1.data.base import CommittableModelManager, ValueSetLookupQuerySet
|
|
5
5
|
from canvas_sdk.v1.data.patient import Patient
|
|
6
6
|
from canvas_sdk.v1.data.user import CanvasUser
|
|
7
7
|
|
|
@@ -14,13 +14,12 @@ class AllergyIntolerance(models.Model):
|
|
|
14
14
|
app_label = "canvas_sdk"
|
|
15
15
|
db_table = "canvas_sdk_data_api_allergyintolerance_001"
|
|
16
16
|
|
|
17
|
-
objects = CommittableModelManager()
|
|
17
|
+
objects = CommittableModelManager().from_queryset(ValueSetLookupQuerySet)()
|
|
18
18
|
|
|
19
19
|
id = models.UUIDField()
|
|
20
20
|
dbid = models.BigIntegerField(primary_key=True)
|
|
21
21
|
created = models.DateTimeField()
|
|
22
22
|
modified = models.DateTimeField()
|
|
23
|
-
editors = ArrayField(models.IntegerField())
|
|
24
23
|
deleted = models.BooleanField()
|
|
25
24
|
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
26
25
|
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
canvas_sdk/v1/data/base.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Container
|
|
2
|
+
from typing import TYPE_CHECKING, Type, cast
|
|
2
3
|
|
|
3
4
|
from django.db import models
|
|
4
5
|
from django.db.models import Q
|
|
@@ -8,19 +9,26 @@ if TYPE_CHECKING:
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class CommittableModelManager(models.Manager):
|
|
12
|
+
"""A manager for commands that can be committed."""
|
|
13
|
+
|
|
11
14
|
def get_queryset(self) -> "models.QuerySet":
|
|
15
|
+
"""Return a queryset that filters out deleted objects."""
|
|
12
16
|
# TODO: Should we just filter these out at the view level?
|
|
13
17
|
return super().get_queryset().filter(deleted=False)
|
|
14
18
|
|
|
15
19
|
def committed(self) -> "models.QuerySet":
|
|
20
|
+
"""Return a queryset that filters for objects that have been committed."""
|
|
16
21
|
# The committer_id IS set, and the entered_in_error_id IS NOT set
|
|
17
22
|
return self.filter(committer_id__isnull=False, entered_in_error_id__isnull=True)
|
|
18
23
|
|
|
19
24
|
def for_patient(self, patient_id: str) -> "models.QuerySet":
|
|
25
|
+
"""Return a queryset that filters objects for a specific patient."""
|
|
20
26
|
return self.filter(patient__id=patient_id)
|
|
21
27
|
|
|
22
28
|
|
|
23
29
|
class ValueSetLookupQuerySet(models.QuerySet):
|
|
30
|
+
"""A QuerySet that can filter objects based on a ValueSet."""
|
|
31
|
+
|
|
24
32
|
def find(self, value_set: Type["ValueSet"]) -> models.QuerySet:
|
|
25
33
|
"""
|
|
26
34
|
Filters conditions, medications, etc. to those found in the inherited ValueSet class that is passed.
|
|
@@ -35,13 +43,51 @@ class ValueSetLookupQuerySet(models.QuerySet):
|
|
|
35
43
|
|
|
36
44
|
Condition.objects.find(MorbidObesity).find(AnaphylacticReactionToCommonBakersYeast)
|
|
37
45
|
"""
|
|
38
|
-
values_dict = value_set.values
|
|
39
|
-
uri_codes = [
|
|
40
|
-
(i[1], values_dict[i[0]])
|
|
41
|
-
for i in value_set.CODE_SYSTEM_MAPPING.items()
|
|
42
|
-
if i[0] in values_dict
|
|
43
|
-
]
|
|
44
46
|
q_filter = Q()
|
|
45
|
-
for system, codes in
|
|
46
|
-
q_filter |=
|
|
47
|
+
for system, codes in self.codings(value_set):
|
|
48
|
+
q_filter |= self.q_object(system, codes)
|
|
47
49
|
return self.filter(q_filter).distinct()
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]:
|
|
53
|
+
"""Provide a sequence of tuples where each tuple is a code system URL and a set of codes."""
|
|
54
|
+
values_dict = value_set.values
|
|
55
|
+
return cast(
|
|
56
|
+
tuple[tuple[str, set[str]]],
|
|
57
|
+
tuple(
|
|
58
|
+
(i[1], values_dict[i[0]])
|
|
59
|
+
for i in value_set.CODE_SYSTEM_MAPPING.items()
|
|
60
|
+
if i[0] in values_dict
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def q_object(system: str, codes: Container[str]) -> Q:
|
|
66
|
+
"""
|
|
67
|
+
This method can be overridden if a Q object with different filtering options is needed.
|
|
68
|
+
"""
|
|
69
|
+
return Q(codings__system=system, codings_code_in=codes)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ValueSetLookupByNameQuerySet(ValueSetLookupQuerySet):
|
|
73
|
+
"""
|
|
74
|
+
QuerySet for ValueSet lookups using code system name rather than URL.
|
|
75
|
+
|
|
76
|
+
Some models, like Questionnaire, store the code system by name (e.g. "LOINC") rather than by the
|
|
77
|
+
url (e.g. "http://loinc.org"). This subclass accommodates these models.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]:
|
|
82
|
+
"""
|
|
83
|
+
Provide a sequence of tuples where each tuple is a code system name and a set of codes.
|
|
84
|
+
"""
|
|
85
|
+
values_dict = value_set.values
|
|
86
|
+
return cast(
|
|
87
|
+
tuple[tuple[str, set[str]]],
|
|
88
|
+
tuple(
|
|
89
|
+
(i[0], values_dict[i[0]])
|
|
90
|
+
for i in value_set.CODE_SYSTEM_MAPPING.items()
|
|
91
|
+
if i[0] in values_dict
|
|
92
|
+
),
|
|
93
|
+
)
|