Some minor cleanup on the study status and automatic events. I wanted to avoid having one database model automatically generating other database models as a side effect. The study service now has full responsiblity to recording study events.

To help in running tests, adding __init__.py methods to all the test directories.
This commit is contained in:
Dan Funk 2020-08-17 14:56:00 -04:00
parent 4f05d99cf0
commit 85ad477b2b
9 changed files with 62 additions and 73 deletions

View File

@ -23,14 +23,12 @@ def add_study(body):
primary_investigator_id=body['primary_investigator_id'], primary_investigator_id=body['primary_investigator_id'],
last_updated=datetime.now(), last_updated=datetime.now(),
status=StudyStatus.in_progress) status=StudyStatus.in_progress)
study_model.update_event(
status=StudyStatus.in_progress,
event_type=StudyEventType.automatic,
user_uid=g.user.uid
)
session.add(study_model) session.add(study_model)
StudyService.add_study_update_event(study_model,
status=StudyStatus.in_progress,
event_type=StudyEventType.user,
user_uid=g.user.uid)
errors = StudyService._add_all_workflow_specs_to_study(study_model) errors = StudyService._add_all_workflow_specs_to_study(study_model)
session.commit() session.commit()
study = StudyService().get_study(study_model.id) study = StudyService().get_study(study_model.id)
@ -40,6 +38,7 @@ def add_study(body):
def update_study(study_id, body): def update_study(study_id, body):
"""Pretty limited, but allows manual modifications to the study status """
if study_id is None: if study_id is None:
raise ApiError('unknown_study', 'Please provide a valid Study ID.') raise ApiError('unknown_study', 'Please provide a valid Study ID.')
@ -48,7 +47,20 @@ def update_study(study_id, body):
raise ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.') raise ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.')
study: Study = StudyForUpdateSchema().load(body) study: Study = StudyForUpdateSchema().load(body)
study.update_model(study_model)
status = StudyStatus(study.status)
study_model.last_updated = datetime.now()
if study_model.status != status:
study_model.status = status
StudyService.add_study_update_event(study_model, status, StudyEventType.user,
user_uid=UserService.current_user().uid if UserService.has_user() else None,
comment='' if not hasattr(study, 'comment') else study.comment,
)
if status == StudyStatus.open_for_enrollment:
study_model.enrollment_date = study.enrollment_date
session.add(study_model) session.add(study_model)
session.commit() session.commit()
# Need to reload the full study to return it to the frontend # Need to reload the full study to return it to the frontend

View File

@ -61,36 +61,6 @@ class StudyModel(db.Model):
self.irb_status = IrbStatus.incomplete_in_protocol_builder self.irb_status = IrbStatus.incomplete_in_protocol_builder
self.status = StudyStatus.in_progress self.status = StudyStatus.in_progress
self.update_event(
status=StudyStatus.in_progress,
event_type=StudyEventType.automatic,
user_uid=self.user_uid
)
if pbs.HSRNUMBER:
self.irb_status = IrbStatus.hsr_assigned
self.status = StudyStatus.open_for_enrollment
self.update_event(
status=StudyStatus.open_for_enrollment,
event_type=StudyEventType.automatic,
user_uid=self.user_uid
)
if self.on_hold:
self.status = StudyStatus.hold
self.update_event(
status=StudyStatus.hold,
event_type=StudyEventType.automatic,
user_uid=self.user_uid
)
def update_event(self, status, event_type, user_uid, comment=''):
study_event = StudyEvent(study=self,
status=status,
event_type=event_type,
user_uid=user_uid,
comment=comment)
db.session.add(study_event)
db.session.commit()
class StudyEvent(db.Model): class StudyEvent(db.Model):
@ -202,24 +172,6 @@ class Study(object):
instance = cls(**args) instance = cls(**args)
return instance return instance
def update_model(self, study_model: StudyModel):
"""As the case for update was very reduced, it's mostly and specifically
updating only the study status and generating a history record
"""
status = StudyStatus(self.status)
study_model.last_updated = datetime.datetime.now()
study_model.status = status
if status == StudyStatus.open_for_enrollment:
study_model.enrollment_date = self.enrollment_date
study_model.update_event(
status=status,
comment='' if not hasattr(self, 'comment') else self.comment,
event_type=StudyEventType.user,
user_uid=UserService.current_user().uid if UserService.has_user() else None
)
def model_args(self): def model_args(self):
"""Arguments that can be passed into the Study Model to update it.""" """Arguments that can be passed into the Study Model to update it."""
self_dict = self.__dict__.copy() self_dict = self.__dict__.copy()

