canvas 0.21.0__py3-none-any.whl → 0.22.1__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 (32) hide show
  1. {canvas-0.21.0.dist-info → canvas-0.22.1.dist-info}/METADATA +1 -1
  2. {canvas-0.21.0.dist-info → canvas-0.22.1.dist-info}/RECORD +31 -25
  3. canvas_generated/messages/effects_pb2.py +2 -2
  4. canvas_generated/messages/effects_pb2.pyi +40 -0
  5. canvas_generated/messages/events_pb2.py +2 -2
  6. canvas_generated/messages/events_pb2.pyi +108 -0
  7. canvas_sdk/commands/__init__.py +8 -0
  8. canvas_sdk/commands/base.py +1 -0
  9. canvas_sdk/commands/commands/__init__.py +0 -0
  10. canvas_sdk/commands/commands/adjust_prescription.py +24 -0
  11. canvas_sdk/commands/commands/imaging_order.py +39 -0
  12. canvas_sdk/commands/commands/instruct.py +17 -1
  13. canvas_sdk/commands/commands/lab_order.py +11 -6
  14. canvas_sdk/commands/commands/prescribe.py +1 -1
  15. canvas_sdk/commands/commands/questionnaire/__init__.py +102 -0
  16. canvas_sdk/commands/commands/questionnaire/question.py +134 -0
  17. canvas_sdk/commands/commands/refer.py +50 -0
  18. canvas_sdk/commands/commands/refill.py +28 -0
  19. canvas_sdk/commands/commands/resolve_condition.py +43 -0
  20. canvas_sdk/commands/constants.py +34 -0
  21. canvas_sdk/commands/tests/protocol/tests.py +10 -1
  22. canvas_sdk/commands/tests/test_utils.py +4 -1
  23. canvas_sdk/effects/banner_alert/tests.py +10 -2
  24. canvas_sdk/v1/data/condition.py +4 -2
  25. canvas_sdk/v1/data/medication.py +4 -2
  26. plugin_runner/installation.py +1 -2
  27. plugin_runner/plugin_runner.py +34 -2
  28. protobufs/canvas_generated/messages/effects.proto +25 -1
  29. protobufs/canvas_generated/messages/events.proto +54 -36
  30. canvas_sdk/commands/commands/questionnaire.py +0 -16
  31. {canvas-0.21.0.dist-info → canvas-0.22.1.dist-info}/WHEEL +0 -0
  32. {canvas-0.21.0.dist-info → canvas-0.22.1.dist-info}/entry_points.txt +0 -0
@@ -108,6 +108,26 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
108
108
  MEDICATION_STATEMENT__MEDICATION__PRE_SEARCH: _ClassVar[EventType]
109
109
  MEDICATION_STATEMENT__MEDICATION__POST_SEARCH: _ClassVar[EventType]
110
110
  MEDICATION_STATEMENT__MEDICATION__SELECTED: _ClassVar[EventType]
111
+ ADJUST_PRESCRIPTION_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
112
+ ADJUST_PRESCRIPTION_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
113
+ ADJUST_PRESCRIPTION_COMMAND__PRE_UPDATE: _ClassVar[EventType]
114
+ ADJUST_PRESCRIPTION_COMMAND__POST_UPDATE: _ClassVar[EventType]
115
+ ADJUST_PRESCRIPTION_COMMAND__PRE_COMMIT: _ClassVar[EventType]
116
+ ADJUST_PRESCRIPTION_COMMAND__POST_COMMIT: _ClassVar[EventType]
117
+ ADJUST_PRESCRIPTION_COMMAND__PRE_DELETE: _ClassVar[EventType]
118
+ ADJUST_PRESCRIPTION_COMMAND__POST_DELETE: _ClassVar[EventType]
119
+ ADJUST_PRESCRIPTION_COMMAND__PRE_ENTER_IN_ERROR: _ClassVar[EventType]
120
+ ADJUST_PRESCRIPTION_COMMAND__POST_ENTER_IN_ERROR: _ClassVar[EventType]
121
+ ADJUST_PRESCRIPTION_COMMAND__PRE_EXECUTE_ACTION: _ClassVar[EventType]
122
+ ADJUST_PRESCRIPTION_COMMAND__POST_EXECUTE_ACTION: _ClassVar[EventType]
123
+ ADJUST_PRESCRIPTION__PRESCRIBE__POST_SEARCH: _ClassVar[EventType]
124
+ ADJUST_PRESCRIPTION__PRESCRIBE__PRE_SEARCH: _ClassVar[EventType]
125
+ ADJUST_PRESCRIPTION__INDICATIONS__PRE_SEARCH: _ClassVar[EventType]
126
+ ADJUST_PRESCRIPTION__INDICATIONS__POST_SEARCH: _ClassVar[EventType]
127
+ ADJUST_PRESCRIPTION__PHARMACY__PRE_SEARCH: _ClassVar[EventType]
128
+ ADJUST_PRESCRIPTION__PHARMACY__POST_SEARCH: _ClassVar[EventType]
129
+ ADJUST_PRESCRIPTION__CHANGE_MEDICATION_TO__PRE_SEARCH: _ClassVar[EventType]
130
+ ADJUST_PRESCRIPTION__CHANGE_MEDICATION_TO__POST_SEARCH: _ClassVar[EventType]
111
131
  ALLERGY_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
