canvas 0.49.0__py3-none-any.whl → 0.50.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.49.0.dist-info → canvas-0.50.0.dist-info}/METADATA +1 -1
- {canvas-0.49.0.dist-info → canvas-0.50.0.dist-info}/RECORD +18 -15
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +14 -0
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +96 -0
- canvas_sdk/commands/base.py +12 -1
- canvas_sdk/commands/commands/lab_order.py +8 -3
- canvas_sdk/commands/commands/prescribe.py +165 -2
- canvas_sdk/effects/compound_medications/__init__.py +3 -0
- canvas_sdk/effects/compound_medications/compound_medication.py +275 -0
- canvas_sdk/v1/data/__init__.py +2 -0
- canvas_sdk/v1/data/compound_medication.py +67 -0
- plugin_runner/allowed-module-imports.json +13 -1
- protobufs/canvas_generated/messages/effects.proto +9 -0
- protobufs/canvas_generated/messages/events.proto +49 -0
- {canvas-0.49.0.dist-info → canvas-0.50.0.dist-info}/WHEEL +0 -0
- {canvas-0.49.0.dist-info → canvas-0.50.0.dist-info}/entry_points.txt +0 -0
canvas_sdk/commands/base.py
CHANGED
|
@@ -17,6 +17,7 @@ class _BaseCommand(TrackableFieldsModel):
|
|
|
17
17
|
key = ""
|
|
18
18
|
originate_required_fields = ("note_uuid",)
|
|
19
19
|
edit_required_fields = ("command_uuid",)
|
|
20
|
+
send_required_fields = ("command_uuid",)
|
|
20
21
|
delete_required_fields = ("command_uuid",)
|
|
21
22
|
commit_required_fields = ("command_uuid",)
|
|
22
23
|
enter_in_error_required_fields = ("command_uuid",)
|
|
@@ -161,4 +162,14 @@ class _BaseCommand(TrackableFieldsModel):
|
|
|
161
162
|
return Recommendation(title=title, button=button, command=command, context=self.values)
|
|
162
163
|
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
class _SendableCommandMixin:
|
|
166
|
+
def send(self) -> Effect:
|
|
167
|
+
"""Fire the send effect the command."""
|
|
168
|
+
self._validate_before_effect("send") # type: ignore[attr-defined]
|
|
169
|
+
return Effect(
|
|
170
|
+
type=f"SEND_{self.constantized_key()}_COMMAND", # type: ignore[attr-defined]
|
|
171
|
+
payload=json.dumps({"command": self.command_uuid}), # type: ignore[attr-defined]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
__exports__ = ("_BaseCommand", "_SendableCommandMixin")
|
|
@@ -6,10 +6,11 @@ from pydantic import Field
|
|
|
6
6
|
from pydantic_core import InitErrorDetails
|
|
7
7
|
|
|
8
8
|
from canvas_sdk.commands.base import _BaseCommand as BaseCommand
|
|
9
|
+
from canvas_sdk.commands.base import _SendableCommandMixin
|
|
9
10
|
from canvas_sdk.v1.data.lab import LabPartner, LabPartnerTest
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class LabOrderCommand(BaseCommand):
|
|
13
|
+
class LabOrderCommand(_SendableCommandMixin, BaseCommand):
|
|
13
14
|
"""A class for managing a Lab Order command within a specific note."""
|
|
14
15
|
|
|
15
16
|
class Meta:
|
|
@@ -29,7 +30,7 @@ class LabOrderCommand(BaseCommand):
|
|
|
29
30
|
comment: str | None = None
|
|
30
31
|
|
|
31
32
|
def _get_error_details(
|
|
32
|
-
self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error"]
|
|
33
|
+
self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error", "send"]
|
|
33
34
|
) -> list[InitErrorDetails]:
|
|
34
35
|
errors = super()._get_error_details(method)
|
|
35
36
|
|
|
@@ -43,7 +44,11 @@ class LabOrderCommand(BaseCommand):
|
|
|
43
44
|
else:
|
|
44
45
|
query["id"] = self.lab_partner
|
|
45
46
|
|
|
46
|
-
lab_partner_obj =
|
|
47
|
+
lab_partner_obj = (
|
|
48
|
+
LabPartner.objects.filter(**query)
|
|
49
|
+
.values("id", "dbid", "electronic_ordering_enabled")
|
|
50
|
+
.first()
|
|
51
|
+
)
|
|
47
52
|
|
|
48
53
|
if not lab_partner_obj:
|
|
49
54
|
errors.append(
|
|
@@ -1,13 +1,43 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass
|
|
1
3
|
from decimal import Decimal
|
|
2
4
|
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
3
6
|
|
|
4
7
|
from pydantic import Field, conlist
|
|
8
|
+
from pydantic_core import InitErrorDetails
|
|
5
9
|
|
|
6
|
-
from canvas_sdk.commands.base import _BaseCommand
|
|
10
|
+
from canvas_sdk.commands.base import _BaseCommand, _SendableCommandMixin
|
|
7
11
|
from canvas_sdk.commands.constants import ClinicalQuantity
|
|
12
|
+
from canvas_sdk.effects import Effect
|
|
13
|
+
from canvas_sdk.effects.compound_medications.compound_medication import (
|
|
14
|
+
CompoundMedication as CompoundMedicationEffect,
|
|
15
|
+
)
|
|
16
|
+
from canvas_sdk.v1.data.compound_medication import CompoundMedication as CompoundMedicationModel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CompoundMedicationData:
|
|
21
|
+
"""Data for creating a compound medication inline within a prescription."""
|
|
22
|
+
|
|
23
|
+
formulation: str
|
|
24
|
+
potency_unit_code: str
|
|
25
|
+
controlled_substance: str
|
|
26
|
+
controlled_substance_ndc: str = ""
|
|
27
|
+
active: bool = True
|
|
8
28
|
|
|
29
|
+
def to_dict(self) -> dict[str, Any]:
|
|
30
|
+
"""Convert dataclass to dictionary, excluding None values."""
|
|
31
|
+
return {
|
|
32
|
+
"formulation": self.formulation,
|
|
33
|
+
"potency_unit_code": self.potency_unit_code,
|
|
34
|
+
"controlled_substance": self.controlled_substance,
|
|
35
|
+
"controlled_substance_ndc": self.controlled_substance_ndc,
|
|
36
|
+
"active": self.active,
|
|
37
|
+
}
|
|
9
38
|
|
|
10
|
-
|
|
39
|
+
|
|
40
|
+
class PrescribeCommand(_SendableCommandMixin, _BaseCommand):
|
|
11
41
|
"""A class for managing a Prescribe command within a specific note."""
|
|
12
42
|
|
|
13
43
|
class Meta:
|
|
@@ -36,6 +66,86 @@ class PrescribeCommand(_BaseCommand):
|
|
|
36
66
|
)
|
|
37
67
|
note_to_pharmacist: str | None = None
|
|
38
68
|
|
|
69
|
+
# Compound medication fields
|
|
70
|
+
compound_medication_id: str | None = Field(
|
|
71
|
+
default=None, json_schema_extra={"commands_api_name": "compound_medication_id"}
|
|
72
|
+
)
|
|
73
|
+
compound_medication_data: CompoundMedicationData | None = Field(
|
|
74
|
+
default=None, json_schema_extra={"commands_api_name": "compound_medication_data"}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
78
|
+
"""Add compound medication validation to the base validation."""
|
|
79
|
+
errors = super()._get_error_details(method)
|
|
80
|
+
|
|
81
|
+
# Validate that exactly one medication type is provided
|
|
82
|
+
has_fdb_code = self.fdb_code is not None and self.fdb_code.strip() != ""
|
|
83
|
+
has_compound_medication_id = (
|
|
84
|
+
self.compound_medication_id is not None and self.compound_medication_id.strip() != ""
|
|
85
|
+
)
|
|
86
|
+
has_compound_medication_data = self.compound_medication_data is not None
|
|
87
|
+
|
|
88
|
+
medication_types_provided = sum(
|
|
89
|
+
[has_fdb_code, has_compound_medication_id, has_compound_medication_data]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if medication_types_provided > 1:
|
|
93
|
+
errors.append(
|
|
94
|
+
self._create_error_detail(
|
|
95
|
+
"value",
|
|
96
|
+
"Cannot specify multiple medication types. Choose one of: 'fdb_code', 'compound_medication_id', or 'compound_medication_data'.",
|
|
97
|
+
None,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Validate compound medication ID if provided
|
|
102
|
+
if (
|
|
103
|
+
self.compound_medication_id
|
|
104
|
+
and self.compound_medication_id.strip() != ""
|
|
105
|
+
and
|
|
106
|
+
# Check if compound medication exists
|
|
107
|
+
not CompoundMedicationModel.objects.filter(id=self.compound_medication_id).exists()
|
|
108
|
+
):
|
|
109
|
+
errors.append(
|
|
110
|
+
self._create_error_detail(
|
|
111
|
+
"value",
|
|
112
|
+
f"Compound medication with ID {self.compound_medication_id} does not exist.",
|
|
113
|
+
self.compound_medication_id,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Validate compound medication data if provided
|
|
118
|
+
if self.compound_medication_data is not None:
|
|
119
|
+
compound_med_errors = CompoundMedicationEffect.validate_compound_medication_fields(
|
|
120
|
+
formulation=self.compound_medication_data.formulation,
|
|
121
|
+
potency_unit_code=self.compound_medication_data.potency_unit_code,
|
|
122
|
+
controlled_substance=self.compound_medication_data.controlled_substance,
|
|
123
|
+
controlled_substance_ndc=self.compound_medication_data.controlled_substance_ndc,
|
|
124
|
+
)
|
|
125
|
+
errors.extend(compound_med_errors)
|
|
126
|
+
|
|
127
|
+
return errors
|
|
128
|
+
|
|
129
|
+
def _process_compound_medication_fields(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
130
|
+
"""
|
|
131
|
+
Process compound medication fields in prescription data.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
data: Dictionary containing prescription data
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Processed dictionary with compound medication transformations applied
|
|
138
|
+
"""
|
|
139
|
+
# Process nested compound medication values if present
|
|
140
|
+
if "compound_medication_values" in data:
|
|
141
|
+
compound_med_data = data["compound_medication_values"]
|
|
142
|
+
processed_compound_data = CompoundMedicationEffect.process_compound_medication_data(
|
|
143
|
+
compound_med_data
|
|
144
|
+
)
|
|
145
|
+
data["compound_medication_values"] = processed_compound_data
|
|
146
|
+
|
|
147
|
+
return data
|
|
148
|
+
|
|
39
149
|
@property
|
|
40
150
|
def values(self) -> dict:
|
|
41
151
|
"""The Prescribe command's field values."""
|
|
@@ -46,11 +156,64 @@ class PrescribeCommand(_BaseCommand):
|
|
|
46
156
|
str(Decimal(self.quantity_to_dispense)) if self.quantity_to_dispense else None
|
|
47
157
|
)
|
|
48
158
|
|
|
159
|
+
values["compound_medication_values"] = {}
|
|
160
|
+
|
|
161
|
+
if self.is_dirty("compound_medication_id") and self.compound_medication_id:
|
|
162
|
+
values["compound_medication_values"]["id"] = values.pop("compound_medication_id")
|
|
163
|
+
|
|
164
|
+
# Handle compound medication data
|
|
165
|
+
elif (
|
|
166
|
+
"compound_medication_data" in values and values["compound_medication_data"] is not None
|
|
167
|
+
):
|
|
168
|
+
# Convert CompoundMedicationData to the expected nested structure
|
|
169
|
+
compound_data = values.pop("compound_medication_data")
|
|
170
|
+
if isinstance(compound_data, CompoundMedicationData):
|
|
171
|
+
values["compound_medication_values"] = compound_data.to_dict()
|
|
172
|
+
|
|
49
173
|
return values
|
|
50
174
|
|
|
175
|
+
def originate(self, line_number: int = -1) -> Effect:
|
|
176
|
+
"""Originate a new command in the note body with explicit compound medication processing."""
|
|
177
|
+
self._validate_before_effect("originate")
|
|
178
|
+
|
|
179
|
+
# Get raw values and apply explicit processing for compound medications
|
|
180
|
+
raw_data = self.values
|
|
181
|
+
processed_data = self._process_compound_medication_fields(raw_data)
|
|
182
|
+
|
|
183
|
+
return Effect(
|
|
184
|
+
type=f"ORIGINATE_{self.constantized_key()}_COMMAND",
|
|
185
|
+
payload=json.dumps(
|
|
186
|
+
{
|
|
187
|
+
"command": self.command_uuid,
|
|
188
|
+
"note": self.note_uuid,
|
|
189
|
+
"data": processed_data,
|
|
190
|
+
"line_number": line_number,
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def edit(self) -> Effect:
|
|
196
|
+
"""Edit the command with explicit compound medication processing."""
|
|
197
|
+
self._validate_before_effect("edit")
|
|
198
|
+
|
|
199
|
+
# Get raw values and apply explicit processing for compound medications
|
|
200
|
+
raw_data = self.values
|
|
201
|
+
processed_data = self._process_compound_medication_fields(raw_data)
|
|
202
|
+
|
|
203
|
+
return Effect(
|
|
204
|
+
type=f"EDIT_{self.constantized_key()}_COMMAND",
|
|
205
|
+
payload=json.dumps(
|
|
206
|
+
{
|
|
207
|
+
"command": self.command_uuid,
|
|
208
|
+
"data": processed_data,
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
|
|
51
213
|
|
|
52
214
|
__exports__ = (
|
|
53
215
|
"PrescribeCommand",
|
|
216
|
+
"CompoundMedicationData",
|
|
54
217
|
# Not defined here but used in a current plugin
|
|
55
218
|
"ClinicalQuantity",
|
|
56
219
|
"Decimal",
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic_core import InitErrorDetails, PydanticCustomError
|
|
5
|
+
|
|
6
|
+
from canvas_generated.messages.effects_pb2 import Effect
|
|
7
|
+
from canvas_sdk.base import TrackableFieldsModel
|
|
8
|
+
from canvas_sdk.v1.data.compound_medication import CompoundMedication as CompoundMedicationModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CompoundMedication(TrackableFieldsModel):
|
|
12
|
+
"""Effect to create or update a Compound Medication record."""
|
|
13
|
+
|
|
14
|
+
class Meta:
|
|
15
|
+
effect_type = "COMPOUND_MEDICATION"
|
|
16
|
+
|
|
17
|
+
instance_id: str | None = None
|
|
18
|
+
formulation: str | None = None
|
|
19
|
+
potency_unit_code: str | None = None
|
|
20
|
+
controlled_substance: str | None = None
|
|
21
|
+
controlled_substance_ndc: str | None = None
|
|
22
|
+
active: bool | None = None
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def values(self) -> dict[str, Any]:
|
|
26
|
+
"""Return the values of the compound medication as a dictionary, only including dirty (modified) fields."""
|
|
27
|
+
return super().values
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def process_formulation(formulation: str | None) -> str | None:
|
|
31
|
+
"""
|
|
32
|
+
Process compound medication formulation by stripping whitespace.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
formulation: The formulation string to process
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Processed formulation string, or None if input was None
|
|
39
|
+
"""
|
|
40
|
+
if formulation is None:
|
|
41
|
+
return None
|
|
42
|
+
return formulation.strip()
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def process_ndc(ndc: str | None) -> str | None:
|
|
46
|
+
"""
|
|
47
|
+
Process compound medication NDC by removing dashes.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
ndc: The NDC string to process
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Processed NDC string with dashes removed, or None if input was None
|
|
54
|
+
"""
|
|
55
|
+
if ndc is None:
|
|
56
|
+
return None
|
|
57
|
+
return ndc.replace("-", "")
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def process_compound_medication_data(data: dict[str, Any]) -> dict[str, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Process compound medication data by applying all necessary transformations.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
data: Dictionary containing compound medication data
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Processed dictionary with transformations applied
|
|
69
|
+
"""
|
|
70
|
+
processed_data = data.copy()
|
|
71
|
+
|
|
72
|
+
# Process formulation
|
|
73
|
+
if "formulation" in processed_data and processed_data["formulation"] is not None:
|
|
74
|
+
processed_data["formulation"] = CompoundMedication.process_formulation(
|
|
75
|
+
processed_data["formulation"]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Process NDC
|
|
79
|
+
if (
|
|
80
|
+
"controlled_substance_ndc" in processed_data
|
|
81
|
+
and processed_data["controlled_substance_ndc"] is not None
|
|
82
|
+
):
|
|
83
|
+
processed_data["controlled_substance_ndc"] = CompoundMedication.process_ndc(
|
|
84
|
+
processed_data["controlled_substance_ndc"]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return processed_data
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def validate_compound_medication_fields(
|
|
91
|
+
formulation: str | None,
|
|
92
|
+
potency_unit_code: str | None,
|
|
93
|
+
controlled_substance: str | None,
|
|
94
|
+
controlled_substance_ndc: str | None,
|
|
95
|
+
) -> list[InitErrorDetails]:
|
|
96
|
+
"""
|
|
97
|
+
Static method to validate compound medication fields.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
formulation: The formulation value to validate
|
|
101
|
+
potency_unit_code: The potency unit code to validate
|
|
102
|
+
controlled_substance: The controlled substance to validate
|
|
103
|
+
controlled_substance_ndc: The NDC to validate
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of InitErrorDetails for any validation errors
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def create_error(error_type: str, message: str, value: Any) -> InitErrorDetails:
|
|
110
|
+
"""Helper function to mimic _create_error_details."""
|
|
111
|
+
return InitErrorDetails(
|
|
112
|
+
{"type": PydanticCustomError(error_type, message), "input": value}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
errors = []
|
|
116
|
+
|
|
117
|
+
# Formulation validation
|
|
118
|
+
if formulation is not None:
|
|
119
|
+
stripped_formulation = formulation.strip()
|
|
120
|
+
if not stripped_formulation:
|
|
121
|
+
errors.append(
|
|
122
|
+
create_error(
|
|
123
|
+
"value",
|
|
124
|
+
"Field 'formulation' cannot be empty.",
|
|
125
|
+
formulation,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
elif len(stripped_formulation) > 105:
|
|
129
|
+
errors.append(
|
|
130
|
+
create_error(
|
|
131
|
+
"value",
|
|
132
|
+
"Field 'formulation' must be 105 characters or less.",
|
|
133
|
+
formulation,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Potency unit code validation
|
|
138
|
+
if potency_unit_code and potency_unit_code not in [
|
|
139
|
+
choice[0] for choice in CompoundMedicationModel.PotencyUnits.choices
|
|
140
|
+
]:
|
|
141
|
+
errors.append(
|
|
142
|
+
create_error(
|
|
143
|
+
"value",
|
|
144
|
+
f"Invalid potency unit code: {potency_unit_code}. Must be one of: {[choice[0] for choice in CompoundMedicationModel.PotencyUnits.choices]}",
|
|
145
|
+
potency_unit_code,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Controlled substance validation
|
|
150
|
+
if controlled_substance and controlled_substance not in [
|
|
151
|
+
choice[0] for choice in CompoundMedicationModel.ControlledSubstanceOptions.choices
|
|
152
|
+
]:
|
|
153
|
+
errors.append(
|
|
154
|
+
create_error(
|
|
155
|
+
"value",
|
|
156
|
+
f"Invalid controlled substance: {controlled_substance}. Must be one of: {[choice[0] for choice in CompoundMedicationModel.ControlledSubstanceOptions.choices]}",
|
|
157
|
+
controlled_substance,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# NDC validation - required when controlled substance is not "N" (Not Scheduled)
|
|
162
|
+
if (
|
|
163
|
+
controlled_substance
|
|
164
|
+
and controlled_substance
|
|
165
|
+
!= CompoundMedicationModel.ControlledSubstanceOptions.SCHEDULE_NOT_SCHEDULED.value
|
|
166
|
+
and (not controlled_substance_ndc or not controlled_substance_ndc.strip())
|
|
167
|
+
):
|
|
168
|
+
errors.append(
|
|
169
|
+
create_error(
|
|
170
|
+
"value",
|
|
171
|
+
"NDC is required when a Controlled Substance is specified.",
|
|
172
|
+
controlled_substance_ndc,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return errors
|
|
177
|
+
|
|
178
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
179
|
+
errors = super()._get_error_details(method)
|
|
180
|
+
|
|
181
|
+
if method == "create":
|
|
182
|
+
if not self.formulation:
|
|
183
|
+
errors.append(
|
|
184
|
+
self._create_error_detail(
|
|
185
|
+
"missing",
|
|
186
|
+
"Field 'formulation' is required to create a compound medication.",
|
|
187
|
+
None,
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if not self.potency_unit_code:
|
|
192
|
+
errors.append(
|
|
193
|
+
self._create_error_detail(
|
|
194
|
+
"missing",
|
|
195
|
+
"Field 'potency_unit_code' is required to create a compound medication.",
|
|
196
|
+
None,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if not self.controlled_substance:
|
|
201
|
+
errors.append(
|
|
202
|
+
self._create_error_detail(
|
|
203
|
+
"missing",
|
|
204
|
+
"Field 'controlled_substance' is required to create a compound medication.",
|
|
205
|
+
None,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
elif method == "update":
|
|
210
|
+
if not self.instance_id:
|
|
211
|
+
errors.append(
|
|
212
|
+
self._create_error_detail(
|
|
213
|
+
"missing",
|
|
214
|
+
"Field 'instance_id' is required to update a compound medication.",
|
|
215
|
+
None,
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if (
|
|
220
|
+
self.instance_id
|
|
221
|
+
and not CompoundMedicationModel.objects.filter(id=self.instance_id).exists()
|
|
222
|
+
):
|
|
223
|
+
errors.append(
|
|
224
|
+
self._create_error_detail(
|
|
225
|
+
"value",
|
|
226
|
+
f"Compound medication with ID {self.instance_id} does not exist.",
|
|
227
|
+
self.instance_id,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
compound_med_errors = self.validate_compound_medication_fields(
|
|
232
|
+
formulation=self.formulation,
|
|
233
|
+
potency_unit_code=self.potency_unit_code,
|
|
234
|
+
controlled_substance=self.controlled_substance,
|
|
235
|
+
controlled_substance_ndc=self.controlled_substance_ndc,
|
|
236
|
+
)
|
|
237
|
+
errors.extend(compound_med_errors)
|
|
238
|
+
|
|
239
|
+
return errors
|
|
240
|
+
|
|
241
|
+
def create(self) -> Effect:
|
|
242
|
+
"""Create a new Compound Medication."""
|
|
243
|
+
self._validate_before_effect("create")
|
|
244
|
+
|
|
245
|
+
# Get raw values and apply explicit processing
|
|
246
|
+
raw_data = self.values
|
|
247
|
+
processed_data = self.process_compound_medication_data(raw_data)
|
|
248
|
+
|
|
249
|
+
payload = {"data": processed_data}
|
|
250
|
+
if self.active is None:
|
|
251
|
+
payload["data"]["active"] = True
|
|
252
|
+
|
|
253
|
+
return Effect(
|
|
254
|
+
type=f"CREATE_{self.Meta.effect_type}",
|
|
255
|
+
payload=json.dumps(payload),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def update(self) -> Effect:
|
|
259
|
+
"""Update an existing Compound Medication."""
|
|
260
|
+
self._validate_before_effect("update")
|
|
261
|
+
|
|
262
|
+
# Get raw values and apply explicit processing
|
|
263
|
+
raw_data = self.values
|
|
264
|
+
processed_data = self.process_compound_medication_data(raw_data)
|
|
265
|
+
|
|
266
|
+
payload = {"data": processed_data}
|
|
267
|
+
payload["data"]["instance_id"] = str(self.instance_id)
|
|
268
|
+
|
|
269
|
+
return Effect(
|
|
270
|
+
type=f"UPDATE_{self.Meta.effect_type}",
|
|
271
|
+
payload=json.dumps(payload),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
__exports__ = ("CompoundMedication",)
|
canvas_sdk/v1/data/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from .charge_description_master import ChargeDescriptionMaster
|
|
|
9
9
|
from .claim import Claim, ClaimCoverage, ClaimPatient, ClaimQueue, InstallmentPlan
|
|
10
10
|
from .claim_line_item import ClaimLineItem
|
|
11
11
|
from .command import Command
|
|
12
|
+
from .compound_medication import CompoundMedication
|
|
12
13
|
from .condition import Condition, ConditionCoding
|
|
13
14
|
from .coverage import Coverage, Transactor, TransactorAddress, TransactorPhone
|
|
14
15
|
from .detected_issue import DetectedIssue, DetectedIssueEvidence
|
|
@@ -107,6 +108,7 @@ __all__ = __exports__ = (
|
|
|
107
108
|
"ClaimPatient",
|
|
108
109
|
"ClaimQueue",
|
|
109
110
|
"Command",
|
|
111
|
+
"CompoundMedication",
|
|
110
112
|
"Condition",
|
|
111
113
|
"ConditionCoding",
|
|
112
114
|
"Coverage",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.db.models import TextChoices
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CompoundMedication(models.Model):
|
|
6
|
+
"""CompoundMedication."""
|
|
7
|
+
|
|
8
|
+
class Meta:
|
|
9
|
+
managed = False
|
|
10
|
+
db_table = "canvas_sdk_data_api_compoundmedication_001"
|
|
11
|
+
|
|
12
|
+
class PotencyUnits(TextChoices):
|
|
13
|
+
"""Potency Units."""
|
|
14
|
+
|
|
15
|
+
APPLICATOR = "C62412", "Applicator"
|
|
16
|
+
BLISTER = "C54564", "Blister"
|
|
17
|
+
CAPLET = "C64696", "Caplet"
|
|
18
|
+
CAPSULE = "C48480", "Capsule"
|
|
19
|
+
EACH = "C64933", "Each"
|
|
20
|
+
FILM = "C53499", "Film"
|
|
21
|
+
GRAM = "C48155", "Gram"
|
|
22
|
+
GUM = "C69124", "Gum"
|
|
23
|
+
IMPLANT = "C48499", "Implant"
|
|
24
|
+
INSERT = "C62276", "Insert"
|
|
25
|
+
KIT = "C48504", "Kit"
|
|
26
|
+
LANCET = "C120263", "Lancet"
|
|
27
|
+
LOZENGE = "C48506", "Lozenge"
|
|
28
|
+
MILLILITER = "C28254", "Milliliter"
|
|
29
|
+
PACKET = "C48521", "Packet"
|
|
30
|
+
PAD = "C65032", "Pad"
|
|
31
|
+
PATCH = "C48524", "Patch"
|
|
32
|
+
PEN_NEEDLE = "C120216", "Pen Needle"
|
|
33
|
+
RING = "C62609", "Ring"
|
|
34
|
+
SPONGE = "C53502", "Sponge"
|
|
35
|
+
STICK = "C53503", "Stick"
|
|
36
|
+
STRIP = "C48538", "Strip"
|
|
37
|
+
SUPPOSITORY = "C48539", "Suppository"
|
|
38
|
+
SWAB = "C53504", "Swab"
|
|
39
|
+
TABLET = "C48542", "Tablet"
|
|
40
|
+
TROCHE = "C48548", "Troche"
|
|
41
|
+
UNSPECIFIED = "C38046", "Unspecified"
|
|
42
|
+
WAFER = "C48552", "Wafer"
|
|
43
|
+
|
|
44
|
+
class ControlledSubstanceOptions(TextChoices):
|
|
45
|
+
"""Controlled Substance Options."""
|
|
46
|
+
|
|
47
|
+
SCHEDULE_NOT_SCHEDULED = "N", "None"
|
|
48
|
+
SCHEDULE_II = "II", "Schedule II"
|
|
49
|
+
SCHEDULE_III = "III", "Schedule III"
|
|
50
|
+
SCHEDULE_IV = "IV", "Schedule IV"
|
|
51
|
+
SCHEDULE_V = "V", "Schedule V"
|
|
52
|
+
|
|
53
|
+
id = models.UUIDField()
|
|
54
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
55
|
+
active = models.BooleanField(default=True)
|
|
56
|
+
formulation = models.CharField(max_length=105)
|
|
57
|
+
potency_unit_code = models.CharField(max_length=20, choices=PotencyUnits.choices)
|
|
58
|
+
controlled_substance = models.CharField(
|
|
59
|
+
max_length=3, choices=ControlledSubstanceOptions.choices
|
|
60
|
+
)
|
|
61
|
+
controlled_substance_ndc = models.CharField(max_length=20, blank=True, default="")
|
|
62
|
+
|
|
63
|
+
def __str__(self) -> str:
|
|
64
|
+
return f'CompoundMedication: "{self.formulation}"'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__exports__ = ("CompoundMedication",)
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"VitalsCommand"
|
|
44
44
|
],
|
|
45
45
|
"canvas_sdk.commands.base": [
|
|
46
|
-
"_BaseCommand"
|
|
46
|
+
"_BaseCommand",
|
|
47
|
+
"_SendableCommandMixin"
|
|
47
48
|
],
|
|
48
49
|
"canvas_sdk.commands.commands.adjust_prescription": [
|
|
49
50
|
"AdjustPrescriptionCommand"
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
],
|
|
113
114
|
"canvas_sdk.commands.commands.prescribe": [
|
|
114
115
|
"ClinicalQuantity",
|
|
116
|
+
"CompoundMedicationData",
|
|
115
117
|
"Decimal",
|
|
116
118
|
"PrescribeCommand"
|
|
117
119
|
],
|
|
@@ -204,6 +206,12 @@
|
|
|
204
206
|
"canvas_sdk.effects.billing_line_item.update_billing_line_item": [
|
|
205
207
|
"UpdateBillingLineItem"
|
|
206
208
|
],
|
|
209
|
+
"canvas_sdk.effects.compound_medications": [
|
|
210
|
+
"CompoundMedication"
|
|
211
|
+
],
|
|
212
|
+
"canvas_sdk.effects.compound_medications.compound_medication": [
|
|
213
|
+
"CompoundMedication"
|
|
214
|
+
],
|
|
207
215
|
"canvas_sdk.effects.launch_modal": [
|
|
208
216
|
"LaunchModalEffect"
|
|
209
217
|
],
|
|
@@ -491,6 +499,7 @@
|
|
|
491
499
|
"ClaimPatient",
|
|
492
500
|
"ClaimQueue",
|
|
493
501
|
"Command",
|
|
502
|
+
"CompoundMedication",
|
|
494
503
|
"Condition",
|
|
495
504
|
"ConditionCoding",
|
|
496
505
|
"Coverage",
|
|
@@ -640,6 +649,9 @@
|
|
|
640
649
|
"ReviewStatus",
|
|
641
650
|
"TaxIDType"
|
|
642
651
|
],
|
|
652
|
+
"canvas_sdk.v1.data.compound_medication": [
|
|
653
|
+
"CompoundMedication"
|
|
654
|
+
],
|
|
643
655
|
"canvas_sdk.v1.data.condition": [
|
|
644
656
|
"ClinicalStatus",
|
|
645
657
|
"Condition",
|