clinicedc 2.0.28__py3-none-any.whl → 2.0.30__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 clinicedc might be problematic. Click here for more details.
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.30.dist-info}/METADATA +2 -2
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.30.dist-info}/RECORD +98 -98
- edc_auth/admin/fieldsets.py +11 -15
- edc_auth/admin/group_admin.py +2 -5
- edc_auth/admin/list_filters.py +8 -7
- edc_auth/admin/role_admin.py +7 -10
- edc_auth/admin/user_admin.py +2 -2
- edc_auth/admin/user_profile_admin.py +3 -7
- edc_auth/apps.py +2 -2
- edc_auth/auth_updater/group_updater.py +4 -4
- edc_auth/auth_updater/role_updater.py +1 -3
- edc_auth/backends.py +1 -1
- edc_auth/export_users.py +6 -5
- edc_auth/fix_export_permissions.py +8 -8
- edc_auth/forms.py +21 -24
- edc_auth/get_app_codenames.py +2 -2
- edc_auth/import_users.py +25 -24
- edc_auth/management/commands/export_users.py +1 -1
- edc_auth/management/commands/fix_export_permissions.py +1 -1
- edc_auth/management/commands/import_users.py +1 -1
- edc_auth/management/commands/reset_password.py +2 -2
- edc_auth/models/signals.py +1 -1
- edc_auth/models/user_profile.py +4 -5
- edc_auth/password_setter.py +4 -4
- edc_auth/post_migrate_signals.py +2 -2
- edc_auth/send_new_credentials_to_user.py +1 -1
- edc_auth/system_checks.py +7 -6
- edc_auth/utils.py +10 -12
- edc_consent/consent_definition.py +4 -1
- edc_dashboard/view_mixins/edc_view_mixin.py +1 -1
- edc_model_admin/mixins/model_admin_form_instructions_mixin.py +9 -3
- edc_model_admin/mixins/model_admin_next_url_redirect_mixin.py +3 -3
- edc_model_admin/mixins/model_admin_protect_pii_mixin.py +7 -7
- edc_model_admin/mixins/model_admin_redirect_on_delete_mixin.py +6 -8
- edc_pharmacy/admin_mixin.py +1 -1
- edc_pharmacy/approve_prescription.py +10 -8
- edc_pharmacy/auth_objects.py +1 -1
- edc_pharmacy/exceptions.py +7 -7
- edc_pharmacy/settings.py +1 -1
- edc_pharmacy/views/add_to_storage_bin_view.py +22 -24
- edc_pharmacy/views/allocate_to_subject_view.py +10 -18
- edc_pharmacy/views/celery_task_status_view.py +1 -2
- edc_pharmacy/views/confirm_stock_from_instance_view.py +1 -1
- edc_pharmacy/views/confirm_stock_from_queryset_view.py +5 -8
- edc_pharmacy/views/dispense_view.py +1 -1
- edc_pharmacy/views/move_to_storage_bin_view.py +7 -3
- edc_pharmacy/views/prepare_and_review_stock_request_view.py +62 -70
- edc_pharmacy/views/print_labels_view.py +1 -1
- edc_pharmacy/views/transfer_stock_view.py +1 -1
- edc_prn/modelform_mixins.py +8 -9
- edc_prn/models.py +3 -1
- edc_prn/site_prn_forms.py +2 -2
- edc_prn/templatetags/edc_prn_extras.py +1 -1
- edc_protocol/middleware.py +1 -1
- edc_protocol/research_protocol_config.py +7 -5
- edc_protocol_incident/admin/protocol_deviation_violation_admin.py +1 -1
- edc_protocol_incident/form_validators/mixins.py +9 -6
- edc_protocol_incident/modeladmin_mixins.py +1 -1
- edc_pylabels/admin/label_configuration_admin.py +1 -1
- edc_pylabels/admin/label_specification_admin.py +1 -1
- edc_pylabels/auth_objects.py +1 -1
- edc_pylabels/site_label_configs.py +2 -2
- edc_qareports/admin/qa_report_log_admin.py +5 -5
- edc_qareports/admin/qa_report_log_summary_admin.py +1 -1
- edc_qareports/forms/note_form.py +2 -3
- edc_qareports/modeladmin_mixins/list_filters.py +23 -19
- edc_qareports/modeladmin_mixins/note_modeladmin_mixin.py +7 -7
- edc_qareports/modeladmin_mixins/on_study_missing_values_modeladmin_mixin.py +8 -8
- edc_qareports/modeladmin_mixins/qa_report_modeladmin_mixin.py +3 -7
- edc_qareports/sql_generator/crf_case.py +1 -1
- edc_qareports/sql_generator/crf_subquery.py +1 -1
- edc_qareports/sql_generator/requisition_subquery.py +1 -1
- edc_qareports/sql_generator/sql_view_generator.py +1 -1
- edc_qareports/sql_generator/subquery_from_dict.py +39 -39
- edc_qareports/utils.py +12 -13
- edc_randomization/model_mixins.py +1 -1
- edc_randomization/system_checks.py +1 -1
- edc_refusal/admin.py +4 -2
- edc_registration/modeladmin_mixins.py +18 -20
- edc_registration/modelform_mixins.py +2 -2
- edc_registration/models/signals.py +1 -1
- edc_registration/utils.py +1 -1
- edc_reportable/age_evaluator.py +4 -4
- edc_reportable/data/grading_data/daids_july_2017.py +3 -3
- edc_reportable/evaluator.py +15 -15
- edc_reportable/exceptions.py +4 -4
- edc_reportable/forms/reportables_form_validator_mixin.py +6 -2
- edc_reportable/management/commands/export_reportables.py +1 -1
- edc_reportable/models/grading_exception.py +1 -6
- edc_reportable/models/normal_data.py +3 -3
- edc_reportable/models/reference_range_collection.py +1 -6
- edc_reportable/utils/get_grade_for_value.py +15 -15
- edc_reportable/utils/get_normal_data_or_raise.py +14 -14
- edc_reportable/utils/in_normal_bounds_or_raise.py +6 -6
- edc_reportable/utils/load_data.py +1 -1
- edc_reportable/utils/update_grading_exceptions.py +5 -5
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.30.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.30.dist-info}/licenses/LICENSE +0 -0
|
@@ -27,7 +27,7 @@ class RequisitionSubquery(CrfSubquery):
|
|
|
27
27
|
template: str = field(
|
|
28
28
|
init=False,
|
|
29
29
|
default=Template(
|
|
30
|
-
"select req.subject_identifier, req.id as original_id, " # nosec B608
|
|
30
|
+
"select req.subject_identifier, req.id as original_id, " # nosec B608 # noqa: S608
|
|
31
31
|
"req.subject_visit_id, req.report_datetime, req.site_id, v.visit_code, "
|
|
32
32
|
"v.visit_code_sequence, "
|
|
33
33
|
"v.schedule_name, req.modified, '${label_lower}' as label_lower, "
|
|
@@ -30,7 +30,7 @@ class SqlViewGenerator:
|
|
|
30
30
|
self.footer = f") as A ORDER BY {self.order_by}"
|
|
31
31
|
|
|
32
32
|
@staticmethod
|
|
33
|
-
def transpile(sql: str, read: str | None = None, write: str = None) -> str:
|
|
33
|
+
def transpile(sql: str, read: str | None = None, write: str | None = None) -> str:
|
|
34
34
|
read = read or "mysql"
|
|
35
35
|
sql = sql.replace(";", "")
|
|
36
36
|
return sqlglot.transpile(sql, read=read, write=write)[0]
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
from .subquery import Subquery
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def subquery_from_dict(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
) -> str | list:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
# from __future__ import annotations
|
|
2
|
+
#
|
|
3
|
+
# from typing import TYPE_CHECKING
|
|
4
|
+
#
|
|
5
|
+
# from .subquery import Subquery
|
|
6
|
+
#
|
|
7
|
+
# if TYPE_CHECKING:
|
|
8
|
+
# from .qa_case import QaCase
|
|
9
|
+
#
|
|
10
|
+
#
|
|
11
|
+
# def subquery_from_dict(
|
|
12
|
+
# cases: list[dict[str:str, str:str, str:str] | QaCase],
|
|
13
|
+
# as_list: bool | None = False,
|
|
14
|
+
# ) -> str | list:
|
|
15
|
+
# """Returns an SQL select statement as a union of the select
|
|
16
|
+
# statements of each case.
|
|
17
|
+
#
|
|
18
|
+
# args:
|
|
19
|
+
# cases = [{
|
|
20
|
+
# "label_lower": "my_app.hivhistory",
|
|
21
|
+
# "dbtable": "my_app_hivhistory",
|
|
22
|
+
# "field": "hiv_init_date",
|
|
23
|
+
# "label": "missing HIV initiation date",
|
|
24
|
+
# "list_tables": [(list_field, list_dbtable, alias), ...],
|
|
25
|
+
# }, ...]
|
|
26
|
+
#
|
|
27
|
+
# Note: `list_field` is the CRF id field, for example:
|
|
28
|
+
# left join <list_dbtable> as <alias> on crf.<list_field>=<alias>.id
|
|
29
|
+
# """
|
|
30
|
+
# subqueries = []
|
|
31
|
+
# for case in cases:
|
|
32
|
+
# try:
|
|
33
|
+
# subquery = case.sql
|
|
34
|
+
# except AttributeError:
|
|
35
|
+
# subquery = Subquery(**case).sql
|
|
36
|
+
# subqueries.append(subquery)
|
|
37
|
+
# if as_list:
|
|
38
|
+
# return subqueries
|
|
39
|
+
# return " UNION ".join(subqueries)
|
edc_qareports/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import sys
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from warnings import warn
|
|
@@ -27,10 +28,10 @@ def read_unmanaged_model_sql(
|
|
|
27
28
|
parsed_sql = []
|
|
28
29
|
with fullpath.open("r") as f:
|
|
29
30
|
for line in f:
|
|
30
|
-
line = line.split("#", maxsplit=1)[0]
|
|
31
|
-
line = line.split("-- ", maxsplit=1)[0]
|
|
32
|
-
line = line.replace("\n", "")
|
|
33
|
-
line = line.strip()
|
|
31
|
+
line = line.split("#", maxsplit=1)[0] # noqa: PLW2901
|
|
32
|
+
line = line.split("-- ", maxsplit=1)[0] # noqa: PLW2901
|
|
33
|
+
line = line.replace("\n", "") # noqa: PLW2901
|
|
34
|
+
line = line.strip() # noqa: PLW2901
|
|
34
35
|
if line:
|
|
35
36
|
parsed_sql.append(line)
|
|
36
37
|
|
|
@@ -88,17 +89,15 @@ def recreate_db_view(model_cls, drop: bool | None = None, verbose: bool | None =
|
|
|
88
89
|
except AttributeError as e:
|
|
89
90
|
raise AttributeError(
|
|
90
91
|
f"Is this model linked to a view? Declare model with `DBView`. Got {e}"
|
|
91
|
-
)
|
|
92
|
+
) from e
|
|
92
93
|
else:
|
|
93
94
|
sql = sql.replace(";", "")
|
|
94
95
|
if verbose:
|
|
95
|
-
|
|
96
|
+
sys.stdout.write(f"create view {model_cls._meta.db_table} as {sql};\n")
|
|
96
97
|
with connection.cursor() as c:
|
|
97
98
|
if drop:
|
|
98
|
-
|
|
99
|
+
with contextlib.suppress(OperationalError):
|
|
99
100
|
c.execute(f"drop view {model_cls._meta.db_table};")
|
|
100
|
-
except OperationalError:
|
|
101
|
-
pass
|
|
102
101
|
c.execute(f"create view {model_cls._meta.db_table} as {sql};")
|
|
103
102
|
if verbose:
|
|
104
103
|
sys.stdout.write(
|
|
@@ -107,14 +106,14 @@ def recreate_db_view(model_cls, drop: bool | None = None, verbose: bool | None =
|
|
|
107
106
|
|
|
108
107
|
|
|
109
108
|
def recreate_dbview_for_all():
|
|
110
|
-
from .model_mixins import QaReportModelMixin
|
|
109
|
+
from .model_mixins import QaReportModelMixin # noqa: PLC0415
|
|
111
110
|
|
|
112
111
|
for model_cls in django_apps.get_models():
|
|
113
112
|
if issubclass(model_cls, (QaReportModelMixin,)):
|
|
114
|
-
|
|
113
|
+
sys.stdout.write(f"{model_cls}\n")
|
|
115
114
|
try:
|
|
116
115
|
model_cls.recreate_db_view()
|
|
117
116
|
except AttributeError as e:
|
|
118
|
-
|
|
117
|
+
sys.stdout.write(f"{e}\n")
|
|
119
118
|
except TypeError as e:
|
|
120
|
-
|
|
119
|
+
sys.stdout.write(f"{e}\n")
|
|
@@ -71,7 +71,7 @@ class RandomizationListModelMixin(models.Model):
|
|
|
71
71
|
def save(self, *args, **kwargs):
|
|
72
72
|
self.randomizer_name = self.randomizer_cls.name
|
|
73
73
|
try:
|
|
74
|
-
self.assignment_description
|
|
74
|
+
self.assignment_description # noqa: B018
|
|
75
75
|
except RandomizationError as e:
|
|
76
76
|
raise RandomizationListModelError(e) from e
|
|
77
77
|
try:
|
|
@@ -57,7 +57,7 @@ def randomizationlist_check(app_configs, **kwargs) -> list:
|
|
|
57
57
|
):
|
|
58
58
|
error_msgs = randomizer.verify_list()
|
|
59
59
|
for error_msg in error_msgs:
|
|
60
|
-
errors.append(error.cls(error_msg, hint=None, obj=None, id=error.id))
|
|
60
|
+
errors.append(error.cls(error_msg, hint=None, obj=None, id=error.id)) # noqa: PERF401
|
|
61
61
|
if not settings.DEBUG:
|
|
62
62
|
if not randomizer.get_randomizationlist_path().is_relative_to(settings.ETC_DIR):
|
|
63
63
|
errors.append(
|
edc_refusal/admin.py
CHANGED
|
@@ -41,7 +41,7 @@ class SubjectRefusalModelAdminMixin:
|
|
|
41
41
|
|
|
42
42
|
search_fields = ("screening_identifier",)
|
|
43
43
|
|
|
44
|
-
radio_fields = {"reason": admin.VERTICAL}
|
|
44
|
+
radio_fields = {"reason": admin.VERTICAL} # noqa: RUF012
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
@admin.register(SubjectRefusal, site=edc_refusal_admin)
|
|
@@ -70,5 +70,7 @@ class SubjectRefusalAdmin(
|
|
|
70
70
|
if callable(super().view_on_site):
|
|
71
71
|
url = super().view_on_site(obj)
|
|
72
72
|
else:
|
|
73
|
-
raise NoReverseMatch(
|
|
73
|
+
raise NoReverseMatch(
|
|
74
|
+
f"{e}. See subject_dashboard_url_name for {self!r}."
|
|
75
|
+
) from e
|
|
74
76
|
return url
|
|
@@ -10,7 +10,7 @@ class RegisteredSubjectModelAdminMixin(ModelAdminSubjectDashboardMixin, admin.Mo
|
|
|
10
10
|
|
|
11
11
|
date_hierarchy = "registration_datetime"
|
|
12
12
|
|
|
13
|
-
instructions =
|
|
13
|
+
instructions = ()
|
|
14
14
|
|
|
15
15
|
@staticmethod
|
|
16
16
|
def show_pii(request) -> bool:
|
|
@@ -29,25 +29,23 @@ class RegisteredSubjectModelAdminMixin(ModelAdminSubjectDashboardMixin, admin.Mo
|
|
|
29
29
|
def get_readonly_fields(self, request, obj=None) -> tuple[str, ...]:
|
|
30
30
|
readonly_fields = super().get_readonly_fields(request, obj=obj)
|
|
31
31
|
return (
|
|
32
|
-
readonly_fields
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
+ audit_fields
|
|
32
|
+
*readonly_fields,
|
|
33
|
+
"subject_identifier",
|
|
34
|
+
"sid",
|
|
35
|
+
"first_name",
|
|
36
|
+
"last_name",
|
|
37
|
+
"initials",
|
|
38
|
+
"dob",
|
|
39
|
+
"gender",
|
|
40
|
+
"subject_type",
|
|
41
|
+
"registration_status",
|
|
42
|
+
"identity",
|
|
43
|
+
"screening_identifier",
|
|
44
|
+
"screening_datetime",
|
|
45
|
+
"registration_datetime",
|
|
46
|
+
"randomization_datetime",
|
|
47
|
+
"consent_datetime",
|
|
48
|
+
*audit_fields,
|
|
51
49
|
)
|
|
52
50
|
|
|
53
51
|
def get_list_display(self, request):
|
|
@@ -21,6 +21,6 @@ class ModelFormSubjectIdentifierMixin(SiteModelFormMixin):
|
|
|
21
21
|
get_registered_subject_model_cls().objects.get(
|
|
22
22
|
subject_identifier=subject_identifier
|
|
23
23
|
)
|
|
24
|
-
except ObjectDoesNotExist:
|
|
25
|
-
raise forms.ValidationError({"subject_identifier": "Invalid."})
|
|
24
|
+
except ObjectDoesNotExist as e:
|
|
25
|
+
raise forms.ValidationError({"subject_identifier": "Invalid."}) from e
|
|
26
26
|
return cleaned_data
|
edc_registration/utils.py
CHANGED
edc_reportable/age_evaluator.py
CHANGED
|
@@ -13,7 +13,7 @@ class AgeEvaluator(Evaluator):
|
|
|
13
13
|
def __init__(
|
|
14
14
|
self,
|
|
15
15
|
age_lower: int | None = None,
|
|
16
|
-
age_upper: int = None,
|
|
16
|
+
age_upper: int | None = None,
|
|
17
17
|
age_units: str | None = None,
|
|
18
18
|
age_lower_inclusive: bool | None = None,
|
|
19
19
|
age_upper_inclusive: bool | None = None,
|
|
@@ -29,15 +29,15 @@ class AgeEvaluator(Evaluator):
|
|
|
29
29
|
def __repr__(self) -> str:
|
|
30
30
|
return f"{self.__class__.__name__}({self.description(show_as_int=True)})"
|
|
31
31
|
|
|
32
|
-
def description(self, value: int = None, **kwargs):
|
|
32
|
+
def description(self, value: int | None = None, **kwargs):
|
|
33
33
|
kwargs["show_as_int"] = True
|
|
34
34
|
kwargs["placeholder"] = "AGE"
|
|
35
35
|
return super().description(value=value, **kwargs)
|
|
36
36
|
|
|
37
37
|
def in_bounds_or_raise(
|
|
38
38
|
self,
|
|
39
|
-
dob: date = None,
|
|
40
|
-
report_datetime: datetime = None,
|
|
39
|
+
dob: date | None = None,
|
|
40
|
+
report_datetime: datetime | None = None,
|
|
41
41
|
age_units: str | None = None,
|
|
42
42
|
) -> bool:
|
|
43
43
|
report_datetime = report_datetime or timezone.now()
|
|
@@ -5,9 +5,9 @@ Creatinine Clearance14 or eGFR, Low
|
|
|
5
5
|
*Report only one
|
|
6
6
|
NA
|
|
7
7
|
G1 N/A
|
|
8
|
-
G2 < 90 to 60 ml/min or ml/min/1.73 m2 OR 10 to < 30% decrease from participant
|
|
9
|
-
G3 < 60 to 30 ml/min or ml/min/1.73 m2 OR 30 to < 50% decrease from participant
|
|
10
|
-
G4 < 30 ml/min or ml/min/1.73 m2 OR ≥ 50% decrease from participant
|
|
8
|
+
G2 < 90 to 60 ml/min or ml/min/1.73 m2 OR 10 to < 30% decrease from participant's baseline
|
|
9
|
+
G3 < 60 to 30 ml/min or ml/min/1.73 m2 OR 30 to < 50% decrease from participant's baseline
|
|
10
|
+
G4 < 30 ml/min or ml/min/1.73 m2 OR ≥ 50% decrease from participant's baseline
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from edc_constants.constants import FEMALE, MALE
|
edc_reportable/evaluator.py
CHANGED
|
@@ -6,40 +6,40 @@ from .constants import HIGH_VALUE
|
|
|
6
6
|
from .exceptions import ValueBoundryError
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class InvalidUnits(Exception):
|
|
9
|
+
class InvalidUnits(Exception): # noqa: N818
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class InvalidLowerBound(Exception):
|
|
13
|
+
class InvalidLowerBound(Exception): # noqa: N818
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class InvalidLowerLimitNormal(Exception):
|
|
17
|
+
class InvalidLowerLimitNormal(Exception): # noqa: N818
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class InvalidUpperLimitNormal(Exception):
|
|
21
|
+
class InvalidUpperLimitNormal(Exception): # noqa: N818
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class InvalidUpperBound(Exception):
|
|
25
|
+
class InvalidUpperBound(Exception): # noqa: N818
|
|
26
26
|
pass
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class InvalidCombination(Exception):
|
|
29
|
+
class InvalidCombination(Exception): # noqa: N818
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class Evaluator:
|
|
34
34
|
def __init__(
|
|
35
35
|
self,
|
|
36
|
-
name: str = None,
|
|
37
|
-
lower: int | float = None,
|
|
38
|
-
upper: int | float = None,
|
|
39
|
-
units: str = None,
|
|
36
|
+
name: str | None = None,
|
|
37
|
+
lower: int | float | None = None,
|
|
38
|
+
upper: int | float | None = None,
|
|
39
|
+
units: str | None = None,
|
|
40
40
|
lower_inclusive: bool | None = None,
|
|
41
41
|
upper_inclusive: bool | None = None,
|
|
42
|
-
**kwargs,
|
|
42
|
+
**kwargs, # noqa: ARG002
|
|
43
43
|
) -> None:
|
|
44
44
|
self.name = name
|
|
45
45
|
if lower is not None and not re.match(r"\d+", str(lower)):
|
|
@@ -78,8 +78,8 @@ class Evaluator:
|
|
|
78
78
|
|
|
79
79
|
def description(
|
|
80
80
|
self,
|
|
81
|
-
value: int | float = None,
|
|
82
|
-
show_as_int: bool = None,
|
|
81
|
+
value: int | float | None = None,
|
|
82
|
+
show_as_int: bool | None = None,
|
|
83
83
|
placeholder: str | None = None,
|
|
84
84
|
) -> str:
|
|
85
85
|
placeholder = placeholder or "x"
|
|
@@ -98,7 +98,7 @@ class Evaluator:
|
|
|
98
98
|
f"{self.upper_operator or ''}{upper} {self.units}"
|
|
99
99
|
)
|
|
100
100
|
|
|
101
|
-
def in_bounds_or_raise(self, value: int | float, units: str
|
|
101
|
+
def in_bounds_or_raise(self, value: int | float, units: str) -> bool:
|
|
102
102
|
"""Raises a ValueBoundryError exception if condition not met.
|
|
103
103
|
|
|
104
104
|
The condition is evaluated to True or False as a string
|
|
@@ -116,6 +116,6 @@ class Evaluator:
|
|
|
116
116
|
f"{'' if self.lower is None else self.lower}{self.lower_operator or ''}{value}"
|
|
117
117
|
f"{self.upper_operator or ''}{'' if self.upper is None else self.upper}"
|
|
118
118
|
)
|
|
119
|
-
if not eval(condition_str): # nosec B307
|
|
119
|
+
if not eval(condition_str): # nosec B307 # noqa: S307
|
|
120
120
|
raise ValueBoundryError(condition_str)
|
|
121
121
|
return True
|
edc_reportable/exceptions.py
CHANGED
|
@@ -18,7 +18,7 @@ class SiteReportablesError(Exception):
|
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class AlreadyRegistered(Exception):
|
|
21
|
+
class AlreadyRegistered(Exception): # noqa: N818
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
|
|
@@ -30,13 +30,13 @@ class ValueBoundryError(Exception):
|
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class NotEvaluated(Exception):
|
|
33
|
+
class NotEvaluated(Exception): # noqa: N818
|
|
34
34
|
pass
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
class BoundariesOverlap(Exception):
|
|
37
|
+
class BoundariesOverlap(Exception): # noqa: N818
|
|
38
38
|
pass
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
class ConversionNotHandled(Exception):
|
|
41
|
+
class ConversionNotHandled(Exception): # noqa: N818
|
|
42
42
|
pass
|
|
@@ -23,7 +23,10 @@ class ReportablesFormValidatorMixin:
|
|
|
23
23
|
return {"age_units": "years"}
|
|
24
24
|
|
|
25
25
|
def validate_reportable_fields(
|
|
26
|
-
self,
|
|
26
|
+
self,
|
|
27
|
+
reference_range_collection_name: str,
|
|
28
|
+
age_units: str | None = None,
|
|
29
|
+
**reportables_evaluator_options,
|
|
27
30
|
):
|
|
28
31
|
"""Called in clean() method of the FormValidator.
|
|
29
32
|
|
|
@@ -47,9 +50,10 @@ class ReportablesFormValidatorMixin:
|
|
|
47
50
|
report_datetime=self.report_datetime,
|
|
48
51
|
value_field_suffix=self.value_field_suffix,
|
|
49
52
|
)
|
|
53
|
+
age_units = age_units or self.age_units
|
|
50
54
|
options.update(**reportables_evaluator_options)
|
|
51
55
|
reference_range_evaluator = self.reference_range_evaluator_cls(
|
|
52
|
-
reference_range_collection_name, **options
|
|
56
|
+
reference_range_collection_name, age_units=age_units, **options
|
|
53
57
|
)
|
|
54
58
|
try:
|
|
55
59
|
reference_range_evaluator.validate_reportable_fields()
|
|
@@ -4,7 +4,6 @@ from edc_model.models import BaseUuidModel
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class GradingException(BaseUuidModel):
|
|
7
|
-
|
|
8
7
|
reference_range_collection = models.ForeignKey(
|
|
9
8
|
"edc_reportable.ReferenceRangeCollection", on_delete=models.PROTECT
|
|
10
9
|
)
|
|
@@ -17,11 +16,7 @@ class GradingException(BaseUuidModel):
|
|
|
17
16
|
grade4 = models.BooleanField(default=False)
|
|
18
17
|
|
|
19
18
|
def grades(self) -> list[int]:
|
|
20
|
-
|
|
21
|
-
for i in range(1, 5):
|
|
22
|
-
if getattr(self, f"grade{i}"):
|
|
23
|
-
grades.append(i)
|
|
24
|
-
return grades
|
|
19
|
+
return [i for i in range(1, 5) if getattr(self, f"grade{i}")]
|
|
25
20
|
|
|
26
21
|
class Meta(BaseUuidModel.Meta):
|
|
27
22
|
verbose_name = "Grading Exception"
|
|
@@ -22,8 +22,8 @@ class NormalData(ReferenceModelMixin, BaseUuidModel):
|
|
|
22
22
|
def value_in_normal_range_or_raise(
|
|
23
23
|
self,
|
|
24
24
|
value: int | float,
|
|
25
|
-
dob: date
|
|
26
|
-
report_datetime: datetime
|
|
25
|
+
dob: date,
|
|
26
|
+
report_datetime: datetime,
|
|
27
27
|
age_units: str | None = None,
|
|
28
28
|
) -> bool:
|
|
29
29
|
"""Raises a ValueBoundryError exception if condition not met.
|
|
@@ -45,7 +45,7 @@ class NormalData(ReferenceModelMixin, BaseUuidModel):
|
|
|
45
45
|
value_condition_str = self.get_eval_phrase(value)
|
|
46
46
|
if not re.match(pattern, value_condition_str):
|
|
47
47
|
raise ValueError(f"Invalid condition string. Got {value_condition_str}.")
|
|
48
|
-
if not eval(value_condition_str): # nosec B307
|
|
48
|
+
if not eval(value_condition_str): # nosec B307 # noqa: S307
|
|
49
49
|
raise ValueBoundryError(
|
|
50
50
|
f"{self.label}: {value_condition_str}{self.units} [{self.gender}]"
|
|
51
51
|
)
|
|
@@ -38,11 +38,7 @@ class ReferenceRangeCollection(BaseUuidModel):
|
|
|
38
38
|
|
|
39
39
|
See also model GradingException.
|
|
40
40
|
"""
|
|
41
|
-
|
|
42
|
-
for i in range(1, 5):
|
|
43
|
-
if getattr(self, f"grade{i}"):
|
|
44
|
-
grades.append(i)
|
|
45
|
-
return grades
|
|
41
|
+
return [i for i in range(1, 5) if getattr(self, f"grade{i}")]
|
|
46
42
|
|
|
47
43
|
def reportable_grades(self, label: str) -> list[int]:
|
|
48
44
|
if not label:
|
|
@@ -96,7 +92,6 @@ class ReferenceRangeCollection(BaseUuidModel):
|
|
|
96
92
|
gender: str | None = None,
|
|
97
93
|
dob: date | None = None,
|
|
98
94
|
age_units: str | None = None,
|
|
99
|
-
site: Site | None = None,
|
|
100
95
|
) -> tuple[bool, NormalData]:
|
|
101
96
|
if subject_identifier:
|
|
102
97
|
rs_obj = RegisteredSubject.objects.get(subject_identifier=subject_identifier)
|
|
@@ -19,12 +19,12 @@ __all__ = ["get_grade_for_value"]
|
|
|
19
19
|
|
|
20
20
|
def get_grade_for_value(
|
|
21
21
|
reference_range_collection: ReferenceRangeCollection,
|
|
22
|
-
value: float | int = None,
|
|
23
|
-
label: str = None,
|
|
24
|
-
units: str = None,
|
|
25
|
-
gender: str = None,
|
|
26
|
-
dob: date = None,
|
|
27
|
-
report_datetime: datetime = None,
|
|
22
|
+
value: float | int | None = None,
|
|
23
|
+
label: str | None = None,
|
|
24
|
+
units: str | None = None,
|
|
25
|
+
gender: str | None = None,
|
|
26
|
+
dob: date | None = None,
|
|
27
|
+
report_datetime: datetime | None = None,
|
|
28
28
|
age_units: str | None = None,
|
|
29
29
|
site: Site | None = None,
|
|
30
30
|
create_missing_normal: bool | None = None,
|
|
@@ -56,11 +56,11 @@ def get_grade_for_value(
|
|
|
56
56
|
upper_limit = get_upper_limit(normal_data, grading_data)
|
|
57
57
|
value = float(value)
|
|
58
58
|
condition_str = (
|
|
59
|
-
f'
|
|
60
|
-
f
|
|
61
|
-
f
|
|
59
|
+
f"{'' if lower_limit is None else lower_limit}"
|
|
60
|
+
f"{grading_data.lower_operator or ''}{value}"
|
|
61
|
+
f"{grading_data.upper_operator or ''}{'' if upper_limit is None else upper_limit}"
|
|
62
62
|
)
|
|
63
|
-
if eval(condition_str): # nosec B307
|
|
63
|
+
if eval(condition_str): # nosec B307 # noqa: S307
|
|
64
64
|
if not found_grading_data:
|
|
65
65
|
found_grading_data = grading_data
|
|
66
66
|
found_condition_str = (
|
|
@@ -104,11 +104,11 @@ def get_upper_limit(
|
|
|
104
104
|
|
|
105
105
|
def get_grading_data_instances(
|
|
106
106
|
reference_range_collection: ReferenceRangeCollection,
|
|
107
|
-
label: str = None,
|
|
108
|
-
units: str = None,
|
|
109
|
-
gender: str = None,
|
|
110
|
-
dob: date = None,
|
|
111
|
-
report_datetime: datetime = None,
|
|
107
|
+
label: str | None = None,
|
|
108
|
+
units: str | None = None,
|
|
109
|
+
gender: str | None = None,
|
|
110
|
+
dob: date | None = None,
|
|
111
|
+
report_datetime: datetime | None = None,
|
|
112
112
|
age_units: str | None = None,
|
|
113
113
|
site: Site | None = None,
|
|
114
114
|
) -> list[GradingData]:
|
|
@@ -25,11 +25,11 @@ __all__ = ["get_normal_data_or_raise"]
|
|
|
25
25
|
|
|
26
26
|
def get_normal_data_or_raise(
|
|
27
27
|
reference_range_collection: ReferenceRangeCollection = None,
|
|
28
|
-
label: str = None,
|
|
29
|
-
units: str = None,
|
|
30
|
-
gender: str = None,
|
|
31
|
-
dob: date = None,
|
|
32
|
-
report_datetime: datetime = None,
|
|
28
|
+
label: str | None = None,
|
|
29
|
+
units: str | None = None,
|
|
30
|
+
gender: str | None = None,
|
|
31
|
+
dob: date | None = None,
|
|
32
|
+
report_datetime: datetime | None = None,
|
|
33
33
|
age_units: str | None = None,
|
|
34
34
|
site: Site | None = None,
|
|
35
35
|
create_missing_normal: bool | None = None,
|
|
@@ -48,7 +48,7 @@ def get_normal_data_or_raise(
|
|
|
48
48
|
units=units,
|
|
49
49
|
age_units=age_units,
|
|
50
50
|
)
|
|
51
|
-
except ObjectDoesNotExist:
|
|
51
|
+
except ObjectDoesNotExist as e:
|
|
52
52
|
if create_missing_normal:
|
|
53
53
|
opts = dict(
|
|
54
54
|
reference_range_collection=reference_range_collection,
|
|
@@ -68,23 +68,23 @@ def get_normal_data_or_raise(
|
|
|
68
68
|
f"age={getattr(age_rdelta, age_units)}{age_units}. "
|
|
69
69
|
"Perhaps add this to the default normal reference range data or "
|
|
70
70
|
"pass 'create_missing=True' to convert an existing normal reference."
|
|
71
|
-
)
|
|
72
|
-
except MultipleObjectsReturned:
|
|
71
|
+
) from e
|
|
72
|
+
except MultipleObjectsReturned as e:
|
|
73
73
|
raise NotEvaluated(
|
|
74
74
|
f"Value not evaluated. "
|
|
75
75
|
f"Multiple normal references found for `{label}`. "
|
|
76
76
|
f"Using units={units}, gender={gender}, age={getattr(age_rdelta, age_units)}. "
|
|
77
|
-
)
|
|
77
|
+
) from e
|
|
78
78
|
return obj
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def create_obj_for_new_units_or_raise(
|
|
82
82
|
reference_range_collection: ReferenceRangeCollection = None,
|
|
83
|
-
label: str = None,
|
|
84
|
-
units: str = None,
|
|
85
|
-
gender: str = None,
|
|
86
|
-
dob: date = None,
|
|
87
|
-
report_datetime: datetime = None,
|
|
83
|
+
label: str | None = None,
|
|
84
|
+
units: str | None = None,
|
|
85
|
+
gender: str | None = None,
|
|
86
|
+
dob: date | None = None,
|
|
87
|
+
report_datetime: datetime | None = None,
|
|
88
88
|
age_units: str | None = None,
|
|
89
89
|
) -> NormalData | None:
|
|
90
90
|
opts = {}
|