112
132
  ALLERGY_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
113
133
  ALLERGY_COMMAND__PRE_UPDATE: _ClassVar[EventType]
@@ -484,6 +504,26 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
484
504
  REASON_FOR_VISIT_COMMAND__POST_INSERTED_INTO_NOTE: _ClassVar[EventType]
485
505
  REASON_FOR_VISIT__CODING__POST_SEARCH: _ClassVar[EventType]
486
506
  REASON_FOR_VISIT__CODING__PRE_SEARCH: _ClassVar[EventType]
507
+ REFER_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
508
+ REFER_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
509
+ REFER_COMMAND__PRE_UPDATE: _ClassVar[EventType]
510
+ REFER_COMMAND__POST_UPDATE: _ClassVar[EventType]
511
+ REFER_COMMAND__PRE_COMMIT: _ClassVar[EventType]
512
+ REFER_COMMAND__POST_COMMIT: _ClassVar[EventType]
513
+ REFER_COMMAND__PRE_DELETE: _ClassVar[EventType]
514
+ REFER_COMMAND__POST_DELETE: _ClassVar[EventType]
515
+ REFER_COMMAND__PRE_ENTER_IN_ERROR: _ClassVar[EventType]
516
+ REFER_COMMAND__POST_ENTER_IN_ERROR: _ClassVar[EventType]
517
+ REFER_COMMAND__PRE_EXECUTE_ACTION: _ClassVar[EventType]
518
+ REFER_COMMAND__POST_EXECUTE_ACTION: _ClassVar[EventType]
519
+ REFER_COMMAND__REFER_TO__POST_SEARCH: _ClassVar[EventType]
520
+ REFER_COMMAND__REFER_TO__PRE_SEARCH: _ClassVar[EventType]
521
+ REFER_COMMAND__INDICATIONS__POST_SEARCH: _ClassVar[EventType]
522
+ REFER_COMMAND__INDICATIONS__PRE_SEARCH: _ClassVar[EventType]
523
+ REFER_COMMAND__DOCUMENTS_TO_INCLUDE__POST_SEARCH: _ClassVar[EventType]
524
+ REFER_COMMAND__DOCUMENTS_TO_INCLUDE__PRE_SEARCH: _ClassVar[EventType]
525
+ REFER_COMMAND__LINKED_ITEMS__POST_SEARCH: _ClassVar[EventType]
526
+ REFER_COMMAND__LINKED_ITEMS_INCLUDE__PRE_SEARCH: _ClassVar[EventType]
487
527
  REFILL_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
488
528
  REFILL_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
489
529
  REFILL_COMMAND__PRE_UPDATE: _ClassVar[EventType]
@@ -518,6 +558,20 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
518
558
  REMOVE_ALLERGY_COMMAND__POST_INSERTED_INTO_NOTE: _ClassVar[EventType]
519
559
  REMOVE_ALLERGY__ALLERGY__PRE_SEARCH: _ClassVar[EventType]
520
560
  REMOVE_ALLERGY__ALLERGY__POST_SEARCH: _ClassVar[EventType]
561
+ RESOLVE_CONDITION_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
562
+ RESOLVE_CONDITION_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
563
+ RESOLVE_CONDITION_COMMAND__PRE_UPDATE: _ClassVar[EventType]
564
+ RESOLVE_CONDITION_COMMAND__POST_UPDATE: _ClassVar[EventType]
565
+ RESOLVE_CONDITION_COMMAND__PRE_COMMIT: _ClassVar[EventType]
566
+ RESOLVE_CONDITION_COMMAND__POST_COMMIT: _ClassVar[EventType]
567
+ RESOLVE_CONDITION_COMMAND__PRE_DELETE: _ClassVar[EventType]
568
+ RESOLVE_CONDITION_COMMAND__POST_DELETE: _ClassVar[EventType]
569
+ RESOLVE_CONDITION_COMMAND__PRE_ENTER_IN_ERROR: _ClassVar[EventType]
570
+ RESOLVE_CONDITION_COMMAND__POST_ENTER_IN_ERROR: _ClassVar[EventType]
571
+ RESOLVE_CONDITION_COMMAND__PRE_EXECUTE_ACTION: _ClassVar[EventType]
572
+ RESOLVE_CONDITION_COMMAND__POST_EXECUTE_ACTION: _ClassVar[EventType]
573
+ RESOLVE_CONDITION__CONDITION__PRE_SEARCH: _ClassVar[EventType]
574
+ RESOLVE_CONDITION__CONDITION__POST_SEARCH: _ClassVar[EventType]
521
575
  ROS_COMMAND__PRE_ORIGINATE: _ClassVar[EventType]
