clinicedc 2.0.27__py3-none-any.whl → 2.0.29__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.

Files changed (100) hide show
  1. {clinicedc-2.0.27.dist-info → clinicedc-2.0.29.dist-info}/METADATA +2 -2
  2. {clinicedc-2.0.27.dist-info → clinicedc-2.0.29.dist-info}/RECORD +100 -100
  3. edc_auth/admin/fieldsets.py +11 -15
  4. edc_auth/admin/group_admin.py +2 -5
  5. edc_auth/admin/list_filters.py +8 -7
  6. edc_auth/admin/role_admin.py +7 -10
  7. edc_auth/admin/user_admin.py +2 -2
  8. edc_auth/admin/user_profile_admin.py +3 -7
  9. edc_auth/apps.py +2 -2
  10. edc_auth/auth_updater/group_updater.py +4 -4
  11. edc_auth/auth_updater/role_updater.py +1 -3
  12. edc_auth/backends.py +1 -1
  13. edc_auth/export_users.py +6 -5
  14. edc_auth/fix_export_permissions.py +8 -8
  15. edc_auth/forms.py +21 -24
  16. edc_auth/get_app_codenames.py +2 -2
  17. edc_auth/import_users.py +25 -24
  18. edc_auth/management/commands/export_users.py +1 -1
  19. edc_auth/management/commands/fix_export_permissions.py +1 -1
  20. edc_auth/management/commands/import_users.py +1 -1
  21. edc_auth/management/commands/reset_password.py +2 -2
  22. edc_auth/models/signals.py +1 -1
  23. edc_auth/models/user_profile.py +4 -5
  24. edc_auth/password_setter.py +4 -4
  25. edc_auth/post_migrate_signals.py +2 -2
  26. edc_auth/send_new_credentials_to_user.py +1 -1
  27. edc_auth/system_checks.py +7 -6
  28. edc_auth/utils.py +9 -11
  29. edc_consent/consent_definition.py +4 -1
  30. edc_consent/models/__init__.py +2 -2
  31. edc_consent/models/signals.py +32 -26
  32. edc_model_admin/mixins/model_admin_form_instructions_mixin.py +9 -3
  33. edc_model_admin/mixins/model_admin_protect_pii_mixin.py +7 -7
  34. edc_pharmacy/admin_mixin.py +1 -1
  35. edc_pharmacy/approve_prescription.py +10 -8
  36. edc_pharmacy/auth_objects.py +1 -1
  37. edc_pharmacy/exceptions.py +7 -7
  38. edc_pharmacy/settings.py +1 -1
  39. edc_pharmacy/views/add_to_storage_bin_view.py +22 -24
  40. edc_pharmacy/views/allocate_to_subject_view.py +10 -18
  41. edc_pharmacy/views/celery_task_status_view.py +1 -2
  42. edc_pharmacy/views/confirm_stock_from_instance_view.py +1 -1
  43. edc_pharmacy/views/confirm_stock_from_queryset_view.py +5 -8
  44. edc_pharmacy/views/dispense_view.py +1 -1
  45. edc_pharmacy/views/move_to_storage_bin_view.py +7 -3
  46. edc_pharmacy/views/prepare_and_review_stock_request_view.py +62 -70
  47. edc_pharmacy/views/print_labels_view.py +1 -1
  48. edc_pharmacy/views/transfer_stock_view.py +1 -1
  49. edc_prn/modelform_mixins.py +8 -9
  50. edc_prn/models.py +3 -1
  51. edc_prn/site_prn_forms.py +2 -2
  52. edc_prn/templatetags/edc_prn_extras.py +1 -1
  53. edc_protocol/middleware.py +1 -1
  54. edc_protocol/research_protocol_config.py +7 -5
  55. edc_protocol_incident/admin/protocol_deviation_violation_admin.py +1 -1
  56. edc_protocol_incident/form_validators/mixins.py +9 -6
  57. edc_protocol_incident/modeladmin_mixins.py +1 -1
  58. edc_pylabels/admin/label_configuration_admin.py +1 -1
  59. edc_pylabels/admin/label_specification_admin.py +1 -1
  60. edc_pylabels/auth_objects.py +1 -1
  61. edc_pylabels/site_label_configs.py +2 -2
  62. edc_qareports/admin/qa_report_log_admin.py +5 -5
  63. edc_qareports/admin/qa_report_log_summary_admin.py +1 -1
  64. edc_qareports/forms/note_form.py +2 -3
  65. edc_qareports/modeladmin_mixins/list_filters.py +23 -19
  66. edc_qareports/modeladmin_mixins/note_modeladmin_mixin.py +7 -7
  67. edc_qareports/modeladmin_mixins/on_study_missing_values_modeladmin_mixin.py +8 -8
  68. edc_qareports/modeladmin_mixins/qa_report_modeladmin_mixin.py +3 -7
  69. edc_qareports/sql_generator/crf_case.py +1 -1
  70. edc_qareports/sql_generator/crf_subquery.py +1 -1
  71. edc_qareports/sql_generator/requisition_subquery.py +1 -1
  72. edc_qareports/sql_generator/sql_view_generator.py +1 -1
  73. edc_qareports/sql_generator/subquery_from_dict.py +39 -39
  74. edc_qareports/utils.py +12 -13
  75. edc_randomization/model_mixins.py +1 -1
  76. edc_randomization/randomization_list_importer.py +10 -4
  77. edc_randomization/randomization_list_verifier.py +5 -3
  78. edc_randomization/system_checks.py +1 -1
  79. edc_refusal/admin.py +4 -2
  80. edc_registration/modeladmin_mixins.py +18 -20
  81. edc_registration/modelform_mixins.py +2 -2
  82. edc_registration/models/signals.py +1 -1
  83. edc_registration/utils.py +1 -1
  84. edc_reportable/age_evaluator.py +4 -4
  85. edc_reportable/data/grading_data/daids_july_2017.py +3 -3
  86. edc_reportable/evaluator.py +15 -15
  87. edc_reportable/exceptions.py +4 -4
  88. edc_reportable/forms/reportables_form_validator_mixin.py +6 -2
  89. edc_reportable/management/commands/export_reportables.py +1 -1
  90. edc_reportable/models/grading_exception.py +1 -6
  91. edc_reportable/models/normal_data.py +3 -3
  92. edc_reportable/models/reference_range_collection.py +1 -6
  93. edc_reportable/utils/get_grade_for_value.py +15 -15
  94. edc_reportable/utils/get_normal_data_or_raise.py +14 -14
  95. edc_reportable/utils/in_normal_bounds_or_raise.py +6 -6
  96. edc_reportable/utils/load_data.py +1 -1
  97. edc_reportable/utils/update_grading_exceptions.py +5 -5
  98. edc_visit_schedule/site_visit_schedules.py +8 -12
  99. {clinicedc-2.0.27.dist-info → clinicedc-2.0.29.dist-info}/WHEEL +0 -0
  100. {clinicedc-2.0.27.dist-info → clinicedc-2.0.29.dist-info}/licenses/LICENSE +0 -0
