mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-22 12:48:25 +00:00
Merge branch 'feature/protocol_status' into feature/previous_task
# Conflicts: # crc/services/study_service.py
This commit is contained in:
commit
714b5f3be0
@ -5,7 +5,7 @@ from jinja2 import UndefinedError
|
||||
from crc import session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
|
||||
from crc.models.workflow import WorkflowSpecModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowModel
|
||||
from docxtpl import DocxTemplate
|
||||
import jinja2
|
||||
|
||||
@ -33,11 +33,12 @@ Takes two arguments:
|
||||
def do_task(self, task, study_id, *args, **kwargs):
|
||||
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
|
||||
final_document_stream = self.process_template(task, study_id, *args, **kwargs)
|
||||
|
||||
workflow = session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first()
|
||||
file_name = args[0]
|
||||
irb_doc_code = args[1]
|
||||
FileService.add_task_file(study_id=study_id,
|
||||
workflow_id=workflow_id,
|
||||
workflow_spec_id=workflow.workflow_spec_id,
|
||||
task_id=task.id,
|
||||
name=file_name,
|
||||
content_type=CONTENT_TYPES['docx'],
|
||||
|
@ -1,81 +0,0 @@
|
||||
from crc.api.common import ApiError
|
||||
from crc.scripts.script import Script, ScriptValidationError
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
|
||||
|
||||
class Documents(Script):
|
||||
"""Provides information about the documents required by Protocol Builder."""
|
||||
pb = ProtocolBuilderService()
|
||||
|
||||
def get_description(self):
|
||||
return """
|
||||
Provides detailed information about the documents loaded as a part of completing tasks.
|
||||
Makes an immediate call to the IRB Protocol Builder API to get a list of currently required
|
||||
documents. It then collects all the information in a reference file called 'irb_pro_categories.xls',
|
||||
if the Id from Protocol Builder matches an Id in this table, all data available in that row
|
||||
is also provided.
|
||||
|
||||
This place a dictionary of values in the current task, where the key is the code in the lookup table.
|
||||
|
||||
For example:
|
||||
``` "Documents" :
|
||||
{
|
||||
"UVACompliance_PRCApproval": {
|
||||
"name": "Cancer Center's PRC Approval Form",
|
||||
"category1": "UVA Compliance",
|
||||
"category2": "PRC Approval",
|
||||
"category3": "",
|
||||
"Who Uploads?": "CRC",
|
||||
"required": True,
|
||||
"Id": 6
|
||||
"count": 0
|
||||
},
|
||||
24: { ...
|
||||
}
|
||||
```
|
||||
"""
|
||||
def do_task_validate_only(self, task, study_id, *args, **kwargs):
|
||||
"""For validation only, pretend no results come back from pb"""
|
||||
pb_docs = []
|
||||
self.add_data_to_task(task, self.get_documents(study_id, pb_docs))
|
||||
|
||||
def do_task(self, task, study_id, *args, **kwargs):
|
||||
"""Takes data from the protocol builder, and merges it with data from the IRB Pro Categories
|
||||
spreadsheet to return pertinent details about the required documents."""
|
||||
pb_docs = self.pb.get_required_docs(study_id, as_objects=True)
|
||||
self.add_data_to_task(task, self.get_documents(study_id, pb_docs))
|
||||
|
||||
def get_documents(self, study_id, pb_docs):
|
||||
"""Takes data from the protocol builder, and merges it with data from the IRB Pro Categories spreadsheet to return
|
||||
pertinent details about the required documents."""
|
||||
|
||||
doc_dictionary = FileService.get_file_reference_dictionary()
|
||||
required_docs = {}
|
||||
for code, required_doc in doc_dictionary.items():
|
||||
try:
|
||||
pb_data = next((item for item in pb_docs if int(item.AUXDOCID) == int(required_doc['Id'])), None)
|
||||
except:
|
||||
pb_data = None
|
||||
required_doc['count'] = self.get_count(study_id, code)
|
||||
required_doc['required'] = False
|
||||
if pb_data:
|
||||
required_doc['required'] = True
|
||||
required_docs[code] = required_doc
|
||||
return required_docs
|
||||
|
||||
def get_count(self, study_id, irb_doc_code):
|
||||
"""Returns the total number of documents that have been uploaded that match
|
||||
the given document id. """
|
||||
return(len(FileService.get_files(study_id=study_id, irb_doc_code=irb_doc_code)))
|
||||
|
||||
# Verifies that information is available for this script task to function
|
||||
# correctly. Returns a list of validation errors.
|
||||
@staticmethod
|
||||
def validate():
|
||||
errors = []
|
||||
try:
|
||||
dict = FileService.get_file_reference_dictionary()
|
||||
except ApiError as ae:
|
||||
errors.append(ScriptValidationError.from_api_error(ae))
|
||||
return errors
|
@ -24,13 +24,6 @@ class Script(object):
|
||||
"does must provide a validate_only option that mimics the do_task, " +
|
||||
"but does not make external calls or database updates." )
|
||||
|
||||
def validate(self):
|
||||
"""Override this method to perform an early check that the script has access to
|
||||
everything it needs to properly process requests.
|
||||
Should return an array of ScriptValidationErrors.
|
||||
"""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_all_subclasses():
|
||||
return Script._get_all_subclasses(Script)
|
||||
|
@ -4,7 +4,8 @@ from crc import session, app
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.study import StudyModel, StudySchema
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from crc.scripts.script import Script
|
||||
from crc.scripts.script import Script, ScriptValidationError
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.ldap_service import LdapService
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from crc.services.study_service import StudyService
|
||||
@ -15,21 +16,24 @@ 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', 'details', 'approvals', 'documents_status']
|
||||
type_options = ['info', 'investigators', 'details', 'approvals', 'documents', 'protocol']
|
||||
|
||||
def get_description(self):
|
||||
return """StudyInfo [TYPE], where TYPE is one of 'info', 'investigators', or 'details'
|
||||
return """StudyInfo [TYPE], where TYPE is one of 'info', 'investigators', or 'details', 'approvals',
|
||||
'documents' or 'protocol'.
|
||||
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.
|
||||
provided as an argument. 'info' 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 'documents',
|
||||
lists all the documents that can be a part of the study, with documents from Protocol Builder
|
||||
marked as required, and details about any files that were uploaded' .
|
||||
"""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, *args, **kwargs):
|
||||
"""For validation only, pretend no results come back from pb"""
|
||||
self.check_args(args)
|
||||
# Assure the reference file exists (a bit hacky, but we want to raise this error early, and cleanly.)
|
||||
FileService.get_file_reference_dictionary()
|
||||
data = {
|
||||
"study":{
|
||||
"info": {
|
||||
@ -57,7 +61,8 @@ class StudyInfo(Script):
|
||||
"status": WorkflowStatus.not_started.value,
|
||||
"workflow_spec_id": "irb_api_details",
|
||||
},
|
||||
"documents_status": [
|
||||
"documents": {
|
||||
"AD_CoCApp":
|
||||
{
|
||||
'category1': 'Ancillary Document',
|
||||
'category2': 'CoC Application',
|
||||
@ -75,7 +80,10 @@ class StudyInfo(Script):
|
||||
'workflow_spec_id': 'irb_api_details',
|
||||
'status': 'complete',
|
||||
}
|
||||
]
|
||||
},
|
||||
'protocol': {
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
self.add_data_to_task(task=task, data=data["study"])
|
||||
@ -99,8 +107,10 @@ class StudyInfo(Script):
|
||||
self.add_data_to_task(task, {cmd: self.pb.get_study_details(study_id)})
|
||||
if cmd == 'approvals':
|
||||
self.add_data_to_task(task, {cmd: StudyService().get_approvals(study_id)})
|
||||
if cmd == 'documents_status':
|
||||
if cmd == 'documents':
|
||||
self.add_data_to_task(task, {cmd: StudyService().get_documents_status(study_id)})
|
||||
if cmd == 'protocol':
|
||||
self.add_data_to_task(task, {cmd: StudyService().get_protocol(study_id)})
|
||||
|
||||
|
||||
def check_args(self, args):
|
||||
|
@ -56,7 +56,7 @@ class FileService(object):
|
||||
data_model = FileService.get_reference_file_data(FileService.IRB_PRO_CATEGORIES_FILE)
|
||||
xls = ExcelFile(data_model.data)
|
||||
df = xls.parse(xls.sheet_names[0])
|
||||
return code in df['Code'].values
|
||||
return code in df['code'].values
|
||||
|
||||
@staticmethod
|
||||
def get_file_reference_dictionary():
|
||||
@ -65,11 +65,11 @@ class FileService(object):
|
||||
data_model = FileService.get_reference_file_data(FileService.IRB_PRO_CATEGORIES_FILE)
|
||||
xls = ExcelFile(data_model.data)
|
||||
df = xls.parse(xls.sheet_names[0])
|
||||
df['Id'] = df['Id'].fillna(0)
|
||||
df = df.astype({'Id': 'Int64'})
|
||||
df['id'] = df['id'].fillna(0)
|
||||
df = df.astype({'id': 'Int64'})
|
||||
df = df.fillna('')
|
||||
df = df.applymap(str)
|
||||
df = df.set_index('Code')
|
||||
df = df.set_index('code')
|
||||
# IF we need to convert the column names to something more sensible.
|
||||
# df.columns = [snakeCase(x) for x in df.columns]
|
||||
return json.loads(df.to_json(orient='index'))
|
||||
@ -80,12 +80,13 @@ class FileService(object):
|
||||
# all_dict = df.set_index('Id').to_dict('index')
|
||||
|
||||
@staticmethod
|
||||
def add_task_file(study_id, workflow_id, task_id, name, content_type, binary_data,
|
||||
def add_task_file(study_id, workflow_id, workflow_spec_id, task_id, name, content_type, binary_data,
|
||||
irb_doc_code=None):
|
||||
"""Create a new file and associate it with an executing task within a workflow."""
|
||||
file_model = FileModel(
|
||||
study_id=study_id,
|
||||
workflow_id=workflow_id,
|
||||
workflow_spec_id=workflow_spec_id,
|
||||
task_id=task_id,
|
||||
name=name,
|
||||
irb_doc_code=irb_doc_code
|
||||
@ -168,7 +169,7 @@ class FileService(object):
|
||||
if form_field_key:
|
||||
query = query.filter_by(form_field_key=form_field_key)
|
||||
if name:
|
||||
query = query.filter_by(name=form_field_key)
|
||||
query = query.filter_by(name=name)
|
||||
if irb_doc_code:
|
||||
query = query.filter_by(irb_doc_code=irb_doc_code)
|
||||
|
||||
|
@ -41,14 +41,11 @@ class ProtocolBuilderService(object):
|
||||
(response.status_code, response.text))
|
||||
|
||||
@staticmethod
|
||||
def get_required_docs(study_id, as_objects=False) -> Optional[List[ProtocolBuilderRequiredDocument]]:
|
||||
def get_required_docs(study_id) -> Optional[List[ProtocolBuilderRequiredDocument]]:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.REQUIRED_DOCS_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
if as_objects:
|
||||
return ProtocolBuilderRequiredDocumentSchema(many=True).loads(response.text)
|
||||
else:
|
||||
return json.loads(response.text)
|
||||
return json.loads(response.text)
|
||||
else:
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
|
@ -1,17 +1,17 @@
|
||||
from datetime import datetime
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
from SpiffWorkflow import WorkflowException
|
||||
|
||||
from crc import db, session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.file import FileModel, FileModelSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.study import StudyModel, Study, Category, WorkflowMetadata
|
||||
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
|
||||
WorkflowStatus
|
||||
from crc.scripts.documents import Documents
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
@ -39,6 +39,9 @@ class StudyService(object):
|
||||
study = Study.from_model(study_model)
|
||||
study.categories = StudyService.get_categories()
|
||||
workflow_metas = StudyService.__get_workflow_metas(study_id)
|
||||
|
||||
# Calling this line repeatedly is very very slow. It creates the
|
||||
# master spec and runs it.
|
||||
status = StudyService.__get_study_status(study_model)
|
||||
study.warnings = StudyService.__update_status_of_workflow_meta(workflow_metas, status)
|
||||
|
||||
@ -101,52 +104,63 @@ class StudyService(object):
|
||||
|
||||
@staticmethod
|
||||
def get_documents_status(study_id):
|
||||
"""Returns a list of required documents and related workflow status."""
|
||||
doc_service = Documents()
|
||||
"""Returns a list of documents related to the study, if they are required, and any file information
|
||||
that is available.."""
|
||||
|
||||
# Get PB required docs
|
||||
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id, as_objects=True)
|
||||
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
|
||||
|
||||
# Get required docs for study
|
||||
study_docs = doc_service.get_documents(study_id=study_id, pb_docs=pb_docs)
|
||||
|
||||
# Container for results
|
||||
documents = []
|
||||
|
||||
# For each required doc, get file(s)
|
||||
for code, doc in study_docs.items():
|
||||
if not doc['required']:
|
||||
continue
|
||||
# Loop through all known document types, get the counts for those files, and use pb_docs to mark those required.
|
||||
doc_dictionary = FileService.get_file_reference_dictionary()
|
||||
documents = {}
|
||||
for code, doc in doc_dictionary.items():
|
||||
|
||||
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
|
||||
doc['required'] = False
|
||||
if pb_data:
|
||||
doc['required'] = True
|
||||
doc['study_id'] = study_id
|
||||
doc['code'] = code
|
||||
|
||||
# Make a display name out of categories if none exists
|
||||
if 'Name' in doc and len(doc['Name']) > 0:
|
||||
doc['display_name'] = doc['Name']
|
||||
else:
|
||||
name_list = []
|
||||
for cat_key in ['category1', 'category2', 'category3']:
|
||||
if doc[cat_key] not in ['', 'NULL']:
|
||||
name_list.append(doc[cat_key])
|
||||
doc['display_name'] = ' '.join(name_list)
|
||||
# Make a display name out of categories
|
||||
name_list = []
|
||||
for cat_key in ['category1', 'category2', 'category3']:
|
||||
if doc[cat_key] not in ['', 'NULL']:
|
||||
name_list.append(doc[cat_key])
|
||||
doc['display_name'] = ' / '.join(name_list)
|
||||
|
||||
# For each file, get associated workflow status
|
||||
doc_files = FileService.get_files(study_id=study_id, irb_doc_code=code)
|
||||
doc['count'] = len(doc_files)
|
||||
doc['files'] = []
|
||||
for file in doc_files:
|
||||
doc['file_id'] = file.id
|
||||
doc['task_id'] = file.task_id
|
||||
doc['workflow_id'] = file.workflow_id
|
||||
doc['workflow_spec_id'] = file.workflow_spec_id
|
||||
doc['files'].append({'file_id': file.id,
|
||||
'task_id': file.task_id,
|
||||
'workflow_id': file.workflow_id,
|
||||
'workflow_spec_id': file.workflow_spec_id})
|
||||
|
||||
if doc['status'] is None:
|
||||
# update the document status to match the status of the workflow it is in.
|
||||
if not 'status' in doc or doc['status'] is None:
|
||||
workflow: WorkflowModel = session.query(WorkflowModel).filter_by(id=file.workflow_id).first()
|
||||
doc['status'] = workflow.status.value
|
||||
|
||||
documents.append(doc)
|
||||
documents[code] = doc
|
||||
|
||||
return documents
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_protocol(study_id):
|
||||
"""Returns the study protocol, if it has been uploaded."""
|
||||
file = db.session.query(FileModel)\
|
||||
.filter_by(study_id=study_id)\
|
||||
.filter_by(form_field_key='Study_Protocol_Document')\
|
||||
.first()
|
||||
|
||||
return FileModelSchema().dump(file)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def synch_all_studies_with_protocol_builder(user):
|
||||
"""Assures that the studies we have locally for the given user are
|
||||
|
Binary file not shown.
@ -13,23 +13,29 @@
|
||||
<bpmn:documentation># Documents & Approvals
|
||||
|
||||
> ## Protocol Document Management
|
||||
> [Upload Protocol Here](/)
|
||||
{% if StudyInfo.protocol is defined -%}
|
||||
{%- set p = StudyInfo.protocol -%}
|
||||
|
||||
> [{{p.name}}](/study/{{p.study_id}}/workflow/{{p.workflow_id}}/task/{{p.task_id}})
|
||||
{%- else -%}
|
||||
> No protocol uploaded yet.
|
||||
{% endif %}
|
||||
|
||||
> ## Approvals
|
||||
> | Name | Status | Help |
|
||||
|:---- |:------ |:---- |
|
||||
{% for approval in StudyInfo.approvals -%}
|
||||
| [{{approval.display_name}}](/study/{{approval.study_id}}/workflow/{{approval.workflow_id}}) | {{approval.status}} | [Context here](/help/{{approval.workflow_spec_id}}) |
|
||||
| [{{approval.display_name}}](/study/{{approval.study_id}}/workflow/{{approval.workflow_id}}) | {{approval.status}} | [?](/help/{{approval.workflow_spec_id}}) |
|
||||
{% endfor %}
|
||||
|
||||
> ## Documents
|
||||
> | Name | Status | Help | Download |
|
||||
|:---- |:------ |:---- |:-------- |
|
||||
{% for doc in StudyInfo.documents_status -%}
|
||||
{% for doc in StudyInfo.documents -%}
|
||||
{% if doc.file_id is defined -%}
|
||||
| [{{doc.display_name}}](/study/{{doc.study_id}}/workflow/{{doc.workflow_id}}/task/{{doc.task_id}}) | {{doc.status}} | [Context here](/help/documents/{{doc.code}}) | [Download](/file/{{doc.file_id}}) |
|
||||
| [{{doc.display_name}}](/study/{{doc.study_id}}/workflow/{{doc.workflow_id}}/task/{{doc.task_id}}) | {{doc.status}} | [Context here](/help/documents/{{doc.code}}) | [Download](/file/{{doc.file_id}}/data) |
|
||||
{%- else -%}
|
||||
| {{doc.display_name}} | Not started | [Context here](/help/documents/{{doc.code}}) | No file yet |
|
||||
| {{doc.display_name}} | Not started | [?](/help/documents/{{doc.code}}) | No file yet |
|
||||
{%- endif %}
|
||||
{% endfor %}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
@ -46,47 +52,60 @@
|
||||
<bpmn:script>StudyInfo approvals</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:scriptTask id="Activity_1aju60t" name="Load Documents">
|
||||
<bpmn:incoming>Flow_1k3su2q</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_0w20w9j</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0c7ryff</bpmn:outgoing>
|
||||
<bpmn:script>StudyInfo documents_status</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_142jtxs" sourceRef="Activity_0a14x7j" targetRef="Activity_DisplayDocsAndApprovals" />
|
||||
<bpmn:sequenceFlow id="Flow_0c7ryff" sourceRef="Activity_1aju60t" targetRef="Activity_0a14x7j" />
|
||||
<bpmn:sequenceFlow id="Flow_1k3su2q" sourceRef="StartEvent_1" targetRef="Activity_1aju60t" />
|
||||
<bpmn:sequenceFlow id="Flow_1k3su2q" sourceRef="StartEvent_1" targetRef="Activity_0b4ojeq" />
|
||||
<bpmn:scriptTask id="Activity_0b4ojeq" name="Load Protocol">
|
||||
<bpmn:incoming>Flow_1k3su2q</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0w20w9j</bpmn:outgoing>
|
||||
<bpmn:script>StudyInfo protocol</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0w20w9j" sourceRef="Activity_0b4ojeq" targetRef="Activity_1aju60t" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1gmf4la">
|
||||
<bpmndi:BPMNEdge id="Flow_1k3su2q_di" bpmnElement="Flow_1k3su2q">
|
||||
<di:waypoint x="228" y="117" />
|
||||
<di:waypoint x="290" y="117" />
|
||||
<di:waypoint x="188" y="117" />
|
||||
<di:waypoint x="240" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0c7ryff_di" bpmnElement="Flow_0c7ryff">
|
||||
<di:waypoint x="390" y="117" />
|
||||
<di:waypoint x="440" y="117" />
|
||||
<di:waypoint x="490" y="117" />
|
||||
<di:waypoint x="540" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_142jtxs_di" bpmnElement="Flow_142jtxs">
|
||||
<di:waypoint x="540" y="117" />
|
||||
<di:waypoint x="610" y="117" />
|
||||
<di:waypoint x="640" y="117" />
|
||||
<di:waypoint x="710" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0m7unlb_di" bpmnElement="Flow_0m7unlb">
|
||||
<di:waypoint x="710" y="117" />
|
||||
<di:waypoint x="782" y="117" />
|
||||
<di:waypoint x="810" y="117" />
|
||||
<di:waypoint x="882" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="192" y="99" width="36" height="36" />
|
||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_1qvyxg7_di" bpmnElement="EndEvent_1qvyxg7">
|
||||
<dc:Bounds x="782" y="99" width="36" height="36" />
|
||||
<dc:Bounds x="882" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_19nawos_di" bpmnElement="Activity_DisplayDocsAndApprovals">
|
||||
<dc:Bounds x="610" y="77" width="100" height="80" />
|
||||
<dc:Bounds x="710" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1bxk8h3_di" bpmnElement="Activity_0a14x7j">
|
||||
<dc:Bounds x="440" y="77" width="100" height="80" />
|
||||
<dc:Bounds x="540" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_07ytvmv_di" bpmnElement="Activity_1aju60t">
|
||||
<dc:Bounds x="290" y="77" width="100" height="80" />
|
||||
<dc:Bounds x="390" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0wf6u3m_di" bpmnElement="Activity_0b4ojeq">
|
||||
<dc:Bounds x="240" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_0w20w9j_di" bpmnElement="Flow_0w20w9j">
|
||||
<di:waypoint x="340" y="117" />
|
||||
<di:waypoint x="390" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
||||
|
@ -217,7 +217,7 @@ Protocol Owner: **(need to insert value here)**</bpmn:documentation>
|
||||
<bpmn:scriptTask id="Activity_LoadDocuments" name="Load Documents">
|
||||
<bpmn:incoming>SequenceFlow_1dexemq</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1x9d2mo</bpmn:outgoing>
|
||||
<bpmn:script>Documents</bpmn:script>
|
||||
<bpmn:script>StudyInfo documents</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="Investigator's Brochure Form Upload Count">
|
||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||
<text>Documents.DrugDevDoc_InvestBrochure.count</text>
|
||||
<text>StudyInfo.documents.DrugDevDoc_InvestBrochure.count</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Investigator's Brochure(s) Uploaded?" name="isInvestigatorsBrochure" typeRef="boolean" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="IVRS-IWRS-IXRS Manual Count">
|
||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||
<text>Documents.DrugDevDoc_IVRSIWRSIXRSMan.count</text>
|
||||
<text>StudyInfo.documents.DrugDevDoc_IVRSIWRSIXRSMan.count</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="IVRS-IWRS-IXRS Manual Uploaded?" name="isIVRS-IWRS-IXRS" typeRef="boolean" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="Pharmacy Manual Upload Count">
|
||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||
<text>Documents["DrugDevDoc_PharmManual"]["count"]</text>
|
||||
<text>StudyInfo.documents.DrugDevDoc_PharmManual.count</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Pharmacy Manual(s) Uploaded?" name="isPharmacyManual" typeRef="boolean" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="DecisionTable_1mjqwlv">
|
||||
<input id="InputClause_18pwfqu" label="Required Doc Keys">
|
||||
<inputExpression id="LiteralExpression_1y84stb" typeRef="boolean" expressionLanguage="feel">
|
||||
<text>Documents['Study_DataSecurityPlan']['required']</text>
|
||||
<text>StudyInfo.documents.Study_DataSecurityPlan.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="OutputClause_05y0j7c" label="data_security_plan" name="data_security_plan" typeRef="string" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="InputClause_1ki80j6" label="required doc ids">
|
||||
<inputExpression id="LiteralExpression_10mfcy7" typeRef="boolean" expressionLanguage="Python">
|
||||
<text>Documents['UVACompl_PRCAppr']['required']</text>
|
||||
<text>StudyInfo.documents.UVACompl_PRCAppr.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="enter_core_info" name="enter_core_info" typeRef="string" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="IRB API IDS Waiver Status">
|
||||
<inputExpression id="inputExpression_1" typeRef="boolean">
|
||||
<text>Documents.UVACompl_IDSWaiverApp.required</text>
|
||||
<text>StudyInfo.documents.UVACompl_IDSWaiverApp.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Menu Status" name="ids_full_submission" typeRef="string" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="IRB API Input">
|
||||
<inputExpression id="inputExpression_1" typeRef="boolean">
|
||||
<text>Documents.UVACompl_IDSWaiverApp.required</text>
|
||||
<text>StudyInfo.documents.UVACompl_IDSWaiverApp.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Menu State" name="ids_waiver" typeRef="string" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="DecisionTable_00zdxg0">
|
||||
<input id="InputClause_02n3ccs" label="CoCApplication Required?">
|
||||
<inputExpression id="LiteralExpression_1ju4o1o" typeRef="boolean" expressionLanguage="feel">
|
||||
<text>Documents['AD_LabManual']['required']</text>
|
||||
<text>StudyInfo.documents.AD_LabManual.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="OutputClause_1ybi1ud" label="sponsor_funding_source" name="sponsor_funding_source" typeRef="string" />
|
||||
|
@ -11,7 +11,7 @@
|
||||
<bpmn:scriptTask id="Task_Load_Requirements" name="Load Documents From PB">
|
||||
<bpmn:incoming>SequenceFlow_1ees8ka</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_17ct47v</bpmn:outgoing>
|
||||
<bpmn:script>Documents</bpmn:script>
|
||||
<bpmn:script>StudyInfo documents</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:businessRuleTask id="Activity_1yqy50i" name="Enter Core Info " camunda:decisionRef="enter_core_info">
|
||||
<bpmn:incoming>Flow_1m8285h</bpmn:incoming>
|
||||
@ -109,159 +109,159 @@
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0jhpidf">
|
||||
<bpmndi:BPMNEdge id="Flow_1ybicki_di" bpmnElement="Flow_1ybicki">
|
||||
<di:waypoint x="1630" y="439" />
|
||||
<di:waypoint x="1712" y="439" />
|
||||
<di:waypoint x="1540" y="439" />
|
||||
<di:waypoint x="1622" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0vo6ul1_di" bpmnElement="Flow_0vo6ul1">
|
||||
<di:waypoint x="1460" y="439" />
|
||||
<di:waypoint x="1530" y="439" />
|
||||
<di:waypoint x="1370" y="439" />
|
||||
<di:waypoint x="1440" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1qyrmzn_di" bpmnElement="Flow_1qyrmzn">
|
||||
<di:waypoint x="1305" y="439" />
|
||||
<di:waypoint x="1360" y="439" />
|
||||
<di:waypoint x="1215" y="439" />
|
||||
<di:waypoint x="1270" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0ffvg2f_di" bpmnElement="Flow_0ffvg2f">
|
||||
<di:waypoint x="1220" y="530" />
|
||||
<di:waypoint x="1280" y="530" />
|
||||
<di:waypoint x="1280" y="464" />
|
||||
<di:waypoint x="1130" y="530" />
|
||||
<di:waypoint x="1190" y="530" />
|
||||
<di:waypoint x="1190" y="464" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_145qxh8_di" bpmnElement="Flow_145qxh8">
|
||||
<di:waypoint x="1210" y="360" />
|
||||
<di:waypoint x="1280" y="360" />
|
||||
<di:waypoint x="1280" y="414" />
|
||||
<di:waypoint x="1120" y="360" />
|
||||
<di:waypoint x="1190" y="360" />
|
||||
<di:waypoint x="1190" y="414" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1jini69_di" bpmnElement="Flow_1jini69">
|
||||
<di:waypoint x="1040" y="414" />
|
||||
<di:waypoint x="1040" y="360" />
|
||||
<di:waypoint x="1110" y="360" />
|
||||
<di:waypoint x="950" y="414" />
|
||||
<di:waypoint x="950" y="360" />
|
||||
<di:waypoint x="1020" y="360" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_14ce1d7_di" bpmnElement="Flow_14ce1d7">
|
||||
<di:waypoint x="1040" y="464" />
|
||||
<di:waypoint x="1040" y="530" />
|
||||
<di:waypoint x="1120" y="530" />
|
||||
<di:waypoint x="950" y="464" />
|
||||
<di:waypoint x="950" y="530" />
|
||||
<di:waypoint x="1030" y="530" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0eq6px2_di" bpmnElement="Flow_0eq6px2">
|
||||
<di:waypoint x="980" y="439" />
|
||||
<di:waypoint x="1015" y="439" />
|
||||
<di:waypoint x="890" y="439" />
|
||||
<di:waypoint x="925" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_18pax8n_di" bpmnElement="Flow_18pax8n">
|
||||
<di:waypoint x="740" y="560" />
|
||||
<di:waypoint x="800" y="560" />
|
||||
<di:waypoint x="800" y="464" />
|
||||
<di:waypoint x="650" y="560" />
|
||||
<di:waypoint x="710" y="560" />
|
||||
<di:waypoint x="710" y="464" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0lrz4jq_di" bpmnElement="Flow_0lrz4jq">
|
||||
<di:waypoint x="583" y="464" />
|
||||
<di:waypoint x="583" y="560" />
|
||||
<di:waypoint x="640" y="560" />
|
||||
<di:waypoint x="493" y="464" />
|
||||
<di:waypoint x="493" y="560" />
|
||||
<di:waypoint x="550" y="560" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1bdr0gi_di" bpmnElement="Flow_1bdr0gi">
|
||||
<di:waypoint x="740" y="439" />
|
||||
<di:waypoint x="775" y="439" />
|
||||
<di:waypoint x="650" y="439" />
|
||||
<di:waypoint x="685" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1tgxyp5_di" bpmnElement="Flow_1tgxyp5">
|
||||
<di:waypoint x="608" y="439" />
|
||||
<di:waypoint x="640" y="439" />
|
||||
<di:waypoint x="518" y="439" />
|
||||
<di:waypoint x="550" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0x9580l_di" bpmnElement="Flow_0x9580l">
|
||||
<di:waypoint x="740" y="690" />
|
||||
<di:waypoint x="800" y="690" />
|
||||
<di:waypoint x="800" y="464" />
|
||||
<di:waypoint x="650" y="690" />
|
||||
<di:waypoint x="710" y="690" />
|
||||
<di:waypoint x="710" y="464" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_18pl92p_di" bpmnElement="Flow_18pl92p">
|
||||
<di:waypoint x="583" y="464" />
|
||||
<di:waypoint x="583" y="690" />
|
||||
<di:waypoint x="640" y="690" />
|
||||
<di:waypoint x="493" y="464" />
|
||||
<di:waypoint x="493" y="690" />
|
||||
<di:waypoint x="550" y="690" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_17ct47v_di" bpmnElement="SequenceFlow_17ct47v">
|
||||
<di:waypoint x="500" y="439" />
|
||||
<di:waypoint x="558" y="439" />
|
||||
<di:waypoint x="410" y="439" />
|
||||
<di:waypoint x="468" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1m8285h_di" bpmnElement="Flow_1m8285h">
|
||||
<di:waypoint x="583" y="414" />
|
||||
<di:waypoint x="583" y="300" />
|
||||
<di:waypoint x="640" y="300" />
|
||||
<di:waypoint x="493" y="414" />
|
||||
<di:waypoint x="493" y="300" />
|
||||
<di:waypoint x="550" y="300" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0pwtiqm_di" bpmnElement="Flow_0pwtiqm">
|
||||
<di:waypoint x="825" y="439" />
|
||||
<di:waypoint x="880" y="439" />
|
||||
<di:waypoint x="735" y="439" />
|
||||
<di:waypoint x="790" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1sggkit_di" bpmnElement="Flow_1sggkit">
|
||||
<di:waypoint x="740" y="300" />
|
||||
<di:waypoint x="800" y="300" />
|
||||
<di:waypoint x="800" y="414" />
|
||||
<di:waypoint x="650" y="300" />
|
||||
<di:waypoint x="710" y="300" />
|
||||
<di:waypoint x="710" y="414" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1ees8ka_di" bpmnElement="SequenceFlow_1ees8ka">
|
||||
<di:waypoint x="318" y="439" />
|
||||
<di:waypoint x="400" y="439" />
|
||||
<di:waypoint x="228" y="439" />
|
||||
<di:waypoint x="310" y="439" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="282" y="421" width="36" height="36" />
|
||||
<dc:Bounds x="192" y="421" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_135x8jg_di" bpmnElement="Event_135x8jg">
|
||||
<dc:Bounds x="1712" y="421" width="36" height="36" />
|
||||
<dc:Bounds x="1622" y="421" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_0x4a3pe_di" bpmnElement="Task_Load_Requirements">
|
||||
<dc:Bounds x="400" y="399" width="100" height="80" />
|
||||
<dc:Bounds x="310" y="399" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1yqy50i_di" bpmnElement="Activity_1yqy50i">
|
||||
<dc:Bounds x="640" y="260" width="100" height="80" />
|
||||
<dc:Bounds x="550" y="260" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1kk6x70_di" bpmnElement="Gateway_12tpgcy">
|
||||
<dc:Bounds x="775" y="414" width="50" height="50" />
|
||||
<dc:Bounds x="685" y="414" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1m22g4p_di" bpmnElement="Gateway_1nta7st">
|
||||
<dc:Bounds x="558" y="414" width="50" height="50" />
|
||||
<dc:Bounds x="468" y="414" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_16cm213_di" bpmnElement="Activity_16cm213">
|
||||
<dc:Bounds x="640" y="650" width="100" height="80" />
|
||||
<dc:Bounds x="550" y="650" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0zpnt48_di" bpmnElement="Activity_1bqc7fa">
|
||||
<dc:Bounds x="640" y="399" width="100" height="80" />
|
||||
<dc:Bounds x="550" y="399" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0cxqj36_di" bpmnElement="Activity_0a14ftj">
|
||||
<dc:Bounds x="640" y="520" width="100" height="80" />
|
||||
<dc:Bounds x="550" y="520" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1hkeo8n_di" bpmnElement="Activity_0f295la">
|
||||
<dc:Bounds x="880" y="399" width="100" height="80" />
|
||||
<dc:Bounds x="790" y="399" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1u4ccm9_di" bpmnElement="Activity_0ahlc3u">
|
||||
<dc:Bounds x="1120" y="490" width="100" height="80" />
|
||||
<dc:Bounds x="1030" y="490" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0qkzul9_di" bpmnElement="Activity_0teqy3w">
|
||||
<dc:Bounds x="1110" y="320" width="100" height="80" />
|
||||
<dc:Bounds x="1020" y="320" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1y87e9r_di" bpmnElement="Gateway_1so972f">
|
||||
<dc:Bounds x="1015" y="414" width="50" height="50" />
|
||||
<dc:Bounds x="925" y="414" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1c142bm_di" bpmnElement="Gateway_15ksf70">
|
||||
<dc:Bounds x="1255" y="414" width="50" height="50" />
|
||||
<dc:Bounds x="1165" y="414" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1s8l694_di" bpmnElement="Activity_0g3qa1c">
|
||||
<dc:Bounds x="1360" y="399" width="100" height="80" />
|
||||
<dc:Bounds x="1270" y="399" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1onpeul_di" bpmnElement="Activity_13ep6ar">
|
||||
<dc:Bounds x="1530" y="399" width="100" height="80" />
|
||||
<dc:Bounds x="1440" y="399" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_1pv8ygy_di" bpmnElement="TextAnnotation_1pv8ygy">
|
||||
<dc:Bounds x="400" y="247" width="100" height="68" />
|
||||
<dc:Bounds x="310" y="247" width="100" height="68" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_0ydnva4_di" bpmnElement="TextAnnotation_0ydnva4">
|
||||
<dc:Bounds x="245" y="210" width="110" height="82" />
|
||||
<dc:Bounds x="155" y="210" width="110" height="82" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_1f52jro_di" bpmnElement="TextAnnotation_1f52jro">
|
||||
<dc:Bounds x="461" y="80" width="243" height="124" />
|
||||
<dc:Bounds x="371" y="80" width="243" height="124" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Association_0w69z3w_di" bpmnElement="Association_0w69z3w">
|
||||
<di:waypoint x="450" y="399" />
|
||||
<di:waypoint x="450" y="315" />
|
||||
<di:waypoint x="360" y="399" />
|
||||
<di:waypoint x="360" y="315" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Association_0a41ixa_di" bpmnElement="Association_0a41ixa">
|
||||
<di:waypoint x="300" y="421" />
|
||||
<di:waypoint x="300" y="292" />
|
||||
<di:waypoint x="210" y="421" />
|
||||
<di:waypoint x="210" y="292" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Association_1mzqzwj_di" bpmnElement="Association_1mzqzwj">
|
||||
<di:waypoint x="583" y="414" />
|
||||
<di:waypoint x="583" y="204" />
|
||||
<di:waypoint x="493" y="414" />
|
||||
<di:waypoint x="493" y="204" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
|
Binary file not shown.
Binary file not shown.
@ -7,7 +7,7 @@
|
||||
<decisionTable id="DecisionTable_1mjqwlv">
|
||||
<input id="InputClause_18pwfqu" label="Data Plan Required in PB?">
|
||||
<inputExpression id="LiteralExpression_1y84stb" typeRef="boolean" expressionLanguage="feel">
|
||||
<text>Documents['Study_DataSecurityPlan']['required']</text>
|
||||
<text>StudyInfo.documents.Study_DataSecurityPlan.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="OutputClause_05y0j7c" label="data_security_plan" name="data_security_plan" typeRef="string" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="InputClause_1ki80j6" label="required doc ids">
|
||||
<inputExpression id="LiteralExpression_10mfcy7" typeRef="boolean" expressionLanguage="Python">
|
||||
<text>Documents['UVACompl_PRCAppr']['required']</text>
|
||||
<text>StudyInfo.documents.UVACompl_PRCAppr.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="enter_core_info" name="enter_core_info" typeRef="string" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<decisionTable id="DecisionTable_00zdxg0">
|
||||
<input id="InputClause_02n3ccs" label="Sponsor Document Required in PB?">
|
||||
<inputExpression id="LiteralExpression_1ju4o1o" typeRef="boolean" expressionLanguage="feel">
|
||||
<text>Documents['AD_LabManual']['required']</text>
|
||||
<text>StudyInfo.documents.AD_LabManual.required</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="OutputClause_1ybi1ud" label="Sponsor Funding Source" name="sponsor_funding_source" typeRef="string" />
|
||||
|
@ -11,7 +11,7 @@
|
||||
<bpmn:scriptTask id="Task_Load_Requirements" name="Load Required Documents From PM">
|
||||
<bpmn:incoming>SequenceFlow_1ees8ka</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_17ct47v</bpmn:outgoing>
|
||||
<bpmn:script>Documents</bpmn:script>
|
||||
<bpmn:script>StudyInfo documents</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:businessRuleTask id="Activity_1yqy50i" name="Enter Core Info " camunda:decisionRef="enter_core_info">
|
||||
<bpmn:incoming>Flow_1m8285h</bpmn:incoming>
|
||||
|
@ -1,82 +0,0 @@
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc import db
|
||||
from crc.models.file import FileDataModel, FileModel
|
||||
from crc.models.protocol_builder import ProtocolBuilderRequiredDocumentSchema
|
||||
from crc.scripts.documents import Documents
|
||||
from crc.services.file_service import FileService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestRequiredDocsScript(BaseTest):
|
||||
test_uid = "dhf8r"
|
||||
test_study_id = 1
|
||||
|
||||
"""
|
||||
1. get a list of only the required documents for the study.
|
||||
2. For this study, is this document required accroding to the protocol builder?
|
||||
3. For ALL uploaded documents, what the total number of files that were uploaded? per instance of this document naming
|
||||
convention that we are implementing for the IRB.
|
||||
"""
|
||||
|
||||
def test_validate_returns_error_if_reference_files_do_not_exist(self):
|
||||
file_model = db.session.query(FileModel). \
|
||||
filter(FileModel.is_reference == True). \
|
||||
filter(FileModel.name == FileService.IRB_PRO_CATEGORIES_FILE).first()
|
||||
if file_model:
|
||||
db.session.query(FileDataModel).filter(FileDataModel.file_model_id == file_model.id).delete()
|
||||
db.session.query(FileModel).filter(FileModel.id == file_model.id).delete()
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
errors = Documents.validate()
|
||||
self.assertTrue(len(errors) > 0)
|
||||
self.assertEqual("file_not_found", errors[0].code)
|
||||
|
||||
def test_no_validation_error_when_correct_file_exists(self):
|
||||
self.create_reference_document()
|
||||
errors = Documents.validate()
|
||||
self.assertTrue(len(errors) == 0)
|
||||
|
||||
def test_load_lookup_data(self):
|
||||
self.create_reference_document()
|
||||
dict = FileService.get_file_reference_dictionary()
|
||||
self.assertIsNotNone(dict)
|
||||
|
||||
def get_required_docs(self):
|
||||
string_data = self.protocol_builder_response('required_docs.json')
|
||||
return ProtocolBuilderRequiredDocumentSchema(many=True).loads(string_data)
|
||||
|
||||
def test_get_required_docs(self):
|
||||
pb_docs = self.get_required_docs()
|
||||
self.create_reference_document()
|
||||
script = Documents()
|
||||
documents = script.get_documents(12, pb_docs) # Mocked out, any random study id works.
|
||||
self.assertIsNotNone(documents)
|
||||
self.assertTrue("UVACompl_PRCAppr" in documents.keys())
|
||||
self.assertEqual("Cancer Center's PRC Approval Form", documents["UVACompl_PRCAppr"]['Name'])
|
||||
self.assertEqual("UVA Compliance", documents["UVACompl_PRCAppr"]['category1'])
|
||||
self.assertEqual("PRC Approval", documents["UVACompl_PRCAppr"]['category2'])
|
||||
self.assertEqual("CRC", documents["UVACompl_PRCAppr"]['Who Uploads?'])
|
||||
self.assertEqual(0, documents["UVACompl_PRCAppr"]['count'])
|
||||
self.assertEqual(True, documents["UVACompl_PRCAppr"]['required'])
|
||||
self.assertEqual('6', documents["UVACompl_PRCAppr"]['Id'])
|
||||
|
||||
def test_get_required_docs_has_correct_count_when_a_file_exists(self):
|
||||
self.load_example_data()
|
||||
pb_docs = self.get_required_docs()
|
||||
# Make sure the xslt reference document is in place.
|
||||
self.create_reference_document()
|
||||
script = Documents()
|
||||
|
||||
# Add a document to the study with the correct code.
|
||||
workflow = self.create_workflow('docx')
|
||||
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
|
||||
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
|
||||
task_id="fakingthisout",
|
||||
name="anything.png", content_type="text",
|
||||
binary_data=b'1234', irb_doc_code=irb_code)
|
||||
|
||||
docs = script.get_documents(workflow.study_id, pb_docs)
|
||||
self.assertIsNotNone(docs)
|
||||
self.assertEqual(1, docs["UVACompl_PRCAppr"]['count'])
|
67
tests/test_study_details_documents.py
Normal file
67
tests/test_study_details_documents.py
Normal file
@ -0,0 +1,67 @@
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc import db, session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileDataModel, FileModel
|
||||
from crc.models.protocol_builder import ProtocolBuilderRequiredDocumentSchema
|
||||
from crc.models.study import StudyModel
|
||||
from crc.scripts.study_info import StudyInfo
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestStudyDetailsDocumentsScript(BaseTest):
|
||||
test_uid = "dhf8r"
|
||||
test_study_id = 1
|
||||
|
||||
"""
|
||||
1. get a list of all documents related to the study.
|
||||
2. For this study, is this document required accroding to the protocol builder?
|
||||
3. For ALL uploaded documents, what the total number of files that were uploaded? per instance of this document naming
|
||||
convention that we are implementing for the IRB.
|
||||
"""
|
||||
|
||||
def test_validate_returns_error_if_reference_files_do_not_exist(self):
|
||||
self.load_example_data()
|
||||
self.create_reference_document()
|
||||
study = session.query(StudyModel).first()
|
||||
workflow_spec_model = self.load_test_spec("two_forms")
|
||||
workflow_model = StudyService._create_workflow_model(study, workflow_spec_model)
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
task = processor.next_task()
|
||||
|
||||
# Remove the reference file.
|
||||
file_model = db.session.query(FileModel). \
|
||||
filter(FileModel.is_reference == True). \
|
||||
filter(FileModel.name == FileService.IRB_PRO_CATEGORIES_FILE).first()
|
||||
if file_model:
|
||||
db.session.query(FileDataModel).filter(FileDataModel.file_model_id == file_model.id).delete()
|
||||
db.session.query(FileModel).filter(FileModel.id == file_model.id).delete()
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
|
||||
with self.assertRaises(ApiError):
|
||||
StudyInfo().do_task_validate_only(task, study.id, "documents")
|
||||
|
||||
def test_no_validation_error_when_correct_file_exists(self):
|
||||
self.load_example_data()
|
||||
self.create_reference_document()
|
||||
study = session.query(StudyModel).first()
|
||||
workflow_spec_model = self.load_test_spec("two_forms")
|
||||
workflow_model = StudyService._create_workflow_model(study, workflow_spec_model)
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
task = processor.next_task()
|
||||
StudyInfo().do_task_validate_only(task, study.id, "documents")
|
||||
|
||||
def test_load_lookup_data(self):
|
||||
self.create_reference_document()
|
||||
dict = FileService.get_file_reference_dictionary()
|
||||
self.assertIsNotNone(dict)
|
||||
|
||||
def get_required_docs(self):
|
||||
string_data = self.protocol_builder_response('required_docs.json')
|
||||
return ProtocolBuilderRequiredDocumentSchema(many=True).loads(string_data)
|
||||
|
@ -8,6 +8,7 @@ from crc.models.study import StudyModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowStatus, \
|
||||
WorkflowSpecCategoryModel
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from example_data import ExampleDataLoader
|
||||
@ -17,22 +18,28 @@ from tests.base_test import BaseTest
|
||||
class TestStudyService(BaseTest):
|
||||
"""Largely tested via the test_study_api, and time is tight, but adding new tests here."""
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
def test_total_tasks_updated(self, mock_docs):
|
||||
"""Assure that as a users progress is available when getting a list of studies for that user."""
|
||||
def create_user_with_study_and_workflow(self):
|
||||
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
# clear it all out.
|
||||
from example_data import ExampleDataLoader
|
||||
ExampleDataLoader.clean_db()
|
||||
|
||||
# Assure some basic models are in place, This is a damn mess. Our database models need an overhaul to make
|
||||
# this easier - better relationship modeling is now critical.
|
||||
self.load_test_spec("top_level_workflow", master_spec=True)
|
||||
user = UserModel(uid="dhf8r", email_address="whatever@stuff.com", display_name="Stayathome Smellalots")
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
user = db.session.query(UserModel).filter(UserModel.uid == "dhf8r").first()
|
||||
if not user:
|
||||
user = UserModel(uid="dhf8r", email_address="whatever@stuff.com", display_name="Stayathome Smellalots")
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
else:
|
||||
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)
|
||||
db.session.add(study)
|
||||
cat = WorkflowSpecCategoryModel(name="approvals", display_name="Approvals", display_order=0)
|
||||
db.session.add_all([study, cat])
|
||||
db.session.add(cat)
|
||||
db.session.commit()
|
||||
|
||||
self.assertIsNotNone(cat.id)
|
||||
@ -45,6 +52,16 @@ class TestStudyService(BaseTest):
|
||||
db.session.commit()
|
||||
# Assure there is a master specification, one standard spec, and lookup tables.
|
||||
ExampleDataLoader().load_reference_documents()
|
||||
return user
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
def test_total_tasks_updated(self, mock_docs):
|
||||
"""Assure that as a users progress is available when getting a list of studies for that user."""
|
||||
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
|
||||
user = self.create_user_with_study_and_workflow()
|
||||
|
||||
# The load example data script should set us up a user and at least one study, one category, and one workflow.
|
||||
studies = StudyService.get_studies_for_user(user)
|
||||
@ -86,3 +103,63 @@ class TestStudyService(BaseTest):
|
||||
approvals = StudyService.get_approvals(studies[0].id)
|
||||
self.assertGreater(len(approvals), 0)
|
||||
self.assertIsNotNone(approvals[0]['display_order'])
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
def test_get_required_docs(self, mock_docs):
|
||||
|
||||
# mock out the protocol builder
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
|
||||
user = self.create_user_with_study_and_workflow()
|
||||
studies = StudyService.get_studies_for_user(user)
|
||||
study = studies[0]
|
||||
|
||||
|
||||
study_service = StudyService()
|
||||
documents = study_service.get_documents_status(study_id=study.id) # Mocked out, any random study id works.
|
||||
self.assertIsNotNone(documents)
|
||||
self.assertTrue("UVACompl_PRCAppr" in documents.keys())
|
||||
self.assertEqual("UVACompl_PRCAppr", documents["UVACompl_PRCAppr"]['code'])
|
||||
self.assertEqual("UVA Compliance / PRC Approval", documents["UVACompl_PRCAppr"]['display_name'])
|
||||
self.assertEqual("Cancer Center's PRC Approval Form", documents["UVACompl_PRCAppr"]['description'])
|
||||
self.assertEqual("UVA Compliance", documents["UVACompl_PRCAppr"]['category1'])
|
||||
self.assertEqual("PRC Approval", documents["UVACompl_PRCAppr"]['category2'])
|
||||
self.assertEqual("", documents["UVACompl_PRCAppr"]['category3'])
|
||||
self.assertEqual("CRC", documents["UVACompl_PRCAppr"]['Who Uploads?'])
|
||||
self.assertEqual(0, documents["UVACompl_PRCAppr"]['count'])
|
||||
self.assertEqual(True, documents["UVACompl_PRCAppr"]['required'])
|
||||
self.assertEqual('6', documents["UVACompl_PRCAppr"]['id'])
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
def test_get_documents_has_file_details(self, mock_docs):
|
||||
|
||||
# mock out the protocol builder
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
|
||||
user = self.create_user_with_study_and_workflow()
|
||||
|
||||
# Add a document to the study with the correct code.
|
||||
workflow = self.create_workflow('docx')
|
||||
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
|
||||
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
|
||||
workflow_spec_id=workflow.workflow_spec_id,
|
||||
task_id="fakingthisout",
|
||||
name="anything.png", content_type="text",
|
||||
binary_data=b'1234', irb_doc_code=irb_code)
|
||||
|
||||
docs = StudyService().get_documents_status(workflow.study_id)
|
||||
self.assertIsNotNone(docs)
|
||||
self.assertEqual("not_started", docs["UVACompl_PRCAppr"]['status'])
|
||||
self.assertEqual(1, docs["UVACompl_PRCAppr"]['count'])
|
||||
self.assertIsNotNone(docs["UVACompl_PRCAppr"]['files'][0])
|
||||
self.assertIsNotNone(docs["UVACompl_PRCAppr"]['files'][0]['file_id'])
|
||||
self.assertEquals(workflow.id, docs["UVACompl_PRCAppr"]['files'][0]['workflow_id'])
|
||||
self.assertEquals(workflow.workflow_spec_id, docs["UVACompl_PRCAppr"]['files'][0]['workflow_spec_id'])
|
||||
|
||||
# 'file_id': 123,
|
||||
# 'task_id': 'abcdef14236890',
|
||||
# 'workflow_id': 456,
|
||||
# 'workflow_spec_id': 'irb_api_details',
|
||||
# 'status': 'complete',
|
||||
|
Loading…
x
Reference in New Issue
Block a user