clinicedc 2.0.11__py3-none-any.whl → 2.0.13__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.11.dist-info → clinicedc-2.0.13.dist-info}/METADATA +2 -1
- {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/RECORD +137 -24
- edc_action_item/migrations/0017_auto_20190305_0123.py +1 -1
- edc_action_item/migrations/0030_edcpermissions.py +1 -1
- edc_action_item/migrations/0041_alter_actionitem_revision_alter_actiontype_revision_and_more.py +86 -0
- edc_adverse_event/migrations/0001_initial.py +1 -1
- edc_adverse_event/migrations/0002_auto_20190802_0059.py +1 -1
- edc_adverse_event/migrations/0008_auto_20220825_0451.py +1 -1
- edc_adverse_event/migrations/0009_auto_20220907_0157.py +1 -1
- edc_adverse_event/migrations/0017_alter_aeactionclassification_revision_and_more.py +77 -0
- edc_adverse_event/model_mixins/hospitaization/hospitalization_model_mixin.py +1 -3
- edc_analytics/__init__.py +3 -0
- edc_analytics/apps.py +8 -0
- edc_analytics/constants.py +26 -0
- edc_analytics/custom_tables/__init__.py +11 -0
- edc_analytics/custom_tables/age.py +72 -0
- edc_analytics/custom_tables/art.py +88 -0
- edc_analytics/custom_tables/bmi.py +125 -0
- edc_analytics/custom_tables/bp.py +103 -0
- edc_analytics/custom_tables/fasting.py +126 -0
- edc_analytics/custom_tables/fbg.py +98 -0
- edc_analytics/custom_tables/fbg_ogtt.py +384 -0
- edc_analytics/custom_tables/gender.py +12 -0
- edc_analytics/custom_tables/hba1c.py +87 -0
- edc_analytics/custom_tables/ogtt.py +95 -0
- edc_analytics/custom_tables/waist.py +105 -0
- edc_analytics/data.py +36 -0
- edc_analytics/row/__init__.py +4 -0
- edc_analytics/row/row_definition.py +43 -0
- edc_analytics/row/row_definitions.py +32 -0
- edc_analytics/row/row_statistics.py +88 -0
- edc_analytics/row/row_statistics_with_gender.py +115 -0
- edc_analytics/stata/__init__.py +1 -0
- edc_analytics/stata/get_stata_labels_from_model.py +44 -0
- edc_analytics/styler.py +93 -0
- edc_analytics/table.py +108 -0
- edc_analytics/urls.py +6 -0
- edc_appointment/migrations/0018_auto_20190305_0123.py +1 -1
- edc_appointment/migrations/0051_alter_appointment_revision_and_more.py +38 -0
- edc_auth/migrations/0001_squashed_0033_alter_userprofile_is_multisite_viewer.py +1 -1
- edc_auth/migrations/0012_auto_20191026_0034.py +1 -1
- edc_auth/migrations/0013_auto_20191026_0055.py +1 -1
- edc_auth/migrations/0025_permissions.py +1 -1
- edc_auth/migrations/0037_alter_edcpermissions_revision_alter_role_revision.py +38 -0
- edc_consent/migrations/0001_initial.py +1 -1
- edc_consent/migrations/0007_alter_edcpermissions_revision.py +26 -0
- edc_crf/migrations/0010_alter_crfstatus_revision.py +26 -0
- edc_dashboard/migrations/0001_initial.py +1 -1
- edc_dashboard/migrations/0007_alter_edcpermissions_revision.py +26 -0
- edc_data_manager/migrations/0001_initial.py +1 -1
- edc_data_manager/migrations/0025_edcpermissions.py +1 -1
- edc_data_manager/migrations/0042_alter_datadictionary_revision_and_more.py +98 -0
- edc_dx/__init__.py +6 -0
- edc_dx/apps.py +5 -0
- edc_dx/diagnoses.py +250 -0
- edc_dx/form_validators/__init__.py +2 -0
- edc_dx/form_validators/diagnosis_form_validator_mixin.py +54 -0
- edc_dx/form_validators/result_form_validator_mixin.py +65 -0
- edc_dx/utils.py +42 -0
- edc_dx_review/__init__.py +0 -0
- edc_dx_review/apps.py +5 -0
- edc_dx_review/auth_objects.py +13 -0
- edc_dx_review/auths.py +12 -0
- edc_dx_review/choices.py +24 -0
- edc_dx_review/constants.py +7 -0
- edc_dx_review/fieldsets.py +47 -0
- edc_dx_review/form_mixins/__init__.py +3 -0
- edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py +25 -0
- edc_dx_review/form_validator_mixins/__init__.py +6 -0
- edc_dx_review/form_validator_mixins/clinical_review_baseline_form_validator_mixin.py +7 -0
- edc_dx_review/form_validator_mixins/clinical_review_followup_form_validator_mixin.py +25 -0
- edc_dx_review/list_data.py +19 -0
- edc_dx_review/medical_date.py +195 -0
- edc_dx_review/migrations/0001_initial.py +307 -0
- edc_dx_review/migrations/0002_diagnosislocations_extra_value_and_more.py +32 -0
- edc_dx_review/migrations/0003_alter_diagnosislocations_options_and_more.py +148 -0
- edc_dx_review/migrations/0004_remove_diagnosislocations_edc_dx_revi_name_a39b40_idx_and_more.py +20 -0
- edc_dx_review/migrations/__init__.py +0 -0
- edc_dx_review/model_mixins/__init__.py +20 -0
- edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py +25 -0
- edc_dx_review/model_mixins/clinical_review_followup/__init__.py +5 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py +54 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py +54 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py +54 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py +56 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py +25 -0
- edc_dx_review/model_mixins/dx_location_model_mixin.py +17 -0
- edc_dx_review/model_mixins/factory/__init__.py +4 -0
- edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py +55 -0
- edc_dx_review/model_mixins/factory/calculate_date.py +43 -0
- edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py +97 -0
- edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py +39 -0
- edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py +69 -0
- edc_dx_review/model_mixins/followup_review/__init__.py +2 -0
- edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py +22 -0
- edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py +32 -0
- edc_dx_review/model_mixins/initial_review/__init__.py +6 -0
- edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py +34 -0
- edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py +119 -0
- edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py +42 -0
- edc_dx_review/models.py +20 -0
- edc_dx_review/radio_fields.py +30 -0
- edc_dx_review/utils.py +220 -0
- edc_export/migrations/0004_auto_20190305_0123.py +1 -1
- edc_export/migrations/0013_edcpermissions.py +1 -1
- edc_export/migrations/0024_alter_datarequest_revision_and_more.py +170 -0
- edc_facility/migrations/0005_healthfacility_healthfacilitytypes_and_more.py +1 -1
- edc_facility/migrations/0018_alter_healthfacility_revision_and_more.py +38 -0
- edc_form_runners/migrations/0006_alter_issue_revision.py +26 -0
- edc_identifier/migrations/0012_alter_identifiermodel_revision.py +26 -0
- edc_lab/migrations/0039_alter_aliquot_revision_alter_box_revision_and_more.py +269 -0
- edc_lab_dashboard/migrations/0006_alter_edcpermissions_revision.py +26 -0
- edc_label/migrations/0008_alter_zpllabeltemplates_revision.py +26 -0
- edc_listboard/migrations/0008_alter_listboard_revision.py +26 -0
- edc_locator/migrations/0042_alter_historicalsubjectlocator_revision_and_more.py +38 -0
- edc_metadata/migrations/0032_alter_crfmetadata_revision_and_more.py +38 -0
- edc_navbar/migrations/0010_alter_edcpermissions_revision.py +26 -0
- edc_notification/migrations/0012_alter_notification_revision.py +26 -0
- edc_offstudy/migrations/0025_alter_historicalsubjectoffstudy_revision_and_more.py +41 -0
- edc_pharmacy/migrations/0091_alter_allocation_revision_alter_assignment_revision_and_more.py +794 -0
- edc_protocol_incident/migrations/0026_alter_historicalprotocoldeviationviolation_revision_and_more.py +65 -0
- edc_pylabels/migrations/0014_alter_labelconfiguration_revision.py +26 -0
- edc_qareports/migrations/0021_alter_edcpermissions_revision_alter_note_revision.py +38 -0
- edc_randomization/migrations/0015_alter_edcpermissions_revision_and_more.py +50 -0
- edc_refusal/migrations/0014_alter_historicalsubjectrefusal_revision_and_more.py +38 -0
- edc_registration/migrations/0034_alter_historicalregisteredsubject_revision_and_more.py +41 -0
- edc_reportable/migrations/0008_alter_gradingdata_revision_and_more.py +110 -0
- edc_review_dashboard/migrations/0007_alter_edcpermissions_revision.py +26 -0
- edc_screening/migrations/0006_alter_edcpermissions_revision.py +26 -0
- edc_sites/migrations/0011_alter_edcpermissions_revision.py +26 -0
- edc_subject_dashboard/migrations/0006_alter_edcpermissions_revision.py +26 -0
- edc_unblinding/migrations/0016_alter_historicalunblindingrequest_revision_and_more.py +65 -0
- edc_visit_schedule/migrations/0021_alter_historicalonschedule_revision_and_more.py +89 -0
- edc_visit_tracking/migrations/0011_alter_historicalsubjectvisit_revision_and_more.py +65 -0
- edc_vitals/model_mixins/blood_pressure_model_mixin.py +1 -0
- {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from edc_constants.choices import YES_NO_NA
|
|
3
|
+
from edc_constants.constants import NOT_APPLICABLE
|
|
4
|
+
|
|
5
|
+
from .followup_review_model_mixin import FollowupReviewModelMixin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HivFollowupReviewModelMixin(FollowupReviewModelMixin, models.Model):
|
|
9
|
+
dx = models.CharField(
|
|
10
|
+
verbose_name="Has the patient been infected with HIV?",
|
|
11
|
+
max_length=15,
|
|
12
|
+
choices=YES_NO_NA,
|
|
13
|
+
default=NOT_APPLICABLE,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
arv_initiated = models.CharField(
|
|
17
|
+
verbose_name="Has the patient started antiretroviral therapy (ART)?",
|
|
18
|
+
max_length=15,
|
|
19
|
+
choices=YES_NO_NA,
|
|
20
|
+
default=NOT_APPLICABLE,
|
|
21
|
+
help_text="Select `not applicable` if previously reported.",
|
|
22
|
+
)
|
|
23
|
+
arv_initiation_actual_date = models.DateField(
|
|
24
|
+
verbose_name="Date started antiretroviral therapy (ART)",
|
|
25
|
+
null=True,
|
|
26
|
+
blank=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
class Meta(FollowupReviewModelMixin.Meta):
|
|
30
|
+
abstract = True
|
|
31
|
+
verbose_name = "HIV Review"
|
|
32
|
+
verbose_name_plural = "HIV Review"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from edc_constants.choices import YES_NO
|
|
3
|
+
from edc_constants.constants import NOT_APPLICABLE
|
|
4
|
+
|
|
5
|
+
from ...choices import CHOL_MANAGEMENT
|
|
6
|
+
from ..dx_location_model_mixin import DxLocationModelMixin
|
|
7
|
+
from .ncd_initial_review_model_mixin import NcdInitialReviewModelMixin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CholInitialReviewModelMixin(
|
|
11
|
+
DxLocationModelMixin,
|
|
12
|
+
NcdInitialReviewModelMixin,
|
|
13
|
+
):
|
|
14
|
+
ncd_condition_label = "cholesterol"
|
|
15
|
+
|
|
16
|
+
managed_by = models.CharField(
|
|
17
|
+
verbose_name="How is the patient's cholesterol managed?",
|
|
18
|
+
max_length=25,
|
|
19
|
+
choices=CHOL_MANAGEMENT,
|
|
20
|
+
default=NOT_APPLICABLE,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
chol_performed = models.CharField(
|
|
24
|
+
verbose_name=(
|
|
25
|
+
"Has the patient had their cholesterol measured in the last few months?"
|
|
26
|
+
),
|
|
27
|
+
max_length=15,
|
|
28
|
+
choices=YES_NO,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
class Meta:
|
|
32
|
+
abstract = True
|
|
33
|
+
verbose_name = "High Cholesterol Initial Review"
|
|
34
|
+
verbose_name_plural = "High Cholesterol Initial Reviews"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
2
|
+
from django.db import models
|
|
3
|
+
from edc_constants.choices import YES_NO, YES_NO_NA, YES_NO_PENDING_NA
|
|
4
|
+
from edc_constants.constants import NOT_APPLICABLE, YES
|
|
5
|
+
from edc_lab.choices import VL_QUANTIFIER_NA
|
|
6
|
+
from edc_model import estimated_date_from_ago
|
|
7
|
+
from edc_model import models as edc_models
|
|
8
|
+
from edc_model.validators import date_not_future
|
|
9
|
+
from edc_reportable import CELLS_PER_MILLIMETER_CUBED_DISPLAY, COPIES_PER_MILLILITER
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HivArvInitiationModelMixin(models.Model):
|
|
13
|
+
arv_initiated = models.CharField(
|
|
14
|
+
verbose_name="Has the patient started antiretroviral therapy (ART)?",
|
|
15
|
+
max_length=15,
|
|
16
|
+
choices=YES_NO,
|
|
17
|
+
default=YES,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
arv_initiation_ago = edc_models.DurationYMDField(
|
|
21
|
+
verbose_name="How long ago did the patient start ART?",
|
|
22
|
+
null=True,
|
|
23
|
+
blank=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
arv_initiation_actual_date = models.DateField(
|
|
27
|
+
verbose_name="Date started antiretroviral therapy (ART)",
|
|
28
|
+
validators=[date_not_future],
|
|
29
|
+
null=True,
|
|
30
|
+
blank=True,
|
|
31
|
+
help_text="Calculated based on response to `arv_initiation_ago`",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
arv_initiation_estimated_date = models.DateField(
|
|
35
|
+
verbose_name="Estimated Date started antiretroviral therapy (ART)",
|
|
36
|
+
validators={date_not_future},
|
|
37
|
+
null=True,
|
|
38
|
+
editable=False,
|
|
39
|
+
help_text="Calculated based on response to `arv_initiation_ago`",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
arv_initiation_date_is_estimated = models.CharField(
|
|
43
|
+
verbose_name="Was the ART start date estimated?",
|
|
44
|
+
max_length=15,
|
|
45
|
+
choices=YES_NO,
|
|
46
|
+
default=YES,
|
|
47
|
+
editable=False,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def save(self, *args, **kwargs):
|
|
51
|
+
self.dx_estimated_date = estimated_date_from_ago(instance=self, ago_field="dx_ago")
|
|
52
|
+
self.arv_initiation_estimated_date = estimated_date_from_ago(
|
|
53
|
+
instance=self, ago_field="arv_initiation_ago"
|
|
54
|
+
)
|
|
55
|
+
super().save(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def best_art_initiation_date(self):
|
|
59
|
+
return self.arv_initiation_actual_date or self.arv_initiation_estimated_date
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
abstract = True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class HivArvMonitoringModelMixin(models.Model):
|
|
66
|
+
# Viral Load
|
|
67
|
+
has_vl = models.CharField(
|
|
68
|
+
verbose_name="Is the patient's most recent viral load result available?",
|
|
69
|
+
max_length=25,
|
|
70
|
+
choices=YES_NO_PENDING_NA,
|
|
71
|
+
default=NOT_APPLICABLE,
|
|
72
|
+
)
|
|
73
|
+
vl = models.IntegerField(
|
|
74
|
+
verbose_name="Most recent viral load",
|
|
75
|
+
validators=[MinValueValidator(0), MaxValueValidator(9999999)],
|
|
76
|
+
null=True,
|
|
77
|
+
blank=True,
|
|
78
|
+
help_text=COPIES_PER_MILLILITER,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
vl_quantifier = models.CharField(
|
|
82
|
+
max_length=10,
|
|
83
|
+
choices=VL_QUANTIFIER_NA,
|
|
84
|
+
null=True,
|
|
85
|
+
blank=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
vl_date = models.DateField(
|
|
89
|
+
verbose_name="Date of most recent viral load",
|
|
90
|
+
validators=[date_not_future],
|
|
91
|
+
null=True,
|
|
92
|
+
blank=True,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# CD4
|
|
96
|
+
has_cd4 = models.CharField(
|
|
97
|
+
verbose_name="Is the patient's most recent CD4 result available?",
|
|
98
|
+
max_length=25,
|
|
99
|
+
choices=YES_NO_NA,
|
|
100
|
+
default=NOT_APPLICABLE,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
cd4 = models.IntegerField(
|
|
104
|
+
verbose_name="Most recent CD4",
|
|
105
|
+
validators=[MinValueValidator(0), MaxValueValidator(3000)],
|
|
106
|
+
null=True,
|
|
107
|
+
blank=True,
|
|
108
|
+
help_text=CELLS_PER_MILLIMETER_CUBED_DISPLAY,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
cd4_date = models.DateField(
|
|
112
|
+
verbose_name="Date of most recent CD4",
|
|
113
|
+
validators=[date_not_future],
|
|
114
|
+
null=True,
|
|
115
|
+
blank=True,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
class Meta:
|
|
119
|
+
abstract = True
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from edc_constants.choices import YES_NO
|
|
3
|
+
from edc_constants.constants import YES
|
|
4
|
+
from edc_model import models as edc_models
|
|
5
|
+
from edc_model.utils import duration_to_date
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NcdInitialReviewModelMixin(models.Model):
|
|
9
|
+
ncd_condition_label: str = None
|
|
10
|
+
|
|
11
|
+
med_start_ago = edc_models.DurationYMDField(
|
|
12
|
+
verbose_name=(
|
|
13
|
+
"If the patient is taking medicines for ncd_condition_label, "
|
|
14
|
+
"how long have they been taking these?"
|
|
15
|
+
),
|
|
16
|
+
null=True,
|
|
17
|
+
blank=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
med_start_estimated_date = models.DateField(
|
|
21
|
+
verbose_name="Estimated medication start date",
|
|
22
|
+
null=True,
|
|
23
|
+
editable=False,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
med_start_date_is_estimated = models.CharField(
|
|
27
|
+
verbose_name="Was the medication start date estimated?",
|
|
28
|
+
max_length=15,
|
|
29
|
+
choices=YES_NO,
|
|
30
|
+
default=YES,
|
|
31
|
+
editable=False,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def save(self, *args, **kwargs):
|
|
35
|
+
if self.med_start_ago:
|
|
36
|
+
self.med_start_estimated_date = duration_to_date(
|
|
37
|
+
self.med_start_ago, self.report_datetime
|
|
38
|
+
)
|
|
39
|
+
super().save(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
class Meta:
|
|
42
|
+
abstract = True
|
edc_dx_review/models.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from edc_list_data.model_mixins import ListModelManager, ListModelMixin
|
|
2
|
+
from edc_model.models import HistoricalRecords
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ReasonsForTesting(ListModelMixin):
|
|
6
|
+
objects = ListModelManager()
|
|
7
|
+
history = HistoricalRecords()
|
|
8
|
+
|
|
9
|
+
class Meta(ListModelMixin.Meta):
|
|
10
|
+
verbose_name = "Reasons for Testing"
|
|
11
|
+
verbose_name_plural = "Reasons for Testing"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DiagnosisLocations(ListModelMixin):
|
|
15
|
+
objects = ListModelManager()
|
|
16
|
+
history = HistoricalRecords()
|
|
17
|
+
|
|
18
|
+
class Meta(ListModelMixin.Meta):
|
|
19
|
+
verbose_name = "Diagnosis Locations"
|
|
20
|
+
verbose_name_plural = "Diagnosis Locations"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
from edc_dx import get_diagnosis_labels_prefixes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_clinical_review_cond_radio_fields() -> Dict[str, int]:
|
|
8
|
+
radio_fields = {}
|
|
9
|
+
for prefix in get_diagnosis_labels_prefixes():
|
|
10
|
+
cond = prefix.lower()
|
|
11
|
+
radio_fields.update(
|
|
12
|
+
{
|
|
13
|
+
f"{cond}_dx": admin.VERTICAL,
|
|
14
|
+
f"{cond}_test": admin.VERTICAL,
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
return radio_fields
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_clinical_review_baseline_cond_radio_fields() -> Dict[str, int]:
|
|
21
|
+
radio_fields = {}
|
|
22
|
+
for prefix in get_diagnosis_labels_prefixes():
|
|
23
|
+
cond = prefix.lower()
|
|
24
|
+
radio_fields.update(
|
|
25
|
+
{
|
|
26
|
+
f"{cond}_dx": admin.VERTICAL,
|
|
27
|
+
f"{cond}_dx_at_screening": admin.VERTICAL,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
return radio_fields
|
edc_dx_review/utils.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from django import forms
|
|
7
|
+
from django.apps import apps as django_apps
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
10
|
+
from edc_constants.constants import HIV, YES
|
|
11
|
+
from edc_model.utils import model_exists_or_raise
|
|
12
|
+
from edc_visit_schedule.baseline import VisitScheduleBaselineError
|
|
13
|
+
from edc_visit_schedule.utils import is_baseline
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from edc_model.models import BaseUuidModel
|
|
17
|
+
from edc_visit_tracking.model_mixins import VisitTrackingCrfModelMixin
|
|
18
|
+
|
|
19
|
+
class CrfLikeModel(VisitTrackingCrfModelMixin, BaseUuidModel):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
EDC_DX_REVIEW_APP_LABEL = getattr(settings, "EDC_DX_REVIEW_APP_LABEL", "edc_dx_review")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ModelNotDefined(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaselineModelError(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_list_model_app():
|
|
35
|
+
return getattr(
|
|
36
|
+
settings, "EDC_DX_REVIEW_LIST_MODEL_APP_LABEL", settings.LIST_MODEL_APP_LABEL
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_clinical_review_baseline_model_cls():
|
|
41
|
+
clinicalreviewbaseline = get_extra_attrs().get(
|
|
42
|
+
"clinicalreviewbaseline", "clinicalreviewbaseline"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return django_apps.get_model(f"{EDC_DX_REVIEW_APP_LABEL}.{clinicalreviewbaseline}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_clinical_review_model_cls():
|
|
49
|
+
clinicalreview = get_extra_attrs().get("clinicalreview", "clinicalreview")
|
|
50
|
+
return django_apps.get_model(f"{EDC_DX_REVIEW_APP_LABEL}.{clinicalreview}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_medication_model_cls():
|
|
54
|
+
medications = get_extra_attrs().get("medications", "medications")
|
|
55
|
+
return django_apps.get_model(f"{EDC_DX_REVIEW_APP_LABEL}.{medications}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_initial_review_model_cls(prefix):
|
|
59
|
+
initialreview = get_extra_attrs().get("initialreview", "initialreview")
|
|
60
|
+
return django_apps.get_model(f"{EDC_DX_REVIEW_APP_LABEL}.{prefix.lower()}{initialreview}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_review_model_cls(prefix):
|
|
64
|
+
review = get_extra_attrs().get("review", "review")
|
|
65
|
+
return django_apps.get_model(f"{EDC_DX_REVIEW_APP_LABEL}.{prefix.lower()}{review}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_extra_attrs():
|
|
69
|
+
"""Settings from EDC_DX_REVIEW_EXTRA_ATTRS.
|
|
70
|
+
|
|
71
|
+
See model name suffixes used in model_cls getters in utils.py.
|
|
72
|
+
"""
|
|
73
|
+
extra_attrs = {
|
|
74
|
+
"clinicalreview": "clinicalreview",
|
|
75
|
+
"clinicalreviewbaseline": "clinicalreviewbaseline",
|
|
76
|
+
"initialreview": "initialreview",
|
|
77
|
+
"medications": "medications",
|
|
78
|
+
"review": "review",
|
|
79
|
+
}
|
|
80
|
+
try:
|
|
81
|
+
data = getattr(settings, "EDC_DX_REVIEW_EXTRA_ATTRS")
|
|
82
|
+
except AttributeError:
|
|
83
|
+
pass
|
|
84
|
+
else:
|
|
85
|
+
extra_attrs.update(data)
|
|
86
|
+
return extra_attrs
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def raise_if_clinical_review_does_not_exist(subject_visit) -> CrfLikeModel:
|
|
90
|
+
try:
|
|
91
|
+
baseline = is_baseline(instance=subject_visit)
|
|
92
|
+
except VisitScheduleBaselineError as e:
|
|
93
|
+
raise forms.ValidationError(str(e))
|
|
94
|
+
else:
|
|
95
|
+
if baseline:
|
|
96
|
+
model_cls = get_clinical_review_baseline_model_cls()
|
|
97
|
+
else:
|
|
98
|
+
model_cls = get_clinical_review_model_cls()
|
|
99
|
+
try:
|
|
100
|
+
obj = model_exists_or_raise(subject_visit=subject_visit, model_cls=model_cls)
|
|
101
|
+
except ObjectDoesNotExist:
|
|
102
|
+
raise forms.ValidationError(f"Complete {model_cls._meta.verbose_name} CRF first.")
|
|
103
|
+
return obj
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def raise_if_both_ago_and_actual_date(
|
|
107
|
+
cleaned_data: dict | None = None,
|
|
108
|
+
date_fld: str | None = None,
|
|
109
|
+
ago_fld: str | None = None,
|
|
110
|
+
dx_ago: str | None = None,
|
|
111
|
+
dx_date: date | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
if cleaned_data:
|
|
114
|
+
dx_ago = cleaned_data.get(date_fld)
|
|
115
|
+
dx_date = cleaned_data.get(ago_fld)
|
|
116
|
+
if dx_ago and dx_date:
|
|
117
|
+
raise forms.ValidationError(
|
|
118
|
+
{
|
|
119
|
+
ago_fld: (
|
|
120
|
+
"Date conflict. Do not provide a response "
|
|
121
|
+
"here if the date of diagnosis is available."
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def requires_clinical_review_at_baseline(subject_visit):
|
|
128
|
+
try:
|
|
129
|
+
get_clinical_review_baseline_model_cls().objects.get(
|
|
130
|
+
subject_visit__subject_identifier=subject_visit.subject_identifier
|
|
131
|
+
)
|
|
132
|
+
except ObjectDoesNotExist:
|
|
133
|
+
raise forms.ValidationError(
|
|
134
|
+
"Please complete the "
|
|
135
|
+
f"{get_clinical_review_baseline_model_cls()._meta.verbose_name} first."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def raise_if_initial_review_does_not_exist(subject_visit, prefix):
|
|
140
|
+
model_exists_or_raise(
|
|
141
|
+
subject_visit=subject_visit,
|
|
142
|
+
model_cls=get_initial_review_model_cls(prefix),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def raise_if_review_does_not_exist(subject_visit, prefix):
|
|
147
|
+
model_exists_or_raise(
|
|
148
|
+
subject_visit=subject_visit,
|
|
149
|
+
model_cls=get_review_model_cls(prefix),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def medications_exists_or_raise(subject_visit) -> bool:
|
|
154
|
+
if subject_visit:
|
|
155
|
+
try:
|
|
156
|
+
get_medication_model_cls().objects.get(subject_visit=subject_visit)
|
|
157
|
+
except ObjectDoesNotExist:
|
|
158
|
+
raise forms.ValidationError(
|
|
159
|
+
f"Complete the `{get_medication_model_cls()._meta.verbose_name}` CRF first."
|
|
160
|
+
)
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def is_rx_initiated(
|
|
165
|
+
subject_identifier: str, report_datetime: datetime, instance_id: str | None = None
|
|
166
|
+
) -> bool:
|
|
167
|
+
"""Return True if already initiated"""
|
|
168
|
+
try:
|
|
169
|
+
get_initial_review_model_cls(HIV).objects.get(
|
|
170
|
+
subject_visit__subject_identifier=subject_identifier,
|
|
171
|
+
report_datetime__lte=report_datetime,
|
|
172
|
+
rx_init=YES,
|
|
173
|
+
)
|
|
174
|
+
except ObjectDoesNotExist:
|
|
175
|
+
exclude = {}
|
|
176
|
+
if instance_id:
|
|
177
|
+
exclude = {"id": instance_id}
|
|
178
|
+
rx_initiated = (
|
|
179
|
+
get_review_model_cls(HIV)
|
|
180
|
+
.objects.filter(
|
|
181
|
+
subject_visit__subject_identifier=subject_identifier,
|
|
182
|
+
report_datetime__lte=report_datetime,
|
|
183
|
+
rx_init=YES,
|
|
184
|
+
)
|
|
185
|
+
.exclude(**exclude)
|
|
186
|
+
.exists()
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
rx_initiated = True
|
|
190
|
+
return rx_initiated
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def art_initiation_date(subject_identifier: str, report_datetime: datetime) -> date:
|
|
194
|
+
"""Returns date initiated on ART or None by querying
|
|
195
|
+
the HIV Initial Review and then the HIV Review.
|
|
196
|
+
"""
|
|
197
|
+
art_date = None
|
|
198
|
+
try:
|
|
199
|
+
initial_review = get_initial_review_model_cls(HIV).objects.get(
|
|
200
|
+
subject_visit__subject_identifier=subject_identifier,
|
|
201
|
+
report_datetime__lte=report_datetime,
|
|
202
|
+
)
|
|
203
|
+
except ObjectDoesNotExist:
|
|
204
|
+
pass
|
|
205
|
+
else:
|
|
206
|
+
if initial_review.arv_initiated == YES:
|
|
207
|
+
art_date = initial_review.best_art_initiation_date
|
|
208
|
+
else:
|
|
209
|
+
for review in (
|
|
210
|
+
get_review_model_cls(HIV)
|
|
211
|
+
.objects.filter(
|
|
212
|
+
subject_visit__subject_identifier=subject_identifier,
|
|
213
|
+
report_datetime__lte=report_datetime,
|
|
214
|
+
)
|
|
215
|
+
.order_by("-report_datetime")
|
|
216
|
+
):
|
|
217
|
+
if review.arv_initiated == YES:
|
|
218
|
+
art_date = review.arv_initiation_actual_date
|
|
219
|
+
break
|
|
220
|
+
return art_date
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import django_audit_fields.fields.hostname_modification_field
|
|
4
4
|
import django_audit_fields.fields.userfield
|
|
5
|
-
import
|
|
5
|
+
import django.utils.timezone
|
|
6
6
|
from django.db import migrations, models
|
|
7
7
|
import django.utils.timezone
|
|
8
8
|
|
|
@@ -4,7 +4,7 @@ import _socket
|
|
|
4
4
|
import django_audit_fields.fields.hostname_modification_field
|
|
5
5
|
import django_audit_fields.fields.userfield
|
|
6
6
|
import django_audit_fields.fields.uuid_auto_field
|
|
7
|
-
import
|
|
7
|
+
import django.utils.timezone
|
|
8
8
|
import django_revision.revision_field
|
|
9
9
|
from django.db import migrations, models
|
|
10
10
|
import django.utils.timezone
|