@@ -36,5 +36,5 @@ class CrfCase:
36
36
  try:
37
37
  cursor.execute(self.sql)
38
38
  except OperationalError as e:
39
- raise CrfCaseError(f"{e}. See {self}.")
39
+ raise CrfCaseError(f"{e}. See {self}.") from e
40
40
  return cursor.fetchall()
@@ -59,5 +59,5 @@ class CrfSubquery:
59
59
  try:
60
60
  sql = self.template.substitute(**opts).replace(";", "")
61
61
  except KeyError as e:
62
- raise CrfSubqueryError(e)
62
+ raise CrfSubqueryError(e) from e
63
63
  return sql
@@ -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
- 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)
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
- print(f"create view {model_cls._meta.db_table} as {sql};")
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
- try:
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
- print(model_cls)
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
- print(e)
117
+ sys.stdout.write(f"{e}\n")
119
118
  except TypeError as e:
120
- print(e)
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:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import csv
4
4
  import sys
5
+ from itertools import islice
5
6
  from pathlib import Path
6
7
  from pprint import pprint
7
8
  from uuid import uuid4
@@ -72,6 +73,7 @@ class RandomizationListImporter:
72
73
  username: str | None = None,
73
74
  revision: str | None = None,
74
75
  sid_count_for_tests: int | None = None,
76
+ skip_verify: bool | None = None,
75
77
  extra_csv_fieldnames: tuple[str] | None = None,
76
78
  **kwargs, # noqa: ARG002
77
79
  ):
@@ -84,6 +86,7 @@ class RandomizationListImporter:
84
86
  self.revision = revision
85
87
  self.user = username
86
88
  self.sid_count_for_tests = sid_count_for_tests
89
+ self.skip_verify = skip_verify
87
90
  self.randomizer_model_cls = randomizer_model_cls
88
91
  self.randomizer_name = randomizer_name
89
92
  self.assignment_map = assignment_map
@@ -120,8 +123,9 @@ class RandomizationListImporter:
120
123
  )
121
124
  )
122
125
  rec_count = self._import_csv_to_model()