View File

@ -12,7 +12,8 @@ from crc.api.common import ApiError
from crc.models.file import FileModel, FileModelSchema, File from crc.models.file import FileModel, FileModelSchema, File
from crc.models.ldap import LdapSchema from crc.models.ldap import LdapSchema
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
from crc.models.study import StudyModel, Study, StudyStatus, Category, WorkflowMetadata, StudyEventType from crc.models.study import StudyModel, Study, StudyStatus, Category, WorkflowMetadata, StudyEventType, StudyEvent, \
IrbStatus
from crc.models.task_event import TaskEventModel, TaskEvent from crc.models.task_event import TaskEventModel, TaskEvent
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \ from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
WorkflowStatus WorkflowStatus
@ -229,9 +230,9 @@ class StudyService(object):
@staticmethod @staticmethod
def get_protocol(study_id): def get_protocol(study_id):
"""Returns the study protocol, if it has been uploaded.""" """Returns the study protocol, if it has been uploaded."""
file = db.session.query(FileModel)\ file = db.session.query(FileModel) \
.filter_by(study_id=study_id)\ .filter_by(study_id=study_id) \
.filter_by(form_field_key='Study_Protocol_Document')\ .filter_by(form_field_key='Study_Protocol_Document') \
.first() .first()
return FileModelSchema().dump(file) return FileModelSchema().dump(file)
@ -253,30 +254,54 @@ class StudyService(object):
db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all() db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
# Update all studies from the protocol builder, create new studies as needed. # Update all studies from the protocol builder, create new studies as needed.
# Futher assures that every active study (that does exist in the protocol builder) # Further assures that every active study (that does exist in the protocol builder)
# has a reference to every available workflow (though some may not have started yet) # has a reference to every available workflow (though some may not have started yet)
for pb_study in pb_studies: for pb_study in pb_studies:
new_status = None
db_study = next((s for s in db_studies if s.id == pb_study.STUDYID), None) db_study = next((s for s in db_studies if s.id == pb_study.STUDYID), None)
if not db_study: if not db_study:
db_study = StudyModel(id=pb_study.STUDYID) db_study = StudyModel(id=pb_study.STUDYID)
db_study.status = None # Force a new sa
new_status = StudyStatus.in_progress
session.add(db_study) session.add(db_study)
db_studies.append(db_study) db_studies.append(db_study)
if pb_study.HSRNUMBER:
db_study.irb_status = IrbStatus.hsr_assigned
if db_study.status != StudyStatus.open_for_enrollment:
new_status = StudyStatus.open_for_enrollment
db_study.update_from_protocol_builder(pb_study) db_study.update_from_protocol_builder(pb_study)
StudyService._add_all_workflow_specs_to_study(db_study) StudyService._add_all_workflow_specs_to_study(db_study)
# If there is a new automatic status change and there isn't a manual change in place, record it.
if new_status and db_study.status != StudyStatus.hold:
db_study.status = new_status
StudyService.add_study_update_event(db_study,
status=new_status,
event_type=StudyEventType.automatic)
# Mark studies as inactive that are no longer in Protocol Builder # Mark studies as inactive that are no longer in Protocol Builder
for study in db_studies: for study in db_studies:
pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study.id), None) pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study.id), None)
if not pb_study: if not pb_study and study.status != StudyStatus.abandoned:
study.status = StudyStatus.abandoned study.status = StudyStatus.abandoned
study.update_event( StudyService.add_study_update_event(study,
status=StudyStatus.abandoned, status=StudyStatus.abandoned,
event_type=StudyEventType.automatic, event_type=StudyEventType.automatic)
user_uid=study.user_uid
)
db.session.commit() db.session.commit()
@staticmethod
def add_study_update_event(study, status, event_type, user_uid=None, comment=''):
study_event = StudyEvent(study=study,
status=status,
event_type=event_type,
user_uid=user_uid,
comment=comment)
db.session.add(study_event)
db.session.commit()
@staticmethod @staticmethod
def __update_status_of_workflow_meta(workflow_metas, status): def __update_status_of_workflow_meta(workflow_metas, status):
# Update the status on each workflow # Update the status on each workflow
@ -322,7 +347,7 @@ class StudyService(object):
return WorkflowProcessor.run_master_spec(master_specs[0], study_model) return WorkflowProcessor.run_master_spec(master_specs[0], study_model)
@staticmethod @staticmethod
def _add_all_workflow_specs_to_study(study_model:StudyModel): def _add_all_workflow_specs_to_study(study_model: StudyModel):
existing_models = session.query(WorkflowModel).filter(WorkflowModel.study == study_model).all() existing_models = session.query(WorkflowModel).filter(WorkflowModel.study == study_model).all()
existing_specs = list(m.workflow_spec_id for m in existing_models) existing_specs = list(m.workflow_spec_id for m in existing_models)
new_specs = session.query(WorkflowSpecModel). \ new_specs = session.query(WorkflowSpecModel). \

