diff --git a/crc/api.yml b/crc/api.yml index b3d61fc1..4bb63af3 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -1062,9 +1062,9 @@ components: user_uid: type: string example: dhf8r - protocol_builder_status: + status: type: string - enum: ['incomplete', 'active', 'hold', 'open', 'abandoned'] + enum: ['in progress', 'hold', 'open for enrollment', 'abandoned'] example: done sponsor: type: string diff --git a/crc/api/study.py b/crc/api/study.py index b5572527..3247f47d 100644 --- a/crc/api/study.py +++ b/crc/api/study.py @@ -6,7 +6,7 @@ from sqlalchemy.exc import IntegrityError from crc import session from crc.api.common import ApiError, ApiErrorSchema from crc.models.protocol_builder import ProtocolBuilderStatus -from crc.models.study import StudySchema, StudyModel, Study +from crc.models.study import Study, StudyModel, StudySchema, StudyStatus from crc.services.study_service import StudyService @@ -21,7 +21,7 @@ def add_study(body): title=body['title'], primary_investigator_id=body['primary_investigator_id'], last_updated=datetime.now(), - protocol_builder_status=ProtocolBuilderStatus.active) + status=StudyStatus.in_progress) session.add(study_model) errors = StudyService._add_all_workflow_specs_to_study(study_model) diff --git a/crc/models/protocol_builder.py b/crc/models/protocol_builder.py index a91ae84b..2706cefe 100644 --- a/crc/models/protocol_builder.py +++ b/crc/models/protocol_builder.py @@ -17,6 +17,7 @@ class ProtocolBuilderInvestigatorType(enum.Enum): SCI = "Scientific Contact" +# Deprecated: Marked for removal class ProtocolBuilderStatus(enum.Enum): # • Active: found in PB and no HSR number and not hold # • Hold: store boolean value in CR Connect (add to Study Model) diff --git a/crc/models/study.py b/crc/models/study.py index 32697896..d1114349 100644 --- a/crc/models/study.py +++ b/crc/models/study.py @@ -1,4 +1,5 @@ import datetime +import enum import json import marshmallow @@ -14,12 +15,26 @@ from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowState, Workfl WorkflowModel +class StudyStatus(enum.Enum): + in_progress = 'in progress' + hold = 'hold' + open_for_enrollment = 'open for enrollment' + abandoned = 'abandoned' + + +class IrbStatus(enum.Enum): + incomplete_in_protocol_builder = 'incomplete in protocol builder' + completed_in_protocol_builder = 'completed in protocol builder' + hsr_assigned = 'hsr number assigned' + + class StudyModel(db.Model): __tablename__ = 'study' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) last_updated = db.Column(db.DateTime(timezone=True), default=func.now()) - protocol_builder_status = db.Column(db.Enum(ProtocolBuilderStatus)) + status = db.Column(db.Enum(StudyStatus)) + irb_status = db.Column(db.Enum(IrbStatus)) primary_investigator_id = db.Column(db.String, nullable=True) sponsor = db.Column(db.String, nullable=True) hsr_number = db.Column(db.String, nullable=True) @@ -29,7 +44,6 @@ class StudyModel(db.Model): requirements = db.Column(db.ARRAY(db.Integer), nullable=True) on_hold = db.Column(db.Boolean, default=False) enrollment_date = db.Column(db.DateTime(timezone=True), nullable=True) - changes_history = db.Column(db.JSON, nullable=True) def update_from_protocol_builder(self, pbs: ProtocolBuilderStudy): self.hsr_number = pbs.HSRNUMBER @@ -37,11 +51,13 @@ class StudyModel(db.Model): self.user_uid = pbs.NETBADGEID self.last_updated = pbs.DATE_MODIFIED - self.protocol_builder_status = ProtocolBuilderStatus.active + self.irb_status = IrbStatus.incomplete_in_protocol_builder + self.status = StudyStatus.in_progress if pbs.HSRNUMBER: - self.protocol_builder_status = ProtocolBuilderStatus.open + self.irb_status = IrbStatus.hsr_assigned + self.status = StudyStatus.open_for_enrollment if self.on_hold: - self.protocol_builder_status = ProtocolBuilderStatus.hold + self.status = StudyStatus.hold class WorkflowMetadata(object): @@ -112,15 +128,15 @@ class CategorySchema(ma.Schema): class Study(object): def __init__(self, title, last_updated, primary_investigator_id, user_uid, - id=None, - protocol_builder_status=None, + id=None, status=None, irb_status=None, sponsor="", hsr_number="", ind_number="", categories=[], files=[], approvals=[], enrollment_date=None, **argsv): self.id = id self.user_uid = user_uid self.title = title self.last_updated = last_updated - self.protocol_builder_status = protocol_builder_status + self.status = status + self.irb_status = irb_status self.primary_investigator_id = primary_investigator_id self.sponsor = sponsor self.hsr_number = hsr_number @@ -142,25 +158,25 @@ class Study(object): """As the case for update was very reduced, it's mostly and specifically updating only the study status and generating a history record """ - pb_status = ProtocolBuilderStatus(self.protocol_builder_status) + status = StudyStatus(self.status) study_model.last_updated = datetime.datetime.now() - study_model.protocol_builder_status = pb_status + study_model.status = status - if pb_status == ProtocolBuilderStatus.open: + if status == StudyStatus.open_for_enrollment: study_model.enrollment_date = self.enrollment_date - change = { - 'status': ProtocolBuilderStatus(self.protocol_builder_status).value, - 'comment': '' if not hasattr(self, 'comment') else self.comment, - 'date': str(datetime.datetime.now()) - } + # change = { + # 'status': ProtocolBuilderStatus(self.protocol_builder_status).value, + # 'comment': '' if not hasattr(self, 'comment') else self.comment, + # 'date': str(datetime.datetime.now()) + # } - if study_model.changes_history: - changes_history = json.loads(study_model.changes_history) - changes_history.append(change) - else: - changes_history = [change] - study_model.changes_history = json.dumps(changes_history) + # if study_model.changes_history: + # changes_history = json.loads(study_model.changes_history) + # changes_history.append(change) + # else: + # changes_history = [change] + # study_model.changes_history = json.dumps(changes_history) def model_args(self): @@ -174,7 +190,7 @@ class Study(object): class StudyForUpdateSchema(ma.Schema): id = fields.Integer(required=False, allow_none=True) - protocol_builder_status = EnumField(ProtocolBuilderStatus, by_value=True) + status = EnumField(StudyStatus, by_value=True) hsr_number = fields.String(allow_none=True) sponsor = fields.String(allow_none=True) ind_number = fields.String(allow_none=True) @@ -196,7 +212,8 @@ class StudySchema(ma.Schema): id = fields.Integer(required=False, allow_none=True) categories = fields.List(fields.Nested(CategorySchema), dump_only=True) warnings = fields.List(fields.Nested(ApiErrorSchema), dump_only=True) - protocol_builder_status = EnumField(ProtocolBuilderStatus, by_value=True) + protocol_builder_status = EnumField(StudyStatus, by_value=True) + status = EnumField(StudyStatus, by_value=True) hsr_number = fields.String(allow_none=True) sponsor = fields.String(allow_none=True) ind_number = fields.String(allow_none=True) diff --git a/crc/services/study_service.py b/crc/services/study_service.py index 1d15d361..9d94dc60 100644 --- a/crc/services/study_service.py +++ b/crc/services/study_service.py @@ -12,7 +12,7 @@ from crc.api.common import ApiError from crc.models.file import FileModel, FileModelSchema, File from crc.models.ldap import LdapSchema from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus -from crc.models.study import StudyModel, Study, Category, WorkflowMetadata +from crc.models.study import StudyModel, Study, StudyStatus, Category, WorkflowMetadata from crc.models.task_event import TaskEventModel, TaskEvent from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \ WorkflowStatus @@ -64,7 +64,7 @@ class StudyService(object): # Calling this line repeatedly is very very slow. It creates the # master spec and runs it. Don't execute this for Abandoned studies, as # we don't have the information to process them. - if study.protocol_builder_status != ProtocolBuilderStatus.abandoned: + if study.status != StudyStatus.abandoned: status = StudyService.__get_study_status(study_model) study.warnings = StudyService.__update_status_of_workflow_meta(workflow_metas, status) @@ -265,7 +265,7 @@ class StudyService(object): for study in db_studies: pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study.id), None) if not pb_study: - study.protocol_builder_status = ProtocolBuilderStatus.abandoned + study.status = StudyStatus.abandoned db.session.commit() diff --git a/migrations/versions/1c3f88dbccc3_.py b/migrations/versions/1c3f88dbccc3_.py new file mode 100644 index 00000000..3801b76c --- /dev/null +++ b/migrations/versions/1c3f88dbccc3_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: 1c3f88dbccc3 +Revises: 2e7b377cbc7b +Create Date: 2020-07-30 18:51:01.816284 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '1c3f88dbccc3' +down_revision = '2e7b377cbc7b' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("CREATE TYPE irbstatus AS ENUM('incomplete_in_protocol_builder', 'completed_in_protocol_builder', 'hsr_assigned')") + op.execute("CREATE TYPE studystatus AS ENUM('in_progress', 'hold', 'open_for_enrollment', 'abandoned')") + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('study', sa.Column('irb_status', sa.Enum('incomplete_in_protocol_builder', 'completed_in_protocol_builder', 'hsr_assigned', name='irbstatus'), nullable=True)) + op.add_column('study', sa.Column('status', sa.Enum('in_progress', 'hold', 'open_for_enrollment', 'abandoned', name='studystatus'), nullable=True)) + op.drop_column('study', 'protocol_builder_status') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('study', sa.Column('protocol_builder_status', postgresql.ENUM('incomplete', 'active', 'hold', 'open', 'abandoned', name='protocolbuilderstatus'), autoincrement=False, nullable=True)) + op.drop_column('study', 'status') + op.drop_column('study', 'irb_status') + # ### end Alembic commands ### + op.execute('DROP TYPE studystatus') + op.execute('DROP TYPE irbstatus') diff --git a/migrations/versions/369d65dcb269_.py b/migrations/versions/369d65dcb269_.py deleted file mode 100644 index d13d7736..00000000 --- a/migrations/versions/369d65dcb269_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 369d65dcb269 -Revises: c4ddb69e7ef4 -Create Date: 2020-07-27 20:05:29.524553 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '369d65dcb269' -down_revision = 'c4ddb69e7ef4' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('study', sa.Column('changes_history', sa.JSON(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('study', 'changes_history') - # ### end Alembic commands ### diff --git a/tests/base_test.py b/tests/base_test.py index af0b1a20..07554378 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -15,9 +15,8 @@ from crc import app, db, session from crc.models.api_models import WorkflowApiSchema, MultiInstanceType from crc.models.approval import ApprovalModel, ApprovalStatus from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES -from crc.models.protocol_builder import ProtocolBuilderStatus from crc.models.task_event import TaskEventModel -from crc.models.study import StudyModel +from crc.models.study import StudyModel, StudyStatus from crc.models.user import UserModel from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel from crc.services.file_service import FileService @@ -60,7 +59,7 @@ class BaseTest(unittest.TestCase): 'id':0, 'title':'The impact of fried pickles on beer consumption in bipedal software developers.', 'last_updated':datetime.datetime.now(), - 'protocol_builder_status':ProtocolBuilderStatus.active, + 'status':StudyStatus.in_progress, 'primary_investigator_id':'dhf8r', 'sponsor':'Sartography Pharmaceuticals', 'ind_number':'1234', @@ -70,7 +69,7 @@ class BaseTest(unittest.TestCase): 'id':1, 'title':'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels', 'last_updated':datetime.datetime.now(), - 'protocol_builder_status':ProtocolBuilderStatus.active, + 'status':StudyStatus.in_progress, 'primary_investigator_id':'dhf8r', 'sponsor':'Makerspace & Co.', 'ind_number':'5678', @@ -241,7 +240,7 @@ class BaseTest(unittest.TestCase): study = session.query(StudyModel).filter_by(user_uid=uid).filter_by(title=title).first() if study is None: user = self.create_user(uid=uid) - study = StudyModel(title=title, protocol_builder_status=ProtocolBuilderStatus.active, + study = StudyModel(title=title, status=StudyStatus.in_progress, user_uid=user.uid, primary_investigator_id=primary_investigator_id) db.session.add(study) db.session.commit() diff --git a/tests/study/test_study_api.py b/tests/study/test_study_api.py index 5e93245e..62860cde 100644 --- a/tests/study/test_study_api.py +++ b/tests/study/test_study_api.py @@ -11,7 +11,7 @@ from crc.models.protocol_builder import ProtocolBuilderStatus, \ ProtocolBuilderStudySchema from crc.models.approval import ApprovalStatus from crc.models.task_event import TaskEventModel -from crc.models.study import StudyModel, StudySchema +from crc.models.study import StudyModel, StudySchema, StudyStatus from crc.models.workflow import WorkflowSpecModel, WorkflowModel from crc.services.file_service import FileService from crc.services.workflow_processor import WorkflowProcessor @@ -30,7 +30,7 @@ class TestStudyApi(BaseTest): def add_test_study(self): study_schema = StudySchema().dump(self.TEST_STUDY) - study_schema['protocol_builder_status'] = ProtocolBuilderStatus.active.value + study_schema['status'] = StudyStatus.in_progress.value rv = self.app.post('/v1.0/study', content_type="application/json", headers=self.logged_in_headers(), @@ -137,7 +137,7 @@ class TestStudyApi(BaseTest): study: StudyModel = session.query(StudyModel).first() study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility" study_schema = StudySchema().dump(study) - study_schema['protocol_builder_status'] = ProtocolBuilderStatus.active.value + study_schema['status'] = StudyStatus.in_progress.value rv = self.app.put('/v1.0/study/%i' % study.id, content_type="application/json", headers=self.logged_in_headers(), @@ -145,7 +145,7 @@ class TestStudyApi(BaseTest): self.assert_success(rv) json_data = json.loads(rv.get_data(as_text=True)) self.assertEqual(study.title, json_data['title']) - self.assertEqual(study.protocol_builder_status.value, json_data['protocol_builder_status']) + self.assertEqual(study.status.value, json_data['status']) @patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies @patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs @@ -183,15 +183,15 @@ class TestStudyApi(BaseTest): num_incomplete = 0 num_abandoned = 0 - num_active = 0 + num_in_progress = 0 num_open = 0 for study in json_data: - if study['protocol_builder_status'] == 'abandoned': # One study does not exist in user_studies.json + if study['status'] == 'abandoned': # One study does not exist in user_studies.json num_abandoned += 1 - if study['protocol_builder_status'] == 'active': # One study is marked complete without HSR Number - num_active += 1 - if study['protocol_builder_status'] == 'open': # One study is marked complete and has an HSR Number + if study['status'] == 'in progress': # One study is marked complete without HSR Number + num_in_progress += 1 + if study['status'] == 'open for enrollment': # One study is marked complete and has an HSR Number num_open += 1 db_studies_after = session.query(StudyModel).all() @@ -199,10 +199,10 @@ class TestStudyApi(BaseTest): self.assertGreater(num_db_studies_after, num_db_studies_before) self.assertEqual(num_abandoned, 1) self.assertEqual(num_open, 1) - self.assertEqual(num_active, 2) + self.assertEqual(num_in_progress, 2) self.assertEqual(num_incomplete, 0) self.assertEqual(len(json_data), num_db_studies_after) - self.assertEqual(num_open + num_active + num_incomplete + num_abandoned, num_db_studies_after) + self.assertEqual(num_open + num_in_progress + num_incomplete + num_abandoned, num_db_studies_after) @patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies @patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs @@ -230,7 +230,7 @@ class TestStudyApi(BaseTest): json_data = json.loads(rv.get_data(as_text=True)) self.assertEqual(study.id, json_data['id']) self.assertEqual(study.title, json_data['title']) - self.assertEqual(study.protocol_builder_status.value, json_data['protocol_builder_status']) + self.assertEqual(study.status.value, json_data['status']) self.assertEqual(study.primary_investigator_id, json_data['primary_investigator_id']) self.assertEqual(study.sponsor, json_data['sponsor']) self.assertEqual(study.ind_number, json_data['ind_number']) diff --git a/tests/study/test_study_service.py b/tests/study/test_study_service.py index e9711362..11de32cd 100644 --- a/tests/study/test_study_service.py +++ b/tests/study/test_study_service.py @@ -6,7 +6,7 @@ from tests.base_test import BaseTest from crc import db, app from crc.models.protocol_builder import ProtocolBuilderStatus -from crc.models.study import StudyModel +from crc.models.study import StudyModel, StudyStatus from crc.models.user import UserModel from crc.models.workflow import WorkflowModel, WorkflowStatus, \ WorkflowSpecCategoryModel @@ -40,7 +40,7 @@ class TestStudyService(BaseTest): for study in db.session.query(StudyModel).all(): StudyService().delete_study(study.id) - study = StudyModel(title="My title", protocol_builder_status=ProtocolBuilderStatus.active, user_uid=user.uid) + study = StudyModel(title="My title", status=StudyStatus.in_progress, user_uid=user.uid) db.session.add(study) self.load_test_spec("random_fact", category_id=cat.id) diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 829d71e3..66053065 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -8,7 +8,7 @@ from tests.base_test import BaseTest from crc import db, app from crc.api.common import ApiError from crc.models.protocol_builder import ProtocolBuilderStatus -from crc.models.study import StudySchema, StudyModel +from crc.models.study import StudySchema, StudyModel, StudyStatus from crc.models.user import UserModel @@ -220,7 +220,7 @@ class TestAuthentication(BaseTest): return { "title": "blah", "last_updated": datetime.now(tz=timezone.utc), - "protocol_builder_status": ProtocolBuilderStatus.active, + "status": StudyStatus.in_progress, "primary_investigator_id": uid, "user_uid": uid, }