canvas 0.2.10__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.

Files changed (46) hide show
  1. {canvas-0.2.10.dist-info → canvas-0.3.0.dist-info}/METADATA +3 -3
  2. {canvas-0.2.10.dist-info → canvas-0.3.0.dist-info}/RECORD +46 -44
  3. canvas_cli/apps/plugin/plugin.py +56 -23
  4. canvas_cli/utils/validators/validators.py +1 -1
  5. canvas_generated/messages/effects_pb2.py +5 -5
  6. canvas_generated/messages/effects_pb2.pyi +4 -2
  7. canvas_generated/messages/events_pb2.py +3 -3
  8. canvas_generated/messages/events_pb2.pyi +62 -0
  9. canvas_generated/messages/plugins_pb2.py +1 -1
  10. canvas_generated/services/plugin_runner_pb2.py +1 -1
  11. canvas_sdk/base.py +2 -2
  12. canvas_sdk/commands/__init__.py +3 -1
  13. canvas_sdk/commands/base.py +2 -2
  14. canvas_sdk/commands/commands/allergy.py +2 -0
  15. canvas_sdk/commands/commands/close_goal.py +3 -0
  16. canvas_sdk/commands/commands/prescribe.py +1 -3
  17. canvas_sdk/commands/commands/refill.py +1 -0
  18. canvas_sdk/commands/commands/task.py +1 -1
  19. canvas_sdk/commands/commands/vitals.py +15 -12
  20. canvas_sdk/commands/tests/schema/tests.py +1 -1
  21. canvas_sdk/commands/tests/test_utils.py +4 -2
  22. canvas_sdk/data/base.py +5 -1
  23. canvas_sdk/data/client.py +1 -1
  24. canvas_sdk/data/patient.py +2 -0
  25. canvas_sdk/data/staff.py +2 -0
  26. canvas_sdk/data/task.py +7 -0
  27. canvas_sdk/effects/protocol_card/protocol_card.py +2 -0
  28. canvas_sdk/effects/protocol_card/tests.py +5 -2
  29. canvas_sdk/protocols/__init__.py +1 -0
  30. canvas_sdk/protocols/clinical_quality_measure.py +1 -0
  31. canvas_sdk/utils/http.py +19 -18
  32. canvas_sdk/utils/stats.py +2 -1
  33. canvas_sdk/v1/data/allergy_intolerance.py +2 -3
  34. canvas_sdk/v1/data/base.py +55 -9
  35. canvas_sdk/v1/data/lab.py +8 -0
  36. canvas_sdk/v1/data/patient.py +3 -0
  37. canvas_sdk/v1/data/questionnaire.py +204 -0
  38. canvas_sdk/v1/data/user.py +2 -0
  39. canvas_sdk/value_set/v2022/condition.py +2 -2
  40. canvas_sdk/value_set/v2022/encounter.py +1 -1
  41. canvas_sdk/value_set/value_set.py +20 -5
  42. logger/__init__.py +1 -0
  43. logger/logger.py +17 -6
  44. settings.py +43 -0
  45. {canvas-0.2.10.dist-info → canvas-0.3.0.dist-info}/WHEEL +0 -0
  46. {canvas-0.2.10.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.0
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.0
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: str) -> tuple[str] | tuple:
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: str) -> list[InitErrorDetails]:
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"
@@ -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 PastSurgicalHistoryCommand
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
@@ -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, Union
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[str]:
30
+ ) -> tuple:
31
31
  base_required_fields: tuple = getattr(
32
32
  _BaseCommand.Meta, f"{method}_required_fields", tuple()
33
33
  )
@@ -7,6 +7,8 @@ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
7
7
 
8
8
 
9
9
  class AllergenType(IntEnum):
10
+ """An enum representing the type of allergen."""
11
+
10
12
  ALLERGEN_GROUP = 1
11
13
  MEDICATION = 2
12
14
  INGREDIENT = 6
@@ -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
@@ -2,6 +2,7 @@ from canvas_sdk.commands.commands.prescribe import PrescribeCommand
2
2
 