View File

0
tests/files/__init__.py Normal file
View File

0
tests/ldap/__init__.py Normal file
View File

0
tests/study/__init__.py Normal file
View File

View File

@ -135,7 +135,7 @@ class TestStudyApi(BaseTest):
study_event = session.query(StudyEvent).first() study_event = session.query(StudyEvent).first()
self.assertIsNotNone(study_event) self.assertIsNotNone(study_event)
self.assertEqual(study_event.status, StudyStatus.in_progress) self.assertEqual(study_event.status, StudyStatus.in_progress)
self.assertEqual(study_event.event_type, StudyEventType.automatic) self.assertEqual(study_event.event_type, StudyEventType.user)
self.assertFalse(study_event.comment) self.assertFalse(study_event.comment)
self.assertEqual(study_event.user_uid, self.test_uid) self.assertEqual(study_event.user_uid, self.test_uid)
@ -145,7 +145,7 @@ class TestStudyApi(BaseTest):
study: StudyModel = session.query(StudyModel).first() study: StudyModel = session.query(StudyModel).first()
study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility" study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility"
study_schema = StudySchema().dump(study) study_schema = StudySchema().dump(study)
study_schema['status'] = StudyStatus.in_progress.value study_schema['status'] = StudyStatus.hold.value
study_schema['comment'] = update_comment study_schema['comment'] = update_comment
rv = self.app.put('/v1.0/study/%i' % study.id, rv = self.app.put('/v1.0/study/%i' % study.id,
content_type="application/json", content_type="application/json",
@ -159,7 +159,7 @@ class TestStudyApi(BaseTest):
# Making sure events history is being properly recorded # Making sure events history is being properly recorded
study_event = session.query(StudyEvent).first() study_event = session.query(StudyEvent).first()
self.assertIsNotNone(study_event) self.assertIsNotNone(study_event)
self.assertEqual(study_event.status, StudyStatus.in_progress) self.assertEqual(study_event.status, StudyStatus.hold)
self.assertEqual(study_event.event_type, StudyEventType.user) self.assertEqual(study_event.event_type, StudyEventType.user)
self.assertEqual(study_event.comment, update_comment) self.assertEqual(study_event.comment, update_comment)
self.assertEqual(study_event.user_uid, self.test_uid) self.assertEqual(study_event.user_uid, self.test_uid)
@ -221,7 +221,7 @@ class TestStudyApi(BaseTest):
# Automatic events check # Automatic events check
in_progress_events = session.query(StudyEvent).filter_by(status=StudyStatus.in_progress) in_progress_events = session.query(StudyEvent).filter_by(status=StudyStatus.in_progress)
self.assertEqual(in_progress_events.count(), 3) # 3 studies were started self.assertEqual(in_progress_events.count(), 1) # 1 study is in progress
abandoned_events = session.query(StudyEvent).filter_by(status=StudyStatus.abandoned) abandoned_events = session.query(StudyEvent).filter_by(status=StudyStatus.abandoned)
self.assertEqual(abandoned_events.count(), 1) # 1 study has been abandoned self.assertEqual(abandoned_events.count(), 1) # 1 study has been abandoned

View File