From fd0adb1d435862abc1e6d00a6de3c8fb530bff29 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Tue, 21 Apr 2020 17:13:30 -0400 Subject: [PATCH] Updated the study status to use a different enumeration. Migration correctly handles modifying the enum. INCOMPLETE = 'Incomplete in Protocol Builder', ACTIVE = 'Active / Ready to roll', HOLD = 'On Hold', OPEN = 'Open - this study is in progress', ABANDONED = 'Abandoned, it got deleted in Protocol Builder' --- crc/api.yml | 2 +- crc/api/study.py | 2 +- crc/models/protocol_builder.py | 22 ++++++++++--- crc/models/study.py | 23 ++++++-------- crc/services/study_service.py | 3 +- migrations/versions/0f38d7a36f21_.py | 38 +++++++++++++++++++++++ tests/base_test.py | 4 +-- tests/data/pb_responses/user_studies.json | 6 ++-- tests/test_study_api.py | 29 ++++++++++------- tests/test_study_service.py | 2 +- 10 files changed, 91 insertions(+), 40 deletions(-) create mode 100644 migrations/versions/0f38d7a36f21_.py diff --git a/crc/api.yml b/crc/api.yml index fbe91685..8c191d4a 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -828,7 +828,7 @@ components: example: dhf8r protocol_builder_status: type: string - enum: [DRAFT, IN_PROCESS, IN_REVIEW, REVIEW_COMPLETE, INACTIVE] + enum: [INCOMPLETE, ACTIVE, HOLD, OPEN, ABANDONED] example: done sponsor: type: string diff --git a/crc/api/study.py b/crc/api/study.py index f5f7f4dc..1b2e6850 100644 --- a/crc/api/study.py +++ b/crc/api/study.py @@ -82,7 +82,7 @@ def post_update_study_from_protocol_builder(study_id): db_study.update_from_protocol_builder(pb_study) else: db_study.inactive = True - db_study.protocol_builder_status = ProtocolBuilderStatus.INACTIVE + db_study.protocol_builder_status = ProtocolBuilderStatus.ABANDONED return NoContent, 304 diff --git a/crc/models/protocol_builder.py b/crc/models/protocol_builder.py index b903619d..9ff1098f 100644 --- a/crc/models/protocol_builder.py +++ b/crc/models/protocol_builder.py @@ -18,11 +18,23 @@ class ProtocolBuilderInvestigatorType(enum.Enum): class ProtocolBuilderStatus(enum.Enum): - DRAFT = 'draft', # !Q_COMPLETE - IN_PROCESS = 'in_process', # Q_COMPLETE && !UPLOAD_COMPLETE && !HSRNUMBER - IN_REVIEW = 'in_review', # Q_COMPLETE && (!UPLOAD_COMPLETE || !HSRNUMBER) - REVIEW_COMPLETE = 'review_complete', # Q_COMPLETE && UPLOAD_COMPLETE && HSRNUMBER - INACTIVE = 'inactive', # Not found in PB + # • Active: found in PB and no HSR number and not hold + # • Hold: store boolean value in CR Connect (add to Study Model) + # • Open To Enrollment: has start date and HSR number? + # • Abandoned: deleted in PB + INCOMPLETE = 'incomplete' # Found in PB but not ready to start (not q_complete) + ACTIVE = 'active', # found in PB, marked as "q_complete" and no HSR number and not hold + HOLD = 'hold', # CR Connect side, if the Study ias marked as "hold". + OPEN = 'open', # Open To Enrollment: has start date and HSR number? + ABANDONED = 'Abandoned' # Not found in PB + + + #DRAFT = 'draft', # !Q_COMPLETE + #IN_PROCESS = 'in_process', # Q_COMPLETE && !UPLOAD_COMPLETE && !HSRNUMBER + #IN_REVIEW = 'in_review', # Q_COMPLETE && (!UPLOAD_COMPLETE || !HSRNUMBER) + #REVIEW_COMPLETE = 'review_complete', # Q_COMPLETE && UPLOAD_COMPLETE && HSRNUMBER + #INACTIVE = 'inactive', # Not found in PB + class ProtocolBuilderStudy(object): diff --git a/crc/models/study.py b/crc/models/study.py index d4599c57..964ad7f1 100644 --- a/crc/models/study.py +++ b/crc/models/study.py @@ -22,24 +22,22 @@ class StudyModel(db.Model): ind_number = db.Column(db.String, nullable=True) user_uid = db.Column(db.String, db.ForeignKey('user.uid'), nullable=False) investigator_uids = db.Column(db.ARRAY(db.String), nullable=True) - inactive = db.Column(db.Boolean, default=False) requirements = db.Column(db.ARRAY(db.Integer), nullable=True) + on_hold = db.Column(db.Boolean, default=False) def update_from_protocol_builder(self, pbs: ProtocolBuilderStudy): self.hsr_number = pbs.HSRNUMBER self.title = pbs.TITLE self.user_uid = pbs.NETBADGEID self.last_updated = pbs.DATE_MODIFIED - self.protocol_builder_status = ProtocolBuilderStatus.DRAFT - self.inactive = False - - if pbs.HSRNUMBER: # And Up load complete? - self.protocol_builder_status = ProtocolBuilderStatus.REVIEW_COMPLETE - elif pbs.Q_COMPLETE: - self.protocol_builder_status = ProtocolBuilderStatus.IN_PROCESS - - + self.protocol_builder_status = ProtocolBuilderStatus.INCOMPLETE + if pbs.Q_COMPLETE: + self.protocol_builder_status = ProtocolBuilderStatus.ACTIVE + if pbs.HSRNUMBER: + self.protocol_builder_status = ProtocolBuilderStatus.OPEN + if self.on_hold: + self.protocol_builder_status = ProtocolBuilderStatus.HOLD class WorkflowMetadata(object): @@ -106,7 +104,7 @@ class Study(object): def __init__(self, id, title, last_updated, primary_investigator_id, user_uid, protocol_builder_status=None, - sponsor="", hsr_number="", ind_number="", inactive=False, categories=[], **argsv): + sponsor="", hsr_number="", ind_number="", categories=[], **argsv): self.id = id self.user_uid = user_uid self.title = title @@ -116,7 +114,6 @@ class Study(object): self.sponsor = sponsor self.hsr_number = hsr_number self.ind_number = ind_number - self.inactive = inactive self.categories = categories self.warnings = [] @@ -149,7 +146,7 @@ class StudySchema(ma.Schema): class Meta: model = Study additional = ["id", "title", "last_updated", "primary_investigator_id", "user_uid", - "sponsor", "ind_number", "inactive"] + "sponsor", "ind_number"] unknown = INCLUDE @marshmallow.post_load diff --git a/crc/services/study_service.py b/crc/services/study_service.py index fd011c42..35b7c42d 100644 --- a/crc/services/study_service.py +++ b/crc/services/study_service.py @@ -88,8 +88,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.inactive = True - study.protocol_builder_status = ProtocolBuilderStatus.INACTIVE + study.protocol_builder_status = ProtocolBuilderStatus.ABANDONED db.session.commit() diff --git a/migrations/versions/0f38d7a36f21_.py b/migrations/versions/0f38d7a36f21_.py new file mode 100644 index 00000000..25e85484 --- /dev/null +++ b/migrations/versions/0f38d7a36f21_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: 0f38d7a36f21 +Revises: 476f8a4933ba +Create Date: 2020-04-21 15:42:46.430272 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0f38d7a36f21' +down_revision = '476f8a4933ba' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('study', sa.Column('on_hold', sa.Boolean(), nullable=True)) + op.drop_column('study', 'inactive') + # ### end Alembic commands ### + + op.execute('ALTER TYPE protocolbuilderstatus RENAME TO pbs_old;') + op.execute("CREATE TYPE protocolbuilderstatus AS ENUM('INCOMPLETE', 'ACTIVE', 'HOLD', 'OPEN', 'ABANDONED')") + op.execute("ALTER TABLE study ALTER COLUMN protocol_builder_status TYPE protocolbuilderstatus USING protocol_builder_status::text::protocolbuilderstatus;") + op.execute('DROP TYPE pbs_old;') + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('study', sa.Column('inactive', sa.BOOLEAN(), autoincrement=False, nullable=True)) + op.drop_column('study', 'on_hold') + # ### end Alembic commands ### + + op.execute('ALTER TYPE protocolbuilderstatus RENAME TO pbs_old;') + op.execute("CREATE TYPE protocolbuilderstatus AS ENUM('DRAFT', 'IN_PROCESS', 'IN_REVIEW', 'REVIEW_COMPLETE', 'INACTIVE')") + op.execute("ALTER TABLE study ALTER COLUMN protocol_builder_status TYPE protocolbuilderstatus USING protocol_builder_status::text::protocolbuilderstatus;") + op.execute('DROP TYPE pbs_old;') diff --git a/tests/base_test.py b/tests/base_test.py index 876aaf0f..ec946b10 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -53,7 +53,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.IN_PROCESS, + 'protocol_builder_status':ProtocolBuilderStatus.ACTIVE, 'primary_investigator_id':'dhf8r', 'sponsor':'Sartography Pharmaceuticals', 'ind_number':'1234', @@ -63,7 +63,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.IN_PROCESS, + 'protocol_builder_status':ProtocolBuilderStatus.ACTIVE, 'primary_investigator_id':'dhf8r', 'sponsor':'Makerspace & Co.', 'ind_number':'5678', diff --git a/tests/data/pb_responses/user_studies.json b/tests/data/pb_responses/user_studies.json index 18a0a00d..922c6ddc 100644 --- a/tests/data/pb_responses/user_studies.json +++ b/tests/data/pb_responses/user_studies.json @@ -9,7 +9,7 @@ }, { "DATE_MODIFIED": "2020-02-19T14:24:55.101695", - "HSRNUMBER": "56753", + "HSRNUMBER": "", "NETBADGEID": "dhf8r", "Q_COMPLETE": true, "STUDYID": 65432, @@ -17,9 +17,9 @@ }, { "DATE_MODIFIED": "2020-02-19T14:24:55.101695", - "HSRNUMBER": "45678", + "HSRNUMBER": "", "NETBADGEID": "dhf8r", - "Q_COMPLETE": true, + "Q_COMPLETE": false, "STUDYID": 1, "TITLE": "Efficacy of xenomorph bio-augmented circuits on dexterity of cybernetic prostheses" } diff --git a/tests/test_study_api.py b/tests/test_study_api.py index 373963a2..72e88914 100644 --- a/tests/test_study_api.py +++ b/tests/test_study_api.py @@ -21,7 +21,7 @@ class TestStudyApi(BaseTest): "title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents " "for Interstellar Spacecraft", "last_updated": datetime.now(tz=timezone.utc), - "protocol_builder_status": ProtocolBuilderStatus.IN_PROCESS, + "protocol_builder_status": ProtocolBuilderStatus.ACTIVE, "primary_investigator_id": "tricia.marie.mcmillan@heartofgold.edu", "sponsor": "Sirius Cybernetics Corporation", "ind_number": "567890", @@ -104,7 +104,7 @@ class TestStudyApi(BaseTest): self.load_example_data() study: StudyModel = session.query(StudyModel).first() study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility" - study.protocol_builder_status = ProtocolBuilderStatus.REVIEW_COMPLETE + study.protocol_builder_status = ProtocolBuilderStatus.ACTIVE rv = self.app.put('/v1.0/study/%i' % study.id, content_type="application/json", headers=self.logged_in_headers(), @@ -142,26 +142,31 @@ class TestStudyApi(BaseTest): self.assert_success(api_response) json_data = json.loads(api_response.get_data(as_text=True)) - num_inactive = 0 + num_incomplete = 0 + num_abandoned = 0 num_active = 0 + num_open = 0 for study in json_data: - if study['inactive']: - num_inactive += 1 - else: + if study['protocol_builder_status'] == 'INCOMPLETE': # One study in user_studies.json is not q_complete + num_incomplete += 1 + if study['protocol_builder_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 + num_open += 1 db_studies_after = session.query(StudyModel).all() num_db_studies_after = len(db_studies_after) self.assertGreater(num_db_studies_after, num_db_studies_before) - self.assertGreater(num_inactive, 0) - self.assertGreater(num_active, 0) + self.assertEquals(num_abandoned, 1) + self.assertEquals(num_open, 1) + self.assertEquals(num_active, 1) + self.assertEquals(num_incomplete, 1) self.assertEqual(len(json_data), num_db_studies_after) - self.assertEqual(num_active + num_inactive, num_db_studies_after) + self.assertEqual(num_open + num_active + num_incomplete + num_abandoned, num_db_studies_after) - # Assure that the existing study is properly updated. - test_study = session.query(StudyModel).filter_by(id=54321).first() - self.assertFalse(test_study.inactive) @patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs def test_get_single_study(self, mock_docs): diff --git a/tests/test_study_service.py b/tests/test_study_service.py index c6f9939d..8ab54faa 100644 --- a/tests/test_study_service.py +++ b/tests/test_study_service.py @@ -29,7 +29,7 @@ class TestStudyService(BaseTest): user = UserModel(uid="dhf8r", email_address="whatever@stuff.com", display_name="Stayathome Smellalots") db.session.add(user) db.session.commit() - study = StudyModel(title="My title", protocol_builder_status=ProtocolBuilderStatus.IN_PROCESS, user_uid=user.uid) + study = StudyModel(title="My title", protocol_builder_status=ProtocolBuilderStatus.ACTIVE, user_uid=user.uid) cat = WorkflowSpecCategoryModel(name="cat", display_name="cat", display_order=0) db.session.add_all([study, cat]) db.session.commit()