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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clinicedc
3
- Version: 2.0.19
3
+ Version: 2.0.20
4
4
  Summary: A clinical trials data management framework built on Django
5
5
  Keywords: django,clinicedc,edc,clinical trials,research,data management,esource
6
6
  Author: Erik van Widenfelt, Jonathan Willitts
@@ -63,7 +63,7 @@ Project-URL: Homepage, https://github.com/clinicedc/clinicedc
63
63
  Project-URL: Repository, https://github.com/clinicedc/clinicedc.git
64
64
  Description-Content-Type: text/x-rst
65
65
 
66
- |pypi| |actions| |uv| |ruff| |downloads| |django-packages|
66
+ s|pypi| |actions| |uv| |ruff| |downloads| |django-packages|
67
67
 
68
68
  clinicedc - A clinical trials data management framework built on Django
69
69
  ========================================================================
@@ -72,7 +72,9 @@ A data management framework built on Django for multisite randomized longitudina
72
72
 
73
73
  Documentation: `clinicedc.readthedocs.io <https://clinicedc.readthedocs.io/>`_
74
74
 
75
- `Here are a set of python modules that extend Django <https://github.com/clinicedc/edc>`__ to empower you to build an EDC / eSource system to handle data
75
+ Source code: https://github.com/clinicedc/clinicedc
76
+
77
+ `Here is a python module that extends Django <https://github.com/clinicedc/clinicedc>`__ to empower you to build an EDC / eSource system to handle data
76
78
  collection and management for multi-site longitudinal clinical trials.
77
79
 
78
80
  Refer to the specific open projects listed below for example EDC systems built with these modules.
@@ -93,7 +95,7 @@ See also https://www.ucl.ac.uk/global-health/respond-africa
93
95
 
94
96
  The implementations we develop with this framework are mostly eSource systems rather than the traditional EDCs.
95
97
 
96
- The ``clinicedc's`` listed below consist of a subset of trial-specific modules that make heavy use of modules in this framework.
98
+ The projects listed below consist of a subset of trial-specific modules that make heavy use of modules in this framework.
97
99
 
98
100
  Contacts
99
101
  --------
@@ -113,8 +115,8 @@ python 3.12+ Django 5.2+ mysql 8+
113
115
  ============ ============ =========
114
116
 
115
117
 
116
- How we describe the EDC in our protocol documents
117
- -------------------------------------------------
118
+ How we describe the CLINICEDC projects in our protocol documents
119
+ ----------------------------------------------------------------
118
120
 
119
121
  Here is a simple example of a data management section for a study protocol document: `data_management_section`_
120
122
 
@@ -224,10 +226,7 @@ Optional modules
224
226
 
225
227
  =========================== ============================= ==================================
226
228
  edc-csf_ |edc-csf| |pypi-edc-csf|
227
- edc-dx_ |edc-dx| |pypi-edc-dx|
228
- edc-dx-review_ |edc-dx-review| |pypi-edc-dx-review|
229
229
  edc-he_ |edc-he| |pypi-edc-he|
230
- edc-icecap-a_ |edc-icecap-a| |pypi-edc-icecap-a|
231
230
  edc-microbiology_ |edc-microbiology| |pypi-edc-microbiology|
232
231
  edc-microscopy_ |edc-microscopy| |pypi-edc-microscopy|
233
232
  edc-mnsi_ |edc-mnsi| |pypi-edc-mnsi|
@@ -250,7 +249,7 @@ Env
250
249
 
251
250
  uv venv
252
251
  source .venv/bin/activate
253
- uv sync --no-sources
252
+ uv sync --no-sources --upgrade
254
253
 
255
254
  Tests
256
255
  -----
@@ -930,7 +930,7 @@ edc_egfr/apps.py,sha256=CdF04HAYAsjSXFn-3zZmYilyevUxMsVX5uv2tc9iNpY,111
930
930
  edc_egfr/calculators/__init__.py,sha256=3KRcG4NEA9EzUIcNq_5cTcaEavzRM-OH02GsUV8dRBs,182