522
576
  ROS_COMMAND__POST_ORIGINATE: _ClassVar[EventType]
523
577
  ROS_COMMAND__PRE_UPDATE: _ClassVar[EventType]
@@ -842,6 +896,26 @@ ASSESS_COMMAND__CONDITION_SELECTED: EventType
842
896
  MEDICATION_STATEMENT__MEDICATION__PRE_SEARCH: EventType
843
897
  MEDICATION_STATEMENT__MEDICATION__POST_SEARCH: EventType
844
898
  MEDICATION_STATEMENT__MEDICATION__SELECTED: EventType
899
+ ADJUST_PRESCRIPTION_COMMAND__PRE_ORIGINATE: EventType
900
+ ADJUST_PRESCRIPTION_COMMAND__POST_ORIGINATE: EventType
901
+ ADJUST_PRESCRIPTION_COMMAND__PRE_UPDATE: EventType
902
+ ADJUST_PRESCRIPTION_COMMAND__POST_UPDATE: EventType
903
+ ADJUST_PRESCRIPTION_COMMAND__PRE_COMMIT: EventType
904
+ ADJUST_PRESCRIPTION_COMMAND__POST_COMMIT: EventType
905
+ ADJUST_PRESCRIPTION_COMMAND__PRE_DELETE: EventType
906
+ ADJUST_PRESCRIPTION_COMMAND__POST_DELETE: EventType
907
+ ADJUST_PRESCRIPTION_COMMAND__PRE_ENTER_IN_ERROR: EventType
908
+ ADJUST_PRESCRIPTION_COMMAND__POST_ENTER_IN_ERROR: EventType
909
+ ADJUST_PRESCRIPTION_COMMAND__PRE_EXECUTE_ACTION: EventType
910
+ ADJUST_PRESCRIPTION_COMMAND__POST_EXECUTE_ACTION: EventType
911
+ ADJUST_PRESCRIPTION__PRESCRIBE__POST_SEARCH: EventType
912
+ ADJUST_PRESCRIPTION__PRESCRIBE__PRE_SEARCH: EventType
913
+ ADJUST_PRESCRIPTION__INDICATIONS__PRE_SEARCH: EventType
914
+ ADJUST_PRESCRIPTION__INDICATIONS__POST_SEARCH: EventType
915
+ ADJUST_PRESCRIPTION__PHARMACY__PRE_SEARCH: EventType
916
+ ADJUST_PRESCRIPTION__PHARMACY__POST_SEARCH: EventType
917
+ ADJUST_PRESCRIPTION__CHANGE_MEDICATION_TO__PRE_SEARCH: EventType
918
+ ADJUST_PRESCRIPTION__CHANGE_MEDICATION_TO__POST_SEARCH: EventType
845
919
  ALLERGY_COMMAND__PRE_ORIGINATE: EventType
846
920
  ALLERGY_COMMAND__POST_ORIGINATE: EventType
847
921
  ALLERGY_COMMAND__PRE_UPDATE: EventType
@@ -1218,6 +1292,26 @@ REASON_FOR_VISIT_COMMAND__POST_EXECUTE_ACTION: EventType
1218
1292
  REASON_FOR_VISIT_COMMAND__POST_INSERTED_INTO_NOTE: EventType
1219
1293
  REASON_FOR_VISIT__CODING__POST_SEARCH: EventType
1220
1294
  REASON_FOR_VISIT__CODING__PRE_SEARCH: EventType
1295
+ REFER_COMMAND__PRE_ORIGINATE: EventType
1296
+ REFER_COMMAND__POST_ORIGINATE: EventType
1297
+ REFER_COMMAND__PRE_UPDATE: EventType
1298
+ REFER_COMMAND__POST_UPDATE: EventType
1299
+ REFER_COMMAND__PRE_COMMIT: EventType
1300
+ REFER_COMMAND__POST_COMMIT: EventType
1301
+ REFER_COMMAND__PRE_DELETE: EventType
1302
+ REFER_COMMAND__POST_DELETE: EventType
1303
+ REFER_COMMAND__PRE_ENTER_IN_ERROR: EventType
1304
+ REFER_COMMAND__POST_ENTER_IN_ERROR: EventType
1305
+ REFER_COMMAND__PRE_EXECUTE_ACTION: EventType
1306
+ REFER_COMMAND__POST_EXECUTE_ACTION: EventType
1307
+ REFER_COMMAND__REFER_TO__POST_SEARCH: EventType
1308
+ REFER_COMMAND__REFER_TO__PRE_SEARCH: EventType
1309
+ REFER_COMMAND__INDICATIONS__POST_SEARCH: EventType
1310
+ REFER_COMMAND__INDICATIONS__PRE_SEARCH: EventType
1311
+ REFER_COMMAND__DOCUMENTS_TO_INCLUDE__POST_SEARCH: EventType
1312
+ REFER_COMMAND__DOCUMENTS_TO_INCLUDE__PRE_SEARCH: EventType
1313
+ REFER_COMMAND__LINKED_ITEMS__POST_SEARCH: EventType
1314
+ REFER_COMMAND__LINKED_ITEMS_INCLUDE__PRE_SEARCH: EventType
1221
1315
  REFILL_COMMAND__PRE_ORIGINATE: EventType