3
3
 
4
4
  class RefillCommand(PrescribeCommand):
5
+ """A class for managing a Refill command within a specific note."""
5
6
 
6
7
  class Meta:
7
8
  key = "refill"
@@ -1,7 +1,7 @@
1
1
  from datetime import date
2
2
  from enum import Enum, StrEnum
3
3
 
4
- from typing_extensions import TypedDict, NotRequired
4
+ from typing_extensions import NotRequired, TypedDict
5
5
 
6
6
  from canvas_sdk.commands.base import _BaseCommand as BaseCommand
7
7
 
@@ -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 Coding, ClinicalQuantity
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 Coding, ClinicalQuantity
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(note_uuid: str, plugin_name: str, commands: list[_BaseCommand]) -> None:
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
@@ -65,7 +65,7 @@ class _CanvasGQLClient:
65
65
  self,
66
66
  gql_query: str,
67
67
  variables: dict[str, Any] | None = None,
68
- extra_args: dict[str, Any] | None = None
68
+ extra_args: dict[str, Any] | None = None,
69
69
  ) -> dict[str, Any]:
70
70
  if variables is None:
71
71
  query_variables = {}
@@ -2,5 +2,7 @@ from canvas_sdk.data import DataModel
2
2
 
3
3
 
4
4
  class Patient(DataModel):
5
+ """Data model for a patient."""
6
+
5
7
  id: str | None = None
6
8
  # TODO - populate more attributes
canvas_sdk/data/staff.py CHANGED
@@ -2,5 +2,7 @@ from canvas_sdk.data import DataModel
2
2
 
3
3
 
4
4
  class Staff(DataModel):
5
+ """Data model for a staff member."""
6
+
5
7
  id: str | None = None
6
8
  # TODO - populate with first name, last name, additional attributes, etc.
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[str, str], rec2_params: dict[str, str]
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
 
@@ -1 +1,2 @@
1
1
  from canvas_sdk.protocols.base import BaseProtocol
2
+ from canvas_sdk.protocols.clinical_quality_measure import ClinicalQualityMeasure
@@ -14,6 +14,7 @@ class ClinicalQualityMeasure(BaseProtocol):
14
14
  description: str = ""
15
15
  information: str = ""
16
16
  references: list[str] = []
17
+ source_attributes: dict[str, str]
17
18
  types: list[str] = []
18
19
  authors: list[str] = []
19
20
  show_in_chart: bool = True
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 | None = None) -> Callable[[F], F] | F:
19
+ def measure_time(fn: F) -> F:
20
20
  """A decorator to store timing of HTTP calls."""
21
21
 
22
- def _decorator(fn: F) -> F:
23
- @wraps(fn)
24
- def wrapper(self: "Http", *args: Any, **kwargs: Any) -> Any:
25
- start_time = time.time()
26
- result = fn(self, *args, **kwargs)
27
- end_time = time.time()
28
- timing = int((end_time - start_time) * 1000)
29
- self.statsd_client.timing(f"http_{fn.__name__}", timing)
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
- return wrapper
33
-
34
- return _decorator(fn) if fn else _decorator
31
+ return wrapper
35
32
 
36
33
  @measure_time
37
- def get(self, url: str, headers: dict = {}) -> requests.Response:
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: dict = {},
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: dict = {},
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: dict = {},
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
@@ -2,7 +2,8 @@ from time import time
2
2
  from typing import Any
3
3
 
4
4
 
5
- def get_duration_ms(start_time: time) -> int:
5
+ def get_duration_ms(start_time: float) -> int:
6
+ """Get the duration in milliseconds since the given start time."""
6
7
  return int((time() - start_time) * 1000)
7
8
 
8
9
 
@@ -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)
@@ -1,4 +1,5 @@
1
- from typing import TYPE_CHECKING, Type
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 uri_codes:
46
- q_filter |= Q(codings__system=system, codings__code__in=codes)
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
+ )