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.

Files changed (137) hide show
  1. {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/METADATA +2 -1
  2. {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/RECORD +137 -24
  3. edc_action_item/migrations/0017_auto_20190305_0123.py +1 -1
  4. edc_action_item/migrations/0030_edcpermissions.py +1 -1
  5. edc_action_item/migrations/0041_alter_actionitem_revision_alter_actiontype_revision_and_more.py +86 -0
  6. edc_adverse_event/migrations/0001_initial.py +1 -1
  7. edc_adverse_event/migrations/0002_auto_20190802_0059.py +1 -1
  8. edc_adverse_event/migrations/0008_auto_20220825_0451.py +1 -1
  9. edc_adverse_event/migrations/0009_auto_20220907_0157.py +1 -1
  10. edc_adverse_event/migrations/0017_alter_aeactionclassification_revision_and_more.py +77 -0
  11. edc_adverse_event/model_mixins/hospitaization/hospitalization_model_mixin.py +1 -3
  12. edc_analytics/__init__.py +3 -0
  13. edc_analytics/apps.py +8 -0
  14. edc_analytics/constants.py +26 -0
  15. edc_analytics/custom_tables/__init__.py +11 -0
  16. edc_analytics/custom_tables/age.py +72 -0
  17. edc_analytics/custom_tables/art.py +88 -0
  18. edc_analytics/custom_tables/bmi.py +125 -0
  19. edc_analytics/custom_tables/bp.py +103 -0
  20. edc_analytics/custom_tables/fasting.py +126 -0
  21. edc_analytics/custom_tables/fbg.py +98 -0
  22. edc_analytics/custom_tables/fbg_ogtt.py +384 -0
  23. edc_analytics/custom_tables/gender.py +12 -0
  24. edc_analytics/custom_tables/hba1c.py +87 -0
  25. edc_analytics/custom_tables/ogtt.py +95 -0
  26. edc_analytics/custom_tables/waist.py +105 -0
  27. edc_analytics/data.py +36 -0
  28. edc_analytics/row/__init__.py +4 -0
  29. edc_analytics/row/row_definition.py +43 -0
  30. edc_analytics/row/row_definitions.py +32 -0
  31. edc_analytics/row/row_statistics.py +88 -0
  32. edc_analytics/row/row_statistics_with_gender.py +115 -0
  33. edc_analytics/stata/__init__.py +1 -0
  34. edc_analytics/stata/get_stata_labels_from_model.py +44 -0
  35. edc_analytics/styler.py +93 -0
  36. edc_analytics/table.py +108 -0
  37. edc_analytics/urls.py +6 -0
  38. edc_appointment/migrations/0018_auto_20190305_0123.py +1 -1
  39. edc_appointment/migrations/0051_alter_appointment_revision_and_more.py +38 -0
  40. edc_auth/migrations/0001_squashed_0033_alter_userprofile_is_multisite_viewer.py +1 -1
  41. edc_auth/migrations/0012_auto_20191026_0034.py +1 -1
  42. edc_auth/migrations/0013_auto_20191026_0055.py +1 -1
  43. edc_auth/migrations/0025_permissions.py +1 -1
  44. edc_auth/migrations/0037_alter_edcpermissions_revision_alter_role_revision.py +38 -0
  45. edc_consent/migrations/0001_initial.py +1 -1
  46. edc_consent/migrations/0007_alter_edcpermissions_revision.py +26 -0
  47. edc_crf/migrations/0010_alter_crfstatus_revision.py +26 -0
  48. edc_dashboard/migrations/0001_initial.py +1 -1
  49. edc_dashboard/migrations/0007_alter_edcpermissions_revision.py +26 -0
  50. edc_data_manager/migrations/0001_initial.py +1 -1
  51. edc_data_manager/migrations/0025_edcpermissions.py +1 -1
  52. edc_data_manager/migrations/0042_alter_datadictionary_revision_and_more.py +98 -0
  53. edc_dx/__init__.py +6 -0
  54. edc_dx/apps.py +5 -0
  55. edc_dx/diagnoses.py +250 -0
  56. edc_dx/form_validators/__init__.py +2 -0
  57. edc_dx/form_validators/diagnosis_form_validator_mixin.py +54 -0
  58. edc_dx/form_validators/result_form_validator_mixin.py +65 -0
  59. edc_dx/utils.py +42 -0
  60. edc_dx_review/__init__.py +0 -0
  61. edc_dx_review/apps.py +5 -0
  62. edc_dx_review/auth_objects.py +13 -0
  63. edc_dx_review/auths.py +12 -0
  64. edc_dx_review/choices.py +24 -0
  65. edc_dx_review/constants.py +7 -0
  66. edc_dx_review/fieldsets.py +47 -0
  67. edc_dx_review/form_mixins/__init__.py +3 -0
  68. edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py +25 -0
  69. edc_dx_review/form_validator_mixins/__init__.py +6 -0
  70. edc_dx_review/form_validator_mixins/clinical_review_baseline_form_validator_mixin.py +7 -0
  71. edc_dx_review/form_validator_mixins/clinical_review_followup_form_validator_mixin.py +25 -0
  72. edc_dx_review/list_data.py +19 -0
  73. edc_dx_review/medical_date.py +195 -0
  74. edc_dx_review/migrations/0001_initial.py +307 -0
  75. edc_dx_review/migrations/0002_diagnosislocations_extra_value_and_more.py +32 -0
  76. edc_dx_review/migrations/0003_alter_diagnosislocations_options_and_more.py +148 -0
  77. edc_dx_review/migrations/0004_remove_diagnosislocations_edc_dx_revi_name_a39b40_idx_and_more.py +20 -0
  78. edc_dx_review/migrations/__init__.py +0 -0
  79. edc_dx_review/model_mixins/__init__.py +20 -0
  80. edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py +25 -0
  81. edc_dx_review/model_mixins/clinical_review_followup/__init__.py +5 -0
  82. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py +54 -0
  83. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py +54 -0
  84. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py +54 -0
  85. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py +56 -0
  86. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py +25 -0
  87. edc_dx_review/model_mixins/dx_location_model_mixin.py +17 -0
  88. edc_dx_review/model_mixins/factory/__init__.py +4 -0
  89. edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py +55 -0
  90. edc_dx_review/model_mixins/factory/calculate_date.py +43 -0
  91. edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py +97 -0
  92. edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py +39 -0
  93. edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py +69 -0
  94. edc_dx_review/model_mixins/followup_review/__init__.py +2 -0
  95. edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py +22 -0
  96. edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py +32 -0
  97. edc_dx_review/model_mixins/initial_review/__init__.py +6 -0
  98. edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py +34 -0
  99. edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py +119 -0
  100. edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py +42 -0
  101. edc_dx_review/models.py +20 -0
  102. edc_dx_review/radio_fields.py +30 -0
  103. edc_dx_review/utils.py +220 -0
  104. edc_export/migrations/0004_auto_20190305_0123.py +1 -1
  105. edc_export/migrations/0013_edcpermissions.py +1 -1
  106. edc_export/migrations/0024_alter_datarequest_revision_and_more.py +170 -0
  107. edc_facility/migrations/0005_healthfacility_healthfacilitytypes_and_more.py +1 -1
  108. edc_facility/migrations/0018_alter_healthfacility_revision_and_more.py +38 -0
  109. edc_form_runners/migrations/0006_alter_issue_revision.py +26 -0
  110. edc_identifier/migrations/0012_alter_identifiermodel_revision.py +26 -0
  111. edc_lab/migrations/0039_alter_aliquot_revision_alter_box_revision_and_more.py +269 -0
  112. edc_lab_dashboard/migrations/0006_alter_edcpermissions_revision.py +26 -0
  113. edc_label/migrations/0008_alter_zpllabeltemplates_revision.py +26 -0
  114. edc_listboard/migrations/0008_alter_listboard_revision.py +26 -0
  115. edc_locator/migrations/0042_alter_historicalsubjectlocator_revision_and_more.py +38 -0
  116. edc_metadata/migrations/0032_alter_crfmetadata_revision_and_more.py +38 -0
  117. edc_navbar/migrations/0010_alter_edcpermissions_revision.py +26 -0
  118. edc_notification/migrations/0012_alter_notification_revision.py +26 -0
  119. edc_offstudy/migrations/0025_alter_historicalsubjectoffstudy_revision_and_more.py +41 -0
  120. edc_pharmacy/migrations/0091_alter_allocation_revision_alter_assignment_revision_and_more.py +794 -0
  121. edc_protocol_incident/migrations/0026_alter_historicalprotocoldeviationviolation_revision_and_more.py +65 -0
  122. edc_pylabels/migrations/0014_alter_labelconfiguration_revision.py +26 -0
  123. edc_qareports/migrations/0021_alter_edcpermissions_revision_alter_note_revision.py +38 -0
  124. edc_randomization/migrations/0015_alter_edcpermissions_revision_and_more.py +50 -0
  125. edc_refusal/migrations/0014_alter_historicalsubjectrefusal_revision_and_more.py +38 -0
  126. edc_registration/migrations/0034_alter_historicalregisteredsubject_revision_and_more.py +41 -0
  127. edc_reportable/migrations/0008_alter_gradingdata_revision_and_more.py +110 -0
  128. edc_review_dashboard/migrations/0007_alter_edcpermissions_revision.py +26 -0
  129. edc_screening/migrations/0006_alter_edcpermissions_revision.py +26 -0
  130. edc_sites/migrations/0011_alter_edcpermissions_revision.py +26 -0
  131. edc_subject_dashboard/migrations/0006_alter_edcpermissions_revision.py +26 -0
  132. edc_unblinding/migrations/0016_alter_historicalunblindingrequest_revision_and_more.py +65 -0
  133. edc_visit_schedule/migrations/0021_alter_historicalonschedule_revision_and_more.py +89 -0
  134. edc_visit_tracking/migrations/0011_alter_historicalsubjectvisit_revision_and_more.py +65 -0
  135. edc_vitals/model_mixins/blood_pressure_model_mixin.py +1 -0
  136. {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/WHEEL +0 -0
  137. {clinicedc-2.0.11.dist-info → clinicedc-2.0.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,20 @@
1
+ # Generated by Django 4.2.7 on 2023-12-04 22:19
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("edc_dx_review", "0003_alter_diagnosislocations_options_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.RemoveIndex(
13
+ model_name="diagnosislocations",
14
+ name="edc_dx_revi_name_a39b40_idx",
15
+ ),
16
+ migrations.RemoveIndex(
17
+ model_name="reasonsfortesting",
18
+ name="edc_dx_revi_name_0b2844_idx",
19
+ ),
20
+ ]
File without changes
@@ -0,0 +1,20 @@
1
+ from .clinical_review_baseline_model_mixin import ClinicalReviewBaselineModelMixin
2
+ from .clinical_review_followup import (
3
+ ClinicalReviewCholModelMixin,
4
+ ClinicalReviewDmModelMixin,
5
+ ClinicalReviewHivModelMixin,
6
+ ClinicalReviewHtnModelMixin,
7
+ ClinicalReviewModelMixin,
8
+ )
9
+ from .dx_location_model_mixin import DxLocationModelMixin
10
+ from .factory import (
11
+ dx_initial_review_model_mixin_factory,
12
+ rx_initial_review_model_mixin_factory,
13
+ )
14
+ from .followup_review import FollowupReviewModelMixin, HivFollowupReviewModelMixin
15
+ from .initial_review import (
16
+ CholInitialReviewModelMixin,
17
+ HivArvInitiationModelMixin,
18
+ HivArvMonitoringModelMixin,
19
+ NcdInitialReviewModelMixin,
20
+ )
@@ -0,0 +1,25 @@
1
+ from django.db import models
2
+ from edc_constants.choices import YES_NO
3
+ from edc_constants.constants import YES
4
+ from edc_crf.model_mixins import SingletonCrfModelMixin
5
+ from edc_dx import raise_on_unknown_diagnosis_labels
6
+ from edc_visit_schedule.utils import raise_if_not_baseline
7
+
8
+
9
+ class ClinicalReviewBaselineModelMixin(SingletonCrfModelMixin, models.Model):
10
+ complications = models.CharField(
11
+ verbose_name="Since last seen, has the patient had any complications",
12
+ max_length=15,
13
+ choices=YES_NO,
14
+ help_text="If YES, complete the `Complications` CRF",
15
+ )
16
+
17
+ def save(self, *args, **kwargs):
18
+ raise_if_not_baseline(self.related_visit)
19
+ raise_on_unknown_diagnosis_labels(self, "_dx", YES)
20
+ super().save(*args, **kwargs)
21
+
22
+ class Meta:
23
+ abstract = True
24
+ verbose_name = "Clinical Review: Baseline"
25
+ verbose_name_plural = "Clinical Review: Baseline"
@@ -0,0 +1,5 @@
1
+ from .clinical_review_followup_chol_model_mixin import ClinicalReviewCholModelMixin
2
+ from .clinical_review_followup_dm_model_mixin import ClinicalReviewDmModelMixin
3
+ from .clinical_review_followup_hiv_model_mixin import ClinicalReviewHivModelMixin
4
+ from .clinical_review_followup_htn_model_mixin import ClinicalReviewHtnModelMixin
5
+ from .clinical_review_followup_model_mixin import ClinicalReviewModelMixin
@@ -0,0 +1,54 @@
1
+ from django.db import models
2
+ from django.utils.html import format_html
3
+ from django.utils.safestring import mark_safe
4
+ from edc_constants.choices import YES_NO_NA
5
+ from edc_constants.constants import NOT_APPLICABLE
6
+ from edc_model import models as edc_models
7
+
8
+ from ...utils import get_list_model_app
9
+
10
+
11
+ class ClinicalReviewCholModelMixin(models.Model):
12
+ chol_test = models.CharField(
13
+ verbose_name="Since last seen, was the patient tested for high cholesterol?",
14
+ max_length=15,
15
+ choices=YES_NO_NA,
16
+ default=NOT_APPLICABLE,
17
+ help_text=format_html(
18
+ "{}",
19
+ mark_safe(
20
+ "Note: Select `not applicable` if diagnosis previously reported. <BR>"
21
+ "`Since last seen` includes today.<BR>"
22
+ "If `yes', complete the initial review CRF<BR>"
23
+ "If `not applicable`, complete the review CRF."
24
+ ), # nosec B308, B703
25
+ ),
26
+ )
27
+
28
+ chol_test_date = models.DateField(
29
+ verbose_name="Date test requested",
30
+ null=True,
31
+ blank=True,
32
+ )
33
+
34
+ chol_reason = models.ManyToManyField(
35
+ f"{get_list_model_app()}.reasonsfortesting",
36
+ related_name="chol_reason",
37
+ verbose_name="Why was the patient tested for cholesterol?",
38
+ blank=True,
39
+ )
40
+
41
+ chol_reason_other = edc_models.OtherCharField()
42
+
43
+ chol_dx = models.CharField(
44
+ verbose_name=format_html(
45
+ "As of today, was the patient <u>{text}</u> diagnosed with high cholesterol?",
46
+ text="newly",
47
+ ),
48
+ max_length=15,
49
+ choices=YES_NO_NA,
50
+ default=NOT_APPLICABLE,
51
+ )
52
+
53
+ class Meta:
54
+ abstract = True
@@ -0,0 +1,54 @@
1
+ from django.db import models
2
+ from django.utils.html import format_html
3
+ from django.utils.safestring import mark_safe
4
+ from edc_constants.choices import YES_NO_NA
5
+ from edc_constants.constants import NOT_APPLICABLE
6
+ from edc_model import models as edc_models
7
+
8
+ from ...utils import get_list_model_app
9
+
10
+
11
+ class ClinicalReviewDmModelMixin(models.Model):
12
+ dm_test = models.CharField(
13
+ verbose_name="Since last seen, was the patient tested for diabetes?",
14
+ max_length=15,
15
+ choices=YES_NO_NA,
16
+ default=NOT_APPLICABLE,
17
+ help_text=format_html(
18
+ "{html}",
19
+ html=mark_safe(
20
+ "Note: Select `not applicable` if diagnosis previously reported. <BR>"
21
+ "`Since last seen` includes today.<BR>"
22
+ "If `yes', complete the initial review CRF<BR>"
23
+ "If `not applicable`, complete the review CRF."
24
+ ), # nosec B308, B703
25
+ ),
26
+ )
27
+
28
+ dm_test_date = models.DateField(
29
+ verbose_name="Date test requested",
30
+ null=True,
31
+ blank=True,
32
+ )
33
+
34
+ dm_reason = models.ManyToManyField(
35
+ f"{get_list_model_app()}.reasonsfortesting",
36
+ related_name="dm_reason",
37
+ verbose_name="Why was the patient tested for diabetes?",
38
+ blank=True,
39
+ )
40
+
41
+ dm_reason_other = edc_models.OtherCharField()
42
+
43
+ dm_dx = models.CharField(
44
+ verbose_name=format_html(
45
+ "As of today, was the patient <u>{text}</u> diagnosed with diabetes?",
46
+ text="newly",
47
+ ),
48
+ max_length=15,
49
+ choices=YES_NO_NA,
50
+ default=NOT_APPLICABLE,
51
+ )
52
+
53
+ class Meta:
54
+ abstract = True
@@ -0,0 +1,54 @@
1
+ from django.db import models
2
+ from django.utils.html import format_html
3
+ from django.utils.safestring import mark_safe
4
+ from edc_constants.choices import YES_NO_NA
5
+ from edc_constants.constants import NOT_APPLICABLE
6
+ from edc_model import models as edc_models
7
+
8
+ from ...utils import get_list_model_app
9
+
10
+
11
+ class ClinicalReviewHivModelMixin(models.Model):
12
+ hiv_test = models.CharField(
13
+ verbose_name="Since last seen, was the patient tested for HIV infection?",
14
+ max_length=15,
15
+ choices=YES_NO_NA,
16
+ default=NOT_APPLICABLE,
17
+ help_text=format_html(
18
+ "{html}",
19
+ html=mark_safe(
20
+ "Note: Select `not applicable` if diagnosis previously reported. <BR>"
21
+ "`Since last seen` includes today.<BR>"
22
+ "If `yes', complete the initial review CRF<BR>"
23
+ "If `not applicable`, complete the review CRF."
24
+ ), # nosec B308, B703
25
+ ),
26
+ )
27
+
28
+ hiv_test_date = models.DateField(
29
+ verbose_name="Date test requested",
30
+ null=True,
31
+ blank=True,
32
+ )
33
+
34
+ hiv_reason = models.ManyToManyField(
35
+ f"{get_list_model_app()}.reasonsfortesting",
36
+ related_name="hiv_test_reason",
37
+ verbose_name="Why was the patient tested for HIV infection?",
38
+ blank=True,
39
+ )
40
+
41
+ hiv_reason_other = edc_models.OtherCharField()
42
+
43
+ hiv_dx = models.CharField(
44
+ verbose_name=format_html(
45
+ "As of today, was the patient <u>{text}</u> diagnosed with HIV infection?",
46
+ text="newly",
47
+ ),
48
+ max_length=15,
49
+ choices=YES_NO_NA,
50
+ default=NOT_APPLICABLE,
51
+ )
52
+
53
+ class Meta:
54
+ abstract = True
@@ -0,0 +1,56 @@
1
+ from django.db import models
2
+ from django.utils.html import format_html
3
+ from django.utils.safestring import mark_safe
4
+ from edc_constants.choices import YES_NO_NA
5
+ from edc_constants.constants import NOT_APPLICABLE
6
+ from edc_model import models as edc_models
7
+
8
+ from ...utils import get_list_model_app
9
+
10
+
11
+ class ClinicalReviewHtnModelMixin(models.Model):
12
+ htn_test = models.CharField(
13
+ verbose_name="Since last seen, was the patient tested for hypertension?",
14
+ max_length=15,
15
+ choices=YES_NO_NA,
16
+ default=NOT_APPLICABLE,
17
+ help_text=format_html(
18
+ "{html}",
19
+ html=mark_safe(
20
+ "Note: Select `not applicable` if diagnosis previously reported. <BR>"
21
+ "`Since last seen` includes today.<BR>"
22
+ "If `yes', complete the initial review CRF<BR>"
23
+ "If `not applicable`, complete the review CRF."
24
+ ), # nosec B308, B703
25
+ ),
26
+ )
27
+
28
+ htn_test_date = models.DateField(
29
+ verbose_name="Date test requested",
30
+ null=True,
31
+ blank=True,
32
+ )
33
+
34
+ htn_reason = models.ManyToManyField(
35
+ f"{get_list_model_app()}.reasonsfortesting",
36
+ related_name="htn_test_reason",
37
+ verbose_name="Why was the patient tested for hypertension?",
38
+ blank=True,
39
+ )
40
+
41
+ htn_reason_other = edc_models.OtherCharField()
42
+
43
+ htn_dx = models.CharField(
44
+ verbose_name=format_html(
45
+ "{html}",
46
+ html=mark_safe(
47
+ "As of today, was the patient <u>newly</u> diagnosed with hypertension?"
48
+ ), # nosec B308, B703
49
+ ),
50
+ max_length=15,
51
+ choices=YES_NO_NA,
52
+ default=NOT_APPLICABLE,
53
+ )
54
+
55
+ class Meta:
56
+ abstract = True
@@ -0,0 +1,25 @@
1
+ from django.db import models
2
+ from edc_constants.choices import YES_NO
3
+ from edc_constants.constants import YES
4
+ from edc_crf.model_mixins import SingletonCrfModelMixin
5
+ from edc_dx import raise_on_unknown_diagnosis_labels
6
+ from edc_visit_schedule.utils import raise_if_baseline
7
+
8
+
9
+ class ClinicalReviewModelMixin(SingletonCrfModelMixin, models.Model):
10
+ complications = models.CharField(
11
+ verbose_name="Since last seen, has the patient had any complications",
12
+ max_length=15,
13
+ choices=YES_NO,
14
+ help_text="If YES, complete the `Complications` CRF",
15
+ )
16
+
17
+ def save(self, *args, **kwargs):
18
+ raise_if_baseline(self.related_visit)
19
+ raise_on_unknown_diagnosis_labels(self, "_test", YES)
20
+ super().save(*args, **kwargs)
21
+
22
+ class Meta:
23
+ abstract = True
24
+ verbose_name = "Clinical Review"
25
+ verbose_name_plural = "Clinical Review"
@@ -0,0 +1,17 @@
1
+ from django.db import models
2
+ from edc_model import models as edc_models
3
+
4
+
5
+ class DxLocationModelMixin(models.Model):
6
+ dx_location = models.ForeignKey(
7
+ "edc_dx_review.diagnosislocations",
8
+ verbose_name="Where was the diagnosis made?",
9
+ on_delete=models.PROTECT,
10
+ null=True,
11
+ blank=False,
12
+ )
13
+
14
+ dx_location_other = edc_models.OtherCharField()
15
+
16
+ class Meta:
17
+ abstract = True
@@ -0,0 +1,4 @@
1
+ from .baseline_review_model_mixin_factory import baseline_review_model_mixin_factory
2
+ from .dx_initial_review_model_mixin_factory import dx_initial_review_model_mixin_factory
3
+ from .followup_review_model_mixin_factory import followup_review_model_mixin_factory
4
+ from .rx_initial_review_model_mixin_factory import rx_initial_review_model_mixin_factory
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from django.db import models
4
+ from edc_constants.choices import YES_NO
5
+ from edc_constants.constants import CHOL, DM, HIV, HTN, NO
6
+ from edc_dx import get_diagnosis_labels
7
+
8
+ default_prompts = {
9
+ HIV.lower(): "Has the patient ever tested <U>positive</U> for HIV infection?",
10
+ DM: "Has the patient ever been diagnosed with DIABETES?",
11
+ HTN: "Has the patient ever been diagnosed with HYPERTENSION?",
12
+ CHOL: "Has the patient ever been diagnosed with HIGH CHOLESTEROL?",
13
+ }
14
+
15
+
16
+ def baseline_review_model_mixin_factory(prompts: dict[str, str] | None = None):
17
+ prompts = prompts or default_prompts
18
+
19
+ class AbstractModel(models.Model):
20
+ class Meta:
21
+ abstract = True
22
+
23
+ opts = {}
24
+ for cond, label in get_diagnosis_labels().items():
25
+ opts.update(
26
+ {
27
+ f"{cond}_dx": models.CharField(
28
+ verbose_name=prompts.get(cond),
29
+ max_length=15,
30
+ choices=YES_NO,
31
+ ),
32
+ f"{cond}_dx_at_screening": models.CharField(
33
+ verbose_name=f"Was a diagnosis of {label.upper()} reported at screening?",
34
+ max_length=15,
35
+ choices=YES_NO,
36
+ ),
37
+ }
38
+ )
39
+
40
+ opts.update(
41
+ {
42
+ "protocol_incident": models.CharField(
43
+ verbose_name=(
44
+ "Do any of the above diagnosis differ from those reported at screening?"
45
+ ),
46
+ max_length=15,
47
+ choices=YES_NO,
48
+ default=NO,
49
+ )
50
+ }
51
+ )
52
+ for name, fld_cls in opts.items():
53
+ AbstractModel.add_to_class(name, fld_cls)
54
+
55
+ return AbstractModel
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date
4
+
5
+ from django.db import models
6
+ from edc_constants.constants import NO, NOT_APPLICABLE, YES
7
+ from edc_model import duration_to_date
8
+
9
+
10
+ def calculate_date(
11
+ instance: models.Model, fld_prefix: str, reference_field: str
12
+ ) -> tuple[date | None, str]:
13
+ """Returns tuple date and YES/NO/NA.
14
+
15
+ The date is either the actual date or an estimated date
16
+ based on the ago field value.
17
+ ."""
18
+ calculated_date: date | None = None
19
+ is_estimated: str = NOT_APPLICABLE
20
+ report_datetime = getattr(instance, reference_field)
21
+ rx_date = getattr(instance, f"{fld_prefix}_date")
22
+ rx_ago = getattr(instance, f"{fld_prefix}_ago")
23
+ if rx_ago and not rx_date and report_datetime:
24
+ if rx_ago and not rx_date:
25
+ calculated_date = duration_to_date(rx_ago, report_datetime)
26
+ is_estimated = YES
27
+ elif rx_date:
28
+ calculated_date = rx_date
29
+ is_estimated = NO
30
+
31
+ else:
32
+ calculated_date = None
33
+ is_estimated = NOT_APPLICABLE
34
+ return calculated_date, is_estimated
35
+
36
+
37
+ def update_calculated_date(instance: models.Model, fld_prefix: str, reference_field: str):
38
+ """Wrapper for model save method"""
39
+ calculated_date, is_calculated = calculate_date(
40
+ instance=instance, fld_prefix=fld_prefix, reference_field=reference_field
41
+ )
42
+ setattr(instance, f"{fld_prefix}_calculated_date", calculated_date)
43
+ setattr(instance, f"{fld_prefix}_date_is_estimated", is_calculated)
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date
4
+
5
+ from django.db import models
6
+ from edc_constants.choices import YES_NO
7
+ from edc_constants.constants import NOT_APPLICABLE, YES
8
+ from edc_dx import Diagnoses
9
+ from edc_model.models import DurationYMDField
10
+ from edc_model.utils import get_report_datetime_field_name
11
+ from edc_model.validators import date_not_future
12
+
13
+ from .calculate_date import update_calculated_date
14
+
15
+
16
+ class InitialReviewModelError(Exception):
17
+ pass
18
+
19
+
20
+ def dx_initial_review_methods_model_mixin_factory():
21
+ class AbstractModel(models.Model):
22
+ fld_prefix: str | None = None
23
+
24
+ def save(self, *args, **kwargs):
25
+ if not self.diagnoses.get_dx_by_model(self) == YES:
26
+ raise InitialReviewModelError(
27
+ "No diagnosis has been recorded. See clinical review. "
28
+ "Perhaps catch this in the form."
29
+ )
30
+ update_calculated_date(
31
+ self,
32
+ fld_prefix=self.fld_prefix,
33
+ reference_field=get_report_datetime_field_name(),
34
+ )
35
+ super().save(*args, **kwargs)
36
+
37
+ @property
38
+ def diagnoses(self):
39
+ subject_identifier = getattr(self, "subject_identifier")
40
+ return Diagnoses(
41
+ subject_identifier=subject_identifier,
42
+ report_datetime=getattr(self, get_report_datetime_field_name()),
43
+ lte=True,
44
+ )
45
+
46
+ def get_best_dx_date(self) -> date:
47
+ return getattr(self, f"{self.fld_prefix}_date") or getattr(
48
+ self, f"{self.fld_prefix}_calculated_date"
49
+ )
50
+
51
+ class Meta:
52
+ abstract = True
53
+
54
+ return AbstractModel
55
+
56
+
57
+ def dx_initial_review_model_mixin_factory(fld_prefix: str | None = None):
58
+ fld_prefix = fld_prefix or "dx"
59
+
60
+ class AbstractModel(dx_initial_review_methods_model_mixin_factory(), models.Model):
61
+ class Meta:
62
+ abstract = True
63
+
64
+ opts = {
65
+ "fld_prefix": fld_prefix,
66
+ f"{fld_prefix}_date": models.DateField(
67
+ verbose_name="Date patient diagnosed",
68
+ null=True,
69
+ blank=True,
70
+ validators=[date_not_future],
71
+ help_text="If possible, provide the exact date here instead of estimating.",
72
+ ),
73
+ f"{fld_prefix}_ago": DurationYMDField(
74
+ verbose_name="If date not known, how long ago was the patient diagnosed?",
75
+ null=True,
76
+ blank=True,
77
+ help_text="If possible, provide the exact date above instead of estimating here.",
78
+ ),
79
+ f"{fld_prefix}_calculated_date": models.DateField(
80
+ verbose_name="Estimated diagnosis date",
81
+ null=True,
82
+ help_text=f"Calculated based on response to `{fld_prefix}dx_ago`",
83
+ editable=False,
84
+ ),
85
+ f"{fld_prefix}_date_is_estimated": models.CharField(
86
+ verbose_name="Was the diagnosis date estimated?",
87
+ max_length=15,
88
+ choices=YES_NO,
89
+ default=NOT_APPLICABLE,
90
+ editable=False,
91
+ ),
92
+ }
93
+
94
+ for name, fld_cls in opts.items():
95
+ AbstractModel.add_to_class(name, fld_cls)
96
+
97
+ return AbstractModel
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from django.db import models
4
+ from edc_constants.choices import YES_NO_NA
5
+ from edc_constants.constants import CHOL, DM, HIV, HTN, NOT_APPLICABLE
6
+ from edc_dx import get_diagnosis_labels
7
+
8
+ default_prompts = {
9
+ HIV.lower(): "Since last seen, has the patient tested <U>positive</U> for HIV infection?",
10
+ DM: "Since last seen, has the patient been diagnosed with Diabetes?",
11
+ HTN: "Since last seen, has the patient been diagnosed with Hypertension?",
12
+ CHOL: "Since last seen, has the patient been diagnosed with High Cholesterol?",
13
+ }
14
+
15
+
16
+ def followup_review_model_mixin_factory(prompts: dict[str, str] | None = None):
17
+ prompts = prompts or default_prompts
18
+
19
+ class AbstractModel(models.Model):
20
+ class Meta:
21
+ abstract = True
22
+
23
+ opts = {}
24
+ for dx in get_diagnosis_labels():
25
+ opts.update(
26
+ {
27
+ f"{dx}_dx": models.CharField(
28
+ verbose_name=prompts.get(dx),
29
+ max_length=15,
30
+ choices=YES_NO_NA,
31
+ default=NOT_APPLICABLE,
32
+ )
33
+ }
34
+ )
35
+
36
+ for name, fld_cls in opts.items():
37
+ AbstractModel.add_to_class(name, fld_cls)
38
+
39
+ return AbstractModel
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from django.db import models
4
+ from edc_constants.choices import YES_NO, YES_NO_NA
5
+ from edc_constants.constants import NOT_APPLICABLE, YES
6
+ from edc_model.models import DurationYMDField
7
+ from edc_model.validators import date_not_future
8
+
9
+ from .calculate_date import update_calculated_date
10
+
11
+
12
+ def rx_initial_review_model_mixin_factory(
13
+ fld_prefix: str | None = None, verbose_name_label: str | None = None
14
+ ):
15
+ fld_prefix = fld_prefix or "rx_init"
16
+
17
+ class AbstractModel(models.Model):
18
+ def save(self, *args, **kwargs):
19
+ update_calculated_date(
20
+ self, fld_prefix=fld_prefix, reference_field="report_datetime"
21
+ )
22
+ super().save(*args, **kwargs)
23
+
24
+ class Meta:
25
+ abstract = True
26
+
27
+ opts = {
28
+ "fld_prefix": fld_prefix,
29
+ f"{fld_prefix}": models.CharField(
30
+ verbose_name=f"Has the patient started {verbose_name_label}?",
31
+ max_length=15,
32
+ choices=YES_NO_NA,
33
+ default=YES,
34
+ ),
35
+ f"{fld_prefix}_date": models.DateField(
36
+ verbose_name=f"Date started {verbose_name_label}",
37
+ validators=[date_not_future],
38
+ null=True,
39
+ blank=True,
40
+ help_text="If possible, provide the exact date here instead of estimating.",
41
+ ),
42
+ f"{fld_prefix}_ago": DurationYMDField(
43
+ verbose_name=(
44
+ f"If date not known, how long ago did the patient start {verbose_name_label}?"
45
+ ),
46
+ null=True,
47
+ blank=True,
48
+ help_text="If possible, provide the exact date above instead of estimating here.",
49
+ ),
50
+ f"{fld_prefix}_calculated_date": models.DateField(
51
+ verbose_name=f"Estimated date started {verbose_name_label}",
52
+ validators=[date_not_future],
53
+ null=True,
54
+ editable=False,
55
+ help_text="Calculated based on response to `rx_init_ago`",
56
+ ),
57
+ f"{fld_prefix}_date_is_estimated": models.CharField(
58
+ verbose_name=f"Was {verbose_name_label} start date estimated?",
59
+ max_length=15,
60
+ choices=YES_NO,
61
+ default=NOT_APPLICABLE,
62
+ editable=False,
63
+ ),
64
+ }
65
+
66
+ for name, fld_cls in opts.items():
67
+ AbstractModel.add_to_class(name, fld_cls)
68
+
69
+ return AbstractModel
@@ -0,0 +1,2 @@
1
+ from .followup_review_model_mixin import FollowupReviewModelMixin
2
+ from .hiv_followup_review_model_mixin import HivFollowupReviewModelMixin
@@ -0,0 +1,22 @@
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
+
6
+ class FollowupReviewModelMixin(models.Model):
7
+ care_delivery = models.CharField(
8
+ verbose_name=(
9
+ "Was care for this `condition` delivered in an integrated care clinic today?"
10
+ ),
11
+ max_length=25,
12
+ choices=YES_NO_NA,
13
+ default=NOT_APPLICABLE,
14
+ help_text="Select `not applicable` if site was not selected for integrated care.",
15
+ )
16
+
17
+ care_delivery_other = models.TextField(
18
+ verbose_name="If NO, please explain", null=True, blank=True
19
+ )
20
+
21
+ class Meta:
22
+ abstract = True