canvas 0.1.15__py3-none-any.whl → 0.2.10__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.

Files changed (94) hide show
  1. {canvas-0.1.15.dist-info → canvas-0.2.10.dist-info}/METADATA +7 -1
  2. canvas-0.2.10.dist-info/RECORD +143 -0
  3. canvas_cli/apps/plugin/plugin.py +51 -9
  4. canvas_cli/apps/plugin/tests.py +51 -0
  5. canvas_cli/tests.py +193 -4
  6. canvas_cli/utils/validators/manifest_schema.py +1 -0
  7. canvas_generated/messages/effects_pb2.py +2 -2
  8. canvas_generated/messages/effects_pb2.pyi +138 -0
  9. canvas_generated/messages/events_pb2.py +3 -3
  10. canvas_generated/messages/events_pb2.pyi +616 -0
  11. canvas_sdk/__init__.py +7 -0
  12. canvas_sdk/base.py +6 -2
  13. canvas_sdk/commands/__init__.py +26 -0
  14. canvas_sdk/commands/base.py +35 -32
  15. canvas_sdk/commands/commands/allergy.py +49 -0
  16. canvas_sdk/commands/commands/assess.py +1 -1
  17. canvas_sdk/commands/commands/close_goal.py +22 -0
  18. canvas_sdk/commands/commands/diagnose.py +3 -3
  19. canvas_sdk/commands/commands/family_history.py +18 -0
  20. canvas_sdk/commands/commands/goal.py +3 -3
  21. canvas_sdk/commands/commands/history_present_illness.py +1 -1
  22. canvas_sdk/commands/commands/instruct.py +17 -0
  23. canvas_sdk/commands/commands/lab_order.py +33 -0
  24. canvas_sdk/commands/commands/medical_history.py +34 -0
  25. canvas_sdk/commands/commands/medication_statement.py +1 -1
  26. canvas_sdk/commands/commands/past_surgical_history.py +28 -0
  27. canvas_sdk/commands/commands/perform.py +17 -0
  28. canvas_sdk/commands/commands/plan.py +2 -2
  29. canvas_sdk/commands/commands/prescribe.py +10 -7
  30. canvas_sdk/commands/commands/questionnaire.py +1 -1
  31. canvas_sdk/commands/commands/refill.py +16 -0
  32. canvas_sdk/commands/commands/remove_allergy.py +26 -0
  33. canvas_sdk/commands/commands/stop_medication.py +1 -1
  34. canvas_sdk/commands/commands/task.py +52 -0
  35. canvas_sdk/commands/commands/update_diagnosis.py +27 -0
  36. canvas_sdk/commands/commands/update_goal.py +1 -1
  37. canvas_sdk/commands/commands/vitals.py +78 -0
  38. canvas_sdk/commands/constants.py +7 -0
  39. canvas_sdk/commands/tests/protocol/__init__.py +0 -0
  40. canvas_sdk/commands/tests/protocol/tests.py +55 -0
  41. canvas_sdk/commands/tests/schema/__init__.py +0 -0
  42. canvas_sdk/commands/tests/schema/tests.py +104 -0
  43. canvas_sdk/commands/tests/test_utils.py +170 -6
  44. canvas_sdk/commands/tests/unit/__init__.py +0 -0
  45. canvas_sdk/commands/tests/{tests.py → unit/tests.py} +20 -194
  46. canvas_sdk/data/client.py +82 -0
  47. canvas_sdk/data/patient.py +1 -21
  48. canvas_sdk/effects/banner_alert/add_banner_alert.py +8 -7
  49. canvas_sdk/effects/banner_alert/remove_banner_alert.py +3 -2
  50. canvas_sdk/effects/banner_alert/tests.py +224 -0
  51. canvas_sdk/effects/base.py +3 -5
  52. canvas_sdk/effects/patient_chart_summary_configuration.py +39 -0
  53. canvas_sdk/effects/protocol_card/__init__.py +1 -0
  54. canvas_sdk/effects/protocol_card/protocol_card.py +83 -0
  55. canvas_sdk/effects/protocol_card/tests.py +184 -0
  56. canvas_sdk/handlers/base.py +14 -1
  57. canvas_sdk/protocols/base.py +14 -0
  58. canvas_sdk/protocols/clinical_quality_measure.py +41 -0
  59. canvas_sdk/utils/db.py +17 -0
  60. canvas_sdk/v1/__init__.py +0 -0
  61. canvas_sdk/v1/data/__init__.py +3 -0
  62. canvas_sdk/v1/data/allergy_intolerance.py +63 -0
  63. canvas_sdk/v1/data/base.py +47 -0
  64. canvas_sdk/v1/data/condition.py +48 -0
  65. canvas_sdk/v1/data/lab.py +96 -0
  66. canvas_sdk/v1/data/medication.py +54 -0
  67. canvas_sdk/v1/data/patient.py +49 -0
  68. canvas_sdk/v1/data/user.py +10 -0
  69. canvas_sdk/value_set/tests/test_value_sets.py +65 -0
  70. canvas_sdk/value_set/v2022/adverse_event.py +33 -0
  71. canvas_sdk/value_set/v2022/allergy.py +232 -0
  72. canvas_sdk/value_set/v2022/assessment.py +215 -0
  73. canvas_sdk/value_set/v2022/communication.py +325 -0
  74. canvas_sdk/value_set/v2022/condition.py +40654 -0
  75. canvas_sdk/value_set/v2022/device.py +174 -0
  76. canvas_sdk/value_set/v2022/diagnostic_study.py +4967 -0
  77. canvas_sdk/value_set/v2022/encounter.py +2564 -0
  78. canvas_sdk/value_set/v2022/immunization.py +341 -0
  79. canvas_sdk/value_set/v2022/individual_characteristic.py +307 -0
  80. canvas_sdk/value_set/v2022/intervention.py +1356 -0
  81. canvas_sdk/value_set/v2022/laboratory_test.py +1250 -0
  82. canvas_sdk/value_set/v2022/medication.py +5130 -0
  83. canvas_sdk/value_set/v2022/physical_exam.py +201 -0
  84. canvas_sdk/value_set/v2022/procedure.py +4037 -0
  85. canvas_sdk/value_set/v2022/symptom.py +176 -0
  86. canvas_sdk/value_set/value_set.py +91 -0
  87. canvas-0.1.15.dist-info/RECORD +0 -95
  88. canvas_generated/data_access_layer/data_access_layer_pb2.py +0 -30
  89. canvas_generated/data_access_layer/data_access_layer_pb2.pyi +0 -23
  90. canvas_generated/data_access_layer/data_access_layer_pb2_grpc.py +0 -66
  91. canvas_sdk/data/data_access_layer_client.py +0 -95
  92. canvas_sdk/data/exceptions.py +0 -16
  93. {canvas-0.1.15.dist-info → canvas-0.2.10.dist-info}/WHEEL +0 -0
  94. {canvas-0.1.15.dist-info → canvas-0.2.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,52 @@
1
+ from datetime import date
2
+ from enum import Enum, StrEnum
3
+
4
+ from typing_extensions import TypedDict, NotRequired
5
+
6
+ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
7
+
8
+
9
+ class AssigneeType(StrEnum):
10
+ """The type of assigner for a Task command."""
11
+
12
+ ROLE = "role"
13
+ TEAM = "team"
14
+ UNASSIGNED = "unassigned"
15
+ STAFF = "staff"
16
+
17
+
18
+ class TaskAssigner(TypedDict):
19
+ """A class for managing an assign for a Task command."""
20
+
21
+ to: AssigneeType
22
+ id: NotRequired[int]
23
+
24
+
25
+ class TaskCommand(BaseCommand):
26
+ """A class for managing a Task command within a specific note."""
27
+
28
+ class Meta:
29
+ key = "task"
30
+ commit_required_fields = (
31
+ "title",
32
+ "assign_to",
33
+ )
34
+
35
+ title: str = ""
36
+ assign_to: TaskAssigner | None = None
37
+ due_date: date | None = None
38
+ comment: str | None = None
39
+ labels: list[str] | None = None
40
+ linked_items_urns: list[str] | None = None
41
+
42
+ @property
43
+ def values(self) -> dict:
44
+ """The Task command's field values."""
45
+ return {
46
+ "title": self.title,
47
+ "assign_to": self.assign_to,
48
+ "due_date": self.due_date.isoformat() if self.due_date else None,
49
+ "comment": self.comment,
50
+ "labels": self.labels,
51
+ "linked_items_urns": self.linked_items_urns,
52
+ }
@@ -0,0 +1,27 @@
1
+ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
2
+
3
+
4
+ class UpdateDiagnosisCommand(BaseCommand):
5
+ """A class for managing an Update Diagnosis command within a specific note."""
6
+
7
+ class Meta:
8
+ key = "updateDiagnosis"
9
+ commit_required_fields = (
10
+ "condition_code",
11
+ "new_condition_code",
12
+ )
13
+
14
+ condition_code: str | None = None
15
+ new_condition_code: str | None = None
16
+ background: str | None = None
17
+ narrative: str | None = None
18
+
19
+ @property
20
+ def values(self) -> dict:
21
+ """The Update Diagnosis command's field values."""
22
+ return {
23
+ "condition_code": self.condition_code,
24
+ "new_condition_code": self.new_condition_code,
25
+ "background": self.background,
26
+ "narrative": self.narrative,
27
+ }
@@ -11,7 +11,7 @@ class UpdateGoalCommand(_BaseCommand):
11
11
 
12
12
  class Meta:
13
13
  key = "updateGoal"
14
- originate_required_fields = ("goal_id",)
14
+ commit_required_fields = ("goal_id",)
15
15
 
16
16
  class AchievementStatus(Enum):
17
17
  IN_PROGRESS = "in-progress"
@@ -0,0 +1,78 @@
1
+ from pydantic import conint, constr
2
+ from typing import Optional
3
+ from enum import Enum
4
+
5
+ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
6
+
7
+
8
+ class VitalsCommand(BaseCommand):
9
+
10
+ class Meta:
11
+ key = "vitals"
12
+ commit_required_fields = ()
13
+
14
+ class BodyTemperatureSite(Enum):
15
+ AXILLARY = 0
16
+ ORAL = 1
17
+ RECTAL = 2
18
+ TEMPORAL = 3
19
+ TYMPANIC = 4
20
+
21
+ class BloodPressureSite(Enum):
22
+ SITTING_RIGHT_UPPER = 0
23
+ SITTING_LEFT_UPPER = 1
24
+ SITTING_RIGHT_LOWER = 2
25
+ SITTING_LEFT_LOWER = 3
26
+ STANDING_RIGHT_UPPER = 4
27
+ STANDING_LEFT_UPPER = 5
28
+ STANDING_RIGHT_LOWER = 6
29
+ STANDING_LEFT_LOWER = 7
30
+ SUPINE_RIGHT_UPPER = 8
31
+ SUPINE_LEFT_UPPER = 9
32
+ SUPINE_RIGHT_LOWER = 10
33
+ SUPINE_LEFT_LOWER = 11
34
+
35
+ class PulseRhythm(Enum):
36
+ REGULAR = 0
37
+ IRREGULARLY_IRREGULAR = 1
38
+ REGULARLY_IRREGULAR = 2
39
+
40
+ height: conint(ge=10, le=108) | None = None
41
+ weight_lbs: conint(ge=1, le=1500) | None = None
42
+ 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
+ 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_position_and_site: BloodPressureSite | None = None
49
+ pulse: conint(ge=30, le=250) | None = None
50
+ 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
54
+
55
+ @property
56
+ def values(self) -> dict:
57
+ return {
58
+ "height": self.height,
59
+ "weight_lbs": self.weight_lbs,
60
+ "weight_oz": self.weight_oz,
61
+ "waist_circumference": self.waist_circumference,
62
+ "body_temperature": self.body_temperature,
63
+ "body_temperature_site": (
64
+ self.body_temperature_site.value if self.body_temperature_site else None
65
+ ),
66
+ "blood_pressure_systole": self.blood_pressure_systole,
67
+ "blood_pressure_diastole": self.blood_pressure_diastole,
68
+ "blood_pressure_position_and_site": (
69
+ self.blood_pressure_position_and_site.value
70
+ if self.blood_pressure_position_and_site
71
+ else None
72
+ ),
73
+ "pulse": self.pulse,
74
+ "pulse_rhythm": self.pulse_rhythm.value if self.pulse_rhythm else None,
75
+ "respiration_rate": self.respiration_rate,
76
+ "oxygen_saturation": self.oxygen_saturation,
77
+ "note": self.note,
78
+ }
@@ -7,3 +7,10 @@ class Coding(TypedDict):
7
7
  system: str
8
8
  code: str
9
9
  display: NotRequired[str]
10
+
11
+
12
+ class ClinicalQuantity(TypedDict):
13
+ """A ClinicalQuantity for prescribe/refill commands."""
14
+
15
+ representative_ndc: str
16
+ ncpdp_quantity_qualifier_code: str
File without changes
@@ -0,0 +1,55 @@
1
+ from datetime import datetime
2
+ from typing import Any, Generator
3
+
4
+ import pytest
5
+
6
+ from canvas_sdk.commands.tests.test_utils import (
7
+ COMMANDS,
8
+ MaskedValue,
9
+ clean_up_files_and_plugins,
10
+ create_new_note,
11
+ get_original_note_body_commands,
12
+ get_token,
13
+ install_plugin,
14
+ trigger_plugin_event,
15
+ write_protocol_code,
16
+ )
17
+
18
+
19
+ @pytest.fixture(scope="session")
20
+ def token() -> MaskedValue:
21
+ return get_token()
22
+
23
+
24
+ @pytest.fixture(scope="session")
25
+ def new_note(token: MaskedValue) -> dict:
26
+ return create_new_note(token)
27
+
28
+
29
+ @pytest.fixture(scope="session")
30
+ def plugin_name() -> str:
31
+ return f"commands{datetime.now().timestamp()}".replace(".", "")
32
+
33
+
34
+ @pytest.fixture(autouse=True, scope="session")
35
+ def write_and_install_protocol_and_clean_up(
36
+ plugin_name: str, token: MaskedValue, new_note: dict
37
+ ) -> Generator[Any, Any, Any]:
38
+ write_protocol_code(new_note["externallyExposableId"], plugin_name, COMMANDS)
39
+ install_plugin(plugin_name, token)
40
+
41
+ yield
42
+
43
+ clean_up_files_and_plugins(plugin_name, token)
44
+
45
+
46
+ @pytest.mark.integtest
47
+ def test_protocol_that_inserts_every_command(token: MaskedValue, new_note: dict) -> None:
48
+ trigger_plugin_event(token)
49
+
50
+ commands_in_body = get_original_note_body_commands(new_note["id"], token)
51
+ command_keys = [c.Meta.key for c in COMMANDS]
52
+
53
+ assert len(command_keys) == len(commands_in_body)
54
+ for i, command_key in enumerate(command_keys):
55
+ assert commands_in_body[i] == command_key
File without changes
@@ -0,0 +1,104 @@
1
+ import decimal
2
+ from datetime import date, datetime
3
+ from typing import get_origin
4
+
5
+ import pytest
6
+ import requests
7
+
8
+ import settings
9
+ from canvas_sdk.commands.base import _BaseCommand
10
+ from canvas_sdk.commands.constants import Coding, ClinicalQuantity
11
+ from canvas_sdk.commands.tests.test_utils import (
12
+ COMMANDS,
13
+ MaskedValue,
14
+ create_new_note,
15
+ get_token,
16
+ )
17
+
18
+
19
+ @pytest.fixture(scope="session")
20
+ def token() -> MaskedValue:
21
+ return get_token()
22
+
23
+
24
+ @pytest.fixture(scope="session")
25
+ def new_note(token: MaskedValue) -> dict:
26
+ return create_new_note(token)
27
+
28
+
29
+ @pytest.fixture
30
+ def command_type_map() -> dict[str, type]:
31
+ return {
32
+ "AutocompleteField": str,
33
+ "MultiLineTextField": str,
34
+ "TextField": str,
35
+ "ChoiceField": str,
36
+ "DateField": datetime,
37
+ "ApproximateDateField": date,
38
+ "IntegerField": int,
39
+ "DecimalField": decimal.Decimal,
40
+ }
41
+
42
+
43
+ @pytest.mark.integtest
44
+ @pytest.mark.parametrize(
45
+ "Command",
46
+ COMMANDS,
47
+ )
48
+ def test_command_schema_matches_command_api(
49
+ token: MaskedValue,
50
+ command_type_map: dict[str, str],
51
+ new_note: dict,
52
+ Command: _BaseCommand,
53
+ ) -> None:
54
+ # first create the command in the new note
55
+ data = {"noteKey": new_note["externallyExposableId"], "schemaKey": Command.Meta.key}
56
+ headers = {"Authorization": f"Bearer {token.value}"}
57
+ url = f"{settings.INTEGRATION_TEST_URL}/core/api/v1/commands/"
58
+ command_resp = requests.post(url, headers=headers, data=data).json()
59
+ assert "uuid" in command_resp
60
+ command_uuid = command_resp["uuid"]
61
+
62
+ # next, request the fields of the newly created command
63
+ url = f"{settings.INTEGRATION_TEST_URL}/core/api/v1/commands/{command_uuid}/fields/"
64
+ command_fields_resp = requests.get(url, headers=headers).json()
65
+ assert command_fields_resp["schema"] == Command.Meta.key
66
+
67
+ command_fields = command_fields_resp["fields"]
68
+ if Command.Meta.key == "questionnaire":
69
+ # questionnaire's fields vary per questionnaire, so just check the first two fields which never vary
70
+ command_fields = command_fields[:2]
71
+ expected_fields = Command.command_schema()
72
+ assert len(command_fields) == len(expected_fields)
73
+
74
+ for actual_field in command_fields:
75
+ name = actual_field["name"]
76
+ assert name in expected_fields
77
+ expected_field = expected_fields[name]
78
+
79
+ assert expected_field["required"] == actual_field["required"]
80
+
81
+ expected_type = expected_field["type"]
82
+ if expected_type is Coding:
83
+ expected_type = expected_type.__annotations__["code"]
84
+
85
+ if expected_type is ClinicalQuantity:
86
+ expected_type = expected_type.__annotations__["representative_ndc"]
87
+
88
+ actual_type = command_type_map.get(actual_field["type"])
89
+ if actual_field["type"] == "AutocompleteField" and name[-1] == "s":
90
+ # this condition initially created for Prescribe.indications,
91
+ # but could apply to other AutocompleteField fields that are lists
92
+ # making the assumption here that if the field ends in 's' (like indications), it is a list
93
+ assert get_origin(expected_type) == list
94
+
95
+ else:
96
+ assert expected_type == actual_type
97
+
98
+ if (choices := actual_field["choices"]) is None:
99
+ assert expected_field["choices"] is None
100
+ continue
101
+
102
+ assert len(expected_field["choices"]) == len(choices)
103
+ for choice in choices:
104
+ assert choice["value"] in expected_field["choices"]
@@ -1,12 +1,20 @@
1
1
  import random
2
+ import shutil
2
3
  import string
4
+ from contextlib import chdir
3
5
  from datetime import datetime
4
6
  from decimal import Decimal
7
+ from pathlib import Path
5
8
  from typing import Any
6
9
 
7
10
  import pytest
11
+ import requests
8
12
  from pydantic import ValidationError
13
+ from typer.testing import CliRunner
9
14
 
15
+ import settings
16
+ from canvas_cli.apps.plugin.plugin import _build_package, plugin_url
17
+ from canvas_cli.main import app
10
18
  from canvas_sdk.commands import (
11
19
  AssessCommand,
12
20
  DiagnoseCommand,
@@ -18,12 +26,16 @@ from canvas_sdk.commands import (
18
26
  QuestionnaireCommand,
19
27
  ReasonForVisitCommand,
20
28
  StopMedicationCommand,
29
+ UpdateGoalCommand,
21
30
  )
22
- from canvas_sdk.commands.constants import Coding
31
+ from canvas_sdk.commands.base import _BaseCommand
32
+ from canvas_sdk.commands.constants import Coding, ClinicalQuantity
33
+
34
+ runner = CliRunner()
23
35
 
24
36
 
25
37
  class MaskedValue:
26
- def __init__(self, value):
38
+ def __init__(self, value: str) -> None:
27
39
  self.value = value
28
40
 
29
41
  def __repr__(self) -> str:
@@ -82,8 +94,14 @@ def fake(
82
94
  num_items = random.randint(0, 5)
83
95
  item_props = field_props["anyOf"][0]["items"]
84
96
  return [fake(item_props, Command) for i in range(num_items)]
97
+ case "list":
98
+ num_items = random.randint(0, field_props.get("maxItems", 5))
99
+ item_props = field_props.get("items")
100
+ return [fake(item_props, Command) for i in range(num_items)] if item_props else []
85
101
  case "Coding":
86
102
  return Coding(system=random_string(), code=random_string(), display=random_string())
103
+ case "ClinicalQuantity":
104
+ return ClinicalQuantity(representative_ndc="ndc", ncpdp_quantity_qualifier_code="code")
87
105
  if t[0].isupper():
88
106
  return random.choice([e for e in getattr(Command, t)])
89
107
 
@@ -122,7 +140,9 @@ def raises_wrong_type_error(
122
140
  assert f"1 validation error for {Command.__name__}\n{field}" in err_msg1
123
141
  assert f"1 validation error for {Command.__name__}\n{field}" in err_msg2
124
142
 
125
- field_type = "dictionary" if field_type == "Coding" else field_type
143
+ field_type = (
144
+ "dictionary" if field_type == "Coding" or field_type == "ClinicalQuantity" else field_type
145
+ )
126
146
  if field_type == "number":
127
147
  assert f"Input should be an instance of Decimal" in err_msg1
128
148
  assert f"Input should be an instance of Decimal" in err_msg2
@@ -149,14 +169,158 @@ def raises_none_error_for_effect_method(
149
169
  ),
150
170
  method: str,
151
171
  ) -> None:
172
+ cmd_name = Command.__name__
173
+ cmd_name_article = "an" if cmd_name.startswith(("A", "E", "I", "O", "U")) else "a"
174
+
152
175
  cmd = Command()
153
176
  method_required_fields = cmd._get_effect_method_required_fields(method)
154
177
  with pytest.raises(ValidationError) as e:
155
178
  getattr(cmd, method)()
156
179
  e_msg = repr(e.value)
157
- assert f"{len(method_required_fields)} validation errors for {Command.__name__}" in e_msg
158
- for f in method_required_fields:
180
+ missing_fields = [field for field in method_required_fields if getattr(cmd, field) is None]
181
+ num_errs = len(missing_fields)
182
+ assert f"{num_errs} validation error{'s' if num_errs > 1 else ''} for {cmd_name}" in e_msg
183
+
184
+ for f in missing_fields:
159
185
  assert (
160
- f"Field '{f}' is required to {method.replace('_', ' ')} a command [type=missing, input_value=None, input_type=NoneType]"
186
+ f"Field '{f}' is required to {method.replace('_', ' ')} {cmd_name_article} {cmd_name} [type=missing, input_value=None, input_type=NoneType]"
161
187
  in e_msg
162
188
  )
189
+
190
+
191
+ def write_protocol_code(note_uuid: str, plugin_name: str, commands: list[_BaseCommand]) -> None:
192
+ imports = ", ".join([c.__name__ for c in commands])
193
+ effects = ", ".join([f"{c.__name__}(note_uuid='{note_uuid}').originate()" for c in commands])
194
+
195
+ protocol_code = f"""from canvas_sdk.commands import {imports}
196
+ from canvas_sdk.events import EventType
197
+ from canvas_sdk.protocols import BaseProtocol
198
+
199
+ class Protocol(BaseProtocol):
200
+ RESPONDS_TO = EventType.Name(EventType.ENCOUNTER_CREATED)
201
+ def compute(self):
202
+ return [{effects}]
203
+ """
204
+
205
+ with chdir(Path("./custom-plugins")):
206
+ runner.invoke(app, "init", input=plugin_name)
207
+
208
+ protocol = open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w")
209
+ protocol.write(protocol_code)
210
+ protocol.close()
211
+
212
+
213
+ def install_plugin(plugin_name: str, token: MaskedValue) -> None:
214
+ requests.post(
215
+ plugin_url(settings.INTEGRATION_TEST_URL),
216
+ data={"is_enabled": True},
217
+ files={"package": open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb")},
218
+ headers={"Authorization": f"Bearer {token.value}"},
219
+ )
220
+
221
+
222
+ def trigger_plugin_event(token: MaskedValue) -> None:
223
+ requests.post(
224
+ f"{settings.INTEGRATION_TEST_URL}/api/Note/",
225
+ headers={
226
+ "Authorization": f"Bearer {token.value}",
227
+ "Content-Type": "application/json",
228
+ "Accept": "application/json",
229
+ },
230
+ json={
231
+ "patient": 2,
232
+ "provider": 1,
233
+ "note_type": "office",
234
+ "note_type_version": 1,
235
+ "lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
236
+ },
237
+ )
238
+
239
+
240
+ def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> list[str]:
241
+ original_note = requests.get(
242
+ f"{settings.INTEGRATION_TEST_URL}/api/Note/{new_note_id}",
243
+ headers={
244
+ "Authorization": f"Bearer {token.value}",
245
+ "Content-Type": "application/json",
246
+ "Accept": "application/json",
247
+ },
248
+ ).json()
249
+
250
+ body = original_note["body"]
251
+ return [
252
+ line["value"]
253
+ for line in body
254
+ if "data" in line
255
+ and "commandUuid" in line["data"]
256
+ and "id" in line["data"]
257
+ and line["type"] == "command"
258
+ ]
259
+
260
+
261
+ def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None:
262
+ # clean up
263
+ if Path(f"./custom-plugins/{plugin_name}").exists():
264
+ shutil.rmtree(Path(f"./custom-plugins/{plugin_name}"))
265
+
266
+ # disable
267
+ requests.patch(
268
+ plugin_url(settings.INTEGRATION_TEST_URL, plugin_name),
269
+ data={"is_enabled": False},
270
+ headers={
271
+ "Authorization": f"Bearer {token.value}",
272
+ },
273
+ )
274
+ # delete
275
+ requests.delete(
276
+ plugin_url(settings.INTEGRATION_TEST_URL, plugin_name),
277
+ headers={"Authorization": f"Bearer {token.value}"},
278
+ )
279
+
280
+
281
+ # For reuse with the protocol code
282
+ COMMANDS = [
283
+ AssessCommand,
284
+ DiagnoseCommand,
285
+ GoalCommand,
286
+ HistoryOfPresentIllnessCommand,
287
+ MedicationStatementCommand,
288
+ PlanCommand,
289
+ PrescribeCommand,
290
+ QuestionnaireCommand,
291
+ ReasonForVisitCommand,
292
+ StopMedicationCommand,
293
+ UpdateGoalCommand,
294
+ ]
295
+
296
+
297
+ def create_new_note(token: MaskedValue) -> dict:
298
+ headers = {
299
+ "Authorization": f"Bearer {token.value}",
300
+ "Content-Type": "application/json",
301
+ "Accept": "application/json",
302
+ }
303
+ data = {
304
+ "patient": 1,
305
+ "provider": 1,
306
+ "note_type": "office",
307
+ "note_type_version": 1,
308
+ "lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
309
+ }
310
+ return requests.post(
311
+ f"{settings.INTEGRATION_TEST_URL}/api/Note/", headers=headers, json=data
312
+ ).json()
313
+
314
+
315
+ def get_token() -> MaskedValue:
316
+ return MaskedValue(
317
+ 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
+ },
325
+ ).json()["access_token"]
326
+ )
File without changes