# Generated by Django 6.0.1 on 2026-05-12 20:35 import django.db.models.deletion import generic.mixins from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ('atlas', '0103_casecollection_auto_apply_question_template_and_more'), ('generic', '0032_ciduserexam_shared_with_users'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='ResearchStudy', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255)), ('slug', models.SlugField(max_length=100, unique=True)), ('description', models.TextField(blank=True)), ('active', models.BooleanField(default=True)), ('randomisation_mode', models.CharField(choices=[('RANDOM', 'Completely random'), ('BALANCED', 'Balanced (equal-fill)'), ('BALANCED_GROUP', 'Balanced within candidate group'), ('TARGET_BASED', 'Target-based allocation')], default='BALANCED', help_text='How participants are assigned to packets.', max_length=20)), ('generation_mode', models.CharField(choices=[('AUTO', 'Auto-create on first access'), ('BULK', 'Bulk generation only')], default='AUTO', help_text='How participants are created for this study.', max_length=20)), ('balance_within_candidate_group', models.BooleanField(default=True, help_text='If enabled, balanced randomisation is performed independently per candidate group.')), ('collect_demographics', models.BooleanField(default=True, help_text='If enabled, participants are asked for simple demographic fields before starting.')), ('allocation_targets', models.JSONField(blank=True, default=dict, help_text="For target-based allocation: maps arm_id or group_name to target count. E.g. {'arm_1': 30, 'arm_2': 30}")), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('author', models.ManyToManyField(blank=True, help_text='Users allowed to manage this study.', related_name='research_studies', to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'Research Study', 'verbose_name_plural': 'Research Studies', 'ordering': ('-created_at',), }, bases=(models.Model, generic.mixins.AuthorMixin), ), migrations.CreateModel( name='ResearchStudyArm', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), ('active', models.BooleanField(default=True)), ('allocation_weight', models.PositiveIntegerField(default=1, help_text='Relative weighting used for completely-random assignment.')), ('sort_order', models.PositiveIntegerField(default=100)), ('order_sequence', models.PositiveIntegerField(default=0, help_text='Order in which this arm should be completed (0 = no ordering). Used with prerequisite support.')), ('packet', models.ForeignKey(help_text='CaseCollection used as this packet.', on_delete=django.db.models.deletion.PROTECT, related_name='research_arms', to='atlas.casecollection')), ('required_previous_arm', models.ForeignKey(blank=True, help_text='If set, participant must complete this arm before the dependent arm.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dependent_arms', to='research.researchstudyarm')), ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arms', to='research.researchstudy')), ], options={ 'verbose_name': 'Research Study Arm', 'verbose_name_plural': 'Research Study Arms', 'ordering': ('sort_order', 'id'), 'unique_together': {('study', 'name')}, }, ), migrations.CreateModel( name='ResearchParticipant', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('pseudo_id', models.CharField(help_text='Pseudo-anonymised ID used in participant URL.', max_length=100)), ('candidate_group', models.CharField(blank=True, max_length=100)), ('age_band', models.CharField(blank=True, max_length=100)), ('sex', models.CharField(blank=True, max_length=50)), ('training_grade', models.CharField(blank=True, max_length=100)), ('years_experience', models.CharField(blank=True, max_length=50)), ('demographics_extra', models.JSONField(blank=True, default=dict)), ('email', models.EmailField(blank=True, help_text='Email address for participant result reaccess.', max_length=254)), ('consented', models.BooleanField(default=False)), ('assignment_method', models.CharField(blank=True, choices=[('RANDOM', 'Completely random'), ('BALANCED', 'Balanced'), ('BALANCED_GROUP', 'Balanced within group'), ('TARGET_BASED', 'Target-based'), ('MANUAL', 'Manual')], max_length=20)), ('assigned_at', models.DateTimeField(blank=True, null=True)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('cid_user', models.OneToOneField(blank=True, help_text='CidUser for this participant (1-1 mapping; assigned at intake).', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='research_participant', to='generic.ciduser')), ('user_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='research_participants', to=settings.AUTH_USER_MODEL)), ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='research.researchstudy')), ('assigned_arm', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='participants', to='research.researchstudyarm')), ('completed_arms', models.ManyToManyField(blank=True, help_text='Arms this participant has completed.', related_name='completed_by_participants', to='research.researchstudyarm')), ], options={ 'verbose_name': 'Research Participant', 'verbose_name_plural': 'Research Participants', 'ordering': ('-created_at',), 'constraints': [models.UniqueConstraint(fields=('study', 'cid_user'), name='unique_cid_per_study')], 'unique_together': {('study', 'pseudo_id')}, }, ), ]