c94d2fb1a7
- Implemented models for ResearchStudy, ResearchStudyArm, and ResearchParticipant. - Created views for listing, creating, updating, and exporting research studies. - Added bulk participant generation feature with CSV and JSON support. - Developed templates for study management, participant entry, and bulk generation. - Introduced URL routing for research study operations. - Added utility functions for randomisation and participant assignment. - Implemented participant intake process with demographic data collection. - Ensured proper handling of participant CIDs and assignment methods.
96 lines
7.0 KiB
Python
96 lines
7.0 KiB
Python
# 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')},
|
|
},
|
|
),
|
|
]
|