931
931
  edc_egfr/calculators/base_egrfr.py,sha256=2ut7xv0onJVt2pyt0uuzo4MaDfZuFgaPr7wCtC4Hd4c,1931
932
932
  edc_egfr/calculators/egfr_ckd_epi.py,sha256=YVCuBgiSLvmIyOxXYfLwwJ_y-74vFo9GGUB32TySNk0,2116
933
- edc_egfr/calculators/egfr_cockcroft_gault.py,sha256=vRB1fsDdluvRoPJw0Y7GcE7XvQdrMB2VmsCdYbMicto,1979
933
+ edc_egfr/calculators/egfr_cockcroft_gault.py,sha256=y_HlNUiIb1xf8lOm00qpRp2bGAuKQ3rmEgDE5TT4stc,1992
934
934
  edc_egfr/calculators/percent_change.py,sha256=YVKho6PrqDdfdwV6Ndrzw_9fRJquCWxTlk782YVKmq0,313
935
935
  edc_egfr/constants.py,sha256=9L7NcsG-rbd-yg7TnW95XCF5vSL4BO7eaDQD4-mgD80,59
936
936
  edc_egfr/egfr.py,sha256=1S8okz_ALH_hFfAA6BF77Oz8_f2RkrT9JK4Q4DqpWA8,9485
@@ -1937,7 +1937,7 @@ edc_model_admin/templates/edc_model_admin/navbar_for_admin_templates_b3.html,sha
1937
1937
  edc_model_admin/templates/edc_model_admin/yes_no_coloring.html,sha256=yrvwV6fRoBLWOXVhIOyNrjgQGU_O_QDjPOTR7Y0NoYg,105
1938
1938
  edc_model_admin/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1939
1939
  edc_model_admin/templatetags/edc_admin_modify.py,sha256=JCNVc1G7rPNGyFI_bUIxeXkafsQBzCWy1p4G8foavJk,6372
1940
- edc_model_admin/utils.py,sha256=NzoSKZJHDsiwnLdNAReNWwP85cLI5bqrHt1_UZS1hiQ,1932
1940
+ edc_model_admin/utils.py,sha256=D4VLfgZipQRMJMRAP4gHfoe-0diIcSi5Z9NmHOsEKuw,1969
1941
1941
  edc_model_fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1942
1942
  edc_model_fields/apps.py,sha256=QWZWN2EmsEyp_6I5GldSqSkRfxjcPkyA2zvF1NfzjRM,157
1943
1943
  edc_model_fields/fields/__init__.py,sha256=rqpLbES3_W7CX2wTIafezwgn0qWsS9b_dLZ2rhJFjzM,388
@@ -1952,21 +1952,21 @@ edc_model_fields/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1952
1952
  edc_model_fields/static/edc_model_fields/slider.css,sha256=PutHKEddHvRqm59fv6rR0yj4Msx6UlCyGkbq3C_2KgE,1360
1953
1953
  edc_model_fields/static/edc_model_fields/slider.png,sha256=uQPOOegrQGf0VGhudDMja-WthzmQ0mCLEtUJPYHqxl4,10150
1954
1954
  edc_model_fields/templates/edc_model_fields/widgets/slider.html,sha256=ejTSfyXrB_SZyFI1HHAH2VR5QiLVrgKu_14UN8bJHqY,916
1955
- edc_model_fields/utils.py,sha256=A0gE463okqHaQPgJZOl9W5eDkFUoma-UQ2At5ajdDlY,1610
1955
+ edc_model_fields/utils.py,sha256=_beJkz2Vn2gN_OxqI0PJ17Ycm2wVWfKZhZrDD2EAYlU,1607
1956
1956
  edc_model_fields/widgets/__init__.py,sha256=Q1L6XHWfrMmlOq4iBSvOatqudtp7_Vf3C3pPvxgVwGg,40
1957
- edc_model_fields/widgets/slider_widget.py,sha256=5CWN__T3WRs8gHQYTpJC7TnfTr2NKzSH1gcC1vry6DE,1223
1957
+ edc_model_fields/widgets/slider_widget.py,sha256=oow_e3nA-7cnsEa3OOG9D9_5x4La0-PdNB_La3x-948,1239
1958
1958
  edc_model_form/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1959