1222
1316
  REFILL_COMMAND__POST_ORIGINATE: EventType
1223
1317
  REFILL_COMMAND__PRE_UPDATE: EventType
@@ -1252,6 +1346,20 @@ REMOVE_ALLERGY_COMMAND__POST_EXECUTE_ACTION: EventType
1252
1346
  REMOVE_ALLERGY_COMMAND__POST_INSERTED_INTO_NOTE: EventType
1253
1347
  REMOVE_ALLERGY__ALLERGY__PRE_SEARCH: EventType
1254
1348
  REMOVE_ALLERGY__ALLERGY__POST_SEARCH: EventType
1349
+ RESOLVE_CONDITION_COMMAND__PRE_ORIGINATE: EventType
1350
+ RESOLVE_CONDITION_COMMAND__POST_ORIGINATE: EventType
1351
+ RESOLVE_CONDITION_COMMAND__PRE_UPDATE: EventType
1352
+ RESOLVE_CONDITION_COMMAND__POST_UPDATE: EventType
1353
+ RESOLVE_CONDITION_COMMAND__PRE_COMMIT: EventType
1354
+ RESOLVE_CONDITION_COMMAND__POST_COMMIT: EventType
1355
+ RESOLVE_CONDITION_COMMAND__PRE_DELETE: EventType
1356
+ RESOLVE_CONDITION_COMMAND__POST_DELETE: EventType
1357
+ RESOLVE_CONDITION_COMMAND__PRE_ENTER_IN_ERROR: EventType
1358
+ RESOLVE_CONDITION_COMMAND__POST_ENTER_IN_ERROR: EventType
1359
+ RESOLVE_CONDITION_COMMAND__PRE_EXECUTE_ACTION: EventType
1360
+ RESOLVE_CONDITION_COMMAND__POST_EXECUTE_ACTION: EventType
1361
+ RESOLVE_CONDITION__CONDITION__PRE_SEARCH: EventType
1362
+ RESOLVE_CONDITION__CONDITION__POST_SEARCH: EventType
1255
1363
  ROS_COMMAND__PRE_ORIGINATE: EventType
1256
1364
  ROS_COMMAND__POST_ORIGINATE: EventType
1257
1365
  ROS_COMMAND__PRE_UPDATE: EventType
@@ -1,3 +1,4 @@
1
+ from canvas_sdk.commands.commands.adjust_prescription import AdjustPrescriptionCommand
1
2
  from canvas_sdk.commands.commands.allergy import AllergyCommand
2
3
  from canvas_sdk.commands.commands.assess import AssessCommand
3
4
  from canvas_sdk.commands.commands.close_goal import CloseGoalCommand
@@ -9,6 +10,7 @@ from canvas_sdk.commands.commands.goal import GoalCommand
9
10
  from canvas_sdk.commands.commands.history_present_illness import (
10
11
  HistoryOfPresentIllnessCommand,
11
12
  )
13
+ from canvas_sdk.commands.commands.imaging_order import ImagingOrderCommand
12
14
  from canvas_sdk.commands.commands.instruct import InstructCommand
13
15
  from canvas_sdk.commands.commands.lab_order import LabOrderCommand
14
16
  from canvas_sdk.commands.commands.medical_history import MedicalHistoryCommand
@@ -21,8 +23,10 @@ from canvas_sdk.commands.commands.plan import PlanCommand
21
23
  from canvas_sdk.commands.commands.prescribe import PrescribeCommand
22
24
  from canvas_sdk.commands.commands.questionnaire import QuestionnaireCommand
23
25
  from canvas_sdk.commands.commands.reason_for_visit import ReasonForVisitCommand
26
+ from canvas_sdk.commands.commands.refer import ReferCommand
24
27
  from canvas_sdk.commands.commands.refill import RefillCommand
25
28
  from canvas_sdk.commands.commands.remove_allergy import RemoveAllergyCommand
29
+ from canvas_sdk.commands.commands.resolve_condition import ResolveConditionCommand
26
30
  from canvas_sdk.commands.commands.review_of_systems import ReviewOfSystemsCommand
