clinicedc 2.0.18__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.
- {clinicedc-2.0.18.dist-info → clinicedc-2.0.20.dist-info}/METADATA +9 -10
- {clinicedc-2.0.18.dist-info → clinicedc-2.0.20.dist-info}/RECORD +14 -14
- edc_egfr/calculators/egfr_cockcroft_gault.py +4 -5
- edc_model_admin/utils.py +3 -1
- edc_model_fields/utils.py +5 -4
- edc_model_fields/widgets/slider_widget.py +1 -1
- edc_model_form/mixins/base_model_form_mixin.py +2 -2
- edc_model_form/mixins/inline_model_form_mixin.py +2 -2
- edc_model_to_dataframe/model_to_dataframe.py +4 -4
- edc_reportable/models/reference_model_mixins.py +4 -2
- edc_reportable/reference_range_evaluator.py +20 -19
- edc_visit_schedule/utils.py +2 -2
- {clinicedc-2.0.18.dist-info → clinicedc-2.0.20.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.18.dist-info → clinicedc-2.0.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clinicedc
|
|
3
|
-
Version: 2.0.
|
|
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
|
-
|
|
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
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
1962
|
-
edc_model_form/mixins/inline_model_form_mixin.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
3410
|
-
clinicedc-2.0.
|
|
3411
|
-
clinicedc-2.0.
|
|
3412
|
-
clinicedc-2.0.
|
|
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
|
-
|
|
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(
|
|
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
|
|
48
|
-
|
|
49
|
-
yield store, display, meta
|
|
49
|
+
def __iter__(self) -> Generator[tuple[str, str, str]]:
|
|
50
|
+
yield from self.choices
|
|
@@ -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:
|
|
232
|
-
if
|
|
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
|
|
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 <=
|
|
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): #
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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,
|
|
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
|
-
|
|
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}"})
|
edc_visit_schedule/utils.py
CHANGED
|
@@ -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
|
|
|
File without changes
|
|
File without changes
|