1959
  edc_model_form/apps.py,sha256=bTK1QIa1fNzk3_H4UoweAYYZdEcg8BGz9PD8ksY25f8,238
1960
1960
  edc_model_form/mixins/__init__.py,sha256=Lf2Qld0bPNrdW-uQKE5XT_Uu2obRtIHza57bqv4A6g8,297
1961
- edc_model_form/mixins/base_model_form_mixin.py,sha256=Wjm_ZWtH7lhlv-gWZ4IssdSGcvEyeSExYQKmbprbyFw,2139
1962
- edc_model_form/mixins/inline_model_form_mixin.py,sha256=dxpzFcwHoNi2y7vWF1QdAqObU7rcr1j5J_Pj4Nk1aOY,3647
1961
+ edc_model_form/mixins/base_model_form_mixin.py,sha256=zsLiKfEkQFf3Pm7rvIym9L3_CQnZC1IDYfHncgV5cgw,2151
1962
+ edc_model_form/mixins/inline_model_form_mixin.py,sha256=pywvLu_QCLTB5npoTtgDjk8yU2dLepsKhfBZLr5K4Vg,3659
1963
1963
  edc_model_form/mixins/report_datetime_modelform_mixin.py,sha256=f1DrM-aUh2o3DM65MM3nwiZUXhhnvth8_3tvbS1cWOQ,1008
1964
1964
  edc_model_form/urls.py,sha256=_sVCnQeiAFRYKhxga3_DjoKj_4bgs1s2gjcynDiapvA,111
1965
1965
  edc_model_form/utils.py,sha256=gGe3etrp5YF4AKMGFIR3I9V1CfX4c5Wm6fK-8JkUeT4,538
1966
1966
  edc_model_to_dataframe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1967
1967
  edc_model_to_dataframe/apps.py,sha256=US1ehhJPgkvmIDZXiUPMoSjHTWIztUbYGtx_azTXPIQ,290
1968
1968
  edc_model_to_dataframe/constants.py,sha256=xgCr2ZXUsjZqf4wfpY0JBhQoARzQEVS8tnkfBWwOMVQ,702
1969
- edc_model_to_dataframe/model_to_dataframe.py,sha256=cOTcoe9ivJERAXt3dULtoon6j9_cgh2-T-y3P0FBimo,18493
1969
+ edc_model_to_dataframe/model_to_dataframe.py,sha256=Nix-anMRh_KXOo8SQdWw0UgngJhJYWIRYPKekhYYYxU,18504
1970
1970
  edc_model_to_dataframe/read_frame_edc.py,sha256=UNe2f5bJi1Wkn4Jyva5TMnhyxk8vvGpG4U12kGyqqVY,820
1971
1971
  edc_model_to_dataframe/urls.py,sha256=KChLHeE6yfONC6IdMm3Q4U3u7tmy_Mb1w2lrbpFz96g,148
1972
1972
  edc_navbar/__init__.py,sha256=Y95juevvOawGZmOV3Ofef7oGwYSWWttAYT0v0mehkJc,195
@@ -2926,10 +2926,10 @@ edc_reportable/models/grading_data.py,sha256=JE3f-kNAeQ4uunNIO5pYyc24EbAGA4299Uv
2926
2926
  edc_reportable/models/grading_exception.py,sha256=yFHtLplZJdX1zBtIZk27jWFOjfLgtL0PWFRVwfg7vn8,802
2927
2927
  edc_reportable/models/mw.py,sha256=maFsgwP3buRNF-D3AKPjKUisrpatqncdf5pK3qy9U90,498
2928
2928
  edc_reportable/models/normal_data.py,sha256=AofGm0in2gg5cXNRHEeq08K1BF6iA5z5V66w8exBUq0,1913