123
- self.verify_messages = self._verify_data(**kwargs)
124
- self._summarize_results()
126
+ if not self.skip_verify:
127
+ self.verify_messages = self._verify_data(**kwargs)
128
+ self._summarize_results()
125
129
  if self.verbose:
126
130
  sys.stdout.write(
127
131
  style.SUCCESS("\nDone.------------------------------------------------\n")
@@ -221,13 +225,15 @@ class RandomizationListImporter:
221
225
  sid_count = len(self.get_sid_list())
222
226
  with self.randomizationlist_path.open(mode="r") as f:
223
227
  reader = csv.DictReader(f)
228
+ if self.sid_count_for_tests:
229
+ reader = islice(reader, self.sid_count_for_tests)
224
230
  all_rows = [{k: v.strip() for k, v in row.items() if k} for row in reader]
225
231
  sorted_rows = sorted(
226
232
  all_rows, key=lambda row: (row.get("site_name", ""), row.get("sid", ""))
227
233
  )
228
234
  for row in tqdm(sorted_rows, total=sid_count):
229
- if self.sid_count_for_tests and len(objs) == self.sid_count_for_tests:
230
- break
235
+ # if self.sid_count_for_tests and len(objs) == self.sid_count_for_tests:
236
+ # break
231
237
  try:
232
238
  self.randomizer_model_cls.objects.get(sid=row["sid"])
233
239
  except ObjectDoesNotExist:
@@ -1,5 +1,6 @@
1
1
  import csv
2
2
  import sys
3
+ from itertools import islice
3
4
  from pathlib import Path
4
5
 
5
6
  from django.core.exceptions import ObjectDoesNotExist
@@ -72,15 +73,16 @@ class RandomizationListVerifier:
72
73
 
73
74
  def verify(self) -> str | None:
74
75
  message = None
75
-
76
76
  # read and sort from CSV file
77
77
  with self.randomizationlist_path.open(mode="r") as f:
78
78
  reader = csv.DictReader(f)
79
+ if self.sid_count_for_tests:
80
+ reader = islice(reader, self.sid_count_for_tests)
79
81
  all_rows = [{k: v.strip() for k, v in row.items() if k} for row in reader]
80
82
  sorted_rows = sorted(
81
- all_rows, key=lambda row: (row.get("site_name", ""), int(row.get("sid", 0)))
83
+ all_rows,
84
+ key=lambda row: (row.get("site_name", ""), int(row.get("sid", 0))),
82
85
  )
83
-
84
86
  # compare sorted CSV length with DB
85
87
  if len(sorted_rows) != self.randomizer_model_cls.objects.all().count():
86
88
  expected_cnt = len(sorted_rows)
@@ -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(f"{e}. See subject_dashboard_url_name for {self!r}.")
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
- "subject_identifier",
35
- "sid",
36
- "first_name",
37
- "last_name",
38
- "initials",
39
- "dob",
40
- "gender",
41
- "subject_type",
42
- "registration_status",
43
- "identity",
44
- "screening_identifier",
45
- "screening_datetime",
46
- "registration_datetime",
47
- "randomization_datetime",
48
- "consent_datetime",
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
@@ -18,4 +18,4 @@ def update_registered_subject_from_model_on_post_save(
18
18
  instance.registration_update_or_create()
19
19
  except AttributeError as e:
20
20
  if "registration_update_or_create" not in str(e):
21
- raise AttributeError(str(e))
21
+ raise AttributeError(str(e)) from e
edc_registration/utils.py CHANGED
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  from edc_registration.models import RegisteredSubject
16
16
 
17
17
 
18
- class RegisteredSubjectDoesNotExist(Exception):
18
+ class RegisteredSubjectDoesNotExist(Exception): # noqa: N818
19
19
  pass
20
20
 
21
21
 
@@ -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 participants baseline
9
- G3 < 60 to 30 ml/min or ml/min/1.73 m2 OR 30 to < 50% decrease from participants baseline
10
- G4 < 30 ml/min or ml/min/1.73 m2 OR ≥ 50% decrease from participants baseline
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
@@ -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 = None) -> bool:
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
@@ -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, reference_range_collection_name: str, **reportables_evaluator_options
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()
@@ -50,5 +50,5 @@ class Command(BaseCommand):
50
50
  help="Export path/folder",
51
51
  )
52
52
 
53
- def handle(self, *args, **options):
53
+ def handle(self, *args, **options): # noqa: ARG002
54
54
  export_daids_grading(path=options["path"])
@@ -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
- grades = []
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 = None,
26
- report_datetime: datetime = None,
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
- grades = []
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)