2020-03-18 21:03:36 +00:00
|
|
|
from pandas import ExcelFile
|
|
|
|
|
|
|
|
from crc import session, ma
|
2020-03-03 18:50:22 +00:00
|
|
|
from crc.api.common import ApiError
|
|
|
|
from crc.models.study import StudyModel, StudyModelSchema
|
2020-03-18 21:03:36 +00:00
|
|
|
from crc.scripts.script import Script, ScriptValidationError
|
|
|
|
from crc.services.file_service import FileService
|
2020-03-03 18:50:22 +00:00
|
|
|
from crc.services.protocol_builder import ProtocolBuilderService
|
|
|
|
|
|
|
|
|
|
|
|
class StudyInfo(Script):
|
|
|
|
"""Just your basic class that can pull in data from a few api endpoints and do a basic task."""
|
|
|
|
pb = ProtocolBuilderService()
|
|
|
|
type_options = ['info', 'investigators', 'required_docs', 'details']
|
2020-03-18 21:03:36 +00:00
|
|
|
IRB_PRO_CATEGORIES_FILE = "irb_pro_categories.xls"
|
2020-03-03 18:50:22 +00:00
|
|
|
|
|
|
|
def get_description(self):
|
2020-03-03 20:30:42 +00:00
|
|
|
return """StudyInfo [TYPE], where TYPE is one of 'info', 'investigators','required_docs', or 'details'
|
2020-03-03 18:50:22 +00:00
|
|
|
Adds details about the current study to the Task Data. The type of information required should be
|
|
|
|
provided as an argument. Basic returns the basic information such as the title. Investigators provides
|
|
|
|
detailed information about each investigator in th study. Details provides a large number
|
|
|
|
of details about the study, as gathered within the protocol builder, and 'required_docs',
|
|
|
|
lists all the documents the Protocol Builder has determined will be required as a part of
|
|
|
|
this study.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def do_task(self, task, study_id, *args, **kwargs):
|
|
|
|
if len(args) != 1 or (args[0] not in StudyInfo.type_options):
|
|
|
|
raise ApiError(code="missing_argument",
|
|
|
|
message="The StudyInfo script requires a single argument which must be "
|
|
|
|
"one of %s" % ",".join(StudyInfo.type_options))
|
|
|
|
cmd = args[0]
|
2020-03-09 16:41:35 +00:00
|
|
|
study_info = {}
|
|
|
|
if "study" in task.data:
|
|
|
|
study_info = task.data["study"]
|
|
|
|
|
2020-03-03 18:50:22 +00:00
|
|
|
if cmd == 'info':
|
|
|
|
study = session.query(StudyModel).filter_by(id=study_id).first()
|
|
|
|
schema = StudyModelSchema()
|
2020-03-09 16:41:35 +00:00
|
|
|
study_info["info"] = schema.dump(study)
|
2020-03-03 18:50:22 +00:00
|
|
|
if cmd == 'investigators':
|
2020-03-09 16:41:35 +00:00
|
|
|
study_info["investigators"] = self.pb.get_investigators(study_id)
|
2020-03-03 18:50:22 +00:00
|
|
|
if cmd == 'required_docs':
|
2020-03-18 21:03:36 +00:00
|
|
|
study_info["required_docs"] = self.get_required_docs(study_id)
|
2020-03-03 18:50:22 +00:00
|
|
|
if cmd == 'details':
|
2020-03-09 16:41:35 +00:00
|
|
|
study_info["details"] = self.pb.get_study_details(study_id)
|
|
|
|
task.data["study"] = study_info
|
2020-03-11 20:33:18 +00:00
|
|
|
|
|
|
|
def get_required_docs(self, study_id):
|
2020-03-18 21:03:36 +00:00
|
|
|
"""Takes data from the protocol builder, and merges it with data from the IRB Pro Categories spreadsheet to return
|
|
|
|
pertinant details about the required documents."""
|
|
|
|
pb_docs = self.pb.get_required_docs(study_id)
|
2020-03-19 14:23:50 +00:00
|
|
|
doc_dictionary = self.get_file_reference_dictionary()
|
2020-03-18 21:03:36 +00:00
|
|
|
required_docs = []
|
|
|
|
for doc in pb_docs:
|
2020-03-19 14:23:50 +00:00
|
|
|
id = int(doc['AUXDOCID'])
|
|
|
|
required_doc = {'id': id, 'name': doc['AUXDOC']}
|
|
|
|
if id in doc_dictionary:
|
|
|
|
required_doc = {**required_doc, **doc_dictionary[id]}
|
|
|
|
required_docs.append(required_doc)
|
2020-03-16 12:31:19 +00:00
|
|
|
return required_docs
|
2020-03-11 20:33:18 +00:00
|
|
|
|
2020-03-19 14:23:50 +00:00
|
|
|
|
2020-03-18 21:03:36 +00:00
|
|
|
def get_file_reference_dictionary(self):
|
|
|
|
"""Loads up the xsl file that contains the IRB Pro Categories and converts it to a Panda's data frame for processing."""
|
|
|
|
data_model = FileService.get_reference_file_data(StudyInfo.IRB_PRO_CATEGORIES_FILE)
|
|
|
|
xls = ExcelFile(data_model.data)
|
|
|
|
df = xls.parse(xls.sheet_names[0])
|
2020-03-19 14:23:50 +00:00
|
|
|
# Pandas is lovely, but weird. Here we drop records without an Id, and convert it to an integer.
|
|
|
|
df = df.drop_duplicates(subset='Id').astype({'Id': 'Int64'})
|
|
|
|
# Now we index on the ID column and convert to a dictionary, where the key is the id, and the value
|
|
|
|
# is a dictionary with all the remaining data in it. It's kinda pretty really.
|
|
|
|
all_dict = df.set_index('Id').to_dict('index')
|
|
|
|
return all_dict
|
2020-03-18 21:03:36 +00:00
|
|
|
|
|
|
|
# Verifies that information is available for this script task to function
|
|
|
|
# correctly. Returns a list of validation errors.
|
|
|
|
@staticmethod
|
|
|
|
def validate():
|
|
|
|
errors = []
|
|
|
|
try:
|
|
|
|
FileService.get_reference_file_data(StudyInfo.IRB_PRO_CATEGORIES_FILE)
|
|
|
|
except ApiError as ae:
|
|
|
|
errors.append(ScriptValidationError.from_api_error(ae))
|
|
|
|
return errors
|
|
|
|
|
|
|
|
class RequiredDocument(object):
|
|
|
|
def __init__(self, pb_id, pb_name, category1, category2, category3, who_uploads, required, total_uploaded):
|
|
|
|
self.protocol_builder_id = pb_id
|
|
|
|
self.protocol_builder_name = pb_name
|
|
|
|
self.category1 = category1
|
|
|
|
self.category2 = category2
|
|
|
|
self.category3 = category3
|
|
|
|
self.who_uploads = who_uploads
|
|
|
|
self.required = required
|
|
|
|
self.total_uploaded = total_uploaded
|
|
|
|
|
|
|
|
class RequiredDocumentSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = RequiredDocument
|
|
|
|
fields = ["pb_id", "pb_name", "category1", "category2", "category3",
|
|
|
|
"who_uploads", "required", "total_uploaded"]
|