2929
- edc_reportable/models/reference_model_mixins.py,sha256=iizhIvUM16vEa3M6Qteuv2iaooGWlwCE_jY8Or-eToU,4772
2929
+ edc_reportable/models/reference_model_mixins.py,sha256=Afl45lq1hc5i5O11wgZ_tzo0yuUga9jqRki8hw31wzQ,4791
2930
2930
  edc_reportable/models/reference_range_collection.py,sha256=zRunJ1NPL9C5Zda-gkuBR46eyd-NJTQwv0pNwvZHLzU,4204
2931
2931
  edc_reportable/post_migrate_signals.py,sha256=SgoII3XJgkvTkFVrKvx7BWrSQ-giGqrfCadupklRJl4,717
2932
- edc_reportable/reference_range_evaluator.py,sha256=yKsGf0k66NyMW-91KJvAE0InUHdC7lE3-Ks2XELXwoc,11159
2932
+ edc_reportable/reference_range_evaluator.py,sha256=JbdRlkiT6euAUm9is1Lt4Ztn3WFqph_ZcGVh_OrcgZw,11211
2933
2933
  edc_reportable/units.py,sha256=zrYjO8MqipIYjRFB8hQJy2HSuo17PwNDlUsekdkH_Vk,1207
2934
2934
  edc_reportable/urls.py,sha256=K164a5FVdOBOKt0wBfM4boTvlnGV7KlaE8Xjma5-eh8,309
2935
2935
  edc_reportable/utils/__init__.py,sha256=hYzfyJjWw4_tdlpW-ba3Rdg_YaOny5ywJrt5c9mOvU4,952
@@ -3301,7 +3301,7 @@ edc_visit_schedule/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
3301
3301
  edc_visit_schedule/templatetags/edc_visit_schedule_extras.py,sha256=jaK2EbZinGFe3fPgXBQJbtWDSK4y4M-ds1iYI_ULWMc,2829
3302
3302
  edc_visit_schedule/typing_stubs.py,sha256=NCrQqqL0Z9l296gh_RgdHdnN7mhGBGMIoHQ6u92sy78,247
3303
3303
  edc_visit_schedule/urls.py,sha256=x5Q8_HGtN2GMXY5XVi2E1QiRMcGaiGWRBah5SXy-svI,973
3304
- edc_visit_schedule/utils.py,sha256=YPukJ1lN19K-itqi6A4yCC4FIShMc-zLzIHoyJR551E,13887
3304
+ edc_visit_schedule/utils.py,sha256=D2PmhjImwBkl4OcRhj-7mid7Hwtqn-kqE-WDZ_ACmcA,13899
3305
3305
  edc_visit_schedule/view_mixins.py,sha256=PkM33v8wyMizeX96yTizhMurlmtmIuS-8O2iypZidY8,2292
3306
3306
  edc_visit_schedule/views/__init__.py,sha256=r_po9VhEv8GN_rqvYsHnV0WKpA5s7cgt-fYU7FjGjZA,83
3307
3307
  edc_visit_schedule/views/home_view.py,sha256=nbwQVkQPK7yWrMpo8NftqnN1zu9DpSVkNHErdr8EZBM,350
@@ -3406,7 +3406,7 @@ edc_vitals/models/fields/waist_circumference.py,sha256=fZcHFDdEwWLjIVLktKrFCD9UU
3406
3406
  edc_vitals/models/fields/weight.py,sha256=zo9_9e3Cpu0UqoRbWS-iDkcDo6fK80b1dDQy4x4MyxE,921
3407
3407
  edc_vitals/utils.py,sha256=vXid44KUXxeaSyund_y5MNXc5DFJs052_PwUAjszE2k,1384
3408
3408
  edc_vitals/validators.py,sha256=vNiElWMs0rRnHRNuVoPLRf0rW_C_0xcfUyep1Y_Si5s,156
