canvas 0.63.0__py3-none-any.whl → 0.89.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.
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/METADATA +4 -1
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/RECORD +184 -98
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/WHEEL +1 -1
- canvas_cli/apps/emit/event_fixtures/UNKNOWN.ndjson +1 -0
- canvas_cli/apps/logs/logs.py +386 -22
- canvas_cli/main.py +3 -1
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py +46 -4
- canvas_cli/utils/context/context.py +13 -13
- canvas_cli/utils/validators/manifest_schema.py +26 -1
- canvas_generated/messages/effects_pb2.py +5 -5
- canvas_generated/messages/effects_pb2.pyi +108 -2
- canvas_generated/messages/events_pb2.py +6 -6
- canvas_generated/messages/events_pb2.pyi +282 -2
- canvas_sdk/clients/__init__.py +1 -0
- canvas_sdk/clients/llms/__init__.py +17 -0
- canvas_sdk/clients/llms/libraries/__init__.py +11 -0
- canvas_sdk/clients/llms/libraries/llm_anthropic.py +87 -0
- canvas_sdk/clients/llms/libraries/llm_api.py +143 -0
- canvas_sdk/clients/llms/libraries/llm_google.py +92 -0
- canvas_sdk/clients/llms/libraries/llm_openai.py +98 -0
- canvas_sdk/clients/llms/structures/__init__.py +9 -0
- canvas_sdk/clients/llms/structures/llm_response.py +33 -0
- canvas_sdk/clients/llms/structures/llm_tokens.py +53 -0
- canvas_sdk/clients/llms/structures/llm_turn.py +47 -0
- canvas_sdk/clients/llms/structures/settings/__init__.py +13 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings.py +27 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_anthropic.py +43 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gemini.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt4.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt5.py +48 -0
- canvas_sdk/clients/third_party.py +3 -0
- canvas_sdk/commands/__init__.py +12 -0
- canvas_sdk/commands/base.py +33 -2
- canvas_sdk/commands/commands/adjust_prescription.py +4 -0
- canvas_sdk/commands/commands/custom_command.py +86 -0
- canvas_sdk/commands/commands/family_history.py +17 -1
- canvas_sdk/commands/commands/immunization_statement.py +42 -2
- canvas_sdk/commands/commands/medication_statement.py +16 -1
- canvas_sdk/commands/commands/past_surgical_history.py +16 -1
- canvas_sdk/commands/commands/perform.py +18 -1
- canvas_sdk/commands/commands/prescribe.py +8 -9
- canvas_sdk/commands/commands/refill.py +5 -5
- canvas_sdk/commands/commands/resolve_condition.py +5 -5
- canvas_sdk/commands/commands/review/__init__.py +3 -0
- canvas_sdk/commands/commands/review/base.py +72 -0
- canvas_sdk/commands/commands/review/imaging.py +13 -0
- canvas_sdk/commands/commands/review/lab.py +13 -0
- canvas_sdk/commands/commands/review/referral.py +13 -0
- canvas_sdk/commands/commands/review/uncategorized_document.py +13 -0
- canvas_sdk/commands/validation.py +43 -0
- canvas_sdk/effects/batch_originate.py +22 -0
- canvas_sdk/effects/calendar/__init__.py +13 -3
- canvas_sdk/effects/calendar/{create_calendar.py → calendar.py} +19 -5
- canvas_sdk/effects/calendar/event.py +172 -0
- canvas_sdk/effects/claim_label.py +93 -0
- canvas_sdk/effects/claim_line_item.py +47 -0
- canvas_sdk/effects/claim_queue.py +49 -0
- canvas_sdk/effects/fax/__init__.py +3 -0
- canvas_sdk/effects/fax/base.py +77 -0
- canvas_sdk/effects/fax/note.py +42 -0
- canvas_sdk/effects/metadata.py +15 -1
- canvas_sdk/effects/note/__init__.py +8 -1
- canvas_sdk/effects/note/appointment.py +135 -7
- canvas_sdk/effects/note/base.py +17 -0
- canvas_sdk/effects/note/message.py +22 -14
- canvas_sdk/effects/note/note.py +150 -1
- canvas_sdk/effects/observation/__init__.py +11 -0
- canvas_sdk/effects/observation/base.py +206 -0
- canvas_sdk/effects/patient/__init__.py +2 -0
- canvas_sdk/effects/patient/base.py +8 -0
- canvas_sdk/effects/payment/__init__.py +11 -0
- canvas_sdk/effects/payment/base.py +355 -0
- canvas_sdk/effects/payment/post_claim_payment.py +49 -0
- canvas_sdk/effects/send_contact_verification.py +42 -0
- canvas_sdk/effects/task/__init__.py +2 -1
- canvas_sdk/effects/task/task.py +30 -0
- canvas_sdk/effects/validation/__init__.py +3 -0
- canvas_sdk/effects/validation/base.py +92 -0
- canvas_sdk/events/base.py +15 -0
- canvas_sdk/handlers/application.py +7 -7
- canvas_sdk/handlers/simple_api/api.py +1 -4
- canvas_sdk/handlers/simple_api/websocket.py +1 -4
- canvas_sdk/handlers/utils.py +14 -0
- canvas_sdk/questionnaires/utils.py +1 -0
- canvas_sdk/templates/utils.py +17 -4
- canvas_sdk/test_utils/factories/FACTORY_GUIDE.md +362 -0
- canvas_sdk/test_utils/factories/__init__.py +115 -0
- canvas_sdk/test_utils/factories/calendar.py +24 -0
- canvas_sdk/test_utils/factories/claim.py +81 -0
- canvas_sdk/test_utils/factories/claim_diagnosis_code.py +16 -0
- canvas_sdk/test_utils/factories/coverage.py +17 -0
- canvas_sdk/test_utils/factories/imaging.py +74 -0
- canvas_sdk/test_utils/factories/lab.py +192 -0
- canvas_sdk/test_utils/factories/medication_history.py +75 -0
- canvas_sdk/test_utils/factories/note.py +52 -0
- canvas_sdk/test_utils/factories/organization.py +50 -0
- canvas_sdk/test_utils/factories/practicelocation.py +88 -0
- canvas_sdk/test_utils/factories/referral.py +81 -0
- canvas_sdk/test_utils/factories/staff.py +111 -0
- canvas_sdk/test_utils/factories/task.py +66 -0
- canvas_sdk/test_utils/factories/uncategorized_clinical_document.py +48 -0
- canvas_sdk/utils/metrics.py +4 -1
- canvas_sdk/v1/data/__init__.py +66 -7
- canvas_sdk/v1/data/allergy_intolerance.py +5 -11
- canvas_sdk/v1/data/appointment.py +18 -4
- canvas_sdk/v1/data/assessment.py +2 -12
- canvas_sdk/v1/data/banner_alert.py +2 -4
- canvas_sdk/v1/data/base.py +53 -14
- canvas_sdk/v1/data/billing.py +8 -11
- canvas_sdk/v1/data/calendar.py +64 -0
- canvas_sdk/v1/data/care_team.py +4 -10
- canvas_sdk/v1/data/claim.py +172 -66
- canvas_sdk/v1/data/claim_diagnosis_code.py +19 -0
- canvas_sdk/v1/data/claim_line_item.py +2 -5
- canvas_sdk/v1/data/coding.py +19 -0
- canvas_sdk/v1/data/command.py +2 -4
- canvas_sdk/v1/data/common.py +10 -0
- canvas_sdk/v1/data/compound_medication.py +3 -4
- canvas_sdk/v1/data/condition.py +4 -9
- canvas_sdk/v1/data/coverage.py +66 -26
- canvas_sdk/v1/data/detected_issue.py +20 -20
- canvas_sdk/v1/data/device.py +2 -14
- canvas_sdk/v1/data/discount.py +2 -5
- canvas_sdk/v1/data/encounter.py +44 -0
- canvas_sdk/v1/data/facility.py +1 -0
- canvas_sdk/v1/data/goal.py +2 -14
- canvas_sdk/v1/data/imaging.py +4 -30
- canvas_sdk/v1/data/immunization.py +7 -15
- canvas_sdk/v1/data/lab.py +12 -65
- canvas_sdk/v1/data/line_item_transaction.py +2 -5
- canvas_sdk/v1/data/medication.py +3 -8
- canvas_sdk/v1/data/medication_history.py +142 -0
- canvas_sdk/v1/data/medication_statement.py +41 -0
- canvas_sdk/v1/data/message.py +4 -8
- canvas_sdk/v1/data/note.py +37 -38
- canvas_sdk/v1/data/observation.py +9 -36
- canvas_sdk/v1/data/organization.py +70 -9
- canvas_sdk/v1/data/patient.py +8 -12
- canvas_sdk/v1/data/patient_consent.py +4 -14
- canvas_sdk/v1/data/payment_collection.py +2 -5
- canvas_sdk/v1/data/posting.py +3 -9
- canvas_sdk/v1/data/practicelocation.py +66 -7
- canvas_sdk/v1/data/protocol_override.py +3 -4
- canvas_sdk/v1/data/protocol_result.py +3 -3
- canvas_sdk/v1/data/questionnaire.py +10 -26
- canvas_sdk/v1/data/reason_for_visit.py +2 -6
- canvas_sdk/v1/data/referral.py +41 -17
- canvas_sdk/v1/data/staff.py +34 -26
- canvas_sdk/v1/data/stop_medication_event.py +27 -0
- canvas_sdk/v1/data/task.py +30 -11
- canvas_sdk/v1/data/team.py +2 -4
- canvas_sdk/v1/data/uncategorized_clinical_document.py +84 -0
- canvas_sdk/v1/data/user.py +14 -0
- canvas_sdk/v1/data/utils.py +5 -0
- canvas_sdk/value_set/v2026/__init__.py +1 -0
- canvas_sdk/value_set/v2026/adverse_event.py +157 -0
- canvas_sdk/value_set/v2026/allergy.py +116 -0
- canvas_sdk/value_set/v2026/assessment.py +466 -0
- canvas_sdk/value_set/v2026/communication.py +496 -0
- canvas_sdk/value_set/v2026/condition.py +52934 -0
- canvas_sdk/value_set/v2026/device.py +315 -0
- canvas_sdk/value_set/v2026/diagnostic_study.py +5243 -0
- canvas_sdk/value_set/v2026/encounter.py +2714 -0
- canvas_sdk/value_set/v2026/immunization.py +297 -0
- canvas_sdk/value_set/v2026/individual_characteristic.py +339 -0
- canvas_sdk/value_set/v2026/intervention.py +1703 -0
- canvas_sdk/value_set/v2026/laboratory_test.py +1831 -0
- canvas_sdk/value_set/v2026/medication.py +8218 -0
- canvas_sdk/value_set/v2026/no_qdm_category_assigned.py +26493 -0
- canvas_sdk/value_set/v2026/physical_exam.py +342 -0
- canvas_sdk/value_set/v2026/procedure.py +27869 -0
- canvas_sdk/value_set/v2026/symptom.py +625 -0
- logger/logger.py +30 -31
- logger/logstash.py +282 -0
- logger/pubsub.py +26 -0
- plugin_runner/allowed-module-imports.json +940 -9
- plugin_runner/generate_allowed_imports.py +1 -0
- plugin_runner/installation.py +2 -2
- plugin_runner/plugin_runner.py +21 -24
- plugin_runner/sandbox.py +34 -0
- protobufs/canvas_generated/messages/effects.proto +65 -0
- protobufs/canvas_generated/messages/events.proto +150 -51
- settings.py +27 -11
- canvas_sdk/effects/calendar/create_event.py +0 -43
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.clients.llms.structures.settings.llm_settings import LlmSettings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class LlmSettingsGemini(LlmSettings):
|
|
8
|
+
"""Configuration settings for Google Gemini LLM API.
|
|
9
|
+
|
|
10
|
+
Extends LlmSettings with Gemini-specific parameters.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
api_key: API authentication key for the LLM service (inherited).
|
|
14
|
+
model: Name or identifier of the LLM model to use (inherited).
|
|
15
|
+
temperature: Controls randomness in responses (0.0-1.0).
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python3
|
|
19
|
+
LlmSettingsGemini(
|
|
20
|
+
api_key=environ.get("google_key"),
|
|
21
|
+
model="models/gemini-2.0-flash",
|
|
22
|
+
temperature=2.0,
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
temperature: float
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict:
|
|
30
|
+
"""Convert settings to Google Gemini API request format.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dictionary containing model name and generationConfig with temperature.
|
|
34
|
+
"""
|
|
35
|
+
return super().to_dict() | {
|
|
36
|
+
"generationConfig": {"temperature": self.temperature},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__exports__ = ("LlmSettingsGemini",)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.clients.llms.structures.settings.llm_settings import LlmSettings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class LlmSettingsGpt4(LlmSettings):
|
|
8
|
+
"""Configuration settings for OpenAI GPT-4 LLM API.
|
|
9
|
+
|
|
10
|
+
Extends LlmSettings with GPT-4-specific parameters.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
api_key: API authentication key for the LLM service (inherited).
|
|
14
|
+
model: Name or identifier of the LLM model to use (inherited).
|
|
15
|
+
temperature: Controls randomness in responses (0.0-1.0).
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python3
|
|
19
|
+
LlmSettingsGpt4(
|
|
20
|
+
api_key=environ.get("openai_key"),
|
|
21
|
+
model="gpt-4o",
|
|
22
|
+
temperature=2.0,
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
temperature: float
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict:
|
|
30
|
+
"""Convert settings to OpenAI GPT-4 API request format.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dictionary containing model name and temperature.
|
|
34
|
+
"""
|
|
35
|
+
return super().to_dict() | {
|
|
36
|
+
"temperature": self.temperature,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__exports__ = ("LlmSettingsGpt4",)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.clients.llms.structures.settings.llm_settings import LlmSettings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class LlmSettingsGpt5(LlmSettings):
|
|
8
|
+
"""Configuration settings for OpenAI GPT-5 LLM API.
|
|
9
|
+
|
|
10
|
+
Extends LlmSettings with GPT-5-specific parameters for reasoning and text generation.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
api_key: API authentication key for the LLM service (inherited).
|
|
14
|
+
model: Name or identifier of the LLM model to use (inherited).
|
|
15
|
+
reasoning_effort: Level of reasoning effort ('none', 'low', 'medium', 'high').
|
|
16
|
+
text_verbosity: Level of text verbosity in responses ('low', 'medium', 'high').
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
```python3
|
|
20
|
+
LlmSettingsGpt5(
|
|
21
|
+
api_key=environ.get("openai_key"),
|
|
22
|
+
model="gpt-5.1",
|
|
23
|
+
reasoning_effort="none",
|
|
24
|
+
text_verbosity="low",
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
reasoning_effort: str
|
|
30
|
+
text_verbosity: str
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict:
|
|
33
|
+
"""Convert settings to OpenAI GPT-5 API request format.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary containing model name, reasoning config, and text config.
|
|
37
|
+
"""
|
|
38
|
+
return super().to_dict() | {
|
|
39
|
+
"reasoning": {
|
|
40
|
+
"effort": self.reasoning_effort,
|
|
41
|
+
},
|
|
42
|
+
"text": {
|
|
43
|
+
"verbosity": self.text_verbosity,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__exports__ = ("LlmSettingsGpt5",)
|
canvas_sdk/commands/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@ from canvas_sdk.commands.commands.allergy import AllergyCommand
|
|
|
3
3
|
from canvas_sdk.commands.commands.assess import AssessCommand
|
|
4
4
|
from canvas_sdk.commands.commands.chart_section_review import ChartSectionReviewCommand
|
|
5
5
|
from canvas_sdk.commands.commands.close_goal import CloseGoalCommand
|
|
6
|
+
from canvas_sdk.commands.commands.custom_command import CustomCommand
|
|
6
7
|
from canvas_sdk.commands.commands.diagnose import DiagnoseCommand
|
|
7
8
|
from canvas_sdk.commands.commands.exam import PhysicalExamCommand
|
|
8
9
|
from canvas_sdk.commands.commands.family_history import FamilyHistoryCommand
|
|
@@ -28,6 +29,12 @@ from canvas_sdk.commands.commands.refer import ReferCommand
|
|
|
28
29
|
from canvas_sdk.commands.commands.refill import RefillCommand
|
|
29
30
|
from canvas_sdk.commands.commands.remove_allergy import RemoveAllergyCommand
|
|
30
31
|
from canvas_sdk.commands.commands.resolve_condition import ResolveConditionCommand
|
|
32
|
+
from canvas_sdk.commands.commands.review.imaging import ImagingReviewCommand
|
|
33
|
+
from canvas_sdk.commands.commands.review.lab import LabReviewCommand
|
|
34
|
+
from canvas_sdk.commands.commands.review.referral import ReferralReviewCommand
|
|
35
|
+
from canvas_sdk.commands.commands.review.uncategorized_document import (
|
|
36
|
+
UncategorizedDocumentReviewCommand,
|
|
37
|
+
)
|
|
31
38
|
from canvas_sdk.commands.commands.review_of_systems import ReviewOfSystemsCommand
|
|
32
39
|
from canvas_sdk.commands.commands.stop_medication import StopMedicationCommand
|
|
33
40
|
from canvas_sdk.commands.commands.structured_assessment import StructuredAssessmentCommand
|
|
@@ -47,9 +54,12 @@ __all__ = __exports__ = (
|
|
|
47
54
|
"FollowUpCommand",
|
|
48
55
|
"GoalCommand",
|
|
49
56
|
"HistoryOfPresentIllnessCommand",
|
|
57
|
+
"CustomCommand",
|
|
50
58
|
"ImagingOrderCommand",
|
|
59
|
+
"ImagingReviewCommand",
|
|
51
60
|
"InstructCommand",
|
|
52
61
|
"LabOrderCommand",
|
|
62
|
+
"LabReviewCommand",
|
|
53
63
|
"MedicalHistoryCommand",
|
|
54
64
|
"MedicationStatementCommand",
|
|
55
65
|
"PastSurgicalHistoryCommand",
|
|
@@ -60,6 +70,7 @@ __all__ = __exports__ = (
|
|
|
60
70
|
"QuestionnaireCommand",
|
|
61
71
|
"ReasonForVisitCommand",
|
|
62
72
|
"ReferCommand",
|
|
73
|
+
"ReferralReviewCommand",
|
|
63
74
|
"RefillCommand",
|
|
64
75
|
"RemoveAllergyCommand",
|
|
65
76
|
"ResolveConditionCommand",
|
|
@@ -67,6 +78,7 @@ __all__ = __exports__ = (
|
|
|
67
78
|
"StopMedicationCommand",
|
|
68
79
|
"StructuredAssessmentCommand",
|
|
69
80
|
"TaskCommand",
|
|
81
|
+
"UncategorizedDocumentReviewCommand",
|
|
70
82
|
"UpdateDiagnosisCommand",
|
|
71
83
|
"UpdateGoalCommand",
|
|
72
84
|
"VitalsCommand",
|
canvas_sdk/commands/base.py
CHANGED
|
@@ -18,6 +18,7 @@ class _BaseCommand(TrackableFieldsModel):
|
|
|
18
18
|
originate_required_fields = ("note_uuid",)
|
|
19
19
|
edit_required_fields = ("command_uuid",)
|
|
20
20
|
send_required_fields = ("command_uuid",)
|
|
21
|
+
review_required_fields = ("command_uuid",)
|
|
21
22
|
delete_required_fields = ("command_uuid",)
|
|
22
23
|
commit_required_fields = ("command_uuid",)
|
|
23
24
|
enter_in_error_required_fields = ("command_uuid",)
|
|
@@ -29,11 +30,16 @@ class _BaseCommand(TrackableFieldsModel):
|
|
|
29
30
|
|
|
30
31
|
def __init__(self, /, **data: Any) -> None:
|
|
31
32
|
"""Initialize the command and mark all provided keys as dirty."""
|
|
33
|
+
if getattr(self.Meta, "abstract", False):
|
|
34
|
+
raise TypeError(f"Cannot instantiate abstract class {self.__class__.__name__!r}.")
|
|
35
|
+
|
|
32
36
|
super().__init__(**data)
|
|
33
37
|
|
|
34
38
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
35
39
|
"""Validate that the command has a key and required fields."""
|
|
36
|
-
if not hasattr(cls.Meta, "key") or not cls.Meta.key
|
|
40
|
+
if (not hasattr(cls.Meta, "key") or not cls.Meta.key) and not getattr(
|
|
41
|
+
cls.Meta, "abstract", False
|
|
42
|
+
):
|
|
37
43
|
raise ImproperlyConfigured(f"Command {cls.__name__!r} must specify Meta.key.")
|
|
38
44
|
|
|
39
45
|
if hasattr(cls.Meta, "commit_required_fields"):
|
|
@@ -118,6 +124,17 @@ class _BaseCommand(TrackableFieldsModel):
|
|
|
118
124
|
),
|
|
119
125
|
)
|
|
120
126
|
|
|
127
|
+
def _origination_payload_for_batch(self, line_number: int = -1) -> dict:
|
|
128
|
+
"""Originate a new command in the note body for batch processing."""
|
|
129
|
+
self._validate_before_effect("originate")
|
|
130
|
+
return {
|
|
131
|
+
"type": f"ORIGINATE_{self.constantized_key()}_COMMAND",
|
|
132
|
+
"command": self.command_uuid,
|
|
133
|
+
"note": self.note_uuid,
|
|
134
|
+
"data": self.values,
|
|
135
|
+
"line_number": line_number,
|
|
136
|
+
}
|
|
137
|
+
|
|
121
138
|
def edit(self) -> Effect:
|
|
122
139
|
"""Edit the command."""
|
|
123
140
|
self._validate_before_effect("edit")
|
|
@@ -178,4 +195,18 @@ class _SendableCommandMixin:
|
|
|
178
195
|
)
|
|
179
196
|
|
|
180
197
|
|
|
181
|
-
|
|
198
|
+
class _ReviewableCommandMixin:
|
|
199
|
+
def review(self) -> Effect:
|
|
200
|
+
"""Fire the review effect the command."""
|
|
201
|
+
self._validate_before_effect("review") # type: ignore[attr-defined]
|
|
202
|
+
return Effect(
|
|
203
|
+
type=f"REVIEW_{self.constantized_key()}_COMMAND", # type: ignore[attr-defined]
|
|
204
|
+
payload=json.dumps({"command": self.command_uuid}), # type: ignore[attr-defined]
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
__exports__ = (
|
|
209
|
+
"_BaseCommand",
|
|
210
|
+
"_SendableCommandMixin",
|
|
211
|
+
"_ReviewableCommandMixin",
|
|
212
|
+
)
|
|
@@ -13,5 +13,9 @@ class AdjustPrescriptionCommand(RefillCommand):
|
|
|
13
13
|
default=None, json_schema_extra={"commands_api_name": "change_medication_to"}
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
def _has_fdb_code(self) -> bool:
|
|
17
|
+
"""Check if new_fdb_code is provided and non-empty."""
|
|
18
|
+
return self.new_fdb_code is not None and self.new_fdb_code.strip() != ""
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
__exports__ = ("AdjustPrescriptionCommand",)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic_core import InitErrorDetails
|
|
4
|
+
|
|
5
|
+
from canvas_sdk.commands.base import _BaseCommand
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CustomCommand(_BaseCommand):
|
|
9
|
+
"""A class for managing a custom command within a specific note.
|
|
10
|
+
|
|
11
|
+
This class can be extended to create custom commands with predefined schema_key
|
|
12
|
+
or passing schema_key directly to the constructor.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
class Meta:
|
|
16
|
+
key = "customCommand"
|
|
17
|
+
schema_key: str | None = None
|
|
18
|
+
|
|
19
|
+
_schema_key: str | None = None
|
|
20
|
+
content: str | None = None
|
|
21
|
+
print_content: str | None = None
|
|
22
|
+
|
|
23
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
24
|
+
"""Ensure Meta.key is always 'customCommand' for all CustomCommand subclasses."""
|
|
25
|
+
# Always enforce key = "customCommand" for all subclasses
|
|
26
|
+
cls.Meta.key = "customCommand"
|
|
27
|
+
|
|
28
|
+
# Call parent __init_subclass__ to perform validation
|
|
29
|
+
super().__init_subclass__(**kwargs)
|
|
30
|
+
|
|
31
|
+
def __init__(self, **data: Any) -> None:
|
|
32
|
+
"""Initialize with schema_key from Meta if not provided."""
|
|
33
|
+
meta_schema_key = getattr(self.__class__.Meta, "schema_key", None)
|
|
34
|
+
|
|
35
|
+
# Extract schema_key before init if provided
|
|
36
|
+
instance_schema_key = data.pop("schema_key", None)
|
|
37
|
+
|
|
38
|
+
# If Meta defines schema_key, use it and don't allow override
|
|
39
|
+
if meta_schema_key is not None and instance_schema_key is not None:
|
|
40
|
+
raise AttributeError(
|
|
41
|
+
f"Cannot set schema_key on {self.__class__.__name__} instance. "
|
|
42
|
+
f"schema_key is already defined in Meta as '{meta_schema_key}'."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
super().__init__(**data)
|
|
46
|
+
|
|
47
|
+
# Set _schema_key after init to ensure it's tracked
|
|
48
|
+
if meta_schema_key is None and instance_schema_key is not None:
|
|
49
|
+
self._schema_key = instance_schema_key
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def schema_key(self) -> str | None:
|
|
53
|
+
"""Get schema_key from Meta if defined, otherwise from instance attribute."""
|
|
54
|
+
meta_schema_key = getattr(self.__class__.Meta, "schema_key", None)
|
|
55
|
+
return meta_schema_key if meta_schema_key is not None else self._schema_key
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def values(self) -> dict:
|
|
59
|
+
"""Get values for the custom command."""
|
|
60
|
+
return {**super().values, "schema_key": self.schema_key}
|
|
61
|
+
|
|
62
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
63
|
+
"""Get error details for the custom command."""
|
|
64
|
+
errors = super()._get_error_details(method)
|
|
65
|
+
|
|
66
|
+
if method == "originate":
|
|
67
|
+
if not self.content:
|
|
68
|
+
errors.append(
|
|
69
|
+
self._create_error_detail(
|
|
70
|
+
"value",
|
|
71
|
+
"Content must be provided for a custom command.",
|
|
72
|
+
self.content,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
if not self.schema_key:
|
|
76
|
+
errors.append(
|
|
77
|
+
self._create_error_detail(
|
|
78
|
+
"value",
|
|
79
|
+
"Schema key must be provided for a custom command.",
|
|
80
|
+
self.schema_key,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return errors
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__exports__ = ("CustomCommand",)
|
|
@@ -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 FamilyHistoryCommand(BaseCommand):
|
|
@@ -7,9 +10,22 @@ class FamilyHistoryCommand(BaseCommand):
|
|
|
7
10
|
class Meta:
|
|
8
11
|
key = "familyHistory"
|
|
9
12
|
|
|
10
|
-
family_history: str | None = None
|
|
13
|
+
family_history: str | Coding | None = None
|
|
11
14
|
relative: str | None = None
|
|
12
15
|
note: str | None = None
|
|
13
16
|
|
|
17
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
18
|
+
errors = super()._get_error_details(method)
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
isinstance(self.family_history, dict)
|
|
22
|
+
and self.family_history["system"] != CodeSystems.SNOMED
|
|
23
|
+
and self.family_history["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.family_history))
|
|
27
|
+
|
|
28
|
+
return errors
|
|
29
|
+
|
|
14
30
|
|
|
15
31
|
__exports__ = ("FamilyHistoryCommand",)
|
|
@@ -3,6 +3,7 @@ from datetime import date
|
|
|
3
3
|
from pydantic_core import InitErrorDetails
|
|
4
4
|
|
|
5
5
|
from canvas_sdk.commands.base import _BaseCommand as BaseCommand
|
|
6
|
+
from canvas_sdk.commands.constants import CodeSystems, Coding
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class ImmunizationStatementCommand(BaseCommand):
|
|
@@ -11,14 +12,53 @@ class ImmunizationStatementCommand(BaseCommand):
|
|
|
11
12
|
class Meta:
|
|
12
13
|
key = "immunizationStatement"
|
|
13
14
|
|
|
14
|
-
cpt_code: str
|
|
15
|
-
cvx_code: str
|
|
15
|
+
cpt_code: str | Coding | None = None
|
|
16
|
+
cvx_code: str | Coding | None = None
|
|
17
|
+
unstructured: Coding | None = None
|
|
16
18
|
approximate_date: date | None = None
|
|
17
19
|
comments: str | None = None
|
|
18
20
|
|
|
21
|
+
def _has_value(self, value: str | Coding | None) -> bool:
|
|
22
|
+
"""Check if a value is set."""
|
|
23
|
+
if value is None:
|
|
24
|
+
return False
|
|
25
|
+
if isinstance(value, str):
|
|
26
|
+
return value.strip() != ""
|
|
27
|
+
# For Coding objects (dicts), check if it exists
|
|
28
|
+
return True
|
|
29
|
+
|
|
19
30
|
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
20
31
|
errors = super()._get_error_details(method)
|
|
21
32
|
|
|
33
|
+
has_cpt_code = self._has_value(self.cpt_code)
|
|
34
|
+
has_cvx_code = self._has_value(self.cvx_code)
|
|
35
|
+
|
|
36
|
+
if has_cpt_code ^ has_cvx_code:
|
|
37
|
+
value = self.cpt_code if has_cpt_code else self.cvx_code
|
|
38
|
+
errors.append(
|
|
39
|
+
self._create_error_detail(
|
|
40
|
+
"value",
|
|
41
|
+
"Both cpt_code and cvx_code must be provided if one is specified and cannot be empty.",
|
|
42
|
+
value,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if self.unstructured and (has_cpt_code or has_cvx_code):
|
|
47
|
+
message = "Unstructured codes cannot be used with CPT or CVX codes."
|
|
48
|
+
errors.append(self._create_error_detail("value", message, self.cpt_code))
|
|
49
|
+
|
|
50
|
+
if isinstance(self.cpt_code, dict) and self.cpt_code["system"] != CodeSystems.CPT:
|
|
51
|
+
message = f"The 'cpt_code.system' field must be '{CodeSystems.CPT}'"
|
|
52
|
+
errors.append(self._create_error_detail("value", message, self.cpt_code))
|
|
53
|
+
|
|
54
|
+
if isinstance(self.cvx_code, dict) and self.cvx_code["system"] != CodeSystems.CVX:
|
|
55
|
+
message = f"The 'cvx_code.system' field must be '{CodeSystems.CVX}'"
|
|
56
|
+
errors.append(self._create_error_detail("value", message, self.cvx_code))
|
|
57
|
+
|
|
58
|
+
if self.unstructured and self.unstructured["system"] != CodeSystems.UNSTRUCTURED:
|
|
59
|
+
message = f"The 'unstructured.system' field must be '{CodeSystems.UNSTRUCTURED}'"
|
|
60
|
+
errors.append(self._create_error_detail("value", message, self.unstructured))
|
|
61
|
+
|
|
22
62
|
if self.comments and len(self.comments) > 255:
|
|
23
63
|
errors.append(
|
|
24
64
|
self._create_error_detail(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from pydantic import Field
|
|
2
|
+
from pydantic_core import InitErrorDetails
|
|
2
3
|
|
|
3
4
|
from canvas_sdk.commands.base import _BaseCommand
|
|
5
|
+
from canvas_sdk.commands.constants import CodeSystems, Coding
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class MedicationStatementCommand(_BaseCommand):
|
|
@@ -9,11 +11,24 @@ class MedicationStatementCommand(_BaseCommand):
|
|
|
9
11
|
class Meta:
|
|
10
12
|
key = "medicationStatement"
|
|
11
13
|
|
|
12
|
-
fdb_code: str | None = Field(
|
|
14
|
+
fdb_code: str | Coding | None = Field(
|
|
13
15
|
default=None, json_schema_extra={"commands_api_name": "medication"}
|
|
14
16
|
)
|
|
15
17
|
sig: str | None = None
|
|
16
18
|
|
|
19
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
20
|
+
errors = super()._get_error_details(method)
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
isinstance(self.fdb_code, dict)
|
|
24
|
+
and self.fdb_code["system"] != CodeSystems.FDB
|
|
25
|
+
and self.fdb_code["system"] != CodeSystems.UNSTRUCTURED
|
|
26
|
+
):
|
|
27
|
+
message = f"The 'coding.system' field must be '{CodeSystems.FDB}' or '{CodeSystems.UNSTRUCTURED}'."
|
|
28
|
+
errors.append(self._create_error_detail("value", message, self.fdb_code))
|
|
29
|
+
|
|
30
|
+
return errors
|
|
31
|
+
|
|
17
32
|
|
|
18
33
|
# how do we make sure fdb_code is a valid code?
|
|
19
34
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from datetime import date
|
|
2
2
|
|
|
3
3
|
from pydantic import Field
|
|
4
|
+
from pydantic_core import InitErrorDetails
|
|
4
5
|
|
|
5
6
|
from canvas_sdk.commands.base import _BaseCommand as BaseCommand
|
|
7
|
+
from canvas_sdk.commands.constants import CodeSystems, Coding
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class PastSurgicalHistoryCommand(BaseCommand):
|
|
@@ -11,9 +13,22 @@ class PastSurgicalHistoryCommand(BaseCommand):
|
|
|
11
13
|
class Meta:
|
|
12
14
|
key = "surgicalHistory"
|
|
13
15
|
|
|
14
|
-
past_surgical_history: str | None = None
|
|
16
|
+
past_surgical_history: str | Coding | None = None
|
|
15
17
|
approximate_date: date | None = None
|
|
16
18
|
comment: str | None = Field(max_length=1000, default=None)
|
|
17
19
|
|
|
20
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
21
|
+
errors = super()._get_error_details(method)
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
isinstance(self.past_surgical_history, dict)
|
|
25
|
+
and self.past_surgical_history["system"] != CodeSystems.SNOMED
|
|
26
|
+
and self.past_surgical_history["system"] != CodeSystems.UNSTRUCTURED
|
|
27
|
+
):
|
|
28
|
+
message = f"The 'coding.system' field must be '{CodeSystems.SNOMED}' or '{CodeSystems.UNSTRUCTURED}'."
|
|
29
|
+
errors.append(self._create_error_detail("value", message, self.past_surgical_history))
|
|
30
|
+
|
|
31
|
+
return errors
|
|
32
|
+
|
|
18
33
|
|
|
19
34
|
__exports__ = ("PastSurgicalHistoryCommand",)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from pydantic import Field
|
|
2
|
+
from pydantic_core import InitErrorDetails
|
|
2
3
|
|
|
3
4
|
from canvas_sdk.commands.base import _BaseCommand as BaseCommand
|
|
5
|
+
from canvas_sdk.commands.constants import CodeSystems, Coding
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class PerformCommand(BaseCommand):
|
|
@@ -9,8 +11,23 @@ class PerformCommand(BaseCommand):
|
|
|
9
11
|
class Meta:
|
|
10
12
|
key = "perform"
|
|
11
13
|
|
|
12
|
-
cpt_code: str | None = Field(
|
|
14
|
+
cpt_code: str | Coding | None = Field(
|
|
15
|
+
default=None, json_schema_extra={"commands_api_name": "perform"}
|
|
16
|
+
)
|
|
13
17
|
notes: str | None = None
|
|
14
18
|
|
|
19
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
20
|
+
errors = super()._get_error_details(method)
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
isinstance(self.cpt_code, dict)
|
|
24
|
+
and self.cpt_code["system"] != CodeSystems.CPT
|
|
25
|
+
and self.cpt_code["system"] != CodeSystems.UNSTRUCTURED
|
|
26
|
+
):
|
|
27
|
+
message = f"The 'coding.system' field must be '{CodeSystems.CPT}' or '{CodeSystems.UNSTRUCTURED}'."
|
|
28
|
+
errors.append(self._create_error_detail("value", message, self.cpt_code))
|
|
29
|
+
|
|
30
|
+
return errors
|
|
31
|
+
|
|
15
32
|
|
|
16
33
|
__exports__ = ("PerformCommand",)
|
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
from pydantic import Field, conlist
|
|
8
8
|
from pydantic_core import InitErrorDetails
|
|
9
9
|
|
|
10
|
-
from canvas_sdk.commands.base import _BaseCommand, _SendableCommandMixin
|
|
10
|
+
from canvas_sdk.commands.base import _BaseCommand, _ReviewableCommandMixin, _SendableCommandMixin
|
|
11
11
|
from canvas_sdk.commands.constants import ClinicalQuantity
|
|
12
12
|
from canvas_sdk.effects import Effect
|
|
13
13
|
from canvas_sdk.effects.compound_medications.compound_medication import (
|
|
@@ -37,7 +37,7 @@ class CompoundMedicationData:
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class PrescribeCommand(_SendableCommandMixin, _BaseCommand):
|
|
40
|
+
class PrescribeCommand(_ReviewableCommandMixin, _SendableCommandMixin, _BaseCommand):
|
|
41
41
|
"""A class for managing a Prescribe command within a specific note."""
|
|
42
42
|
|
|
43
43
|
class Meta:
|
|
@@ -74,12 +74,16 @@ class PrescribeCommand(_SendableCommandMixin, _BaseCommand):
|
|
|
74
74
|
default=None, json_schema_extra={"commands_api_name": "compound_medication_data"}
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
+
def _has_fdb_code(self) -> bool:
|
|
78
|
+
"""Check if fdb_code is provided and non-empty."""
|
|
79
|
+
return self.fdb_code is not None and self.fdb_code.strip() != ""
|
|
80
|
+
|
|
77
81
|
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
78
82
|
"""Add compound medication validation to the base validation."""
|
|
79
83
|
errors = super()._get_error_details(method)
|
|
80
84
|
|
|
81
85
|
# Validate that exactly one medication type is provided
|
|
82
|
-
has_fdb_code = self.
|
|
86
|
+
has_fdb_code = self._has_fdb_code()
|
|
83
87
|
has_compound_medication_id = (
|
|
84
88
|
self.compound_medication_id is not None and self.compound_medication_id.strip() != ""
|
|
85
89
|
)
|
|
@@ -156,10 +160,8 @@ class PrescribeCommand(_SendableCommandMixin, _BaseCommand):
|
|
|
156
160
|
str(Decimal(self.quantity_to_dispense)) if self.quantity_to_dispense else None
|
|
157
161
|
)
|
|
158
162
|
|
|
159
|
-
values["compound_medication_values"] = {}
|
|
160
|
-
|
|
161
163
|
if self.is_dirty("compound_medication_id") and self.compound_medication_id:
|
|
162
|
-
values["compound_medication_values"]
|
|
164
|
+
values["compound_medication_values"] = {"id": values.pop("compound_medication_id")}
|
|
163
165
|
|
|
164
166
|
# Handle compound medication data
|
|
165
167
|
elif (
|
|
@@ -170,9 +172,6 @@ class PrescribeCommand(_SendableCommandMixin, _BaseCommand):
|
|
|
170
172
|
if isinstance(compound_data, CompoundMedicationData):
|
|
171
173
|
values["compound_medication_values"] = compound_data.to_dict()
|
|
172
174
|
|
|
173
|
-
if values.get("fdb_code") is not None and values.get("compound_medication_values") == {}:
|
|
174
|
-
del values["compound_medication_values"]
|
|
175
|
-
|
|
176
175
|
return values
|
|
177
176
|
|
|
178
177
|
def originate(self, line_number: int = -1) -> Effect:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import cast
|
|
2
2
|
|
|
3
3
|
from django.db.models.expressions import Subquery
|
|
4
4
|
from pydantic_core import InitErrorDetails
|
|
@@ -13,13 +13,13 @@ class RefillCommand(PrescribeCommand):
|
|
|
13
13
|
class Meta:
|
|
14
14
|
key = "refill"
|
|
15
15
|
|
|
16
|
-
def _get_error_details(
|
|
17
|
-
self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error"]
|
|
18
|
-
) -> list[InitErrorDetails]:
|
|
16
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
19
17
|
errors = super()._get_error_details(method)
|
|
20
18
|
|
|
21
19
|
if self.fdb_code:
|
|
22
|
-
subquery = Subquery(
|
|
20
|
+
subquery = Subquery(
|
|
21
|
+
Note.objects.filter(id=cast(str, self.note_uuid)).values("patient_id")[:1]
|
|
22
|
+
)
|
|
23
23
|
if (
|
|
24
24
|
not Medication.objects.active()
|
|
25
25
|
.filter(codings__code=self.fdb_code, patient=subquery)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import cast
|
|
2
2
|
from uuid import UUID
|
|
3
3
|
|
|
4
4
|
from django.db.models.expressions import Subquery
|
|
@@ -21,13 +21,13 @@ class ResolveConditionCommand(BaseCommand):
|
|
|
21
21
|
show_in_condition_list: bool = False
|
|
22
22
|
rationale: str | None = Field(max_length=1024, default=None)
|
|
23
23
|
|
|
24
|
-
def _get_error_details(
|
|
25
|
-
self, method: Literal["originate", "edit", "delete", "commit", "enter_in_error"]
|
|
26
|
-
) -> list[InitErrorDetails]:
|
|
24
|
+
def _get_error_details(self, method: str) -> list[InitErrorDetails]:
|
|
27
25
|
errors = super()._get_error_details(method)
|
|
28
26
|
|
|
29
27
|
if self.condition_id:
|
|
30
|
-
subquery = Subquery(
|
|
28
|
+
subquery = Subquery(
|
|
29
|
+
Note.objects.filter(id=cast(str, self.note_uuid)).values("patient_id")[:1]
|
|
30
|
+
)
|
|
31
31
|
if (
|
|
32
32
|
not Condition.objects.active()
|
|
33
33
|
.filter(id=self.condition_id, patient=subquery)
|