canvas 0.5.0__py3-none-any.whl → 0.7.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.5.0.dist-info → canvas-0.7.0.dist-info}/METADATA +4 -3
- {canvas-0.5.0.dist-info → canvas-0.7.0.dist-info}/RECORD +32 -31
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +4 -0
- canvas_sdk/base.py +12 -9
- canvas_sdk/commands/base.py +2 -1
- canvas_sdk/effects/banner_alert/add_banner_alert.py +13 -2
- canvas_sdk/effects/banner_alert/remove_banner_alert.py +2 -2
- canvas_sdk/effects/banner_alert/tests.py +20 -4
- canvas_sdk/effects/base.py +2 -0
- canvas_sdk/effects/protocol_card/protocol_card.py +9 -2
- canvas_sdk/effects/protocol_card/tests.py +3 -3
- canvas_sdk/effects/task/task.py +99 -0
- canvas_sdk/protocols/base.py +1 -11
- canvas_sdk/protocols/clinical_quality_measure.py +57 -1
- canvas_sdk/protocols/timeframe.py +39 -0
- canvas_sdk/v1/data/__init__.py +3 -0
- canvas_sdk/v1/data/base.py +95 -12
- canvas_sdk/v1/data/billing.py +59 -0
- canvas_sdk/v1/data/common.py +58 -0
- canvas_sdk/v1/data/condition.py +18 -2
- canvas_sdk/v1/data/medication.py +11 -3
- canvas_sdk/v1/data/note.py +161 -0
- canvas_sdk/v1/data/patient.py +19 -1
- canvas_sdk/v1/data/protocol_override.py +1 -1
- canvas_sdk/v1/data/staff.py +65 -0
- canvas_sdk/v1/data/task.py +112 -0
- canvas_sdk/value_set/custom.py +984 -0
- canvas_sdk/value_set/value_set.py +4 -1
- plugin_runner/sandbox.py +3 -0
- canvas_sdk/data/__init__.py +0 -1
- canvas_sdk/data/base.py +0 -26
- canvas_sdk/data/client.py +0 -82
- canvas_sdk/data/patient.py +0 -8
- canvas_sdk/data/staff.py +0 -8
- canvas_sdk/data/task.py +0 -67
- {canvas-0.5.0.dist-info → canvas-0.7.0.dist-info}/WHEEL +0 -0
- {canvas-0.5.0.dist-info → canvas-0.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
from canvas_sdk.effects.base import EffectType, _BaseEffect
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TaskStatus(Enum):
|
|
9
|
+
"""TaskStatus."""
|
|
10
|
+
|
|
11
|
+
COMPLETED = "COMPLETED"
|
|
12
|
+
CLOSED = "CLOSED"
|
|
13
|
+
OPEN = "OPEN"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AddTask(_BaseEffect):
|
|
17
|
+
"""
|
|
18
|
+
An Effect that will create a Task in Canvas.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
effect_type = EffectType.CREATE_TASK
|
|
23
|
+
apply_required_fields = ("title",)
|
|
24
|
+
|
|
25
|
+
assignee_id: str | None = None
|
|
26
|
+
patient_id: str | None = None
|
|
27
|
+
title: str | None = None
|
|
28
|
+
due: datetime | None = None
|
|
29
|
+
status: TaskStatus = TaskStatus.OPEN
|
|
30
|
+
labels: list[str] = []
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def values(self) -> dict[str, Any]:
|
|
34
|
+
"""The values for Task addition."""
|
|
35
|
+
return {
|
|
36
|
+
"patient": {"id": self.patient_id},
|
|
37
|
+
"due": self.due.isoformat() if self.due else None,
|
|
38
|
+
"assignee": {"id": self.assignee_id},
|
|
39
|
+
"title": self.title,
|
|
40
|
+
"status": self.status.value,
|
|
41
|
+
"labels": self.labels,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AddTaskComment(_BaseEffect):
|
|
46
|
+
"""
|
|
47
|
+
An Effect that will create a Task Comment on a Task.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
class Meta:
|
|
51
|
+
effect_type = EffectType.CREATE_TASK_COMMENT
|
|
52
|
+
apply_required_fields = (
|
|
53
|
+
"body",
|
|
54
|
+
"task_id",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
body: str | None = None
|
|
58
|
+
task_id: str | None = None
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def values(self) -> dict[str, Any]:
|
|
62
|
+
"""The values for adding a task comment."""
|
|
63
|
+
return {"task": {"id": self.task_id}, "body": self.body}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class UpdateTask(_BaseEffect):
|
|
67
|
+
"""
|
|
68
|
+
An Effect that will update a Task in Canvas.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
class Meta:
|
|
72
|
+
effect_type = EffectType.UPDATE_TASK
|
|
73
|
+
apply_required_fields = ("id",)
|
|
74
|
+
|
|
75
|
+
id: str | None = None
|
|
76
|
+
assignee_id: str | None = None
|
|
77
|
+
patient_id: str | None = None
|
|
78
|
+
title: str | None = None
|
|
79
|
+
due: datetime | None = None
|
|
80
|
+
status: TaskStatus = TaskStatus.OPEN
|
|
81
|
+
labels: list[str] = []
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def values(self) -> dict[str, Any]:
|
|
85
|
+
"""The values for adding a task comment."""
|
|
86
|
+
value_dict: dict[str, Any] = {}
|
|
87
|
+
# Only add the fields that have been explicitly set on the model (exclude_unset=True).
|
|
88
|
+
# Oherwise, the effect interpreter will set values to null based on their defaults.
|
|
89
|
+
set_fields = self.model_dump(exclude_unset=True)
|
|
90
|
+
for field, val in set_fields.items():
|
|
91
|
+
if field.endswith("_id"):
|
|
92
|
+
value_dict[field.split("_")[0]] = {"id": val}
|
|
93
|
+
elif field == "due" and val is not None:
|
|
94
|
+
value_dict[field] = cast(datetime, val).isoformat()
|
|
95
|
+
elif field == "status":
|
|
96
|
+
value_dict[field] = cast(TaskStatus, val).value
|
|
97
|
+
else:
|
|
98
|
+
value_dict[field] = getattr(self, field)
|
|
99
|
+
return value_dict
|
canvas_sdk/protocols/base.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from canvas_sdk.data.client import GQL_CLIENT
|
|
4
3
|
from canvas_sdk.handlers.base import BaseHandler
|
|
5
4
|
|
|
6
5
|
|
|
@@ -9,13 +8,4 @@ class BaseProtocol(BaseHandler):
|
|
|
9
8
|
The class that protocols inherit from.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
return GQL_CLIENT.query(
|
|
14
|
-
query,
|
|
15
|
-
variables=variables,
|
|
16
|
-
extra_args={
|
|
17
|
-
"headers": {
|
|
18
|
-
"Authorization": f'Bearer {self.secrets["graphql_jwt"]}',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
)
|
|
11
|
+
pass
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any, cast
|
|
2
2
|
|
|
3
|
+
import arrow
|
|
4
|
+
from django.db.models import Model
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.events import EventType
|
|
3
7
|
from canvas_sdk.protocols.base import BaseProtocol
|
|
8
|
+
from canvas_sdk.protocols.timeframe import Timeframe
|
|
9
|
+
from canvas_sdk.v1.data.condition import Condition
|
|
10
|
+
from canvas_sdk.v1.data.medication import Medication
|
|
4
11
|
|
|
5
12
|
|
|
6
13
|
class ClinicalQualityMeasure(BaseProtocol):
|
|
@@ -23,6 +30,11 @@ class ClinicalQualityMeasure(BaseProtocol):
|
|
|
23
30
|
is_abstract: bool = False
|
|
24
31
|
is_predictive: bool = False
|
|
25
32
|
|
|
33
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
34
|
+
self._patient_id: str | None = None
|
|
35
|
+
self.now = arrow.utcnow()
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
|
|
26
38
|
@classmethod
|
|
27
39
|
def _meta(cls) -> dict[str, Any]:
|
|
28
40
|
"""
|
|
@@ -41,3 +53,47 @@ class ClinicalQualityMeasure(BaseProtocol):
|
|
|
41
53
|
External key used to identify the protocol.
|
|
42
54
|
"""
|
|
43
55
|
return cls.__name__
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def timeframe(self) -> Timeframe:
|
|
59
|
+
"""The default Timeframe (self.timeframe) for all protocols.
|
|
60
|
+
This defaults to have a start of 1 year ago and an end time of the current time.
|
|
61
|
+
Plugin authors can override this if a different timeframe is desired.
|
|
62
|
+
"""
|
|
63
|
+
end = self.now
|
|
64
|
+
return Timeframe(start=end.shift(years=-1), end=end)
|
|
65
|
+
|
|
66
|
+
# TODO: This approach should be considered against the alternative of just including the patient
|
|
67
|
+
# ID in the event context, given that so many events will be patient-centric.
|
|
68
|
+
def patient_id_from_target(self) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Get and return the patient ID from an event target.
|
|
71
|
+
|
|
72
|
+
This method will attempt to obtain the patient ID from the event target for supported event
|
|
73
|
+
types. It stores the patient ID on a member variable so that it can be referenced without
|
|
74
|
+
incurring more SQL queries.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def patient_id(model: type[Model]) -> str:
|
|
78
|
+
return cast(
|
|
79
|
+
str,
|
|
80
|
+
model._default_manager.select_related("patient")
|
|
81
|
+
.values_list("patient__id")
|
|
82
|
+
.get(id=self.event.target)[0],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if not self._patient_id:
|
|
86
|
+
# TODO: Add cases for ProtocolOverride
|
|
87
|
+
match self.event.type:
|
|
88
|
+
case EventType.CONDITION_CREATED | EventType.CONDITION_UPDATED:
|
|
89
|
+
self._patient_id = patient_id(Condition)
|
|
90
|
+
case (
|
|
91
|
+
EventType.MEDICATION_LIST_ITEM_CREATED | EventType.MEDICATION_LIST_ITEM_UPDATED
|
|
92
|
+
):
|
|
93
|
+
self._patient_id = patient_id(Medication)
|
|
94
|
+
case _:
|
|
95
|
+
raise AssertionError(
|
|
96
|
+
f"Event type {self.event.type} not supported by 'patient_id_from_event'"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return self._patient_id
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import arrow
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Timeframe:
|
|
5
|
+
"""A class representing a timeframe with a start and and end."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, start: arrow.Arrow, end: arrow.Arrow):
|
|
8
|
+
self.start = start
|
|
9
|
+
self.end = end
|
|
10
|
+
|
|
11
|
+
def __str__(self) -> str:
|
|
12
|
+
return f"<Timeframe start={self.start}, end={self.end}>"
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def duration(self) -> int:
|
|
16
|
+
"""Returns the number of days in the timeframe."""
|
|
17
|
+
return (self.end - self.start).days
|
|
18
|
+
|
|
19
|
+
def increased_by(self, years: int = 0, months: int = 0, days: int = 0) -> "Timeframe":
|
|
20
|
+
"""Returns a new Timeframe object increased by the years, months, days in the arguments."""
|
|
21
|
+
start = self.start
|
|
22
|
+
end = self.end
|
|
23
|
+
|
|
24
|
+
if years > 0:
|
|
25
|
+
end = end.shift(years=years)
|
|
26
|
+
elif years < 0:
|
|
27
|
+
start = start.shift(years=years)
|
|
28
|
+
|
|
29
|
+
if months > 0:
|
|
30
|
+
end = end.shift(months=months)
|
|
31
|
+
elif months < 0:
|
|
32
|
+
start = start.shift(months=months)
|
|
33
|
+
|
|
34
|
+
if days > 0:
|
|
35
|
+
end = end.shift(days=days)
|
|
36
|
+
elif days < 0:
|
|
37
|
+
start = start.shift(days=days)
|
|
38
|
+
|
|
39
|
+
return Timeframe(start=start, end=end)
|
canvas_sdk/v1/data/__init__.py
CHANGED
canvas_sdk/v1/data/base.py
CHANGED
|
@@ -1,35 +1,70 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
1
2
|
from collections.abc import Container
|
|
2
|
-
from typing import TYPE_CHECKING, Type, cast
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Protocol, Self, Type, cast
|
|
3
4
|
|
|
4
5
|
from django.db import models
|
|
5
6
|
from django.db.models import Q
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
9
|
+
from canvas_sdk.protocols.timeframe import Timeframe
|
|
8
10
|
from canvas_sdk.value_set.value_set import ValueSet
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class CommittableModelManager(models.Manager):
|
|
12
14
|
"""A manager for commands that can be committed."""
|
|
13
15
|
|
|
14
|
-
def get_queryset(self) -> "
|
|
16
|
+
def get_queryset(self) -> "CommittableQuerySet":
|
|
15
17
|
"""Return a queryset that filters out deleted objects."""
|
|
16
18
|
# TODO: Should we just filter these out at the view level?
|
|
17
|
-
return
|
|
19
|
+
return CommittableQuerySet(self.model, using=self._db).filter(deleted=False)
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
class CommittableQuerySet(models.QuerySet):
|
|
23
|
+
"""A queryset for committable objects."""
|
|
24
|
+
|
|
25
|
+
def committed(self) -> "Self":
|
|
20
26
|
"""Return a queryset that filters for objects that have been committed."""
|
|
21
|
-
# The committer_id IS set, and the entered_in_error_id IS NOT set
|
|
22
27
|
return self.filter(committer_id__isnull=False, entered_in_error_id__isnull=True)
|
|
23
28
|
|
|
24
|
-
def for_patient(self, patient_id: str) -> "
|
|
29
|
+
def for_patient(self, patient_id: str) -> "Self":
|
|
25
30
|
"""Return a queryset that filters objects for a specific patient."""
|
|
26
31
|
return self.filter(patient__id=patient_id)
|
|
27
32
|
|
|
28
33
|
|
|
29
|
-
class
|
|
30
|
-
"""A QuerySet
|
|
34
|
+
class BaseQuerySet(models.QuerySet):
|
|
35
|
+
"""A base QuerySet inherited from Django's model.Queryset."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class QuerySetProtocol(Protocol):
|
|
41
|
+
"""A typing protocol for use in mixins into models.QuerySet-inherited classes."""
|
|
31
42
|
|
|
32
|
-
def
|
|
43
|
+
def filter(self, *args: Any, **kwargs: Any) -> models.QuerySet[Any]:
|
|
44
|
+
"""Django's models.QuerySet filter method."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ValueSetLookupQuerySetProtocol(QuerySetProtocol):
|
|
49
|
+
"""A typing protocol for use in mixins using value set lookup methods."""
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]:
|
|
54
|
+
"""A protocol method for defining codings."""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def q_object(system: str, codes: Container[str]) -> Q:
|
|
60
|
+
"""A protocol method for defining Q objects for value set lookups."""
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ValueSetLookupQuerySetMixin(ValueSetLookupQuerySetProtocol):
|
|
65
|
+
"""A QuerySet mixin that can filter objects based on a ValueSet."""
|
|
66
|
+
|
|
67
|
+
def find(self, value_set: Type["ValueSet"]) -> models.QuerySet[Any]:
|
|
33
68
|
"""
|
|
34
69
|
Filters conditions, medications, etc. to those found in the inherited ValueSet class that is passed.
|
|
35
70
|
|
|
@@ -51,7 +86,7 @@ class ValueSetLookupQuerySet(models.QuerySet):
|
|
|
51
86
|
@staticmethod
|
|
52
87
|
def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]:
|
|
53
88
|
"""Provide a sequence of tuples where each tuple is a code system URL and a set of codes."""
|
|
54
|
-
values_dict = value_set.values
|
|
89
|
+
values_dict = cast(dict, value_set.values)
|
|
55
90
|
return cast(
|
|
56
91
|
tuple[tuple[str, set[str]]],
|
|
57
92
|
tuple(
|
|
@@ -69,7 +104,7 @@ class ValueSetLookupQuerySet(models.QuerySet):
|
|
|
69
104
|
return Q(codings__system=system, codings__code__in=codes)
|
|
70
105
|
|
|
71
106
|
|
|
72
|
-
class
|
|
107
|
+
class ValueSetLookupByNameQuerySetMixin(ValueSetLookupQuerySetMixin):
|
|
73
108
|
"""
|
|
74
109
|
QuerySet for ValueSet lookups using code system name rather than URL.
|
|
75
110
|
|
|
@@ -82,7 +117,7 @@ class ValueSetLookupByNameQuerySet(ValueSetLookupQuerySet):
|
|
|
82
117
|
"""
|
|
83
118
|
Provide a sequence of tuples where each tuple is a code system name and a set of codes.
|
|
84
119
|
"""
|
|
85
|
-
values_dict = value_set.values
|
|
120
|
+
values_dict = cast(dict, value_set.values)
|
|
86
121
|
return cast(
|
|
87
122
|
tuple[tuple[str, set[str]]],
|
|
88
123
|
tuple(
|
|
@@ -91,3 +126,51 @@ class ValueSetLookupByNameQuerySet(ValueSetLookupQuerySet):
|
|
|
91
126
|
if i[0] in values_dict
|
|
92
127
|
),
|
|
93
128
|
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TimeframeLookupQuerySetProtocol(QuerySetProtocol):
|
|
132
|
+
"""A typing protocol for use in TimeframeLookupQuerySetMixin."""
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def timeframe_filter_field(self) -> str:
|
|
137
|
+
"""A protocol method for timeframe_filter_field."""
|
|
138
|
+
raise NotImplementedError
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TimeframeLookupQuerySetMixin(TimeframeLookupQuerySetProtocol):
|
|
142
|
+
"""A class that adds queryset functionality to filter using timeframes."""
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def timeframe_filter_field(self) -> str:
|
|
146
|
+
"""Returns the field that should be filtered on. Can be overridden for different models."""
|
|
147
|
+
return "note__datetime_of_service"
|
|
148
|
+
|
|
149
|
+
def within(self, timeframe: "Timeframe") -> models.QuerySet:
|
|
150
|
+
"""A method to filter a queryset for datetimes within a timeframe."""
|
|
151
|
+
return self.filter(
|
|
152
|
+
**{
|
|
153
|
+
f"{self.timeframe_filter_field}__range": (
|
|
154
|
+
timeframe.start.datetime,
|
|
155
|
+
timeframe.end.datetime,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class ValueSetLookupQuerySet(BaseQuerySet, ValueSetLookupQuerySetMixin):
|
|
162
|
+
"""A class that includes methods for looking up value sets."""
|
|
163
|
+
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ValueSetLookupByNameQuerySet(BaseQuerySet, ValueSetLookupByNameQuerySetMixin):
|
|
168
|
+
"""A class that includes methods for looking up value sets by name."""
|
|
169
|
+
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ValueSetTimeframeLookupQuerySet(ValueSetLookupQuerySet, TimeframeLookupQuerySetMixin):
|
|
174
|
+
"""A class that includes methods for looking up value sets and using timeframes."""
|
|
175
|
+
|
|
176
|
+
pass
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Type
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
from canvas_sdk.v1.data.base import ValueSetTimeframeLookupQuerySet
|
|
6
|
+
from canvas_sdk.v1.data.note import Note
|
|
7
|
+
from canvas_sdk.v1.data.patient import Patient
|
|
8
|
+
from canvas_sdk.value_set.value_set import CodeConstants
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from canvas_sdk.value_set.value_set import ValueSet
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BillingLineItemQuerySet(ValueSetTimeframeLookupQuerySet):
|
|
15
|
+
"""A class that adds functionality to filter BillingLineItem objects."""
|
|
16
|
+
|
|
17
|
+
def find(self, value_set: Type["ValueSet"]) -> models.QuerySet:
|
|
18
|
+
"""
|
|
19
|
+
This method is overridden to use for BillingLineItem CPT codes.
|
|
20
|
+
The codes are saved as string values in the BillingLineItem.cpt field,
|
|
21
|
+
which differs from other coding models.
|
|
22
|
+
"""
|
|
23
|
+
values_dict = value_set.values
|
|
24
|
+
return self.filter(cpt__in=values_dict.get(CodeConstants.HCPCS, []))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BillingLineItemStatus(models.TextChoices):
|
|
28
|
+
"""Billing line item status."""
|
|
29
|
+
|
|
30
|
+
ACTIVE = "active", "Active"
|
|
31
|
+
REMOVED = "removed", "Removed"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BillingLineItem(models.Model):
|
|
35
|
+
"""BillingLineItem."""
|
|
36
|
+
|
|
37
|
+
class Meta:
|
|
38
|
+
managed = False
|
|
39
|
+
app_label = "canvas_sdk"
|
|
40
|
+
db_table = "canvas_sdk_data_api_billinglineitem_001"
|
|
41
|
+
|
|
42
|
+
# objects = BillingLineItemQuerySet.as_manager()
|
|
43
|
+
objects = models.Manager().from_queryset(BillingLineItemQuerySet)()
|
|
44
|
+
|
|
45
|
+
id = models.UUIDField()
|
|
46
|
+
dbid = models.BigIntegerField(primary_key=True)
|
|
47
|
+
created = models.DateTimeField()
|
|
48
|
+
modified = models.DateTimeField()
|
|
49
|
+
note = models.ForeignKey(Note, on_delete=models.DO_NOTHING, related_name="billing_line_items")
|
|
50
|
+
patient = models.ForeignKey(
|
|
51
|
+
Patient, on_delete=models.DO_NOTHING, related_name="billing_line_items"
|
|
52
|
+
)
|
|
53
|
+
cpt = models.CharField()
|
|
54
|
+
charge = models.DecimalField()
|
|
55
|
+
description = models.CharField()
|
|
56
|
+
units = models.IntegerField()
|
|
57
|
+
command_type = models.CharField()
|
|
58
|
+
command_id = models.IntegerField()
|
|
59
|
+
status = models.CharField(choices=BillingLineItemStatus.choices)
|
canvas_sdk/v1/data/common.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from django.contrib.postgres.fields import ArrayField
|
|
1
2
|
from django.db import models
|
|
2
3
|
|
|
3
4
|
|
|
@@ -44,3 +45,60 @@ class ReviewStatus(models.TextChoices):
|
|
|
44
45
|
|
|
45
46
|
STATUS_REVIEWING = "reviewing", "reviewing"
|
|
46
47
|
STATUS_REVIEWED = "reviewed", "reviewed"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PersonSex(models.TextChoices):
|
|
51
|
+
"""Status choices for individual sex."""
|
|
52
|
+
|
|
53
|
+
SEX_FEMALE = "F", "female"
|
|
54
|
+
SEX_MALE = "M", "male"
|
|
55
|
+
SEX_OTHER = "O", "other"
|
|
56
|
+
SEX_UNKNOWN = "UNK", "unknown"
|
|
57
|
+
SEX_BLANK = "", ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TaxIDType(models.TextChoices):
|
|
61
|
+
"""Choices for Tax IDs."""
|
|
62
|
+
|
|
63
|
+
EIN = "E", "EIN text"
|
|
64
|
+
SSN = "S", "SSN"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ColorEnum(models.TextChoices):
|
|
68
|
+
"""Choices for colors."""
|
|
69
|
+
|
|
70
|
+
RED = "red", "Red"
|
|
71
|
+
ORANGE = "orange", "Orange"
|
|
72
|
+
YELLOW = "yellow", "Yellow"
|
|
73
|
+
OLIVE = "olive", "Olive"
|
|
74
|
+
GREEN = "green", "Green"
|
|
75
|
+
TEAL = "teal", "Teal"
|
|
76
|
+
BLUE = "blue", "Blue"
|
|
77
|
+
VIOLET = "violet", "Violet"
|
|
78
|
+
PURPLE = "purple", "Purple"
|
|
79
|
+
PINK = "pink", "Pink"
|
|
80
|
+
BROWN = "brown", "Brown"
|
|
81
|
+
GREY = "grey", "Grey"
|
|
82
|
+
BLACK = "black", "Black"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Origin(models.TextChoices):
|
|
86
|
+
"""Choices for origins."""
|
|
87
|
+
|
|
88
|
+
REFERAL = ("REF_CMD", "Referral command")
|
|
89
|
+
COMPLETING_IMAGE_ORDERS = ("CMP_IMG_ORD", "Completing image orders")
|
|
90
|
+
IMAGING_REPORT_REVIEW = ("IMG_REP_REV", "Imaging report review")
|
|
91
|
+
LAB_RESULTS_REVIEW = ("LAB_RES_REV", "Lab results review")
|
|
92
|
+
CONSULT_REPORT_REVIEW = ("CON_REP_REV", "Consult report review")
|
|
93
|
+
UNCATEGORIZED_DOCUMENT_REPORT_REVIEW = (
|
|
94
|
+
"UNC_DOC_REP_REV",
|
|
95
|
+
"Uncategorized document report review",
|
|
96
|
+
)
|
|
97
|
+
ASSIGNED_NOTE_PHONE_CALL_FOR_REVIEW = ("ASN_NOT_PHN_REV", "Assigned note/phone call for review")
|
|
98
|
+
POPULATION_HEALTH_OUTREACH = ("POP_HLT_OUT", "Population health outreach")
|
|
99
|
+
COMPLETING_LAB_ORDERS = ("CMP_LAB_ORD", "Completing lab orders")
|
|
100
|
+
CHART_PDF = ("CHT_PDF", "Chart PDF")
|
|
101
|
+
EXPIRED_CLAIM_SNOOZED = ("EXP_CLM_SNO", "Expired claim snoozed")
|
|
102
|
+
FLAGGED_POSTING_REVIEW = ("FLG_PST_REV", "Flagged posting review")
|
|
103
|
+
BATCH_PATIENT_STATEMENTS = ("BAT_PTN_STA", "Batch patient statements")
|
|
104
|
+
INCOMPLETE_COVERAGE = ("INC_COV", "Incomplete Coverage")
|
canvas_sdk/v1/data/condition.py
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
from django.db import models
|
|
2
|
+
from django.db.models import TextChoices
|
|
2
3
|
|
|
3
|
-
from canvas_sdk.v1.data.base import
|
|
4
|
+
from canvas_sdk.v1.data.base import (
|
|
5
|
+
CommittableModelManager,
|
|
6
|
+
CommittableQuerySet,
|
|
7
|
+
ValueSetLookupQuerySet,
|
|
8
|
+
)
|
|
4
9
|
from canvas_sdk.v1.data.patient import Patient
|
|
5
10
|
from canvas_sdk.v1.data.user import CanvasUser
|
|
6
11
|
|
|
7
12
|
|
|
13
|
+
class ClinicalStatus(TextChoices):
|
|
14
|
+
"""Condition clinical status."""
|
|
15
|
+
|
|
16
|
+
ACTIVE = "active", "active"
|
|
17
|
+
RELAPSE = "relapse", "relapse"
|
|
18
|
+
REMISSION = "remission", "remission"
|
|
19
|
+
RESOLVED = "resolved", "resolved"
|
|
20
|
+
INVESTIGATIVE = "investigative", "investigative"
|
|
21
|
+
|
|
22
|
+
|
|
8
23
|
class ConditionQuerySet(ValueSetLookupQuerySet):
|
|
9
24
|
"""ConditionQuerySet."""
|
|
10
25
|
|
|
@@ -19,12 +34,13 @@ class Condition(models.Model):
|
|
|
19
34
|
app_label = "canvas_sdk"
|
|
20
35
|
db_table = "canvas_sdk_data_api_condition_001"
|
|
21
36
|
|
|
22
|
-
objects =
|
|
37
|
+
objects = ConditionQuerySet.as_manager()
|
|
23
38
|
|
|
24
39
|
id = models.UUIDField()
|
|
25
40
|
dbid = models.BigIntegerField(primary_key=True)
|
|
26
41
|
onset_date = models.DateField()
|
|
27
42
|
resolution_date = models.DateField()
|
|
43
|
+
clinical_status = models.CharField(choices=ClinicalStatus.choices)
|
|
28
44
|
deleted = models.BooleanField()
|
|
29
45
|
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
30
46
|
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
canvas_sdk/v1/data/medication.py
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
from django.db import models
|
|
2
|
+
from django.db.models import TextChoices
|
|
2
3
|
|
|
3
4
|
from canvas_sdk.v1.data.base import CommittableModelManager, ValueSetLookupQuerySet
|
|
4
5
|
from canvas_sdk.v1.data.patient import Patient
|
|
5
6
|
from canvas_sdk.v1.data.user import CanvasUser
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class Status(TextChoices):
|
|
10
|
+
"""Medication status."""
|
|
11
|
+
|
|
12
|
+
ACTIVE = "active", "active"
|
|
13
|
+
INACTIVE = "inactive", "inactive"
|
|
14
|
+
|
|
15
|
+
|
|
8
16
|
class MedicationQuerySet(ValueSetLookupQuerySet):
|
|
9
17
|
"""MedicationQuerySet."""
|
|
10
18
|
|
|
@@ -27,9 +35,9 @@ class Medication(models.Model):
|
|
|
27
35
|
deleted = models.BooleanField()
|
|
28
36
|
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
29
37
|
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
|
|
30
|
-
status = models.CharField()
|
|
31
|
-
start_date = models.
|
|
32
|
-
end_date = models.
|
|
38
|
+
status = models.CharField(choices=Status.choices)
|
|
39
|
+
start_date = models.DateTimeField()
|
|
40
|
+
end_date = models.DateTimeField()
|
|
33
41
|
quantity_qualifier_description = models.CharField()
|
|
34
42
|
clinical_quantity_description = models.CharField()
|
|
35
43
|
potency_unit_code = models.CharField()
|