3409
- clinicedc-2.0.19.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
- clinicedc-2.0.19.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
- clinicedc-2.0.19.dist-info/METADATA,sha256=MxLUOCH2SVCI3h2msdlezc308-PIFLfGaLjFKjKh3JU,15899
3412
- clinicedc-2.0.19.dist-info/RECORD,,
3409
+ clinicedc-2.0.20.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
+ clinicedc-2.0.20.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
+ clinicedc-2.0.20.dist-info/METADATA,sha256=vcdhv-kZx0P0xjAnRKHVdCxVIjy_-BwbGBzjslDSTkU,15755
3412
+ clinicedc-2.0.20.dist-info/RECORD,,
@@ -25,9 +25,9 @@ class EgfrCockcroftGault(BaseEgfr):
25
25
  https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2763564/
26
26
 
27
27
  GFR = 141 × min(Scr/κ, 1)α × max(Scr/κ, 1)-1.209 × 0.993Age
28
- """
28
+ """ # noqa: RUF002
29
29
 
30
- def __init__(self, weight: int | float | Decimal = None, **kwargs):
30
+ def __init__(self, weight: int | float | Decimal | None = None, **kwargs):
31
31
  self.weight = float(weight) if weight else None
32
32
  super().__init__(**kwargs)
33
33
 
@@ -39,7 +39,7 @@ class EgfrCockcroftGault(BaseEgfr):
39
39
  serum creatinine (μmol/L)
40
40
 
41
41
  *constant = 1.23 for males and 1.05 for females
42
- """
42
+ """ # noqa: RUF002
43
43
  if (
44
44
  self.gender
45
45
  and self.age_in_years
@@ -48,10 +48,9 @@ class EgfrCockcroftGault(BaseEgfr):
48
48
  ):
49
49
  gender_factor = 1.05 if self.gender == FEMALE else 1.23
50
50
  adjusted_age = 140.00 - self.age_in_years