27
31
  from canvas_sdk.commands.commands.stop_medication import StopMedicationCommand
28
32
  from canvas_sdk.commands.commands.structured_assessment import StructuredAssessmentCommand
@@ -32,6 +36,7 @@ from canvas_sdk.commands.commands.update_goal import UpdateGoalCommand
32
36
  from canvas_sdk.commands.commands.vitals import VitalsCommand
33
37
 
34
38
  __all__ = (
39
+ "AdjustPrescriptionCommand",
35
40
  "AllergyCommand",
36
41
  "AssessCommand",
37
42
  "CloseGoalCommand",
@@ -40,6 +45,7 @@ __all__ = (
40
45
  "FollowUpCommand",
41
46
  "GoalCommand",
42
47
  "HistoryOfPresentIllnessCommand",
48
+ "ImagingOrderCommand",
43
49
  "InstructCommand",
44
50
  "LabOrderCommand",
45
51
  "MedicalHistoryCommand",
@@ -51,8 +57,10 @@ __all__ = (
51
57
  "PhysicalExamCommand",
52
58
  "QuestionnaireCommand",
53
59
  "ReasonForVisitCommand",
60
+ "ReferCommand",
54
61
  "RefillCommand",
55
62
  "RemoveAllergyCommand",
63
+ "ResolveConditionCommand",
56
64
  "ReviewOfSystemsCommand",
57
65
  "StopMedicationCommand",
58
66
  "StructuredAssessmentCommand",
@@ -132,6 +132,7 @@ class _BaseCommand(Model):
132
132
  type=f"ORIGINATE_{self.constantized_key()}_COMMAND",
133
133
  payload=json.dumps(
134
134
  {
135
+ "command": self.command_uuid,
135
136
  "note": self.note_uuid,
136
137
  "data": self.values,
137
138
  "line_number": line_number,
File without changes
@@ -0,0 +1,24 @@
1
+ from pydantic import Field
2
+
3
+ from canvas_sdk.commands.commands.refill import RefillCommand
4
+
5
+
6
+ class AdjustPrescriptionCommand(RefillCommand):
7
+ """A class for managing Adjust Prescription command within a specific note."""
8
+
9
+ class Meta:
10
+ key = "adjustPrescription"
11
+ commit_required_fields = (
12
+ "fdb_code",
13
+ "sig",
14
+ "quantity_to_dispense",
15
+ "type_to_dispense",
16
+ "refills",
17
+ "substitutions",
18
+ "prescriber_id",
19
+ "new_fdb_code",
20
+ )
21
+
22
+ new_fdb_code: str | None = Field(
23
+ default=None, json_schema_extra={"commands_api_name": "change_medication_to"}
24
+ )
@@ -0,0 +1,39 @@
1
+ from enum import Enum
2
+
3
+ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
4
+ from canvas_sdk.commands.constants import ServiceProvider
5
+
6
+
7
+ class ImagingOrderCommand(BaseCommand):
8
+ """A class for managing an Imaging Order command within a specific note."""
9
+
10
+ class Meta:
11
+ key = "imagingOrder"
12
+ commit_required_fields = (
13
+ "image_code",
14
+ "diagnosis_codes",
15
+ "ordering_provider",
16
+ )
17
+
18
+ class Priority(Enum):
19
+ ROUTINE = "Routine"
20
+ URGENT = "Urgent"
21
+
22
+ image_code: str | None = None
23
+ diagnosis_codes: list[str] = []
24
+ priority: Priority | None = None
25
+ additional_details: str | None = None
26
+ service_provider: ServiceProvider | None = None
27
+ comment: str | None = None
28
+ ordering_provider_key: str | None = None
29
+ linked_items_urns: list[str] | None = None
30
+
31
+ @property
32
+ def values(self) -> dict:
33
+ """The Imaging Order command's field values."""
34
+ values = super().values
35
+
36
+ if self.is_dirty("service_provider"):
37
+ values["service_provider"] = self.service_provider.__dict__
38
+
39
+ return values
@@ -1,4 +1,7 @@
1
+ from pydantic_core import InitErrorDetails
2
+
1
3
  from canvas_sdk.commands.base import _BaseCommand as BaseCommand
4
+ from canvas_sdk.commands.constants import CodeSystems, Coding
2
5
 
3
6
 
4
7
  class InstructCommand(BaseCommand):
@@ -8,5 +11,18 @@ class InstructCommand(BaseCommand):
8
11
  key = "instruct"
9
12
  commit_required_fields = ("instruction",)
10
13
 
11
- instruction: str | None = None
14
+ coding: Coding | None = None
12
15
  comment: str | None = None
16
+
17
+ def _get_error_details(self, method: str) -> list[InitErrorDetails]:
18
+ errors = super()._get_error_details(method)
19
+
20
+ if (
21
+ self.coding
22
+ and self.coding["system"] != CodeSystems.SNOMED
23
+ and self.coding["system"] != CodeSystems.UNSTRUCTURED
24
+ ):
25
+ message = f"The 'coding.system' field must be '{CodeSystems.SNOMED}' or '{CodeSystems.UNSTRUCTURED}'."
26
+ errors.append(self._create_error_detail("value", message, self.coding))
27
+
28
+ return errors
@@ -34,11 +34,16 @@ class LabOrderCommand(BaseCommand):
34
34
 
35
35
  lab_partner_obj = None
36
36
  if self.lab_partner:
37
- lab_partner_obj = (
38
- LabPartner.objects.filter(Q(name=self.lab_partner) | Q(id=self.lab_partner))
39
- .values("id", "dbid")
40
- .first()
41
- )
37
+ query = {}
38
+ try:
39
+ UUID(str(self.lab_partner))
40
+ except ValueError:
41
+ query["name"] = self.lab_partner
42
+ else:
43
+ query["id"] = self.lab_partner
44
+
45
+ lab_partner_obj = LabPartner.objects.filter(**query).values("id", "dbid").first()
46
+
42
47
  if not lab_partner_obj:
43
48
  errors.append(
44
49
  self._create_error_detail(
@@ -61,7 +66,7 @@ class LabOrderCommand(BaseCommand):
61
66
 
62
67
  for code in self.tests_order_codes:
63
68
  try:
64
- uuid_tests.append(UUID(code))
69
+ uuid_tests.append(UUID(str(code)))
65
70
  except ValueError:
66
71
  order_code_tests.append(code)
67
72
 
@@ -28,7 +28,7 @@ class PrescribeCommand(_BaseCommand):
28
28
 
29
29
  fdb_code: str | None = Field(default=None, json_schema_extra={"commands_api_name": "prescribe"})
30
30
  icd10_codes: conlist(str, max_length=2) = Field( # type: ignore[valid-type]
31
- [], json_schema_extra={"commands_api_name": "indications"}
31
+ default=[], json_schema_extra={"commands_api_name": "indications"}
32
32
  )
33
33
  sig: str = ""
34
34
  days_supply: int | None = None
@@ -0,0 +1,102 @@
1
+ from functools import cached_property
2
+ from typing import Any
3
+
4
+ from pydantic import Field
5
+
6
+ from canvas_sdk.commands.base import _BaseCommand
7
+ from canvas_sdk.commands.commands.questionnaire.question import (
8
+ BaseQuestion,
9
+ CheckboxQuestion,
10
+ IntegerQuestion,
11
+ RadioQuestion,
12
+ ResponseOption,
13
+ TextQuestion,
14
+ )
15
+ from canvas_sdk.v1.data import Questionnaire
16
+
17
+ QUESTION_CLASSES: dict[str, type[BaseQuestion]] = {
18
+ ResponseOption.TYPE_TEXT: TextQuestion,
19
+ ResponseOption.TYPE_INTEGER: IntegerQuestion,
20
+ ResponseOption.TYPE_RADIO: RadioQuestion,
21
+ ResponseOption.TYPE_CHECKBOX: CheckboxQuestion,
22
+ }
23
+
24
+
25
+ class QuestionnaireCommand(_BaseCommand):
26
+ """A class for managing a Questionnaire command within a specific note."""
27
+
28
+ class Meta:
29
+ key = "questionnaire"
30
+ commit_required_fields = ("questionnaire_id",)
31
+
32
+ questionnaire_id: str | None = Field(
33
+ default=None, json_schema_extra={"commands_api_name": "questionnaire"}
34
+ )
35
+ result: str | None = None
36
+
37
+ @cached_property
38
+ def _questionnaire(self) -> Questionnaire | None:
39
+ if not self.questionnaire_id:
40
+ return None
41
+ return Questionnaire.objects.get(id=self.questionnaire_id)
42
+
43
+ @cached_property
44
+ def questions(self) -> list[BaseQuestion]:
45
+ """
46
+ Returns a list of question objects.
47
+
48
+ For each question in the questionnaire, creates an instance of the
49
+ appropriate question subclass based on the question.response_option_set.type.
50
+ """
51
+ question_objs: list[BaseQuestion] = []
52
+ if not self._questionnaire:
53
+ return question_objs
54
+
55
+ for question in self._questionnaire.questions.all():
56
+ qdata: dict[str, Any] = {
57
+ "name": f"question-{question.pk}",
58
+ "label": question.name,
59
+ "coding": {
60
+ "system": question.code_system,
61
+ "code": question.code,
62
+ },
63
+ "options": [
64
+ ResponseOption(
65
+ dbid=option.pk, name=option.name, code=option.code, value=option.value
66
+ )
67
+ for option in question.response_option_set.options.all()
68
+ ]
69
+ if question.response_option_set
70
+ else [],
71
+ }
72
+ q_type = question.response_option_set.type if question.response_option_set else None
73
+ q_obj: BaseQuestion
74
+ if q_type in QUESTION_CLASSES:
75
+ question_objs.append(QUESTION_CLASSES[q_type](**qdata))
76
+ else:
77
+ raise ValueError(f"Unsupported question type: {q_type}")
78
+ if q_type == ResponseOption.TYPE_TEXT:
79
+ q_obj = TextQuestion(**qdata)
80
+ elif q_type == ResponseOption.TYPE_INTEGER:
81
+ q_obj = IntegerQuestion(**qdata)
82
+ elif q_type == ResponseOption.TYPE_RADIO:
83
+ q_obj = RadioQuestion(**qdata)
84
+ elif q_type == ResponseOption.TYPE_CHECKBOX:
85
+ q_obj = CheckboxQuestion(**qdata)
86
+ else:
87
+ # This should never happen, but just in case
88
+ raise ValueError(f"Unsupported question type: {q_type}")
89
+ question_objs.append(q_obj)
90
+ return question_objs
91
+
92
+ @property
93
+ def values(self) -> dict:
94
+ """Return the values for the command.
95
+
96
+ For questionnaire-related commands, this includes the responses to the questions.
97
+ """
98
+ values = super().values
99
+
100
+ values["questions"] = {q.name: q.response for q in self.questions if q.response is not None}
101
+
102
+ return values
@@ -0,0 +1,134 @@
1
+ import dataclasses
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any
4
+
5
+
6
+ @dataclasses.dataclass
7
+ class ResponseOption:
8
+ """A response option for a question."""
9
+
10
+ TYPE_TEXT = "TXT"
11
+ TYPE_INTEGER = "INT"
12
+ TYPE_RADIO = "SING"
13
+ TYPE_CHECKBOX = "MULT"
14
+
15
+ def __init__(self, dbid: int, name: str, code: str, value: str) -> None:
16
+ self.dbid: int = dbid
17
+ self.name: str = name
18
+ self.code: str = code
19
+ self.value: str = value
20
+
21
+ def __eq__(self, other: Any) -> bool:
22
+ if isinstance(other, ResponseOption):
23
+ return (
24
+ self.dbid == other.dbid
25
+ and self.name == other.name
26
+ and self.code == other.code
27
+ and self.value == other.value
28
+ )
29
+ return False
30
+
31
+ def __repr__(self) -> str:
32
+ return f"ResponseOption({self.dbid=!r}, {self.name=!r}, {self.code=!r}, {self.value=!r})"
33
+
34
+
35
+ class BaseQuestion(ABC):
36
+ """Base class for questions."""
37
+
38
+ type: str
39
+
40
+ def __init__(
41
+ self, name: str, label: str, coding: dict[str, str], options: list[ResponseOption]
42
+ ) -> None:
43
+ self.name: str = name
44
+ self.label: str = label
45
+ self.coding: dict[str, str] = coding
46
+ self.options: list[ResponseOption] = options
47
+ self.response: Any | None = None
48
+
49
+ def __repr__(self) -> str:
50
+ return f"Question({self.name=!r}, {self.label=!r}, {self.type=!r}, {self.options=!r}, {self.response=!r})"
51
+
52
+ @abstractmethod
53
+ def add_response(self, *args: Any, **kwargs: Any) -> None:
54
+ """Record a response for the question.
55
+
56
+ Subclasses will override this to perform type-specific validation.
57
+ """
58
+ raise NotImplementedError("Subclasses must implement this method.")
59
+
60
+
61
+ class TextQuestion(BaseQuestion):
62
+ """A question that expects a text response."""
63
+
64
+ type = ResponseOption.TYPE_TEXT
65
+
66
+ def add_response(self, /, text: str) -> None:
67
+ """For a text question, the response must be a string."""
68
+ if not isinstance(text, str):
69
+ raise ValueError(
70
+ f"Response for a text question must be a string. Question: {self.label}"
71
+ )
72
+ self.response = text
73
+
74
+
75
+ class IntegerQuestion(BaseQuestion):
76
+ """A question that expects an integer response."""
77
+
78
+ type = ResponseOption.TYPE_INTEGER
79
+
80
+ def add_response(self, /, integer: int) -> None:
81
+ """For an integer question, the response must be convertible to int."""
82
+ try:
83
+ self.response = int(integer)
84
+ except (ValueError, TypeError) as e:
85
+ raise ValueError(
86
+ f"Response for IntegerQuestion must be convertible to an integer. Question: {self.label}"
87
+ ) from e
88
+
89
+
90
+ class RadioQuestion(BaseQuestion):
91
+ """A question that expects a single-choice response."""
92
+
93
+ type = ResponseOption.TYPE_RADIO
94
+
95
+ def add_response(self, /, option: ResponseOption) -> None:
96
+ """Record a radio response.
97
+
98
+ Expects a single keyword argument 'option' of type ResponseOption.
99
+ Validates that the option's value is among the allowed options.
100
+ """
101
+ if option not in self.options:
102
+ raise ValueError(
103
+ f"Invalid response option for radio question '{self.label}'. Allowed options: {self.options}"
104
+ )
105
+
106
+ self.response = option.dbid
107
+
108
+
109
+ class CheckboxQuestion(BaseQuestion):
110
+ """A question that expects a multiple-choice response."""
111
+
112
+ type = ResponseOption.TYPE_CHECKBOX
113
+
114
+ def add_response(
115
+ self, /, option: ResponseOption, selected: bool = True, comment: str = ""
116
+ ) -> None:
117
+ """Record a checkbox response.
118
+
119
+ Validates that:
120
+ - 'option' is a ResponseOption.
121
+ - 'selected' is a bool (defaults to True if not provided).
122
+ - 'comment' is a string (defaults to empty string if not provided).
123
+ The response is stored as a list of dictionaries.
124
+ """
125
+ if option not in self.options:
126
+ raise ValueError(
127
+ f"Invalid response option for checkbox question '{self.label}'. Allowed values: {self.options}"
128
+ )
129
+
130
+ if not self.response:
131
+ self.response = []
132
+ self.response.append(
133
+ {"text": option.name, "value": option.dbid, "comment": comment, "selected": selected}
134
+ )
@@ -0,0 +1,50 @@
1
+ from enum import Enum
2
+
3
+ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
4
+ from canvas_sdk.commands.constants import ServiceProvider
5
+
6
+
7
+ class ReferCommand(BaseCommand):
8
+ """A class for managing a Refer command within a specific note."""
9
+
10
+ class Meta:
11
+ key = "refer"
12
+ commit_required_fields = (
13
+ "service_provider",
14
+ "diagnosis_codes",
15
+ "clinical_question",
16
+ "notes_to_specialist",
17
+ )
18
+
19
+ class ClinicalQuestion(Enum):
20
+ """Clinical question choices."""
21
+
22
+ COGNITIVE_ASSISTANCE = "Cognitive Assistance (Advice/Guidance)"
23
+ ASSISTANCE_WITH_ONGOING_MANAGEMENT = "Assistance with Ongoing Management"
24
+ SPECIALIZED_INTERVENTION = "Specialized intervention"
25
+ DIAGNOSTIC_UNCERTAINTY = "Diagnostic Uncertainty"
26
+
27
+ class Priority(Enum):
28
+ """Priority choices."""
29
+
30
+ ROUTINE = "Routine"
31
+ URGENT = "Urgent"
32
+
33
+ service_provider: ServiceProvider | None = None
34
+ diagnosis_codes: list[str] = []
35
+ clinical_question: ClinicalQuestion | None = None
36
+ priority: Priority | None = None
37
+ notes_to_specialist: str | None = None
38
+ include_visit_note: bool = False
39
+ comment: str | None = None
40
+ linked_items_urns: list[str] | None = None
41
+
42
+ @property
43
+ def values(self) -> dict:
44
+ """The Refer command's field values."""
45
+ values = super().values
46
+
47
+ if self.is_dirty("service_provider"):
48
+ values["service_provider"] = self.service_provider.__dict__
49
+
50
+ return values
@@ -1,4 +1,10 @@
1
+ from typing import Literal
2
+
3
+ from django.db.models.expressions import Subquery
4
+ from pydantic_core import InitErrorDetails
5
+
1
6
  from canvas_sdk.commands.commands.prescribe import PrescribeCommand
7
+ from canvas_sdk.v1.data import Medication, Note
2
8
 
3
9
 
4
10
  class RefillCommand(PrescribeCommand):
@@ -15,3 +21,25 @@ class RefillCommand(PrescribeCommand):
15
21
  "substitutions",
16
22
  "prescriber_id",
17
23
  )
24
+
25
+ def _get_error_details(
26
+ self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error"]
27
+ ) -> list[InitErrorDetails]:
28
+ errors = super()._get_error_details(method)
29
+
30
+ if self.fdb_code:
31
+ subquery = Subquery(Note.objects.filter(id=self.note_uuid).values("patient_id")[:1])
32
+ if (
33
+ not Medication.objects.active()
34
+ .filter(codings__code=self.fdb_code, patient=subquery)
35
+ .exists()
36
+ ):
37
+ errors.append(
38
+ self._create_error_detail(
39
+ "value",
40
+ f"Medication with fdb_code {self.fdb_code} does not exist.",
41
+ self.fdb_code,
42
+ )
43
+ )
44
+
45
+ return errors