meta-edc 1.0.3__py3-none-any.whl → 1.0.5__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.
Files changed (33) hide show
  1. meta_analytics/dataframes/get_eos_df.py +1 -1
  2. meta_analytics/dataframes/get_last_imp_visits_df.py +5 -2
  3. meta_analytics/dataframes/screening/get_screening_df.py +2 -1
  4. meta_edc/settings/debug.py +2 -2
  5. meta_edc/settings/defaults.py +1 -0
  6. {meta_edc-1.0.3.dist-info → meta_edc-1.0.5.dist-info}/METADATA +3 -3
  7. {meta_edc-1.0.3.dist-info → meta_edc-1.0.5.dist-info}/RECORD +33 -22
  8. {meta_edc-1.0.3.dist-info → meta_edc-1.0.5.dist-info}/WHEEL +1 -1
  9. meta_lists/list_data.py +4 -0
  10. meta_lists/migrations/0019_auto_20250128_0143.py +48 -0
  11. meta_pharmacy/admin/rx_admin.py +1 -0
  12. meta_subject/action_items.py +8 -1
  13. meta_subject/admin/__init__.py +1 -0
  14. meta_subject/admin/next_appointment_admin.py +20 -0
  15. meta_subject/forms/__init__.py +1 -0
  16. meta_subject/forms/next_appointment_form.py +37 -0
  17. meta_subject/metadata_rules/metadata_rules.py +14 -0
  18. meta_subject/metadata_rules/predicates.py +29 -3
  19. meta_subject/migrations/0216_historicalnextappointment_nextappointment.py +554 -0
  20. meta_subject/migrations/0217_alter_historicalnextappointment_appt_datetime_and_more.py +42 -0
  21. meta_subject/migrations/0218_alter_historicalnextappointment_appt_date_and_more.py +53 -0
  22. meta_subject/migrations/0219_remove_historicalnextappointment_allow_create_interim_and_more.py +92 -0
  23. meta_subject/migrations/0220_historicalbloodresultsgludummy_bloodresultsgludummy.py +834 -0
  24. meta_subject/models/__init__.py +2 -0
  25. meta_subject/models/blood_results/__init__.py +1 -0
  26. meta_subject/models/blood_results/blood_results_glu.py +29 -0
  27. meta_subject/models/next_appointment.py +16 -0
  28. meta_subject/tests/tests/test_next_appointment.py +231 -0
  29. meta_visit_schedule/visit_schedules/phase_three/crfs.py +20 -1
  30. meta_visit_schedule/visit_schedules/phase_three/schedule_pregnancy.py +1 -1
  31. {meta_edc-1.0.3.dist-info → meta_edc-1.0.5.dist-info}/AUTHORS +0 -0
  32. {meta_edc-1.0.3.dist-info → meta_edc-1.0.5.dist-info}/LICENSE +0 -0
  33. {meta_edc-1.0.3.dist-info → meta_edc-1.0.5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from .birth_outcomes import BirthOutcomes
2
2
  from .blood_results import (
3
3
  BloodResultsFbc,
4
+ BloodResultsGluDummy,
4
5
  BloodResultsHba1c,
5
6
  BloodResultsIns,
6
7
  BloodResultsLft,
@@ -28,6 +29,7 @@ from .hepatitis_test import HepatitisTest
28
29
  from .malaria_test import MalariaTest
29
30
  from .medication_adherence import MedicationAdherence
30
31
  from .mnsi import Mnsi
32
+ from .next_appointment import NextAppointment
31
33
  from .other_arv_regimens import OtherArvRegimens
32
34
  from .other_arv_regimens_detail import OtherArvRegimensDetail
33
35
  from .patient_history import PatientHistory
@@ -1,4 +1,5 @@
1
1
  from .blood_results_fbc import BloodResultsFbc
2
+ from .blood_results_glu import BloodResultsGluDummy
2
3
  from .blood_results_hba1c import BloodResultsHba1c
3
4
  from .blood_results_ins import BloodResultsIns
4
5
  from .blood_results_lft import BloodResultsLft
@@ -0,0 +1,29 @@
1
+ from django.db import models
2
+ from edc_lab.model_mixins import CrfWithRequisitionModelMixin, requisition_fk_options
3
+ from edc_lab_panel.panels import blood_glucose_panel
4
+ from edc_lab_results import BLOOD_RESULTS_GLU_ACTION
5
+ from edc_lab_results.model_mixins import BloodResultsModelMixin, GlucoseModelMixin
6
+ from edc_model.models import BaseUuidModel
7
+
8
+ from ...model_mixins import CrfWithActionModelMixin
9
+
10
+
11
+ # TODO: this is a IFG!!
12
+ class BloodResultsGluDummy(
13
+ CrfWithActionModelMixin,
14
+ GlucoseModelMixin,
15
+ CrfWithRequisitionModelMixin,
16
+ BloodResultsModelMixin,
17
+ BaseUuidModel,
18
+ ):
19
+ action_name = BLOOD_RESULTS_GLU_ACTION
20
+ tracking_identifier_prefix = "GL"
21
+ lab_panel = blood_glucose_panel
22
+
23
+ requisition = models.ForeignKey(
24
+ limit_choices_to={"panel__name": blood_glucose_panel.name}, **requisition_fk_options
25
+ )
26
+
27
+ class Meta(CrfWithActionModelMixin.Meta, BaseUuidModel.Meta):
28
+ verbose_name = "Blood Result: Glucose (DUMMY)"
29
+ verbose_name_plural = "Blood Results: Glucose (DUMMY)"
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from edc_appointment.model_mixins import NextAppointmentCrfModelMixin
4
+ from edc_model.models import BaseUuidModel
5
+
6
+ from ..model_mixins import CrfModelMixin
7
+
8
+ __all__ = ["NextAppointment"]
9
+
10
+
11
+ class NextAppointment(NextAppointmentCrfModelMixin, CrfModelMixin, BaseUuidModel):
12
+
13
+ class Meta(CrfModelMixin.Meta, BaseUuidModel.Meta):
14
+ verbose_name = "Next Appointment"
15
+ verbose_name_plural = "Next Appointments"
16
+ indexes = CrfModelMixin.Meta.indexes + BaseUuidModel.Meta.indexes
@@ -0,0 +1,231 @@
1
+ from datetime import datetime
2
+ from zoneinfo import ZoneInfo
3
+
4
+ import time_machine
5
+ from dateutil.relativedelta import relativedelta
6
+ from django.core.exceptions import ValidationError
7
+ from django.test import TestCase, tag
8
+ from edc_appointment.exceptions import AppointmentWindowError
9
+ from edc_constants.constants import CLINIC
10
+ from edc_facility.models import HealthFacilityTypes
11
+ from edc_facility.utils import get_health_facility_model, get_health_facility_model_cls
12
+ from edc_metadata.metadata_handler import MetadataHandlerError
13
+ from edc_sites.utils import get_site_model_cls
14
+ from edc_visit_schedule.models import VisitSchedule
15
+
16
+ from meta_screening.tests.meta_test_case_mixin import MetaTestCaseMixin
17
+ from meta_screening.tests.options import now
18
+ from meta_subject.models import NextAppointment
19
+
20
+
21
+ @time_machine.travel(datetime(2019, 6, 11, 8, 00, tzinfo=ZoneInfo("UTC")))
22
+ class TestNextAppointment(MetaTestCaseMixin, TestCase):
23
+
24
+ def setUp(self):
25
+ super().setUp()
26
+ self.update_health_facility_model()
27
+ self.subject_visit = self.get_subject_visit(appt_datetime=now)
28
+
29
+ def update_health_facility_model(self):
30
+ from edc_sites.site import sites as site_sites
31
+
32
+ clinic = HealthFacilityTypes.objects.get(name=CLINIC)
33
+ for site_obj in get_site_model_cls().objects.all():
34
+ single_site = site_sites.get(site_obj.id)
35
+ get_health_facility_model_cls().objects.create(
36
+ name=single_site.name,
37
+ title=single_site.title,
38
+ health_facility_type=clinic,
39
+ mon=True,
40
+ tue=True,
41
+ wed=True,
42
+ thu=True,
43
+ fri=True,
44
+ sat=False,
45
+ sun=False,
46
+ site_id=site_obj.id,
47
+ )
48
+
49
+ @tag("6")
50
+ def test_in_visit_crfs(self):
51
+ dt = self.subject_visit.report_datetime.date() + relativedelta(days=10)
52
+ for i in range(1, 7):
53
+ dt = dt + relativedelta(days=1)
54
+ if dt.weekday() < 6:
55
+ break
56
+
57
+ try:
58
+ NextAppointment.objects.create(
59
+ subject_visit=self.subject_visit,
60
+ report_datetime=self.subject_visit.report_datetime,
61
+ appt_date=dt,
62
+ health_facility=get_health_facility_model_cls().objects.all()[0],
63
+ )
64
+ except MetadataHandlerError as e:
65
+ self.fail(f"Unexpected MetadataHandlerError. Got {e}")
66
+
67
+ @tag("6")
68
+ def test_validates_clinic_day(self):
69
+ dt = self.subject_visit.report_datetime.date() + relativedelta(days=10)
70
+ for i in range(1, 7):
71
+ dt = dt + relativedelta(days=1)
72
+ if dt.weekday() > 5:
73
+ break
74
+ with self.assertRaises(ValidationError) as cm:
75
+ NextAppointment.objects.create(
76
+ subject_visit=self.subject_visit,
77
+ report_datetime=self.subject_visit.report_datetime,
78
+ appt_date=dt,
79
+ health_facility=get_health_facility_model_cls().objects.all()[0],
80
+ )
81
+ self.assertIn("Expected Mon-Fri", str(cm.exception))
82
+
83
+ @tag("6")
84
+ def test_appt_date_on_report_date_raises(self):
85
+ with self.assertRaises(ValidationError) as cm:
86
+ NextAppointment.objects.create(
87
+ subject_visit=self.subject_visit,
88
+ report_datetime=self.subject_visit.report_datetime,
89
+ appt_date=self.subject_visit.report_datetime.date(),
90
+ health_facility=get_health_facility_model_cls().objects.all()[0],
91
+ )
92
+ self.assertIn(
93
+ "Cannot be equal to the report datetime",
94
+ str(cm.exception),
95
+ )
96
+
97
+ # @tag("6")
98
+ # def test_next_appt_date_required(self):
99
+ # next_appt = self.subject_visit.appointment.next
100
+ # with self.assertRaises(NextAppointmentModelError) as cm:
101
+ # NextAppointment.objects.create(
102
+ # subject_visit=self.subject_visit,
103
+ # report_datetime=self.subject_visit.report_datetime,
104
+ # health_facility=get_health_facility_model_cls().objects.all()[0],
105
+ # visitschedule=VisitSchedule.objects.get(
106
+ # visit_schedule_name=self.subject_visit.visit_schedule.name,
107
+ # timepoint=next_appt.timepoint,
108
+ # ),
109
+ # )
110
+ # self.assertIn(
111
+ # "Appointment date or datetime is required",
112
+ # str(cm.exception),
113
+ # )
114
+
115
+ @tag("6")
116
+ def test_nextappt_appt_date_updates_nextappt_appt_datetime(self):
117
+ next_appt = self.subject_visit.appointment.next
118
+ original_next_appt_datetime = self.subject_visit.appointment.next.appt_datetime
119
+ obj = NextAppointment.objects.create(
120
+ appt_date=original_next_appt_datetime.date(),
121
+ subject_visit=self.subject_visit,
122
+ report_datetime=self.subject_visit.report_datetime,
123
+ health_facility=get_health_facility_model_cls().objects.all()[0],
124
+ visitschedule=VisitSchedule.objects.get(
125
+ visit_schedule_name=self.subject_visit.visit_schedule.name,
126
+ timepoint=next_appt.timepoint,
127
+ ),
128
+ )
129
+ self.assertIsNotNone(obj.appt_datetime)
130
+
131
+ # @tag("6")
132
+ # def test_nextappt_appt_datetime_updates_nextappt_appt_date(self):
133
+ # next_appt = self.subject_visit.appointment.next
134
+ # original_next_appt_datetime = self.subject_visit.appointment.next.appt_datetime
135
+ # obj = NextAppointment.objects.create(
136
+ # appt_datetime=original_next_appt_datetime,
137
+ # subject_visit=self.subject_visit,
138
+ # report_datetime=self.subject_visit.report_datetime,
139
+ # health_facility=get_health_facility_model_cls().objects.all()[0],
140
+ # visitschedule=VisitSchedule.objects.get(
141
+ # visit_schedule_name=self.subject_visit.visit_schedule.name,
142
+ # timepoint=next_appt.timepoint,
143
+ # ),
144
+ # )
145
+ # self.assertIsNotNone(obj.appt_date)
146
+
147
+ @tag("6")
148
+ def test_next_appt_date_same_as_original_next_appt(self):
149
+ get_health_facility_model()
150
+ next_appt = self.subject_visit.appointment.next
151
+ obj = NextAppointment.objects.create(
152
+ subject_visit=self.subject_visit,
153
+ report_datetime=self.subject_visit.report_datetime,
154
+ appt_date=next_appt.appt_datetime.date(),
155
+ health_facility=get_health_facility_model_cls().objects.all()[0],
156
+ visitschedule=VisitSchedule.objects.get(
157
+ visit_schedule_name=self.subject_visit.visit_schedule.name,
158
+ timepoint=next_appt.timepoint,
159
+ ),
160
+ )
161
+ next_appt.refresh_from_db()
162
+ self.assertEqual(next_appt.appt_datetime, obj.appt_datetime)
163
+
164
+ @tag("6")
165
+ def test_updates_next_appointment_datetime(self):
166
+ next_appt = self.subject_visit.appointment.next
167
+ original_next_appt_datetime = self.subject_visit.appointment.next.appt_datetime
168
+ obj = NextAppointment.objects.create(
169
+ subject_visit=self.subject_visit,
170
+ report_datetime=self.subject_visit.report_datetime,
171
+ appt_date=original_next_appt_datetime.date() + relativedelta(days=1),
172
+ health_facility=get_health_facility_model_cls().objects.all()[0],
173
+ visitschedule=VisitSchedule.objects.get(
174
+ visit_schedule_name=self.subject_visit.visit_schedule.name,
175
+ timepoint=next_appt.timepoint,
176
+ ),
177
+ )
178
+ next_appt.refresh_from_db()
179
+ self.assertEqual(next_appt.appt_datetime, obj.appt_datetime)
180
+
181
+ @tag("6")
182
+ def test_raises_if_next_appointment_datetime_is_before_current(self):
183
+ next_appt = self.subject_visit.appointment.next
184
+ current_appt_datetime = self.subject_visit.appointment.appt_datetime
185
+ self.assertRaises(
186
+ ValidationError,
187
+ NextAppointment.objects.create,
188
+ subject_visit=self.subject_visit,
189
+ report_datetime=self.subject_visit.report_datetime,
190
+ appt_date=current_appt_datetime.date(),
191
+ health_facility=get_health_facility_model_cls().objects.all()[0],
192
+ visitschedule=VisitSchedule.objects.get(
193
+ visit_schedule_name=self.subject_visit.visit_schedule.name,
194
+ timepoint=next_appt.timepoint,
195
+ ),
196
+ )
197
+
198
+ @tag("6")
199
+ def test_raises_on_appt_date_outside_of_window_for_selected_visit_code(self):
200
+ next_appt = self.subject_visit.appointment.next
201
+ bad_next_date = self.subject_visit.report_datetime.date() + relativedelta(days=1)
202
+ self.assertRaises(
203
+ AppointmentWindowError,
204
+ NextAppointment.objects.create,
205
+ subject_visit=self.subject_visit,
206
+ report_datetime=self.subject_visit.report_datetime,
207
+ appt_date=bad_next_date,
208
+ health_facility=get_health_facility_model_cls().objects.all()[0],
209
+ visitschedule=VisitSchedule.objects.get(
210
+ visit_schedule_name=self.subject_visit.visit_schedule.name,
211
+ timepoint=next_appt.timepoint,
212
+ ),
213
+ )
214
+
215
+ @tag("6")
216
+ def test_next_is_interim_or_unscheduled(self):
217
+ dt = self.subject_visit.report_datetime.date() + relativedelta(days=10)
218
+ for i in range(1, 7):
219
+ dt = dt + relativedelta(days=1)
220
+ if dt.weekday() < 6:
221
+ break
222
+ NextAppointment.objects.create(
223
+ subject_visit=self.subject_visit,
224
+ report_datetime=self.subject_visit.report_datetime,
225
+ appt_date=dt,
226
+ health_facility=get_health_facility_model_cls().objects.all()[0],
227
+ visitschedule=VisitSchedule.objects.get(
228
+ visit_schedule_name=self.subject_visit.visit_schedule.name,
229
+ timepoint=self.subject_visit.appointment.timepoint,
230
+ ),
231
+ )
@@ -48,6 +48,7 @@ crfs_d1 = CrfCollection(
48
48
  Crf(show_order=360, model="meta_subject.malariatest"),
49
49
  Crf(show_order=370, model="meta_subject.urinedipsticktest"),
50
50
  Crf(show_order=400, model="meta_subject.studymedication"),
51
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
51
52
  name="day1",
52
53
  )
53
54
 
@@ -61,6 +62,7 @@ crfs_w2 = CrfCollection(
61
62
  Crf(show_order=460, model="meta_subject.eq5d3l"),
62
63
  Crf(show_order=465, model="meta_subject.sf12"),
63
64
  Crf(show_order=480, model="meta_subject.healtheconomicssimple"),
65
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
64
66
  name="week2",
65
67
  )
66
68
 
@@ -75,6 +77,7 @@ crfs_1m = CrfCollection(
75
77
  Crf(show_order=465, model="meta_subject.sf12", required=False),
76
78
  Crf(show_order=470, model="meta_subject.mnsi"),
77
79
  Crf(show_order=480, model="meta_subject.healtheconomicssimple", required=False),
80
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
78
81
  name="1m",
79
82
  )
80
83
 
@@ -91,6 +94,7 @@ crfs_3m = CrfCollection(
91
94
  Crf(show_order=465, model="meta_subject.sf12", required=False),
92
95
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
93
96
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate"),
97
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
94
98
  name="3m",
95
99
  )
96
100
 
@@ -108,6 +112,7 @@ crfs_6m = CrfCollection(
108
112
  Crf(show_order=465, model="meta_subject.sf12", required=False),
109
113
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
110
114
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
115
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
111
116
  name="6m",
112
117
  )
113
118
 
@@ -121,6 +126,7 @@ crfs_9m = CrfCollection(
121
126
  Crf(show_order=460, model="meta_subject.eq5d3l", required=False),
122
127
  Crf(show_order=465, model="meta_subject.sf12", required=False),
123
128
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
129
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
124
130
  name="9m",
125
131
  )
126
132
 
@@ -138,6 +144,7 @@ crfs_12m = CrfCollection(
138
144
  Crf(show_order=460, model="meta_subject.eq5d3l", required=False),
139
145
  Crf(show_order=465, model="meta_subject.sf12", required=False),
140
146
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
147
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
141
148
  name="12m",
142
149
  )
143
150
 
@@ -151,6 +158,7 @@ crfs_15m = CrfCollection(
151
158
  Crf(show_order=460, model="meta_subject.eq5d3l", required=False),
152
159
  Crf(show_order=465, model="meta_subject.sf12", required=False),
153
160
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
161
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
154
162
  name="15m",
155
163
  )
156
164
 
@@ -165,6 +173,7 @@ crfs_18m = CrfCollection(
165
173
  Crf(show_order=460, model="meta_subject.eq5d3l", required=False),
166
174
  Crf(show_order=465, model="meta_subject.sf12", required=False),
167
175
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
176
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
168
177
  name="18m",
169
178
  )
170
179
 
@@ -178,6 +187,7 @@ crfs_21m = CrfCollection(
178
187
  Crf(show_order=460, model="meta_subject.eq5d3l", required=False),
179
188
  Crf(show_order=465, model="meta_subject.sf12", required=False),
180
189
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
190
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
181
191
  name="21m",
182
192
  )
183
193
 
@@ -189,13 +199,14 @@ crfs_24m = CrfCollection(
189
199
  Crf(show_order=220, model="meta_subject.bloodresultsrft"),
190
200
  Crf(show_order=230, model="meta_subject.bloodresultslft"),
191
201
  Crf(show_order=240, model="meta_subject.bloodresultsfbc"),
192
- Crf(show_order=250, model="meta_subject.bloodresultslipids"),
202
+ # Crf(show_order=250, model="meta_subject.bloodresultslipids"),
193
203
  Crf(show_order=300, model="meta_subject.studymedication"),
194
204
  Crf(show_order=310, model="meta_subject.medicationadherence"),
195
205
  Crf(show_order=460, model="meta_subject.eq5d3l", required=False),
196
206
  Crf(show_order=465, model="meta_subject.sf12", required=False),
197
207
  Crf(show_order=470, model="meta_subject.mnsi"),
198
208
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
209
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
199
210
  name="24m",
200
211
  )
201
212
 
@@ -209,6 +220,7 @@ crfs_27m = CrfCollection(
209
220
  Crf(show_order=465, model="meta_subject.sf12", required=False),
210
221
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
211
222
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
223
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
212
224
  name="27m",
213
225
  )
214
226
 
@@ -223,6 +235,7 @@ crfs_30m = CrfCollection(
223
235
  Crf(show_order=465, model="meta_subject.sf12", required=False),
224
236
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
225
237
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
238
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
226
239
  name="30m",
227
240
  )
228
241
 
@@ -236,6 +249,7 @@ crfs_33m = CrfCollection(
236
249
  Crf(show_order=465, model="meta_subject.sf12", required=False),
237
250
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
238
251
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
252
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
239
253
  name="33m",
240
254
  )
241
255
 
@@ -254,6 +268,7 @@ crfs_36m = CrfCollection(
254
268
  Crf(show_order=465, model="meta_subject.sf12"),
255
269
  Crf(show_order=470, model="meta_subject.mnsi"),
256
270
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
271
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
257
272
  name="36m",
258
273
  )
259
274
 
@@ -267,6 +282,7 @@ crfs_39m = CrfCollection(
267
282
  Crf(show_order=465, model="meta_subject.sf12", required=False),
268
283
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
269
284
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
285
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
270
286
  name="39m",
271
287
  )
272
288
 
@@ -281,6 +297,7 @@ crfs_42m = CrfCollection(
281
297
  Crf(show_order=465, model="meta_subject.sf12", required=False),
282
298
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
283
299
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
300
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
284
301
  name="42m",
285
302
  )
286
303
 
@@ -294,6 +311,7 @@ crfs_45m = CrfCollection(
294
311
  Crf(show_order=465, model="meta_subject.sf12", required=False),
295
312
  Crf(show_order=470, model="meta_subject.mnsi", required=False),
296
313
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
314
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
297
315
  name="45m",
298
316
  )
299
317
 
@@ -312,5 +330,6 @@ crfs_48m = CrfCollection(
312
330
  Crf(show_order=465, model="meta_subject.sf12"),
313
331
  Crf(show_order=470, model="meta_subject.mnsi"),
314
332
  Crf(show_order=500, model="meta_subject.healtheconomicsupdate", required=False),
333
+ Crf(show_order=600, model="meta_subject.nextappointment", required=False),
315
334
  name="48m",
316
335
  )
@@ -28,7 +28,7 @@ visit = Visit(
28
28
  timepoint=200,
29
29
  rbase=relativedelta(days=0),
30
30
  rlower=relativedelta(days=0),
31
- rupper=relativedelta(months=12),
31
+ rupper=relativedelta(months=24),
32
32
  crfs=crfs_pregnancy,
33
33
  crfs_prn=crfs_prn or default_crfs_prn,
34
34
  crfs_missed=crfs_missed,