51
- value = (adjusted_age * self.weight * gender_factor) / float(
51
+ return (adjusted_age * self.weight * gender_factor) / float(
52
52
  self.scr.get(MICROMOLES_PER_LITER)
53
53
  )
54
- return value
55
54
  opts = dict(
56
55
  gender=self.gender,
57
56
  age_in_years=self.age_in_years,
edc_model_admin/utils.py CHANGED
@@ -44,7 +44,9 @@ def get_value_from_lookup_string(search_field_name: str = None, obj=None, reques
44
44
  try:
45
45
  value = getattr(value or obj, field)
46
46
  except AttributeError as e:
47
- raise SearchTermLookupError(f"Invalid search term. `{search_field_name}`. Got {e}")
47
+ raise SearchTermLookupError(
48
+ f"Invalid search term. `{search_field_name}`. Got {e}"
49
+ ) from e
48
50
  if value is None:
49
51
  break
50
52
  return value
edc_model_fields/utils.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Generator
4
+
3
5
 
4
6
  class ChoicesError(Exception):
5
7
  pass
@@ -41,9 +43,8 @@ class Choices:
41
43
  def __str__(self) -> str:
42
44
  return str(self.choices)
43
45
 
44
- def __call__(self, *args, **kwargs) -> tuple[tuple[str, str], ...]:
46
+ def __call__(self, *args, **kwargs) -> tuple[tuple[str, str], ...]: # noqa: ARG002
45
47
  return tuple((c[self.STORE], c[self.DISPLAY]) for c in self.choices)
46
48
 
47
- def __iter__(self) -> tuple[str | int, str, str | int]:
48
- for store, display, meta in self.choices:
49
- yield store, display, meta
49
+ def __iter__(self) -> Generator[tuple[str, str, str]]:
50
+ yield from self.choices
@@ -36,4 +36,4 @@ class SliderWidget(Input):
36
36
  return context
37
37
 
38
38
  class Media:
39
- css = {"all": ("edc_model_fields/slider.css",)}
39
+ css = {"all": ("edc_model_fields/slider.css",)} # noqa: RUF012
@@ -53,7 +53,7 @@ class BaseModelFormMixin(ReportDatetimeModelFormMixin):
53
53
  get_registered_subject_model_cls().objects.get(
54
54
  subject_identifier=self.get_subject_identifier()
55
55
  )
56
- except ObjectDoesNotExist:
56
+ except ObjectDoesNotExist as e:
57
57
  raise ValidationError(
58
58
  {
59
59
  "subject_identifier": (
@@ -61,4 +61,4 @@ class BaseModelFormMixin(ReportDatetimeModelFormMixin):
61
61
  f"Got {self.get_subject_identifier()}."
62
62
  )
63
63
  }
64
- )
64
+ ) from e
@@ -50,10 +50,10 @@ class InlineModelFormMixin:
50
50
  if dte_as_str:
51
51
  try:
52
52
  dte = datetime.fromisoformat(dte_as_str)
53
- except ValueError:
53
+ except ValueError as e:
54
54
  raise forms.ValidationError(
55
55
  f"{field_label}: Invalid date or date format. Got {dte_as_str}"
56
- )
56
+ ) from e
57
57
  else:
58
58
  if dte.astimezone(ZoneInfo(settings.TIME_ZONE)) > self.cleaned_data.get(
59
59
  "report_datetime"
@@ -12,8 +12,8 @@ from django.db import OperationalError
12
12
  from django.db.models import QuerySet
13
13
  from django_crypto_fields.utils import get_encrypted_fields, has_encrypted_fields
14
14
  from django_pandas.io import read_frame
15
- from pandas import Series
16
15
 
16
+ from edc_constants.constants import NULL_STRING
17
17
  from edc_lab.models import Panel
18
18
  from edc_list_data.model_mixins import ListModelMixin, ListUuidModelMixin
19
19
 
@@ -228,15 +228,15 @@ class ModelToDataframe:
228
228
  dataframe = dataframe.merge(df_m2m, on="id", how="left")
229
229
  return dataframe
230
230
 
231
- def _clean_chars(self, s: Series) -> Series:
232
- if not s.empty:
231
+ def _clean_chars(self, s: str) -> str:
232
+ if s:
233
233
  for k, v in self.illegal_chars.items():
234
234
  try:
235
235
  s = s.replace(k, v)
236
236
  except (AttributeError, TypeError):
237
237
  break
238
238
  return s
239
- return np.nan
239
+ return NULL_STRING
240
240
 
241
241
  def move_sys_columns_to_end(self, columns: dict[str, str]) -> dict[str, str]:
242
242
  system_columns = [
@@ -10,6 +10,8 @@ from ..exceptions import ValueBoundryError
10
10
  from ..formula import clean_and_validate_phrase
11
11
  from .reference_range_collection import ReferenceRangeCollection
12
12
 
13
+ MAX_AGE = 130.0
14
+
13
15
 
14
16
  class ReferenceModelMixin(models.Model):
15
17
  reference_range_collection = models.ForeignKey(
@@ -117,12 +119,12 @@ class ReferenceModelMixin(models.Model):
117
119
  )
118
120
  rdelta = age(dob, report_datetime)
119
121
  age_value = getattr(rdelta, age_units)
120
- if not isinstance(age_value, (int, float)) or not (0.0 <= age_value <= 130.0):
122
+ if not isinstance(age_value, (int, float)) or not (0.0 <= age_value <= MAX_AGE):
121
123
  raise ValueError(f"Invalid age value. Got {age_value}.")
122
124
  age_condition_str = self.age_phrase % dict(age_value=age_value)
123
125
  if not re.match(pattern, age_condition_str):
124
126
  raise ValueError(f"Invalid age condition string. Got {age_condition_str}.")
125
- if not eval(age_condition_str): # nosec B307
127
+ if not eval(age_condition_str): # noqa: S307
126
128
  raise ValueBoundryError(
127
129
  f"Age is out of bounds. See {self}. Got AGE={age_value} {age_units}."
128
130
  )
@@ -41,12 +41,13 @@ class UserFormResponse:
41
41
  class ReferenceRangeEvaluator:
42
42
  def __init__(
43
43
  self,
44
- reference_range_collection_name=None,
45
- cleaned_data: dict = None,
46
- gender: str = None,
47
- dob: date = None,
48
- report_datetime: datetime = None,
49
- age_units: str = None,
44
+ *,
45
+ reference_range_collection_name,
46
+ cleaned_data: dict,
47
+ gender: str,
48
+ dob: date,
49
+ report_datetime: datetime,
50
+ age_units: str,
50
51
  value_field_suffix: str | None = None,
51
52
  **extra_options,
52
53
  ):
@@ -55,7 +56,7 @@ class ReferenceRangeEvaluator:
55
56
  ).exists():
56
57
  raise forms.ValidationError(
57
58
  {
58
- "Invalid reference range collection. "
59
+ "__all__": "Invalid reference range collection. "
59
60
  f"Got '{reference_range_collection_name}'"
60
61
  },
61
62
  code=INVALID_REFERENCE,
@@ -121,7 +122,7 @@ class ReferenceRangeEvaluator:
121
122
  word="reportable",
122
123
  )
123
124
 
124
- def _grade_or_check_normal_range(self, utest_id, value, field):
125
+ def _grade_or_check_normal_range(self, utest_id: str, value: int | float, field: str):
125
126
  """Evaluate a single result value.
126
127
 
127
128
  Grading is done first. If the value is not gradeable,
@@ -198,11 +199,17 @@ class ReferenceRangeEvaluator:
198
199
  {f"{utest_id}_reportable": "Invalid. Expected 'No' or 'Not applicable'."}
199
200
  )
200
201
  self._check_normal_range(
201
- utest_id, value, field, grading_data, user_form_response, opts
202
+ utest_id=utest_id,
203
+ value=value,
204
+ field=field,
205
+ grading_data=grading_data,
206
+ user_form_response=user_form_response,
207
+ opts=opts,
202
208
  )
203
209
 
204
210
  def _check_normal_range(
205
211
  self,
212
+ *,
206
213
  utest_id: str,
207
214
  value: int | float,
208
215
  field: str,
@@ -217,7 +224,7 @@ class ReferenceRangeEvaluator:
217
224
  **opts,
218
225
  )
219
226
  except NotEvaluated as e:
220
- raise forms.ValidationError({field: str(e)})
227
+ raise forms.ValidationError({field: str(e)}) from e
221
228
  else:
222
229
  try:
223
230
  is_normal = normal_data.value_in_normal_range_or_raise(
@@ -268,12 +275,7 @@ class ReferenceRangeEvaluator:
268
275
  )
269
276
 
270
277
  def _validate_final_assessment(
271
- self,
272
- field: str = None,
273
- responses: list[str] = None,
274
- suffix: str = None,
275
- word: str = None,
276
- utest_id: str = None,
278
+ self, *, field: str, suffix: str, word: str, responses: list[str] | None = None
277
279
  ):
278
280
  """Common code to validate fields `results_abnormal`
279
281
  and `results_reportable`.
@@ -295,6 +297,5 @@ class ReferenceRangeEvaluator:
295
297
  raise forms.ValidationError(
296
298
  {field: f"{len(answers_as_bool)} of the above results {are} {word}"}
297
299
  )
298
- elif self.cleaned_data.get(field) == YES:
299
- if not any(answers_as_bool):
300
- raise forms.ValidationError({field: f"None of the above results are {word}"})
300
+ elif self.cleaned_data.get(field) == YES and not any(answers_as_bool):
301
+ raise forms.ValidationError({field: f"None of the above results are {word}"})
@@ -158,7 +158,7 @@ def off_all_schedules_or_raise(subject_identifier: str = None):
158
158
  schedule.offschedule_model_cls.objects.get(
159
159
  subject_identifier=subject_identifier
160
160
  )
161
- except ObjectDoesNotExist:
161
+ except ObjectDoesNotExist as e:
162
162
  model_name = schedule.offschedule_model_cls()._meta.verbose_name.title()
163
163
  raise OffScheduleError(
164
164
  f"Subject cannot be taken off study. Subject is still on a "
@@ -166,7 +166,7 @@ def off_all_schedules_or_raise(subject_identifier: str = None):
166
166
  f"{schedule.name}. "
167
167
  f"Complete the offschedule form `{model_name}` first. "
168
168
  f"Subject identifier='{subject_identifier}', "
169
- )
169
+ ) from e
170
170
  return True
171
171
 
172
172