diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3bcbb52..e55ed56 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Test and Deploy Prerelease +name: Deploy on: push: diff --git a/.github/workflows/prerelease-deploy.yml b/.github/workflows/prerelease-deploy.yml index 3bcbb52..7fa38bb 100644 --- a/.github/workflows/prerelease-deploy.yml +++ b/.github/workflows/prerelease-deploy.yml @@ -1,4 +1,4 @@ -name: Test and Deploy Prerelease +name: Prerelease Deploy on: push: diff --git a/ppmutils/__init__.py b/ppmutils/__init__.py index 1b37a93..033beff 100644 --- a/ppmutils/__init__.py +++ b/ppmutils/__init__.py @@ -3,12 +3,10 @@ """ __title__ = "PPM Utils" -__version__ = '0.7.0-alpha.3' +__version__ = "0.7.0-alpha.3" __author__ = "Bryan Larson" __license__ = "BSD 2-Clause" -__copyright__ = ( - "Copyright 2011-2020 Harvard Medical School Department of Biomedical Informatics" -) +__copyright__ = "Copyright 2011-2020 Harvard Medical School Department of Biomedical Informatics" # Version synonym VERSION = __version__ diff --git a/ppmutils/fhir.py b/ppmutils/fhir.py index c0df2f5..005ecdc 100644 --- a/ppmutils/fhir.py +++ b/ppmutils/fhir.py @@ -49,30 +49,22 @@ class FHIR: # Set the coding types patient_identifier_system = "https://peoplepoweredmedicine.org/fhir/patient" - enrollment_flag_coding_system = ( - "https://peoplepoweredmedicine.org/enrollment-status" - ) + enrollment_flag_coding_system = "https://peoplepoweredmedicine.org/enrollment-status" research_study_identifier_system = "https://peoplepoweredmedicine.org/fhir/study" research_study_coding_system = "https://peoplepoweredmedicine.org/study" - research_subject_identifier_system = ( - "https://peoplepoweredmedicine.org/fhir/subject" - ) + research_subject_identifier_system = "https://peoplepoweredmedicine.org/fhir/subject" research_subject_coding_system = "https://peoplepoweredmedicine.org/subject" device_identifier_system = "https://peoplepoweredmedicine.org/fhir/device" device_coding_system = "https://peoplepoweredmedicine.org/device" # Type system for PPM data documents - data_document_reference_identifier_system = ( - "https://peoplepoweredmedicine.org/document-type" - ) + data_document_reference_identifier_system = "https://peoplepoweredmedicine.org/document-type" # Type system for PPM documents - ppm_document_reference_type_system = ( - "https://peoplepoweredmedicine.org/fhir/ppm/document-type" - ) + ppm_document_reference_type_system = "https://peoplepoweredmedicine.org/fhir/ppm/document-type" # Type system for PPM consent resources ppm_consent_type_system = "http://loinc.org" @@ -88,34 +80,16 @@ class FHIR: ppm_comm_coding_system = "https://peoplepoweredmedicine.org/ppm-notification" # Patient extension flags - twitter_extension_url = ( - "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-twitter" - ) - fitbit_extension_url = ( - "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-fitbit" - ) - picnichealth_extension_url = ( - "https://p2m2.dbmi.hms.harvard.edu/fhir/" - "StructureDefinition/registered-picnichealth" - ) - facebook_extension_url = ( - "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-facebook" - ) - smart_on_fhir_extension_url = ( - "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-smart-on-fhir" - ) - referral_extension_url = ( - "https://p2m2.dbmi.hms.harvard.edu/fhir/" - "StructureDefinition/how-did-you-hear-about-us" - ) + twitter_extension_url = "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-twitter" + fitbit_extension_url = "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-fitbit" + picnichealth_extension_url = "https://p2m2.dbmi.hms.harvard.edu/fhir/" "StructureDefinition/registered-picnichealth" + facebook_extension_url = "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-facebook" + smart_on_fhir_extension_url = "https://p2m2.dbmi.hms.harvard.edu/fhir/StructureDefinition/uses-smart-on-fhir" + referral_extension_url = "https://p2m2.dbmi.hms.harvard.edu/fhir/" "StructureDefinition/how-did-you-hear-about-us" # Qualtrics IDs - qualtrics_survey_identifier_system = ( - "https://peoplepoweredmedicine.org/fhir/qualtrics/survey" - ) - qualtrics_response_identifier_system = ( - "https://peoplepoweredmedicine.org/fhir/qualtrics/response" - ) + qualtrics_survey_identifier_system = "https://peoplepoweredmedicine.org/fhir/qualtrics/survey" + qualtrics_response_identifier_system = "https://peoplepoweredmedicine.org/fhir/qualtrics/response" # # META @@ -136,9 +110,7 @@ def default_url_for_env(cls, environment): elif "prod" in environment: return "https://fhir.ppm.aws.dbmi.hms.harvard.edu" else: - logger.error( - f"Could not return a default URL for environment: {environment}" - ) + logger.error(f"Could not return a default URL for environment: {environment}") return None @@ -158,11 +130,7 @@ def _bundle_get(bundle, resource_type, query={}): """ # Get matching resources - resources = [ - entry.resource - for entry in bundle.entry - if entry.resource.resource_type == resource_type - ] + resources = [entry.resource for entry in bundle.entry if entry.resource.resource_type == resource_type] # Match for resource in resources: @@ -190,9 +158,7 @@ def _find_resources(obj, resource_type=None): """ # Check for valid object if not obj: - logger.warning( - 'FHIR: Attempt to extract resource from nothing: "{}"'.format(obj) - ) + logger.warning('FHIR: Attempt to extract resource from nothing: "{}"'.format(obj)) return None # Check type @@ -207,11 +173,7 @@ def _find_resources(obj, resource_type=None): else: # Object is resource, return it - return ( - [obj.as_json()] - if not resource_type or obj.resource_type == resource_type - else [] - ) + return [obj.as_json()] if not resource_type or obj.resource_type == resource_type else [] # Check if is a search bundle entry if type(obj) is BundleEntry: @@ -240,18 +202,12 @@ def _find_resources(obj, resource_type=None): elif obj.get("resource") and obj.get("fullUrl"): # Call this with resource - return FHIR._find_resources( - obj["resource"], resource_type=resource_type - ) + return FHIR._find_resources(obj["resource"], resource_type=resource_type) elif obj.get("resourceType"): # Object is a resource, return it - return ( - [obj] - if not resource_type or obj["resourceType"] == resource_type - else [] - ) + return [obj] if not resource_type or obj["resourceType"] == resource_type else [] else: logger.warning( @@ -289,9 +245,7 @@ def _get_resources(bundle, resource_type, query=None): # Get matching resources resources = [ - entry["resource"] - for entry in bundle["entry"] - if entry["resource"]["resourceType"] == resource_type + entry["resource"] for entry in bundle["entry"] if entry["resource"]["resourceType"] == resource_type ] # Match @@ -361,18 +315,12 @@ def _get_referenced_id(resource, resource_type, key=None): else: # Find it for key, value in resource.items(): - if ( - type(value) is dict - and value.get("reference") - and resource_type in value.get("reference") - ): + if type(value) is dict and value.get("reference") and resource_type in value.get("reference"): return value["reference"].replace(f"{resource_type}/", "") except (KeyError, IndexError) as e: logger.exception( - "FHIR Error: {}".format(e), - exc_info=True, - extra={"resource_type": resource_type, "key": key,}, + "FHIR Error: {}".format(e), exc_info=True, extra={"resource_type": resource_type, "key": key,}, ) else: @@ -424,9 +372,7 @@ def _get_resource_type(bundle): def _get_next_url(bundle, relative=False): # Get the next URL - next_url = next( - (link["url"] for link in bundle["link"] if link["relation"] == "next"), None - ) + next_url = next((link["url"] for link in bundle["link"] if link["relation"] == "next"), None) if next_url: # Check URL type @@ -455,19 +401,12 @@ def _fix_bundle_json(bundle_json): # Appeases the FHIR library by ensuring question items all have linkIds, # regardless of an associated answer. for question in [ - entry["resource"] - for entry in bundle_json["entry"] - if entry["resource"]["resourceType"] == "Questionnaire" + entry["resource"] for entry in bundle_json["entry"] if entry["resource"]["resourceType"] == "Questionnaire" ]: for item in question["item"]: if "linkId" not in item: # Assign a random string for the linkId - item["linkId"] = "".join( - [ - random.choice("abcdefghijklmnopqrstuvwxyz1234567890") - for _ in range(10) - ] - ) + item["linkId"] = "".join([random.choice("abcdefghijklmnopqrstuvwxyz1234567890") for _ in range(10)]) # Appeases the FHIR library by ensuring document references have 'indexed' for document in [ @@ -500,11 +439,7 @@ def _get_list(bundle, resource_type): if not bundle.entry: return None - for list in [ - entry.resource - for entry in bundle.entry - if entry.resource.resource_type == "List" - ]: + for list in [entry.resource for entry in bundle.entry if entry.resource.resource_type == "List"]: # Compare the type for item in [entry.item for entry in list.entry]: @@ -523,10 +458,8 @@ def is_ppm_research_subject(research_subject): related to a PPM study or not """ if ( - research_subject.get("identifier", {}).get("system") - == FHIR.research_subject_identifier_system - and research_subject.get("identifier", {}).get("value") - in PPM.Study.identifiers() + research_subject.get("identifier", {}).get("system") == FHIR.research_subject_identifier_system + and research_subject.get("identifier", {}).get("value") in PPM.Study.identifiers() ): return True @@ -571,15 +504,8 @@ def get_study_from_research_subject(research_subject): research_subject = research_subject.as_json() elif type(research_subject) is dict and research_subject.get("resource"): research_subject = research_subject.get("resource") - elif ( - type(research_subject) is not dict - or research_subject.get("resourceType") != "ResearchSubject" - ): - raise ValueError( - "Passed ResearchSubject is not a valid resource: {}".format( - research_subject - ) - ) + elif type(research_subject) is not dict or research_subject.get("resourceType") != "ResearchSubject": + raise ValueError("Passed ResearchSubject is not a valid resource: {}".format(research_subject)) # Parse the identifier identifier = research_subject.get("identifier", {}).get("value") @@ -644,16 +570,10 @@ def _patient_query(identifier): return {"_id": identifier} # Check for an email address - elif type(identifier) is str and re.match( - r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", identifier - ): + elif type(identifier) is str and re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", identifier): # An email address - return { - "identifier": "{}|{}".format( - FHIR.patient_email_identifier_system, identifier - ) - } + return {"identifier": "{}|{}".format(FHIR.patient_email_identifier_system, identifier)} # Check for a resource elif type(identifier) is dict and identifier.get("resourceType") == "Patient": @@ -661,10 +581,7 @@ def _patient_query(identifier): return {"_id": identifier["id"]} # Check for a bundle entry - elif ( - type(identifier) is dict - and identifier.get("resource", {}).get("resourceType") == "Patient" - ): + elif type(identifier) is dict and identifier.get("resource", {}).get("resourceType") == "Patient": return {"_id": identifier["resource"]["id"]} @@ -674,9 +591,7 @@ def _patient_query(identifier): return {"_id": identifier.id} else: - raise ValueError( - "Unhandled instance of a Patient identifier: {}".format(identifier) - ) + raise ValueError("Unhandled instance of a Patient identifier: {}".format(identifier)) @staticmethod def _patient_resource_query(identifier, key="patient"): @@ -695,15 +610,11 @@ def _patient_resource_query(identifier, key="patient"): return {key: identifier} # Check for an email address - elif type(identifier) is str and re.match( - r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", identifier - ): + elif type(identifier) is str and re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", identifier): # An email address return { - "{}:patient.identifier".format(key): "{}|{}".format( - FHIR.patient_email_identifier_system, identifier - ) + "{}:patient.identifier".format(key): "{}|{}".format(FHIR.patient_email_identifier_system, identifier) } # Check for a resource @@ -712,10 +623,7 @@ def _patient_resource_query(identifier, key="patient"): return {key: identifier["id"]} # Check for a bundle entry - elif ( - type(identifier) is dict - and identifier.get("resource", {}).get("resourceType") == "Patient" - ): + elif type(identifier) is dict and identifier.get("resource", {}).get("resourceType") == "Patient": return {key: identifier["resource"]["id"]} @@ -725,9 +633,7 @@ def _patient_resource_query(identifier, key="patient"): return {key: identifier.id} else: - raise ValueError( - "Unhandled instance of a Patient identifier: {}".format(identifier) - ) + raise ValueError("Unhandled instance of a Patient identifier: {}".format(identifier)) @staticmethod def get_created_resource_id(response, resource_type): @@ -775,10 +681,7 @@ def get_created_resource_id(response, resource_type): pattern = rf"{resource_type}\/([0-9]+)\/" matches = re.findall(pattern, response.content.decode()) if matches: - logger.error( - f"FHIR ERROR: Could not determine resource ID from " - f"response: {response.content.decode()}" - ) + logger.error(f"FHIR ERROR: Could not determine resource ID from " f"response: {response.content.decode()}") # Build URL url = furl(PPM.fhir_url()) @@ -838,20 +741,13 @@ def create_ppm_research_subject(project, patient_id): """ # Get the study, or create it study = FHIR._query_resources( - "ResearchStudy", - query={ - "identifier": "{}|{}".format( - FHIR.research_study_identifier_system, project - ) - }, + "ResearchStudy", query={"identifier": "{}|{}".format(FHIR.research_study_identifier_system, project)}, ) if not study: FHIR.create_ppm_research_study(project, PPM.Project.title(project)) # Generate resource data - research_subject_data = FHIR.Resources.ppm_research_subject( - project, "Patient/{}".format(patient_id) - ) + research_subject_data = FHIR.Resources.ppm_research_subject(project, "Patient/{}".format(patient_id)) # Create a placeholder ID for the list. research_subject_id = uuid.uuid1().urn @@ -862,12 +758,8 @@ def create_ppm_research_subject(project, patient_id): # address, no duplicate records will be created. ResearchSubject(research_subject_data) - research_subject_request = BundleEntryRequest( - {"url": "ResearchSubject", "method": "POST",} - ) - research_subject_entry = BundleEntry( - {"resource": research_subject_data, "fullUrl": research_subject_id} - ) + research_subject_request = BundleEntryRequest({"url": "ResearchSubject", "method": "POST",}) + research_subject_entry = BundleEntry({"resource": research_subject_data, "fullUrl": research_subject_id}) research_subject_entry.request = research_subject_request # Validate it. @@ -886,9 +778,7 @@ def create_ppm_research_subject(project, patient_id): return response.ok @staticmethod - def create_ppm_device( - patient_id, item, identifier=None, shipped=None, returned=None - ): + def create_ppm_device(patient_id, item, identifier=None, shipped=None, returned=None): """ Creates a project list if not already created """ @@ -930,12 +820,7 @@ def create_patient(form, project): try: # Get the study, or create it study = FHIR._query_resources( - "ResearchStudy", - query={ - "identifier": "{}|{}".format( - FHIR.research_study_identifier_system, project - ) - }, + "ResearchStudy", query={"identifier": "{}|{}".format(FHIR.research_study_identifier_system, project)}, ) if not study: FHIR.create_ppm_research_study(project, PPM.Project.title(project)) @@ -961,19 +846,10 @@ def create_patient(form, project): { "url": "Patient", "method": "POST", - "ifNoneExist": str( - Query( - { - "identifier": "http://schema.org/email|" - + form.get("email"), - } - ) - ), + "ifNoneExist": str(Query({"identifier": "http://schema.org/email|" + form.get("email"),})), } ) - patient_entry = BundleEntry( - {"resource": patient_data, "fullUrl": patient_uuid.urn} - ) + patient_entry = BundleEntry({"resource": patient_data, "fullUrl": patient_uuid.urn}) patient_entry.request = patient_request # Build enrollment flag. @@ -983,12 +859,8 @@ def create_patient(form, project): flag_entry.request = flag_request # Build research subject - research_subject_data = FHIR.Resources.ppm_research_subject( - project, patient_uuid.urn, "candidate" - ) - research_subject_request = BundleEntryRequest( - {"url": "ResearchSubject", "method": "POST"} - ) + research_subject_data = FHIR.Resources.ppm_research_subject(project, patient_uuid.urn, "candidate") + research_subject_request = BundleEntryRequest({"url": "ResearchSubject", "method": "POST"}) research_subject_entry = BundleEntry({"resource": research_subject_data}) research_subject_entry.request = research_subject_request @@ -1030,9 +902,7 @@ def create_patient_enrollment(patient_id, status="registered"): logger.debug("Patient: {}".format(patient_id)) # Use the FHIR client lib to validate our resource. - flag = Flag( - FHIR.Resources.enrollment_flag("Patient/{}".format(patient_id), status) - ) + flag = Flag(FHIR.Resources.enrollment_flag("Patient/{}".format(patient_id), status)) # Set a date if enrolled. if status == "accepted": @@ -1104,9 +974,7 @@ def create_research_study(patient_id, research_study_title): } ) - research_study_entry = BundleEntry( - {"resource": research_study.as_json(), "fullUrl": research_study_id} - ) + research_study_entry = BundleEntry({"resource": research_study.as_json(), "fullUrl": research_study_id}) research_study_entry.request = research_study_request @@ -1150,10 +1018,7 @@ def create_research_study(patient_id, research_study_title): logger.exception( "Create ResearchStudy error: {}".format(e), exc_info=True, - extra={ - "ppm_id": patient_id, - "research_study_title": research_study_title, - }, + extra={"ppm_id": patient_id, "research_study_title": research_study_title,}, ) return None @@ -1208,9 +1073,7 @@ def create_point_of_care_list(patient_id, point_of_care_list): # Don't recreate Organizations if we can find them by the exact name. # No fuzzy matching. - bundle_item_org_request.ifNoneExist = str( - Query({"name:exact": organization.name}) - ) + bundle_item_org_request.ifNoneExist = str(Query({"name:exact": organization.name})) bundle_item_org = BundleEntry() bundle_item_org.resource = organization @@ -1260,9 +1123,7 @@ def create_point_of_care_list(patient_id, point_of_care_list): return response.ok @staticmethod - def create_consent_document_reference( - study, ppm_id, filename, url, hash, size, composition, identifiers=None - ): + def create_consent_document_reference(study, ppm_id, filename, url, hash, size, composition, identifiers=None): """ Accepts details and rendering of a signed PPM consent and saves that data as a DocumentReference to the participant's FHIR record as well as includes a @@ -1319,15 +1180,7 @@ def create_consent_document_reference( } } ], - "context": { - "related": [ - { - "ref": { - "reference": f"ResearchStudy/{PPM.Study.fhir_id(study)}" - }, - } - ] - }, + "context": {"related": [{"ref": {"reference": f"ResearchStudy/{PPM.Study.fhir_id(study)}"},}]}, } # If passed, add identifiers @@ -1359,9 +1212,7 @@ def create_consent_document_reference( bundle.entry.append(organization_entry) # Update the composition - composition["section"].append( - {"entry": [{"reference": document_reference_id}]} - ) + composition["section"].append({"entry": [{"reference": document_reference_id}]}) # Ensure it's related to a study for entry in [ @@ -1369,23 +1220,12 @@ def create_consent_document_reference( for section in composition["section"] if "entry" in section and len(section["entry"]) ]: - if ( - entry.get("reference") - and PPM.Study.fhir_id(study) in entry["reference"] - ): + if entry.get("reference") and PPM.Study.fhir_id(study) in entry["reference"]: break else: # Add it - logger.debug( - f"PPM/{study}/{ppm_id}: Adding study reference to composition" - ) - composition["section"].append( - { - "entry": [ - {"reference": f"ResearchStudy/{PPM.Study.fhir_id(study)}"} - ] - } - ) + logger.debug(f"PPM/{study}/{ppm_id}: Adding study reference to composition") + composition["section"].append({"entry": [{"reference": f"ResearchStudy/{PPM.Study.fhir_id(study)}"}]}) # Add List objects to bundle. composition_request = BundleEntryRequest() @@ -1575,18 +1415,14 @@ def query_participants(studies=None, enrollments=None, active=None, testing=Fals """ logger.debug( "Querying participants - Enrollments: {} - " - "Studies: {} - Active: {} - Testing: {}".format( - enrollments, studies, active, testing - ) + "Studies: {} - Active: {} - Testing: {}".format(enrollments, studies, active, testing) ) # Ensure we are using values if studies: studies = [PPM.Study.get(study).value for study in studies] if enrollments: - enrollments = [ - PPM.Enrollment.get(enrollment).value for enrollment in enrollments - ] + enrollments = [PPM.Enrollment.get(enrollment).value for enrollment in enrollments] # Build the query query = {"_revinclude": ["ResearchSubject:individual", "Flag:subject"]} @@ -1606,9 +1442,7 @@ def query_participants(studies=None, enrollments=None, active=None, testing=Fals patient_enrollments = { entry.resource.subject.reference.split("/")[1]: { "status": entry.resource.code.coding[0].code, - "date_accepted": entry.resource.period.start.origval - if entry.resource.period - else "", + "date_accepted": entry.resource.period.start.origval if entry.resource.period else "", "date_updated": entry.resource.meta.lastUpdated.origval, } for entry in bundle.entry @@ -1628,11 +1462,7 @@ def query_participants(studies=None, enrollments=None, active=None, testing=Fals # Process patients patients = [] - for patient in [ - entry.resource - for entry in bundle.entry - if entry.resource.resource_type == "Patient" - ]: + for patient in [entry.resource for entry in bundle.entry if entry.resource.resource_type == "Patient"]: try: # Fetch their email email = next( @@ -1649,23 +1479,16 @@ def query_participants(studies=None, enrollments=None, active=None, testing=Fals patient_enrollment = patient_enrollments.get(patient.id) patient_study = patient_studies.get(patient.id) - if ( - enrollments - and patient_enrollment["status"].lower() not in enrollments - ): + if enrollments and patient_enrollment["status"].lower() not in enrollments: continue if studies and patient_study.get("study").lower() not in studies: continue # Pull out dates, both formatted and raw - date_registered = FHIR._format_date( - patient_study.get("date_registered"), "%m/%d/%Y" - ) + date_registered = FHIR._format_date(patient_study.get("date_registered"), "%m/%d/%Y") datetime_registered = patient_study.get("date_registered") - date_enrollment_updated = FHIR._format_date( - patient_enrollment.get("date_updated"), "%m/%d/%Y" - ) + date_enrollment_updated = FHIR._format_date(patient_enrollment.get("date_updated"), "%m/%d/%Y") datetime_enrollment_updated = patient_enrollment.get("date_updated") # Build the dict @@ -1685,17 +1508,11 @@ def query_participants(studies=None, enrollments=None, active=None, testing=Fals # Check acceptance if patient_enrollment.get("date_accepted"): - patient_dict["date_accepted"] = FHIR._format_date( - patient_enrollment["date_accepted"], "%m/%d/%Y" - ) - patient_dict["datetime_accepted"] = patient_enrollment[ - "date_accepted" - ] + patient_dict["date_accepted"] = FHIR._format_date(patient_enrollment["date_accepted"], "%m/%d/%Y") + patient_dict["datetime_accepted"] = patient_enrollment["date_accepted"] # Wrap the patient resource in a fake bundle and flatten them - flattened_patient = FHIR.flatten_patient( - {"entry": [{"resource": patient.as_json()}]} - ) + flattened_patient = FHIR.flatten_patient({"entry": [{"resource": patient.as_json()}]}) if flattened_patient: patient_dict.update(flattened_patient) @@ -1703,16 +1520,12 @@ def query_participants(studies=None, enrollments=None, active=None, testing=Fals patients.append(patient_dict) except Exception as e: - logger.exception( - "Resources malformed for Patient/{}: {}".format(patient.id, e) - ) + logger.exception("Resources malformed for Patient/{}: {}".format(patient.id, e)) return patients @staticmethod - def query_patients( - study=None, enrollment=None, active=None, testing=False, include_deceased=True - ): + def query_patients(study=None, enrollment=None, active=None, testing=False, include_deceased=True): logger.debug( "Getting patients - enrollment: {}, study: {}, " "active: {}, testing: {}".format(enrollment, study, active, testing) @@ -1752,15 +1565,11 @@ def query_participant(patient, flatten_return=False): except requests.HTTPError as e: logger.exception( - "FHIR Connection Error: {}".format(e), - exc_info=True, - extra={"response": content}, + "FHIR Connection Error: {}".format(e), exc_info=True, extra={"response": content}, ) except KeyError as e: - logger.exception( - "FHIR Error: {}".format(e), exc_info=True, extra={"response": content} - ) + logger.exception("FHIR Error: {}".format(e), exc_info=True, extra={"response": content}) return None @@ -1792,15 +1601,11 @@ def query_patient(patient, flatten_return=False): except requests.HTTPError as e: logger.exception( - "FHIR Connection Error: {}".format(e), - exc_info=True, - extra={"response": content}, + "FHIR Connection Error: {}".format(e), exc_info=True, extra={"response": content}, ) except KeyError as e: - logger.exception( - "FHIR Error: {}".format(e), exc_info=True, extra={"response": content} - ) + logger.exception("FHIR Error: {}".format(e), exc_info=True, extra={"response": content}) return None @@ -1837,15 +1642,11 @@ def get_participant(patient, flatten_return=False): except requests.HTTPError as e: logger.exception( - "FHIR Connection Error: {}".format(e), - exc_info=True, - extra={"response": content}, + "FHIR Connection Error: {}".format(e), exc_info=True, extra={"response": content}, ) except KeyError as e: - logger.exception( - "FHIR Error: {}".format(e), exc_info=True, extra={"response": content} - ) + logger.exception("FHIR Error: {}".format(e), exc_info=True, extra={"response": content}) return None @@ -1872,22 +1673,15 @@ def get_patient(patient, flatten_return=False): if flatten_return: return FHIR.flatten_patient(response.json()) else: - return next( - (entry["resource"] for entry in response.json().get("entry", [])), - None, - ) + return next((entry["resource"] for entry in response.json().get("entry", [])), None,) except requests.HTTPError as e: logger.exception( - "FHIR Connection Error: {}".format(e), - exc_info=True, - extra={"response": content}, + "FHIR Connection Error: {}".format(e), exc_info=True, extra={"response": content}, ) except KeyError as e: - logger.exception( - "FHIR Error: {}".format(e), exc_info=True, extra={"response": content} - ) + logger.exception("FHIR Error: {}".format(e), exc_info=True, extra={"response": content}) return None @@ -1902,15 +1696,10 @@ def get_composition(patient, flatten_return=False): :type flatten_return: bool :return: The Composition object """ - logger.warning( - "DEPRECATED: This method should not be used for fetching " - "consent composition resources" - ) + logger.warning("DEPRECATED: This method should not be used for fetching " "consent composition resources") # Just return the first from querying - compositions = FHIR.query_consent_compositions( - patient=patient, flatten_return=flatten_return - ) + compositions = FHIR.query_consent_compositions(patient=patient, flatten_return=flatten_return) if compositions: return compositions[0] @@ -1929,9 +1718,7 @@ def query_consent_compositions(patient, study=None, flatten_return=False): :return: The Composition object """ # Build the query - query = { - "type": f"{FHIR.ppm_consent_type_system}|{FHIR.ppm_consent_type_value}" - } + query = {"type": f"{FHIR.ppm_consent_type_system}|{FHIR.ppm_consent_type_value}"} # Check study if study: @@ -1949,12 +1736,7 @@ def query_consent_compositions(patient, study=None, flatten_return=False): # If flattening, we need to query all related resources per Composition bundles = [ FHIR._query_bundle( - "Composition", - query={ - "id": resource["id"], - "_include": "*", - "_revinclude": "*", - }, + "Composition", query={"id": resource["id"], "_include": "*", "_revinclude": "*",}, ) for resource in resources ] @@ -1992,23 +1774,15 @@ def get_consent_composition(patient, study, flatten_return=False): # Check for multiple if len(resources) > 1: logger.error( - f"FHIR Error: Multiple consent Compositions " - f"returned for {study}/{patient}", - extra={ - "compositions": [f"Composition/{r['id']}" for r in resources], - }, + f"FHIR Error: Multiple consent Compositions " f"returned for {study}/{patient}", + extra={"compositions": [f"Composition/{r['id']}" for r in resources],}, ) # Handle the format of return if flatten_return: # If flattening, we need all related resources bundle = FHIR._query_bundle( - "Composition", - query={ - "id": resources[0]["id"], - "_include": "*", - "_revinclude": "*", - }, + "Composition", query={"id": resources[0]["id"], "_include": "*", "_revinclude": "*",}, ) return FHIR.flatten_consent_composition(bundle) else: @@ -2044,13 +1818,8 @@ def get_consent_document_reference(patient, study, flatten_return=False): # Check for multiple if len(resources) > 1: logger.error( - f"FHIR Error: Multiple consent DocumentReferences " - f"returned for {study}/{patient}", - extra={ - "document_references": [ - f"DocumentReference/{r['id']}" for r in resources - ], - }, + f"FHIR Error: Multiple consent DocumentReferences " f"returned for {study}/{patient}", + extra={"document_references": [f"DocumentReference/{r['id']}" for r in resources],}, ) # Handle the format of return @@ -2072,9 +1841,7 @@ def query_consent_document_references(patient, flatten_return=False): :return: The DocumentReference object """ # Build the query - query = { - "type": f"{FHIR.ppm_consent_type_system}|{FHIR.ppm_consent_type_value}" - } + query = {"type": f"{FHIR.ppm_consent_type_system}|{FHIR.ppm_consent_type_value}"} # Add query for patient query.update(FHIR._patient_resource_query(patient)) @@ -2095,9 +1862,7 @@ def query_patient_id(email): client = FHIR.get_client(PPM.fhir_url()) # Query the Patient - search = Patient.where( - struct={"identifier": "http://schema.org/email|{}".format(email)} - ) + search = Patient.where(struct={"identifier": "http://schema.org/email|{}".format(email)}) resources = search.perform_resources(client.server) for resource in resources: @@ -2106,17 +1871,13 @@ def query_patient_id(email): except Exception as e: logger.exception( - "Could not fetch Patient's ID: {}".format(e), - exc_info=True, - extra={"email": email}, + "Could not fetch Patient's ID: {}".format(e), exc_info=True, extra={"email": email}, ) return None @staticmethod - def query_ppm_devices( - patient=None, item=None, identifier=None, flatten_return=False - ): + def query_ppm_devices(patient=None, item=None, identifier=None, flatten_return=False): """ Queries the participants FHIR record for any PPM-related Device resources. These are used to track kits, etc that @@ -2146,9 +1907,7 @@ def query_ppm_devices( # Check for an identifier if identifier: - query["identifier"] = "{}|{}".format( - FHIR.device_identifier_system, identifier - ) + query["identifier"] = "{}|{}".format(FHIR.device_identifier_system, identifier) # Update for the patient query if patient: @@ -2202,10 +1961,7 @@ def query_research_subjects(patient=None, flatten_return=False): ] if flatten_return: - return [ - FHIR.flatten_research_subject(resource) - for resource in research_subjects - ] + return [FHIR.flatten_research_subject(resource) for resource in research_subjects] else: return research_subjects @@ -2233,15 +1989,11 @@ def query_enrollment_flag(patient, flatten_return=False): except requests.HTTPError as e: logger.exception( - "FHIR Connection Error: {}".format(e), - exc_info=True, - extra={"response": content}, + "FHIR Connection Error: {}".format(e), exc_info=True, extra={"response": content}, ) except KeyError as e: - logger.exception( - "FHIR Error: {}".format(e), exc_info=True, extra={"response": content} - ) + logger.exception("FHIR Error: {}".format(e), exc_info=True, extra={"response": content}) return None @@ -2288,12 +2040,7 @@ def get_questionnaire_response(patient, questionnaire_id, flatten_return=False): # Fetch the questionnaire response from the bundle questionnaire_response = next( - ( - r.resource - for r in bundle.entry - if r.resource.resource_type == "QuestionnaireResponse" - ), - None, + (r.resource for r in bundle.entry if r.resource.resource_type == "QuestionnaireResponse"), None, ) return questionnaire_response.as_json() @@ -2321,9 +2068,7 @@ def query_document_references(patient=None, query=None, flatten_return=False): return resources @staticmethod - def query_data_document_references( - patient=None, provider=None, status=None, flatten_return=False - ): + def query_data_document_references(patient=None, provider=None, status=None, flatten_return=False): """ Queries the current user's FHIR record for any DocumentReferences related to this type @@ -2338,9 +2083,7 @@ def query_data_document_references( # Set the provider, if passed if provider: - query[ - "type" - ] = f"{FHIR.data_document_reference_identifier_system}|{provider}" + query["type"] = f"{FHIR.data_document_reference_identifier_system}|{provider}" else: query["type"] = f"{FHIR.data_document_reference_identifier_system}|" @@ -2368,11 +2111,7 @@ def query_enrollment_status(email): if bundle.total > 0: # Check flags. - for flag in [ - entry.resource - for entry in bundle.entry - if entry.resource.resource_type == "Flag" - ]: + for flag in [entry.resource for entry in bundle.entry if entry.resource.resource_type == "Flag"]: # Get the code's value state = flag.code.coding[0].code @@ -2392,23 +2131,17 @@ def query_enrollment_status(email): def query_ppm_research_studies(email, flatten_return=True): # Find Research subjects (without identifiers, so as to exclude PPM resources) - research_subjects = FHIR.query_ppm_research_subjects( - email, flatten_return=False - ) + research_subjects = FHIR.query_ppm_research_subjects(email, flatten_return=False) if not research_subjects: logger.debug("No Research Subjects, no Research Studies") return None # Get study IDs - research_study_ids = [ - subject["study"]["reference"].split("/")[1] for subject in research_subjects - ] + research_study_ids = [subject["study"]["reference"].split("/")[1] for subject in research_subjects] # Get the IDs - research_studies = FHIR._query_resources( - "ResearchStudy", query={"_id": ",".join(research_study_ids)} - ) + research_studies = FHIR._query_resources("ResearchStudy", query={"_id": ",".join(research_study_ids)}) # Return the titles if flatten_return: @@ -2427,14 +2160,10 @@ def query_research_studies(email, flatten_return=True): return None # Get study IDs - research_study_ids = [ - subject["study"]["reference"].split("/")[1] for subject in research_subjects - ] + research_study_ids = [subject["study"]["reference"].split("/")[1] for subject in research_subjects] # Get the IDs - research_studies = FHIR._query_resources( - "ResearchStudy", query={"_id": ",".join(research_study_ids)} - ) + research_studies = FHIR._query_resources("ResearchStudy", query={"_id": ",".join(research_study_ids)}) # Return the titles if flatten_return: @@ -2463,9 +2192,7 @@ def get_point_of_care_list(patient, flatten_return=False): if flatten_return: return FHIR.flatten_list(bundle, "Organization") else: - return next( - (entry["resource"] for entry in bundle.as_json().get("entry", [])), None - ) + return next((entry["resource"] for entry in bundle.as_json().get("entry", [])), None) @staticmethod def query_ppm_communications(patient=None, identifier=None, flatten_return=False): @@ -2524,9 +2251,7 @@ def update_patient(fhir_id, form): street_address2 = form.get("street_address2") if street_address1: patient["address"][0]["line"] = ( - [street_address1] - if not street_address2 - else [street_address1, street_address2] + [street_address1] if not street_address2 else [street_address1, street_address2] ) city = form.get("city") @@ -2610,15 +2335,11 @@ def update_patient(fhir_id, form): except requests.HTTPError as e: logger.error( - "FHIR Request Error: {}".format(e), - exc_info=True, - extra={"ppm_id": fhir_id, "response": content}, + "FHIR Request Error: {}".format(e), exc_info=True, extra={"ppm_id": fhir_id, "response": content}, ) except Exception as e: - logger.error( - "FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": fhir_id} - ) + logger.error("FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": fhir_id}) return False @@ -2629,9 +2350,7 @@ def update_patient_active(patient_id, active): content = None try: # Build the update - patch = [ - {"op": "replace", "path": "/active", "value": True if active else False} - ] + patch = [{"op": "replace", "path": "/active", "value": True if active else False}] # Build the URL url = furl(PPM.fhir_url()) @@ -2639,11 +2358,7 @@ def update_patient_active(patient_id, active): url.path.segments.append(patient_id) # Put it - response = requests.patch( - url.url, - json=patch, - headers={"content-type": "application/json-patch+json"}, - ) + response = requests.patch(url.url, json=patch, headers={"content-type": "application/json-patch+json"},) content = response.content response.raise_for_status() @@ -2651,34 +2366,24 @@ def update_patient_active(patient_id, active): except requests.HTTPError as e: logger.error( - "FHIR Request Error: {}".format(e), - exc_info=True, - extra={"ppm_id": patient_id, "response": content}, + "FHIR Request Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id, "response": content}, ) except Exception as e: - logger.error( - "FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id} - ) + logger.error("FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id}) return False @staticmethod - def update_ppm_device( - patient_id, item, identifier=None, shipped=None, returned=None - ): + def update_ppm_device(patient_id, item, identifier=None, shipped=None, returned=None): # Make the updates content = None try: # Get the device - device = next( - iter(FHIR.query_ppm_devices(patient=patient_id, item=item)), None - ) + device = next(iter(FHIR.query_ppm_devices(patient=patient_id, item=item)), None) if not device: - logger.debug( - f"No PPM device could be found for {patient_id}/{item}/{identifier}" - ) + logger.debug(f"No PPM device could be found for {patient_id}/{item}/{identifier}") return False # Update the resource identifier @@ -2687,12 +2392,7 @@ def update_ppm_device( # Get the PPM identifier dictionary identifiers = device.get("identifier", []) ppm_identifier = next( - ( - _id - for _id in identifiers - if _id.get("system") == FHIR.device_identifier_system - ), - None, + (_id for _id in identifiers if _id.get("system") == FHIR.device_identifier_system), None, ) if ppm_identifier: @@ -2703,10 +2403,7 @@ def update_ppm_device( # Add a new one identifiers.append( - { - "system": FHIR.device_identifier_system, - "value": identifier.lower(), - } + {"system": FHIR.device_identifier_system, "value": identifier.lower(),} ) # Set it @@ -2737,15 +2434,11 @@ def update_ppm_device( except requests.HTTPError as e: logger.error( - "FHIR Request Error: {}".format(e), - exc_info=True, - extra={"ppm_id": patient_id, "response": content}, + "FHIR Request Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id, "response": content}, ) except Exception as e: - logger.error( - "FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id} - ) + logger.error("FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id}) return False @@ -2765,13 +2458,7 @@ def update_patient_deceased(patient_id, date=None, active=None): try: # Build the update if date: - patch = [ - { - "op": "replace", - "path": "/deceasedDateTime", - "value": date.isoformat(), - } - ] + patch = [{"op": "replace", "path": "/deceasedDateTime", "value": date.isoformat(),}] else: patch = [{"op": "remove", "path": "/deceasedDateTime"}] @@ -2785,11 +2472,7 @@ def update_patient_deceased(patient_id, date=None, active=None): url.path.segments.append(patient_id) # Put it - response = requests.patch( - url.url, - json=patch, - headers={"content-type": "application/json-patch+json"}, - ) + response = requests.patch(url.url, json=patch, headers={"content-type": "application/json-patch+json"},) content = response.content response.raise_for_status() @@ -2797,15 +2480,11 @@ def update_patient_deceased(patient_id, date=None, active=None): except requests.HTTPError as e: logger.error( - "FHIR Request Error: {}".format(e), - exc_info=True, - extra={"ppm_id": patient_id, "response": content}, + "FHIR Request Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id, "response": content}, ) except Exception as e: - logger.error( - "FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id} - ) + logger.error("FHIR Error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id}) return False @@ -2830,9 +2509,7 @@ def update_patient_enrollment(patient_id, status): # Check for nothing. if flag_entries.total == 0: logger.error( - "FHIR Error: Flag does not already exist for Patient/{}".format( - patient_id - ), + "FHIR Error: Flag does not already exist for Patient/{}".format(patient_id), extra={"status": status}, ) @@ -2878,10 +2555,7 @@ def update_patient_enrollment(patient_id, status): now = FHIRDate(datetime.now().isoformat()) flag.period.end = now else: - logger.debug( - "Flag has no period/start, cannot set end: " - "Patient/{}".format(patient_id) - ) + logger.debug("Flag has no period/start, cannot set end: " "Patient/{}".format(patient_id)) elif code.code != "completed" and status == "completed": logger.debug('Setting enrollment flag status to "completed"') @@ -2894,10 +2568,7 @@ def update_patient_enrollment(patient_id, status): now = FHIRDate(datetime.now().isoformat()) flag.period.end = now else: - logger.debug( - "Flag has no period/start, cannot set end: " - "Patient/{}".format(patient_id) - ) + logger.debug("Flag has no period/start, cannot set end: " "Patient/{}".format(patient_id)) elif code.code == "accepted" and status != "accepted": logger.debug("Reverting back to inactive with no dates") @@ -2914,9 +2585,7 @@ def update_patient_enrollment(patient_id, status): flag.period = None else: - logger.debug( - "Unhandled flag update: {} -> {}".format(code.code, status) - ) + logger.debug("Unhandled flag update: {} -> {}".format(code.code, status)) # Set the code. code.code = status @@ -2927,9 +2596,7 @@ def update_patient_enrollment(patient_id, status): flag_url = furl(PPM.fhir_url()) flag_url.path.segments.extend(["Flag", flag.id]) - logger.debug( - 'Updating Flag "{}" with code: "{}"'.format(flag_url.url, status) - ) + logger.debug('Updating Flag "{}" with code: "{}"'.format(flag_url.url, status)) # Post it. response = requests.put(flag_url.url, json=flag.as_json()) @@ -2942,25 +2609,16 @@ def update_patient_enrollment(patient_id, status): logger.exception( "FHIR Connection Error: {}".format(e), exc_info=True, - extra={ - "response": content, - "url": url.url, - "ppm_id": patient_id, - "status": status, - }, + extra={"response": content, "url": url.url, "ppm_id": patient_id, "status": status,}, ) raise except Exception as e: - logger.exception( - "FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id} - ) + logger.exception("FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id}) raise @staticmethod - def update_consent_composition( - patient, study, document_reference_id=None, composition=None - ): + def update_consent_composition(patient, study, document_reference_id=None, composition=None): """ Updates a participant's consent Composition resource for changes in related references, e.g. the DocumentReference referencing a rendered @@ -2973,10 +2631,7 @@ def update_consent_composition( """ logger.debug( "Patient: {}, Composition: {}, Study: {}, DocumentReference: {}".format( - patient, - composition["id"] if composition else None, - study, - document_reference_id, + patient, composition["id"] if composition else None, study, document_reference_id, ) ) @@ -2990,10 +2645,7 @@ def update_consent_composition( references = [ s["entry"][0]["reference"] for s in composition["section"] - if s.get("entry") - and s["entry"] is list - and len(s["entry"]) - and "reference" in s["entry"][0] + if s.get("entry") and s["entry"] is list and len(s["entry"]) and "reference" in s["entry"][0] ] if document_reference_id: for reference in references: @@ -3006,8 +2658,7 @@ def update_consent_composition( "display": FHIR.ppm_consent_type_display, } logger.debug( - f"{study}/Patient/{patient}: Updated Composition " - f"DocumentReference: {reference}" + f"{study}/Patient/{patient}: Updated Composition " f"DocumentReference: {reference}" ) else: # Remove it if included @@ -3015,10 +2666,7 @@ def update_consent_composition( for section in composition["section"]: if "entry" in section: for entry in section.get("entry", []): - if ( - "reference" in entry - and "DocumentReference" in entry["reference"] - ): + if "reference" in entry and "DocumentReference" in entry["reference"]: # Nothing to do as we want to leave it out pass else: @@ -3035,9 +2683,7 @@ def update_consent_composition( break else: # Add it - composition["section"].append( - {"reference": f"ResearchStudy/{PPM.Study.fhir_id(study)}"} - ) + composition["section"].append({"reference": f"ResearchStudy/{PPM.Study.fhir_id(study)}"}) # Build the URL url = furl(PPM.fhir_url()) @@ -3055,21 +2701,14 @@ def update_consent_composition( logger.error( "FHIR Request Error: {}".format(e), exc_info=True, - extra={ - "patient": patient, - "response": content, - "document_reference_id": document_reference_id, - }, + extra={"patient": patient, "response": content, "document_reference_id": document_reference_id,}, ) except Exception as e: logger.error( "FHIR Error: {}".format(e), exc_info=True, - extra={ - "patient": patient, - "document_reference_id": document_reference_id, - }, + extra={"patient": patient, "document_reference_id": document_reference_id,}, ) return False @@ -3077,9 +2716,7 @@ def update_consent_composition( @staticmethod def update_research_subject(patient_id, research_subject_id, start=None, end=None): logger.debug( - "Patient: {}, ResearchSubject: {}, Start: {}, End: {}".format( - patient_id, research_subject_id, start, end - ) + "Patient: {}, ResearchSubject: {}, Start: {}, End: {}".format(patient_id, research_subject_id, start, end) ) content = None @@ -3090,13 +2727,7 @@ def update_research_subject(patient_id, research_subject_id, start=None, end=Non else: patch = [{"op": "remove", "path": "/period/end"}] if start: - patch = [ - { - "op": "update", - "path": "/period/start", - "value": start.isoformat(), - } - ] + patch = [{"op": "update", "path": "/period/start", "value": start.isoformat(),}] # Build the URL url = furl(PPM.fhir_url()) @@ -3104,11 +2735,7 @@ def update_research_subject(patient_id, research_subject_id, start=None, end=Non url.path.segments.append(research_subject_id) # Put it - response = requests.patch( - url.url, - json=patch, - headers={"content-type": "application/json-patch+json"}, - ) + response = requests.patch(url.url, json=patch, headers={"content-type": "application/json-patch+json"},) content = response.content response.raise_for_status() @@ -3118,32 +2745,21 @@ def update_research_subject(patient_id, research_subject_id, start=None, end=Non logger.error( "FHIR Request Error: {}".format(e), exc_info=True, - extra={ - "ppm_id": patient_id, - "response": content, - "research_subject_id": research_subject_id, - }, + extra={"ppm_id": patient_id, "response": content, "research_subject_id": research_subject_id,}, ) except Exception as e: logger.error( "FHIR Error: {}".format(e), exc_info=True, - extra={ - "ppm_id": patient_id, - "research_subject_id": research_subject_id, - }, + extra={"ppm_id": patient_id, "research_subject_id": research_subject_id,}, ) return False @staticmethod def update_ppm_research_subject(patient_id, study=None, start=None, end=None): - logger.debug( - "Patient: {}, Study: {}, Start: {}, End: {}".format( - patient_id, study, start, end - ) - ) + logger.debug("Patient: {}, Study: {}, Start: {}, End: {}".format(patient_id, study, start, end)) # Fetch the flag. url = furl(PPM.fhir_url()) @@ -3167,18 +2783,11 @@ def update_ppm_research_subject(patient_id, study=None, start=None, end=None): research_subjects = FHIR._query_resources("ResearchSubject", query=query) # Iterate studies - for research_subject_id in [ - resource["id"] for resource in research_subjects - ]: - logger.debug( - f"{patient_id}: Found ResearchSubject/{research_subject_id} -> " - f"{end}" - ) + for research_subject_id in [resource["id"] for resource in research_subjects]: + logger.debug(f"{patient_id}: Found ResearchSubject/{research_subject_id} -> " f"{end}") # Do the update - FHIR.update_research_subject( - patient_id, research_subject_id, start, end - ) + FHIR.update_research_subject(patient_id, research_subject_id, start, end) return True @@ -3186,21 +2795,14 @@ def update_ppm_research_subject(patient_id, study=None, start=None, end=None): logger.error( "FHIR Request Error: {}".format(e), exc_info=True, - extra={ - "ppm_id": patient_id, - "response": content, - "research_subject_id": research_subject_id, - }, + extra={"ppm_id": patient_id, "response": content, "research_subject_id": research_subject_id,}, ) except Exception as e: logger.error( "FHIR Error: {}".format(e), exc_info=True, - extra={ - "ppm_id": patient_id, - "research_subject_id": research_subject_id, - }, + extra={"ppm_id": patient_id, "research_subject_id": research_subject_id,}, ) return False @@ -3247,9 +2849,7 @@ def update_point_of_care_list(patient, point_of_care): logger.debug("Found existing organization!") # Get the ID - organization_id = "Organization/{}".format( - results["entry"][0]["resource"]["id"] - ) + organization_id = "Organization/{}".format(results["entry"][0]["resource"]["id"]) logger.debug("Existing organization: {}".format(organization_id)) else: @@ -3313,9 +2913,7 @@ def update_twitter(patient_id, handle=None, uses_twitter=None): out of the integration :return: bool """ - logger.debug( - "Twitter handle: {}, Uses Twitter: {}".format(handle, uses_twitter) - ) + logger.debug("Twitter handle: {}, Uses Twitter: {}".format(handle, uses_twitter)) try: # Fetch the Patient. @@ -3350,11 +2948,7 @@ def update_twitter(patient_id, handle=None, uses_twitter=None): # Check for an existing Twitter status extension extension = next( - ( - extension - for extension in patient.get("extension", []) - if "uses-twitter" in extension.get("url") - ), + (extension for extension in patient.get("extension", []) if "uses-twitter" in extension.get("url")), None, ) @@ -3363,9 +2957,7 @@ def update_twitter(patient_id, handle=None, uses_twitter=None): # Set preference value = handle is not None or uses_twitter - logger.debug( - '({}) Updating "uses_twitter" -> {}'.format(patient["id"], value) - ) + logger.debug('({}) Updating "uses_twitter" -> {}'.format(patient["id"], value)) if not extension: # Add an extension indicating their use of Twitter @@ -3381,9 +2973,7 @@ def update_twitter(patient_id, handle=None, uses_twitter=None): extension["valueBoolean"] = value elif extension: - logger.debug( - '({}) Deleting "uses_twitter" -> None'.format(patient["id"]) - ) + logger.debug('({}) Deleting "uses_twitter" -> None'.format(patient["id"])) # Remove this extension patient["extension"].remove(extension) @@ -3397,9 +2987,7 @@ def update_twitter(patient_id, handle=None, uses_twitter=None): return response.ok except Exception as e: - logger.exception( - "FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id} - ) + logger.exception("FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id}) return False @@ -3439,11 +3027,7 @@ def update_patient_extension(patient_id, extension_url, value=None): None, ) if value is not None: - logger.debug( - '({}) Updating "{}" -> "{}"'.format( - patient["id"], extension_url, value - ) - ) + logger.debug('({}) Updating "{}" -> "{}"'.format(patient["id"], extension_url, value)) # Check if an existing one was found if not extension: @@ -3464,15 +3048,11 @@ def update_patient_extension(patient_id, extension_url, value=None): elif type(value) is datetime: extension["valueDateTime"] = value.isoformat() else: - logger.error( - 'Unhandled value type "{}" : "{}"'.format(value, type(value)) - ) + logger.error('Unhandled value type "{}" : "{}"'.format(value, type(value))) return False elif extension: - logger.debug( - "({}) Deleting {} -> None".format(patient["id"], extension_url) - ) + logger.debug("({}) Deleting {} -> None".format(patient["id"], extension_url)) # Remove this extension patient["extension"].remove(extension) @@ -3486,17 +3066,13 @@ def update_patient_extension(patient_id, extension_url, value=None): return response.ok except Exception as e: - logger.exception( - "FHIR error: {}".format(e), exc_info=True, extra={"patient": patient_id} - ) + logger.exception("FHIR error: {}".format(e), exc_info=True, extra={"patient": patient_id}) return False @staticmethod def update_picnichealth(patient_id, registered=True): - logger.debug( - "Picnichealth registration: {} -> {}".format(patient_id, registered) - ) + logger.debug("Picnichealth registration: {} -> {}".format(patient_id, registered)) try: # Fetch the Patient. @@ -3540,9 +3116,7 @@ def update_picnichealth(patient_id, registered=True): return response.ok except Exception as e: - logger.exception( - "FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id} - ) + logger.exception("FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": patient_id}) return False @@ -3577,10 +3151,7 @@ def update_document_reference(document_reference, status="current"): logger.exception( "FHIR error: {}".format(e), exc_info=True, - extra={ - "document_reference": document_reference, - "patient": patient_ref, - }, + extra={"document_reference": document_reference, "patient": patient_ref,}, ) return False @@ -3590,9 +3161,7 @@ def update_document_reference(document_reference, status="current"): # @staticmethod - def _delete_resources( - source_resource_type, source_resource_id, target_resource_types=[] - ): + def _delete_resources(source_resource_type, source_resource_id, target_resource_types=[]): """ Removes a source resource and all of its related resources. Delete is done in a transaction so if an error occurs, the system will revert to its @@ -3612,11 +3181,7 @@ def _delete_resources( """ content = None try: - logger.debug( - "Target resource: {}/{}".format( - source_resource_type, source_resource_id - ) - ) + logger.debug("Target resource: {}/{}".format(source_resource_type, source_resource_id)) logger.debug("Target related resources: {}".format(target_resource_types)) source_url = furl(PPM.fhir_url()) @@ -3637,8 +3202,7 @@ def _delete_resources( for resource in [ entry["resource"] for entry in entries - if entry.get("resource") is not None - and entry["resource"]["resourceType"] in target_resource_types + if entry.get("resource") is not None and entry["resource"]["resourceType"] in target_resource_types ]: # Get the ID and resource type _id = resource.get("id") @@ -3649,26 +3213,20 @@ def _delete_resources( # Add it. logger.debug("Add: {}".format(resource_id)) - transaction["entry"].append( - {"request": {"url": resource_id, "method": "DELETE"}} - ) + transaction["entry"].append({"request": {"url": resource_id, "method": "DELETE"}}) logger.debug("Delete request: {}".format(json.dumps(transaction))) # Do the delete. response = requests.post( - PPM.fhir_url(), - headers={"content-type": "application/json"}, - data=json.dumps(transaction), + PPM.fhir_url(), headers={"content-type": "application/json"}, data=json.dumps(transaction), ) response.raise_for_status() # Log it. logger.debug("Delete response: {}".format(response.content)) logger.debug( - "Successfully deleted all for resource: {}/{}".format( - source_resource_type, source_resource_id - ) + "Successfully deleted all for resource: {}/{}".format(source_resource_type, source_resource_id) ) return response.ok @@ -3678,9 +3236,7 @@ def _delete_resources( "Delete error: {}".format(e), exc_info=True, extra={ - "resource": "{}/{}".format( - source_resource_type, source_resource_id - ), + "resource": "{}/{}".format(source_resource_type, source_resource_id), "included_resources": target_resource_types, "content": content, }, @@ -3705,9 +3261,7 @@ def _delete_resource(resource_type, resource_id): response.raise_for_status() # Log it. - logger.debug( - "Deleted: {}/{}: {}".format(resource_type, resource_id, response.ok) - ) + logger.debug("Deleted: {}/{}: {}".format(resource_type, resource_id, response.ok)) return response.ok @@ -3715,11 +3269,7 @@ def _delete_resource(resource_type, resource_id): logger.exception( "Delete resource error: {}".format(e), exc_info=True, - extra={ - "resource": "{}/{}".format(resource_type, resource_id), - "content": content, - "url": url, - }, + extra={"resource": "{}/{}".format(resource_type, resource_id), "content": content, "url": url,}, ) return False @@ -3768,9 +3318,7 @@ def delete_research_subjects(patient_id): :return: bool """ # Find it - research_subjects = FHIR.query_research_subjects( - patient_id, flatten_return=False - ) + research_subjects = FHIR.query_research_subjects(patient_id, flatten_return=False) for research_subject in research_subjects: # Attempt to delete the patient and all related resources. @@ -3787,9 +3335,7 @@ def delete_point_of_care_list(patient_id): :return: bool """ # Find it - point_of_care_list = FHIR.get_point_of_care_list( - patient_id, flatten_return=False - ) + point_of_care_list = FHIR.get_point_of_care_list(patient_id, flatten_return=False) if point_of_care_list: # Attempt to delete the patient and all related resources. @@ -3800,19 +3346,13 @@ def delete_point_of_care_list(patient_id): @staticmethod def delete_questionnaire_response(patient_id, project): - logger.debug( - "Deleting questionnaire response: Patient/{} - {}".format( - patient_id, project - ) - ) + logger.debug("Deleting questionnaire response: Patient/{} - {}".format(patient_id, project)) # Get the questionnaire ID questionnaire_id = PPM.Questionnaire.questionnaire_for_study(study=project) # Find it - questionnaire_response = FHIR.get_questionnaire_response( - patient_id, questionnaire_id - ) + questionnaire_response = FHIR.get_questionnaire_response(patient_id, questionnaire_id) if questionnaire_response: # Delete it @@ -3833,32 +3373,17 @@ def delete_consent(patient_id, project): # Add the composition delete transaction["entry"].append( - { - "request": { - "url": "Composition?subject=Patient/{}".format(patient_id), - "method": "DELETE", - } - } + {"request": {"url": "Composition?subject=Patient/{}".format(patient_id), "method": "DELETE",}} ) # Add the consent delete transaction["entry"].append( - { - "request": { - "url": "Consent?patient=Patient/{}".format(patient_id), - "method": "DELETE", - } - } + {"request": {"url": "Consent?patient=Patient/{}".format(patient_id), "method": "DELETE",}} ) # Add the contract delete transaction["entry"].append( - { - "request": { - "url": "Contract?signer=Patient/{}".format(patient_id), - "method": "DELETE", - } - } + {"request": {"url": "Contract?signer=Patient/{}".format(patient_id), "method": "DELETE",}} ) # Add the consent document delete @@ -3895,9 +3420,7 @@ def delete_consent(patient_id, project): { "request": { "url": "QuestionnaireResponse?" - "questionnaire=Questionnaire/{}&source=Patient/{}".format( - questionnaire_id, patient_id - ), + "questionnaire=Questionnaire/{}&source=Patient/{}".format(questionnaire_id, patient_id), "method": "DELETE", } } @@ -3905,22 +3428,12 @@ def delete_consent(patient_id, project): # Add the contract delete transaction["entry"].append( - { - "request": { - "url": "Contract?signer.patient={}".format(patient_id), - "method": "DELETE", - } - } + {"request": {"url": "Contract?signer.patient={}".format(patient_id), "method": "DELETE",}} ) # Remove related persons transaction["entry"].append( - { - "request": { - "url": "RelatedPerson?patient=Patient/{}".format(patient_id), - "method": "DELETE", - } - } + {"request": {"url": "RelatedPerson?patient=Patient/{}".format(patient_id), "method": "DELETE",}} ) elif project == "neer": @@ -3932,24 +3445,18 @@ def delete_consent(patient_id, project): { "request": { "url": "QuestionnaireResponse?" - "questionnaire=Questionnaire/{}&source=Patient/{}".format( - questionnaire_id, patient_id - ), + "questionnaire=Questionnaire/{}&source=Patient/{}".format(questionnaire_id, patient_id), "method": "DELETE", } } ) else: - logger.error( - "Unsupported project: {}".format(project), extra={"ppm_id": patient_id} - ) + logger.error("Unsupported project: {}".format(project), extra={"ppm_id": patient_id}) # Make the FHIR request. response = requests.post( - PPM.fhir_url(), - headers={"content-type": "application/json"}, - data=json.dumps(transaction), + PPM.fhir_url(), headers={"content-type": "application/json"}, data=json.dumps(transaction), ) response.raise_for_status() @@ -3967,9 +3474,7 @@ def get_ppm_research_studies(bundle, flatten_result=True): return None # Get study IDs - research_study_ids = [ - subject["study"]["reference"].split("/")[1] for subject in subjects - ] + research_study_ids = [subject["study"]["reference"].split("/")[1] for subject in subjects] # Make the query research_study_url = furl(PPM.fhir_url()) @@ -3984,10 +3489,7 @@ def get_ppm_research_studies(bundle, flatten_result=True): if flatten_result: # Return the titles - return [ - research_study["resource"]["title"] - for research_study in research_studies - ] + return [research_study["resource"]["title"] for research_study in research_studies] else: return [research_study["resource"] for research_study in research_studies] @@ -4001,9 +3503,7 @@ def get_research_studies(bundle, flatten_result=True): return None # Get study IDs - research_study_ids = [ - subject["study"]["reference"].split("/")[1] for subject in subjects - ] + research_study_ids = [subject["study"]["reference"].split("/")[1] for subject in subjects] # Make the query research_study_url = furl(PPM.fhir_url()) @@ -4018,10 +3518,7 @@ def get_research_studies(bundle, flatten_result=True): if flatten_result: # Return the titles - return [ - research_study["resource"]["title"] - for research_study in research_studies - ] + return [research_study["resource"]["title"] for research_study in research_studies] else: return [research_study["resource"] for research_study in research_studies] @@ -4039,10 +3536,7 @@ def get_ppm_research_subjects(bundle, flatten_result=True): if flatten_result: # Return the titles - return [ - FHIR.flatten_research_subject(resource) - for resource in research_subjects - ] + return [FHIR.flatten_research_subject(resource) for resource in research_subjects] else: return [resource for resource in research_subjects] @@ -4060,10 +3554,7 @@ def get_research_subjects(bundle, flatten_result=True): if flatten_result: # Return the titles - return [ - FHIR.flatten_research_subject(resource) - for resource in research_subjects - ] + return [FHIR.flatten_research_subject(resource) for resource in research_subjects] else: return [resource for resource in research_subjects] @@ -4079,9 +3570,7 @@ def get_ppm_id(email): client = FHIR.get_client(PPM.fhir_url()) # Query the Patient - search = Patient.where( - struct={"identifier": "http://schema.org/email|{}".format(email)} - ) + search = Patient.where(struct={"identifier": "http://schema.org/email|{}".format(email)}) resources = search.perform_resources(client.server) for resource in resources: @@ -4165,15 +3654,11 @@ def flatten_participant(bundle): # Get the PPM study/project resources studies = FHIR.flatten_ppm_studies(bundle) if len(studies) > 1: - logger.warning( - "Patient/{} has more than one PPM study: {}".format(ppm_id, studies) - ) + logger.warning("Patient/{} has more than one PPM study: {}".format(ppm_id, studies)) # Check for accepted and a start date participant["project"] = participant["study"] = studies[0]["study"] - participant["date_registered"] = FHIR._format_date( - studies[0]["start"], "%m/%d/%Y" - ) + participant["date_registered"] = FHIR._format_date(studies[0]["start"], "%m/%d/%Y") participant["datetime_registered"] = studies[0]["start"] # Get the enrollment properties @@ -4181,16 +3666,12 @@ def flatten_participant(bundle): # Set status and dates participant["enrollment"] = enrollment["enrollment"] - participant["date_enrollment_updated"] = FHIR._format_date( - enrollment["updated"], "%m/%d/%Y" - ) + participant["date_enrollment_updated"] = FHIR._format_date(enrollment["updated"], "%m/%d/%Y") participant["datetime_enrollment_updated"] = enrollment["updated"] if enrollment.get("start"): # Convert time zone to assumed ET - participant["enrollment_accepted_date"] = FHIR._format_date( - enrollment["start"], "%m/%d/%Y" - ) + participant["enrollment_accepted_date"] = FHIR._format_date(enrollment["start"], "%m/%d/%Y") else: participant["enrollment_accepted_date"] = "" @@ -4199,9 +3680,7 @@ def flatten_participant(bundle): if enrollment.get("end"): # Convert time zone to assumed ET - participant["enrollment_terminated_date"] = FHIR._format_date( - enrollment["end"], "%m/%d/%Y" - ) + participant["enrollment_terminated_date"] = FHIR._format_date(enrollment["end"], "%m/%d/%Y") # # else: # participant['enrollment_terminated_date'] = '' @@ -4210,14 +3689,10 @@ def flatten_participant(bundle): participant["composition"] = FHIR.flatten_consent_composition(bundle) # Get the project - _questionnaire_id = PPM.Questionnaire.questionnaire_for_study( - study=participant["project"] - ) + _questionnaire_id = PPM.Questionnaire.questionnaire_for_study(study=participant["project"]) # Parse out the responses - participant["questionnaire"] = FHIR.flatten_questionnaire_response( - bundle, _questionnaire_id - ) + participant["questionnaire"] = FHIR.flatten_questionnaire_response(bundle, _questionnaire_id) # Flatten points of care participant["points_of_care"] = FHIR.flatten_list(bundle, "Organization") @@ -4241,9 +3716,7 @@ def flatten_participant(bundle): if participant.get("composition"): # Get the Questionnaire ID used for the quiz portion of the consent - quiz_id = PPM.Questionnaire.questionnaire_for_consent( - participant.get("composition") - ) + quiz_id = PPM.Questionnaire.questionnaire_for_consent(participant.get("composition")) # Flatten the Q's and A's for output quiz = FHIR.flatten_questionnaire_response(bundle, quiz_id) @@ -4251,36 +3724,24 @@ def flatten_participant(bundle): # Add it participant["consent_quiz"] = quiz - participant[ - "consent_quiz_answers" - ] = FHIR.questionnaire_answers(bundle, quiz_id) + participant["consent_quiz_answers"] = FHIR.questionnaire_answers(bundle, quiz_id) # Get study specific resources if PPM.Study.enum(participant["study"]) is PPM.Study.NEER: - participant[PPM.Study.NEER.value] = FHIR._flatten_neer_participant( - bundle=bundle, ppm_id=ppm_id - ) + participant[PPM.Study.NEER.value] = FHIR._flatten_neer_participant(bundle=bundle, ppm_id=ppm_id) elif PPM.Study.enum(participant["study"]) is PPM.Study.RANT: - participant[PPM.Study.RANT.value] = FHIR._flatten_rant_participant( - bundle=bundle, ppm_id=ppm_id - ) + participant[PPM.Study.RANT.value] = FHIR._flatten_rant_participant(bundle=bundle, ppm_id=ppm_id) elif PPM.Study.enum(participant["study"]) is PPM.Study.ASD: - participant[PPM.Study.ASD.value] = FHIR._flatten_asd_participant( - bundle=bundle, ppm_id=ppm_id - ) + participant[PPM.Study.ASD.value] = FHIR._flatten_asd_participant(bundle=bundle, ppm_id=ppm_id) elif PPM.Study.enum(participant["study"]) is PPM.Study.EXAMPLE: - participant[ - PPM.Study.EXAMPLE.value - ] = FHIR._flatten_example_participant(bundle=bundle, ppm_id=ppm_id) + participant[PPM.Study.EXAMPLE.value] = FHIR._flatten_example_participant(bundle=bundle, ppm_id=ppm_id) except Exception as e: logger.exception( - "FHIR error: {}".format(e), - exc_info=True, - extra={"ppm_id": ppm_id, "email": email}, + "FHIR error: {}".format(e), exc_info=True, extra={"ppm_id": ppm_id, "email": email}, ) return participant @@ -4300,10 +3761,7 @@ def _flatten_asd_participant(bundle, ppm_id): values = {} # TODO: Implement this - logger.warning( - f"PPM/ASD/{ppm_id}/FHIR: Flattening ASD participant needs to be " - f"fully implemented" - ) + logger.warning(f"PPM/ASD/{ppm_id}/FHIR: Flattening ASD participant needs to be " f"fully implemented") return values @@ -4326,16 +3784,12 @@ def _flatten_neer_participant(bundle, ppm_id): ( q for q in FHIR._find_resources(bundle, "QuestionnaireResponse") - if q["questionnaire"]["reference"] - == f"Questionnaire/{PPM.Questionnaire.NEERQuestionnaire.value}" + if q["questionnaire"]["reference"] == f"Questionnaire/{PPM.Questionnaire.NEERQuestionnaire.value}" ), None, ) if questionnaire_response: - logger.debug( - f"PPM/{ppm_id}/FHIR: Flattening QuestionnaireResponse/" - f'{questionnaire_response["id"]}' - ) + logger.debug(f"PPM/{ppm_id}/FHIR: Flattening QuestionnaireResponse/" f'{questionnaire_response["id"]}') # Map linkIds to keys text_answers = { @@ -4354,9 +3808,7 @@ def _flatten_neer_participant(bundle, ppm_id): try: # Get the answer answer = next( - i["answer"][0]["valueString"] - for i in questionnaire_response["item"] - if i["linkId"] == link_id + i["answer"][0]["valueString"] for i in questionnaire_response["item"] if i["linkId"] == link_id ) # Assign it @@ -4369,16 +3821,8 @@ def _flatten_neer_participant(bundle, ppm_id): "ppm_id": ppm_id, "link_id": link_id, "key": key, - "questionnaire_response": f"QuestionnaireResponse/" - f'{questionnaire_response["id"]}', - "item": next( - ( - i - for i in questionnaire_response["item"] - if i["linkId"] == link_id - ), - "", - ), + "questionnaire_response": f"QuestionnaireResponse/" f'{questionnaire_response["id"]}', + "item": next((i for i in questionnaire_response["item"] if i["linkId"] == link_id), "",), }, ) @@ -4391,9 +3835,7 @@ def _flatten_neer_participant(bundle, ppm_id): try: # Get the answer answer = next( - i["answer"][0]["valueString"] - for i in questionnaire_response["item"] - if i["linkId"] == link_id + i["answer"][0]["valueString"] for i in questionnaire_response["item"] if i["linkId"] == link_id ) try: @@ -4404,10 +3846,7 @@ def _flatten_neer_participant(bundle, ppm_id): values[key] = answer_date.isoformat() except ValueError: - logger.debug( - f"PPM/{ppm_id}/Questionnaire/{link_id}: " - f"Invalid date: {answer}" - ) + logger.debug(f"PPM/{ppm_id}/Questionnaire/{link_id}: " f"Invalid date: {answer}") # Assign the raw value values[key] = answer @@ -4420,16 +3859,8 @@ def _flatten_neer_participant(bundle, ppm_id): "ppm_id": ppm_id, "link_id": link_id, "key": key, - "questionnaire_response": f"QuestionnaireResponse/" - f'{questionnaire_response["id"]}', - "item": next( - ( - i - for i in questionnaire_response["item"] - if i["linkId"] == link_id - ), - "", - ), + "questionnaire_response": f"QuestionnaireResponse/" f'{questionnaire_response["id"]}', + "item": next((i for i in questionnaire_response["item"] if i["linkId"] == link_id), "",), }, ) @@ -4457,16 +3888,12 @@ def _flatten_rant_participant(bundle, ppm_id): ( q for q in FHIR._find_resources(bundle, "QuestionnaireResponse") - if q["questionnaire"]["reference"] - == f"Questionnaire/{PPM.Questionnaire.RANTQuestionnaire.value}" + if q["questionnaire"]["reference"] == f"Questionnaire/{PPM.Questionnaire.RANTQuestionnaire.value}" ), None, ) if questionnaire_response: - logger.debug( - f"PPM/{ppm_id}/FHIR: Flattening QuestionnaireResponse/" - f'{questionnaire_response["id"]}' - ) + logger.debug(f"PPM/{ppm_id}/FHIR: Flattening QuestionnaireResponse/" f'{questionnaire_response["id"]}') # Map linkIds to keys text_answers = { @@ -4485,9 +3912,7 @@ def _flatten_rant_participant(bundle, ppm_id): try: # Get the answer answer = next( - i["answer"][0]["valueString"] - for i in questionnaire_response["item"] - if i["linkId"] == link_id + i["answer"][0]["valueString"] for i in questionnaire_response["item"] if i["linkId"] == link_id ) # Assign it @@ -4500,16 +3925,8 @@ def _flatten_rant_participant(bundle, ppm_id): "ppm_id": ppm_id, "link_id": link_id, "key": key, - "questionnaire_response": f"QuestionnaireResponse/" - f'{questionnaire_response["id"]}', - "item": next( - ( - i - for i in questionnaire_response["item"] - if i["linkId"] == link_id - ), - "", - ), + "questionnaire_response": f"QuestionnaireResponse/" f'{questionnaire_response["id"]}', + "item": next((i for i in questionnaire_response["item"] if i["linkId"] == link_id), "",), }, ) @@ -4522,9 +3939,7 @@ def _flatten_rant_participant(bundle, ppm_id): try: # Get the answer answer = next( - i["answer"][0]["valueString"] - for i in questionnaire_response["item"] - if i["linkId"] == link_id + i["answer"][0]["valueString"] for i in questionnaire_response["item"] if i["linkId"] == link_id ) try: @@ -4535,10 +3950,7 @@ def _flatten_rant_participant(bundle, ppm_id): values[key] = answer_date.isoformat() except ValueError: - logger.debug( - f"PPM/{ppm_id}/Questionnaire/{link_id}: Invalid date: " - f"{answer}" - ) + logger.debug(f"PPM/{ppm_id}/Questionnaire/{link_id}: Invalid date: " f"{answer}") # Assign the raw value values[key] = answer @@ -4551,16 +3963,8 @@ def _flatten_rant_participant(bundle, ppm_id): "ppm_id": ppm_id, "link_id": link_id, "key": key, - "questionnaire_response": f"QuestionnaireResponse/" - f'{questionnaire_response["id"]}', - "item": next( - ( - i - for i in questionnaire_response["item"] - if i["linkId"] == link_id - ), - "", - ), + "questionnaire_response": f"QuestionnaireResponse/" f'{questionnaire_response["id"]}', + "item": next((i for i in questionnaire_response["item"] if i["linkId"] == link_id), "",), }, ) @@ -4588,16 +3992,12 @@ def _flatten_example_participant(bundle, ppm_id): ( q for q in FHIR._find_resources(bundle, "QuestionnaireResponse") - if q["questionnaire"]["reference"] - == f"Questionnaire/{PPM.Questionnaire.EXAMPLEQuestionnaire.value}" + if q["questionnaire"]["reference"] == f"Questionnaire/{PPM.Questionnaire.EXAMPLEQuestionnaire.value}" ), None, ) if questionnaire_response: - logger.debug( - f"PPM/{ppm_id}/FHIR: Flattening QuestionnaireResponse/" - f'{questionnaire_response["id"]}' - ) + logger.debug(f"PPM/{ppm_id}/FHIR: Flattening QuestionnaireResponse/" f'{questionnaire_response["id"]}') # Map linkIds to keys text_answers = { @@ -4616,9 +4016,7 @@ def _flatten_example_participant(bundle, ppm_id): try: # Get the answer answer = next( - i["answer"][0]["valueString"] - for i in questionnaire_response["item"] - if i["linkId"] == link_id + i["answer"][0]["valueString"] for i in questionnaire_response["item"] if i["linkId"] == link_id ) # Assign it @@ -4631,16 +4029,8 @@ def _flatten_example_participant(bundle, ppm_id): "ppm_id": ppm_id, "link_id": link_id, "key": key, - "questionnaire_response": f"QuestionnaireResponse/" - f'{questionnaire_response["id"]}', - "item": next( - ( - i - for i in questionnaire_response["item"] - if i["linkId"] == link_id - ), - "", - ), + "questionnaire_response": f"QuestionnaireResponse/" f'{questionnaire_response["id"]}', + "item": next((i for i in questionnaire_response["item"] if i["linkId"] == link_id), "",), }, ) @@ -4653,9 +4043,7 @@ def _flatten_example_participant(bundle, ppm_id): try: # Get the answer answer = next( - i["answer"][0]["valueString"] - for i in questionnaire_response["item"] - if i["linkId"] == link_id + i["answer"][0]["valueString"] for i in questionnaire_response["item"] if i["linkId"] == link_id ) try: @@ -4666,10 +4054,7 @@ def _flatten_example_participant(bundle, ppm_id): values[key] = answer_date.isoformat() except ValueError: - logger.debug( - f"PPM/{ppm_id}/Questionnaire/{link_id}: Invalid date: " - f"{answer}" - ) + logger.debug(f"PPM/{ppm_id}/Questionnaire/{link_id}: Invalid date: " f"{answer}") # Assign the raw value values[key] = answer @@ -4682,16 +4067,8 @@ def _flatten_example_participant(bundle, ppm_id): "ppm_id": ppm_id, "link_id": link_id, "key": key, - "questionnaire_response": f"QuestionnaireResponse/" - f'{questionnaire_response["id"]}', - "item": next( - ( - i - for i in questionnaire_response["item"] - if i["linkId"] == link_id - ), - "", - ), + "questionnaire_response": f"QuestionnaireResponse/" f'{questionnaire_response["id"]}', + "item": next((i for i in questionnaire_response["item"] if i["linkId"] == link_id), "",), }, ) @@ -4715,32 +4092,20 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): bundle = Bundle(bundle_dict) # Pick out the questionnaire and its response - questionnaire = next( - ( - entry.resource - for entry in bundle.entry - if entry.resource.id == questionnaire_id - ), - None, - ) + questionnaire = next((entry.resource for entry in bundle.entry if entry.resource.id == questionnaire_id), None,) questionnaire_response = next( ( entry.resource for entry in bundle.entry if entry.resource.resource_type == "QuestionnaireResponse" - and entry.resource.questionnaire.reference - == "Questionnaire/{}".format(questionnaire_id) + and entry.resource.questionnaire.reference == "Questionnaire/{}".format(questionnaire_id) ), None, ) # Ensure resources exist if not questionnaire or not questionnaire_response: - logger.debug( - "User has no responses for Questionnaire/{}, returning".format( - questionnaire_id - ) - ) + logger.debug("User has no responses for Questionnaire/{}, returning".format(questionnaire_id)) return None # Get questions and answers @@ -4749,9 +4114,7 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): # Process sub-questions first for linkId, condition in { - linkId: condition - for linkId, condition in questions.items() - if type(condition) is dict + linkId: condition for linkId, condition in questions.items() if type(condition) is dict }.items(): try: @@ -4759,9 +4122,7 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): parent = next(iter(condition)) if not parent: logger.warning( - "FHIR Error: Subquestion not properly specified: {}:{}".format( - linkId, condition - ), + "FHIR Error: Subquestion not properly specified: {}:{}".format(linkId, condition), extra={ "questionnaire": questionnaire_id, "ppm_id": questionnaire_response.source, @@ -4772,9 +4133,7 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): if len(condition) > 1: logger.warning( - "FHIR Error: Subquestion has multiple conditions: {}:{}".format( - linkId, condition - ), + "FHIR Error: Subquestion has multiple conditions: {}:{}".format(linkId, condition), extra={ "questionnaire": questionnaire_id, "ppm_id": questionnaire_response.source, @@ -4783,9 +4142,7 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): ) # Ensure they've answered this one - if not answers.get(parent) or condition[parent] not in answers.get( - parent - ): + if not answers.get(parent) or condition[parent] not in answers.get(parent): continue # Get the question and answer item @@ -4797,14 +4154,11 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): if "," in next(iter(sub_answers)): # Split it - sub_answers = [ - sub.strip() for sub in next(iter(sub_answers)).split(",") - ] + sub_answers = [sub.strip() for sub in next(iter(sub_answers)).split(",")] # Format them value = '{} {}'.format( - answer[index], - ' '.join(sub_answers), + answer[index], ' '.join(sub_answers), ) # Append the value @@ -4827,11 +4181,7 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): # Process top-level questions first top_questions = collections.OrderedDict( sorted( - { - linkId: question - for linkId, question in questions.items() - if type(question) is str - }.items(), + {linkId: question for linkId, question in questions.items() if type(question) is str}.items(), key=lambda q: int(q[0].split("-")[1]), ) ) @@ -4861,9 +4211,7 @@ def flatten_questionnaire_response(bundle_dict, questionnaire_id): formatted_authored_date = FHIR._format_date(authored_date, "%m/%d/%Y") return { - "ppm_id": FHIR._get_referenced_id( - questionnaire_response.as_json(), "Patient" - ), + "ppm_id": FHIR._get_referenced_id(questionnaire_response.as_json(), "Patient"), "authored": formatted_authored_date, "responses": response, } @@ -4930,8 +4278,7 @@ def _answers(items): # Ensure we've got answers if not item.answer: logger.error( - "FHIR questionnaire error: Missing items for question", - extra={"link_id": item.linkId}, + "FHIR questionnaire error: Missing items for question", extra={"link_id": item.linkId}, ) responses[item.linkId] = ["------"] @@ -4956,8 +4303,7 @@ def _answers(items): else: logger.warning( - "Unhandled answer value type: {}".format(answer.as_json()), - extra={"link_id": item.linkId}, + "Unhandled answer value type: {}".format(answer.as_json()), extra={"link_id": item.linkId}, ) # Check for subtypes @@ -5003,10 +4349,7 @@ def flatten_patient(bundle_dict): ) ) if not patient.get("email"): - logger.error( - "Could not parse email from Patient/{}! This should not be " - "possible".format(resource["id"]) - ) + logger.error("Could not parse email from Patient/{}! This should not be " "possible".format(resource["id"])) return {} # Get status @@ -5015,12 +4358,8 @@ def flatten_patient(bundle_dict): # Get the remaining optional properties patient["firstname"] = FHIR._get_or(resource, ["name", 0, "given", 0], "") patient["lastname"] = FHIR._get_or(resource, ["name", 0, "family"], "") - patient["street_address1"] = FHIR._get_or( - resource, ["address", 0, "line", 0], "" - ) - patient["street_address2"] = FHIR._get_or( - resource, ["address", 0, "line", 1], "" - ) + patient["street_address1"] = FHIR._get_or(resource, ["address", 0, "line", 0], "") + patient["street_address2"] = FHIR._get_or(resource, ["address", 0, "line", 1], "") patient["city"] = FHIR._get_or(resource, ["address", 0, "city"], "") patient["state"] = FHIR._get_or(resource, ["address", 0, "state"], "") patient["zip"] = FHIR._get_or(resource, ["address", 0, "postalCode"], "") @@ -5028,9 +4367,7 @@ def flatten_patient(bundle_dict): # Check for deceased if FHIR._get_or(resource, ["deceasedDateTime"], None): - patient["deceased"] = FHIR._format_date( - resource["deceasedDateTime"], "%m/%d/%Y" - ) + patient["deceased"] = FHIR._format_date(resource["deceasedDateTime"], "%m/%d/%Y") # Parse telecom properties patient["phone"] = next( @@ -5174,9 +4511,7 @@ def flatten_ppm_studies(bundle): if FHIR.is_ppm_research_subject(research_subject): # Flatten it and add it - research_subjects.append( - FHIR.flatten_research_subject(research_subject) - ) + research_subjects.append(FHIR.flatten_research_subject(research_subject)) if not research_subjects: logger.debug("No ResearchSubjects found in bundle") @@ -5255,18 +4590,12 @@ def flatten_enrollment(bundle): for flag in FHIR._find_resources(bundle, "Flag"): # Ensure it's the enrollment flag - if FHIR.enrollment_flag_coding_system == FHIR._get_or( - flag, ["code", "coding", 0, "system"] - ): + if FHIR.enrollment_flag_coding_system == FHIR._get_or(flag, ["code", "coding", 0, "system"]): # Flatten and return it return FHIR.flatten_enrollment_flag(flag) - logger.error( - "No Flag with coding: {} found".format( - FHIR.enrollment_flag_coding_system - ) - ) + logger.error("No Flag with coding: {} found".format(FHIR.enrollment_flag_coding_system)) logger.debug("No Flags found in bundle") return None @@ -5322,9 +4651,7 @@ def flatten_consent_composition(bundle_json): date_time = signed_consent.dateTime.origval # Format it - consent_object["date_signed"] = FHIR._format_date( - date_time, "%m/%d/%Y" - ) + consent_object["date_signed"] = FHIR._format_date(date_time, "%m/%d/%Y") # Exceptions are for when they refuse part of the consent. if signed_consent.except_fhir: @@ -5332,29 +4659,17 @@ def flatten_consent_composition(bundle_json): # Check for conversion display = consent_exception.code[0].display - consent_exceptions.append( - FHIR._exception_description(display) - ) + consent_exceptions.append(FHIR._exception_description(display)) elif bundle_entry.resource.resource_type == "Composition": composition = bundle_entry.resource - entries = [ - section.entry - for section in composition.section - if section.entry is not None - ] + entries = [section.entry for section in composition.section if section.entry is not None] references = [ - entry[0].reference - for entry in entries - if len(entry) > 0 and entry[0].reference is not None + entry[0].reference for entry in entries if len(entry) > 0 and entry[0].reference is not None ] - text = [ - section.text.div - for section in composition.section - if section.text is not None - ][0] + text = [section.text.div for section in composition.section if section.text is not None][0] # Check the references for a Consent object, making this comp the # consent one. @@ -5381,37 +4696,28 @@ def flatten_consent_composition(bundle_json): ( entry.resource for entry in incoming_bundle.entry - if entry.resource.resource_type - == "QuestionnaireResponse" + if entry.resource.resource_type == "QuestionnaireResponse" and entry.resource.id == questionnaire_response_id ), None, ) if not q_response: - logger.error( - "Could not find bindingReference QR for " - "Contract/{}".format(contract.id) - ) + logger.error("Could not find bindingReference QR for " "Contract/{}".format(contract.id)) break # Get the questionnaire and its response. - questionnaire_id = q_response.questionnaire.reference.split( - "/" - )[1] + questionnaire_id = q_response.questionnaire.reference.split("/")[1] questionnaire = [ entry.resource for entry in incoming_bundle.entry - if entry.resource.resource_type == "Questionnaire" - and entry.resource.id == questionnaire_id + if entry.resource.resource_type == "Questionnaire" and entry.resource.id == questionnaire_id ][0] if not q_response or not questionnaire: logger.error( "FHIR Error: Could not find bindingReference " - "Questionnaire/Response for Contract/{}".format( - contract.id - ), + "Questionnaire/Response for Contract/{}".format(contract.id), extra={ "ppm_id": contract.subject, "questionnaire": questionnaire_id, @@ -5422,60 +4728,40 @@ def flatten_consent_composition(bundle_json): # The reference refers to a Questionnaire which is linked to # a part of the consent form. - if ( - q_response.questionnaire.reference - == "Questionnaire/guardian-signature-part-1" - ): + if q_response.questionnaire.reference == "Questionnaire/guardian-signature-part-1": # This is a person consenting for someone else. consent_object["type"] = "GUARDIAN" - related_id = contract.signer[0].party.reference.split("/")[ - 1 - ] + related_id = contract.signer[0].party.reference.split("/")[1] related_person = [ entry.resource for entry in incoming_bundle.entry - if entry.resource.resource_type == "RelatedPerson" - and entry.resource.id == related_id + if entry.resource.resource_type == "RelatedPerson" and entry.resource.id == related_id ][0] consent_object["signer_name"] = related_person.name[0].text - consent_object[ - "signer_relationship" - ] = related_person.relationship.text + consent_object["signer_relationship"] = related_person.relationship.text consent_object["participant_name"] = ( - contract.signer[0] - .signature[0] - .onBehalfOfReference.display + contract.signer[0].signature[0].onBehalfOfReference.display ) consent_object["signer_signature"] = base64.b64decode( contract.signer[0].signature[0].blob ).decode() - elif ( - q_response.questionnaire.reference - == "Questionnaire/guardian-signature-part-2" - ): + elif q_response.questionnaire.reference == "Questionnaire/guardian-signature-part-2": # This is the question about being able to get # acknowledgement from the participant by the # guardian/parent. consent_object["participant_acknowledgement"] = next( - item.answer[0].valueString - for item in q_response.item - if item.linkId == "question-1" + item.answer[0].valueString for item in q_response.item if item.linkId == "question-1" ).title() # If the answer to the question is no, grab the reason. - if ( - consent_object["participant_acknowledgement"].lower() - == "no" - ): - consent_object[ - "participant_acknowledgement_reason" - ] = next( + if consent_object["participant_acknowledgement"].lower() == "no": + consent_object["participant_acknowledgement_reason"] = next( item.answer[0].valueString for item in q_response.item if item.linkId == "question-1-1" @@ -5487,10 +4773,7 @@ def flatten_consent_composition(bundle_json): contract.signer[0].signature[0].blob ).decode() - elif ( - q_response.questionnaire.reference - == "Questionnaire/guardian-signature-part-3" - ): + elif q_response.questionnaire.reference == "Questionnaire/guardian-signature-part-3": # A contract without a reference is the assent page. consent_object["assent_signature"] = base64.b64decode( @@ -5503,13 +4786,9 @@ def flatten_consent_composition(bundle_json): if current_response.answer[0].valueBoolean: answer = [ - item - for item in questionnaire.item - if item.linkId == current_response.linkId + item for item in questionnaire.item if item.linkId == current_response.linkId ][0] - assent_exceptions.append( - FHIR._exception_description(answer.text) - ) + assent_exceptions.append(FHIR._exception_description(answer.text)) # The default is a standard signature Questionnaire. Used for # ASD-I, NEER, and Example studies @@ -5520,9 +4799,7 @@ def flatten_consent_composition(bundle_json): consent_object["signer_signature"] = base64.b64decode( contract.signer[0].signature[0].blob ).decode() - consent_object["participant_name"] = ( - contract.signer[0].signature[0].whoReference.display - ) + consent_object["participant_name"] = contract.signer[0].signature[0].whoReference.display # These don't apply on an Individual consent. consent_object["participant_acknowledgement_reason"] = "N/A" @@ -5535,9 +4812,7 @@ def flatten_consent_composition(bundle_json): # Prepare to parse the questionnaire. questionnaire_object = { - "template": "dashboard/{}.html".format( - questionnaire.id - ), # TODO: Remove this after PPM-603 + "template": "dashboard/{}.html".format(questionnaire.id), # TODO: Remove this after PPM-603 "questionnaire": questionnaire.id, "questions": [], } @@ -5558,9 +4833,7 @@ def flatten_consent_composition(bundle_json): # Process the question, answer and response. if item.type == "boolean": question_object["text"] = item.text - question_object["answer"] = response.answer[ - 0 - ].valueBoolean + question_object["answer"] = response.answer[0].valueBoolean elif item.type == "question": question_object["yes"] = item.text @@ -5569,31 +4842,19 @@ def flatten_consent_composition(bundle_json): "to my child or individual in my care " "who will be participating" ) - question_object["answer"] = ( - response.answer[0].valueString.lower() - == "yes" - ) + question_object["answer"] = response.answer[0].valueString.lower() == "yes" # Add it. questionnaire_object["questions"].append(question_object) # Check the type. - if ( - q_response.questionnaire.reference - == "Questionnaire/guardian-signature-part-3" - ): - consent_object["assent_questionnaires"].append( - questionnaire_object - ) + if q_response.questionnaire.reference == "Questionnaire/guardian-signature-part-3": + consent_object["assent_questionnaires"].append(questionnaire_object) else: - consent_object["consent_questionnaires"].append( - questionnaire_object - ) + consent_object["consent_questionnaires"].append(questionnaire_object) # Link back to participant - consent_object["ppm_id"] = FHIR._get_referenced_id( - q_response.as_json(), "Patient" - ) + consent_object["ppm_id"] = FHIR._get_referenced_id(q_response.as_json(), "Patient") consent_object["exceptions"] = consent_exceptions consent_object["assent_exceptions"] = assent_exceptions @@ -5605,14 +4866,10 @@ def _exception_description(display): # Check the various exception display values if "equipment monitoring" in display.lower() or "fitbit" in display.lower(): - return mark_safe( - 'Fitbit monitoring' - ) + return mark_safe('Fitbit monitoring') elif "referral to clinical trial" in display.lower(): - return mark_safe( - 'Future contact/questionnaires' - ) + return mark_safe('Future contact/questionnaires') elif "saliva" in display.lower(): return mark_safe('Saliva sample') @@ -5624,9 +4881,7 @@ def _exception_description(display): return mark_safe('Stool sample') elif "tumor" in display.lower(): - return mark_safe( - 'Tumor tissue samples' - ) + return mark_safe('Tumor tissue samples') else: logger.warning("Could not format exception: {}".format(display)) @@ -5645,15 +4900,11 @@ def flatten_list(bundle, resource_type): return None # Get the references - references = [ - entry.item.reference for entry in resource.entry if entry.item.reference - ] + references = [entry.item.reference for entry in resource.entry if entry.item.reference] # Find it in the bundle resources = [ - entry.resource - for entry in bundle.entry - if "{}/{}".format(resource_type, entry.resource.id) in references + entry.resource for entry in bundle.entry if "{}/{}".format(resource_type, entry.resource.id) in references ] # Flatten them according to type @@ -5672,10 +4923,7 @@ def flatten_list(bundle, resource_type): @staticmethod def flatten_document_references(bundle): - return [ - FHIR.flatten_document_reference(r) - for r in FHIR._find_resources(bundle, "DocumentReference") - ] + return [FHIR.flatten_document_reference(r) for r in FHIR._find_resources(bundle, "DocumentReference")] @staticmethod def flatten_document_reference(resource): @@ -5696,9 +4944,7 @@ def flatten_document_reference(resource): reference["display"] = FHIR._get_or(resource, ["type", "coding", 0, "display"]) # Get data properties - reference["title"] = FHIR._get_or( - resource, ["content", 0, "attachment", "title"] - ) + reference["title"] = FHIR._get_or(resource, ["content", 0, "attachment", "title"]) reference["size"] = FHIR._get_or(resource, ["content", 0, "attachment", "size"]) reference["hash"] = FHIR._get_or(resource, ["content", 0, "attachment", "hash"]) reference["url"] = FHIR._get_or(resource, ["content", 0, "attachment", "url"]) @@ -5712,9 +4958,7 @@ def flatten_document_reference(resource): # Get person reference["patient"] = FHIR._get_or(resource, ["subject", "reference"]) if reference.get("patient"): - reference["ppm_id"] = reference["fhir_id"] = FHIR._get_referenced_id( - resource, "Patient" - ) + reference["ppm_id"] = reference["fhir_id"] = FHIR._get_referenced_id(resource, "Patient") # Check for data reference["data"] = FHIR._get_or(resource, ["content", 0, "attachment", "data"]) @@ -5758,14 +5002,7 @@ def questionnaire_answers(bundle_dict, questionnaire_id): bundle = Bundle(bundle_dict) # Pick out the questionnaire and its response - questionnaire = next( - ( - entry.resource - for entry in bundle.entry - if entry.resource.id == questionnaire_id - ), - None, - ) + questionnaire = next((entry.resource for entry in bundle.entry if entry.resource.id == questionnaire_id), None,) # Ensure resources exist if not questionnaire: @@ -5805,20 +5042,13 @@ def enrollment_flag(patient_ref, status="proposed", start=None, end=None): "meta": {"lastUpdated": datetime.now().isoformat()}, "status": "active" if status == "accepted" else "inactive", "category": { - "coding": [ - { - "system": "http://hl7.org/fhir/flag-category", - "code": "admin", - "display": "Admin", - } - ], + "coding": [{"system": "http://hl7.org/fhir/flag-category", "code": "admin", "display": "Admin",}], "text": "Admin", }, "code": { "coding": [ { - "system": "https://peoplepoweredmedicine.org/" - "enrollment-status", + "system": "https://peoplepoweredmedicine.org/" "enrollment-status", "code": status, "display": status.title(), } @@ -5863,12 +5093,7 @@ def ppm_research_study(project, title): data = { "resourceType": "ResearchStudy", "id": project, - "identifier": [ - { - "system": FHIR.research_study_identifier_system, - "value": f"ppm-{project}", - } - ], + "identifier": [{"system": FHIR.research_study_identifier_system, "value": f"ppm-{project}",}], "status": "in-progress", "title": "People-Powered Medicine - {}".format(title), } @@ -5883,16 +5108,11 @@ def ppm_research_study(project, title): return data @staticmethod - def ppm_research_subject( - project, patient_ref, status="candidate", consent=None - ): + def ppm_research_subject(project, patient_ref, status="candidate", consent=None): data = { "resourceType": "ResearchSubject", - "identifier": { - "system": FHIR.research_subject_identifier_system, - "value": "ppm-{}".format(project), - }, + "identifier": {"system": FHIR.research_subject_identifier_system, "value": "ppm-{}".format(project),}, "period": {"start": datetime.now().isoformat()}, "status": status, "study": {"reference": "ResearchStudy/ppm-{}".format(project)}, @@ -5907,12 +5127,7 @@ def ppm_research_subject( @staticmethod def ppm_device( - item, - patient_ref, - identifier=None, - shipped=None, - returned=None, - status="active", + item, patient_ref, identifier=None, shipped=None, returned=None, status="active", ): data = { @@ -5943,9 +5158,7 @@ def ppm_device( # Add identifier if identifier: - data["identifier"] = [ - {"system": FHIR.device_identifier_system, "value": identifier} - ] + data["identifier"] = [{"system": FHIR.device_identifier_system, "value": identifier}] # Check dates if shipped: @@ -5958,18 +5171,12 @@ def ppm_device( @staticmethod def communication( - patient_ref, - identifier, - content=None, - status="completed", - sent=datetime.now().isoformat(), + patient_ref, identifier, content=None, status="completed", sent=datetime.now().isoformat(), ): data = { "resourceType": "Communication", - "identifier": [ - {"system": FHIR.ppm_comm_identifier_system, "value": identifier,} - ], + "identifier": [{"system": FHIR.ppm_comm_identifier_system, "value": identifier,}], "sent": sent, "recipient": [{"reference": patient_ref}], "status": status, @@ -5988,54 +5195,29 @@ def patient(form): patient_data = { "resourceType": "Patient", "active": True, - "identifier": [ - { - "system": FHIR.patient_email_identifier_system, - "value": form.get("email"), - }, - ], - "name": [ - { - "use": "official", - "family": form.get("lastname"), - "given": [form.get("firstname")], - }, - ], + "identifier": [{"system": FHIR.patient_email_identifier_system, "value": form.get("email"),},], + "name": [{"use": "official", "family": form.get("lastname"), "given": [form.get("firstname")],},], "address": [ { - "line": [ - form.get("street_address1"), - form.get("street_address2"), - ], + "line": [form.get("street_address1"), form.get("street_address2"),], "city": form.get("city"), "postalCode": form.get("zip"), "state": form.get("state"), } ], - "telecom": [ - { - "system": FHIR.patient_phone_telecom_system, - "value": form.get("phone"), - }, - ], + "telecom": [{"system": FHIR.patient_phone_telecom_system, "value": form.get("phone"),},], } if form.get("contact_email"): logger.debug("Adding contact email") patient_data["telecom"].append( - { - "system": FHIR.patient_email_telecom_system, - "value": form.get("contact_email"), - } + {"system": FHIR.patient_email_telecom_system, "value": form.get("contact_email"),} ) if form.get("how_did_you_hear_about_us"): logger.debug('Adding "How did you hear about is"') patient_data["extension"] = [ - { - "url": FHIR.referral_extension_url, - "valueString": form.get("how_did_you_hear_about_us"), - } + {"url": FHIR.referral_extension_url, "valueString": form.get("how_did_you_hear_about_us"),} ] # Convert the twitter handle to a URL diff --git a/ppmutils/p2md.py b/ppmutils/p2md.py index 1487a8a..c357e2d 100644 --- a/ppmutils/p2md.py +++ b/ppmutils/p2md.py @@ -18,9 +18,7 @@ class P2MD(PPM.Service): # Set identifier systems p2md_identifier_system = "https://peoplepoweredmedicine.org/fhir/p2md/operation" - fileservice_identifier_system = ( - "https://peoplepoweredmedicine.org/fhir/fileservice/file" - ) + fileservice_identifier_system = "https://peoplepoweredmedicine.org/fhir/fileservice/file" class ExportProviders(Enum): Participant = "ppm-participant" @@ -43,9 +41,7 @@ def default_url_for_env(cls, environment): elif "prod" in environment: return "https://p2m2.dbmi.hms.harvard.edu" else: - logger.error( - f"Could not return a default URL for environment: {environment}" - ) + logger.error(f"Could not return a default URL for environment: {environment}") return None @@ -151,9 +147,7 @@ def get_smart_authorizations(cls, request, ppm_id): auths = next(p["authorizations"] for p in data if p["ppm_id"] == ppm_id) # Get list of SMART providers and filter the user's auths list - smart_providers = [ - p["provider"] for p in P2MD.get_smart_endpoints(request)["smart_endpoints"] - ] + smart_providers = [p["provider"] for p in P2MD.get_smart_endpoints(request)["smart_endpoints"]] return [auth for auth in auths if auth in smart_providers] @@ -170,9 +164,7 @@ def get_twitter_data(cls, request, ppm_id, handle): """ Make a request to P2MD to fetch Twitter data and store it in PPM. """ - response = cls.post( - request, f"/sources/api/twitter/{ppm_id}", {"handle": handle}, raw=True - ) + response = cls.post(request, f"/sources/api/twitter/{ppm_id}", {"handle": handle}, raw=True) # Return True if no errors return response.ok @@ -222,12 +214,7 @@ def get_gencove_data(cls, request, ppm_id, gencove_id): """ Make a request to P2MD to fetch Gencove data and store it in PPM. """ - response = cls.post( - request, - f"/sources/api/gencove/{ppm_id}", - data={"gencove_id": gencove_id}, - raw=True, - ) + response = cls.post(request, f"/sources/api/gencove/{ppm_id}", data={"gencove_id": gencove_id}, raw=True,) # Return True if no errors return response.ok @@ -252,9 +239,7 @@ def get_facebook_data(cls, request, ppm_id): """ Make a request to P2MD to fetch Facebook data and store it in PPM. """ - response = cls.post( - request, f"/sources/api/facebook/{ppm_id}", data={}, raw=True - ) + response = cls.post(request, f"/sources/api/facebook/{ppm_id}", data={}, raw=True) # Return True if no errors return response.ok @@ -268,9 +253,7 @@ def download_facebook_data(cls, request, ppm_id): :return: The requested dataset """ # Make the request - response = cls.get( - request, f"/sources/api/facebook/{ppm_id}/download", raw=True - ) + response = cls.get(request, f"/sources/api/facebook/{ppm_id}/download", raw=True) if response: return response.content @@ -281,9 +264,7 @@ def get_smart_data(cls, request, ppm_id, provider): """ Make a request to P2MD to fetch SMART on FHIR EHR data and store it in PPM. """ - response = cls.post( - request, f"/sources/api/smart/{provider}/{ppm_id}", data={}, raw=True - ) + response = cls.post(request, f"/sources/api/smart/{provider}/{ppm_id}", data={}, raw=True) # Return True if no errors return response.ok @@ -298,9 +279,7 @@ def download_smart_data(cls, request, ppm_id, provider): :return: The requested entire dataset """ # Make the request - response = cls.get( - request, f"/sources/api/smart/{provider}/{ppm_id}/download", raw=True - ) + response = cls.get(request, f"/sources/api/smart/{provider}/{ppm_id}/download", raw=True) if response: return response.content @@ -422,11 +401,7 @@ def check_qualtrics_survey(cls, request, study, ppm_id, survey_id): :return: bool """ # Make the request - response = cls.head( - request, - f"/sources/api/qualtrics/survey/{study}/{ppm_id}/{survey_id}/", - raw=True, - ) + response = cls.head(request, f"/sources/api/qualtrics/survey/{study}/{ppm_id}/{survey_id}/", raw=True,) if response: return response.ok @@ -438,9 +413,7 @@ def get_qualtrics_survey_url(cls, study, ppm_id, survey_id): Return the URL to send the participant to for taking the survey """ # Return True if no errors - url = cls._build_url( - path=f"/sources/api/qualtrics/survey/{study}/{ppm_id}/{survey_id}/" - ) + url = cls._build_url(path=f"/sources/api/qualtrics/survey/{study}/{ppm_id}/{survey_id}/") return url @@ -450,25 +423,17 @@ def get_qualtrics_survey_data_url(cls, study, ppm_id, survey_id): Return the URL to manage survey data """ # Return True if no errors - url = cls._build_url( - path=f"/sources/api/qualtrics/{study}/{ppm_id}/{survey_id}/" - ) + url = cls._build_url(path=f"/sources/api/qualtrics/{study}/{ppm_id}/{survey_id}/") return url @classmethod - def get_qualtrics_survey_data( - cls, request, study, ppm_id, survey_id, response_id=None, older_than=None - ): + def get_qualtrics_survey_data(cls, request, study, ppm_id, survey_id, response_id=None, older_than=None): """ Make a call to P2MD to look for a survey response """ # Return True if no errors - url = furl( - cls.get_qualtrics_survey_data_url( - study=study, ppm_id=ppm_id, survey_id=survey_id - ) - ) + url = furl(cls.get_qualtrics_survey_data_url(study=study, ppm_id=ppm_id, survey_id=survey_id)) data = {} if response_id: @@ -495,14 +460,7 @@ def get_file_proxy_url(cls, ppm_id, uuid): @classmethod def uploaded_file( - cls, - request, - study, - ppm_id, - document_type, - uuid, - location, - content_type="application/octect-stream", + cls, request, study, ppm_id, document_type, uuid, location, content_type="application/octect-stream", ): """ Make a request to P2MD to create a file upload @@ -550,11 +508,7 @@ def get_smart_endpoint_urls(cls, request, ppm_id, return_url): organization = endpoint.get("organization") provider = endpoint.get("provider") if not organization or not provider: - logger.error( - "Missing properties for SMART endpoint: {} - {}".format( - organization, provider - ) - ) + logger.error("Missing properties for SMART endpoint: {} - {}".format(organization, provider)) continue # Build the URL @@ -616,16 +570,10 @@ def download_participant_data(cls, request, ppm_id, filename=None, providers=Non :return: The user's entire dataset """ # Build the URL - url = furl( - P2MD.get_participant_data_url( - ppm_id=ppm_id, filename=filename, providers=providers - ) - ) + url = furl(P2MD.get_participant_data_url(ppm_id=ppm_id, filename=filename, providers=providers)) # Make the request - response = cls.get( - request=request, path=url.pathstr, data=url.querystr, raw=True - ) + response = cls.get(request=request, path=url.pathstr, data=url.querystr, raw=True) if response: return response.content @@ -655,13 +603,8 @@ def get_data_document_references(cls, ppm_id, provider=None): :rtype: list """ # Gather data-related DocumentReferences - document_references = FHIR.query_data_document_references( - patient=ppm_id, provider=provider - ) - logger.debug( - f"{ppm_id}: Found {len(document_references)} DocumentReferences " - f"for: {provider}" - ) + document_references = FHIR.query_data_document_references(patient=ppm_id, provider=provider) + logger.debug(f"{ppm_id}: Found {len(document_references)} DocumentReferences " f"for: {provider}") # Flatten resources and pick out relevant identifiers flats = [] @@ -702,9 +645,7 @@ def get_data_document_references_for_providers(cls, ppm_id, providers=None): """ # Get all flattened data document references document_references = [] - for document_reference in P2MD.get_data_document_references( - ppm_id, provider=None - ): + for document_reference in P2MD.get_data_document_references(ppm_id, provider=None): # Check type and filter out non-requested provider documents if not providers or document_reference["type"] not in providers: @@ -712,10 +653,7 @@ def get_data_document_references_for_providers(cls, ppm_id, providers=None): document_references.append(document_reference) - logger.debug( - f"{ppm_id}: Found {len(document_references)} DocumentReferences " - f'for: {", ".join(providers)}' - ) + logger.debug(f"{ppm_id}: Found {len(document_references)} DocumentReferences " f'for: {", ".join(providers)}') return document_references # @@ -723,9 +661,7 @@ def get_data_document_references_for_providers(cls, ppm_id, providers=None): # @classmethod - def check_export( - cls, request, ppm_id, provider=ExportProviders.Participant, age=24 - ): + def check_export(cls, request, ppm_id, provider=ExportProviders.Participant, age=24): """ Checks the presence of the PPM dataset for the passed user :param request: The original Django request object @@ -736,12 +672,7 @@ def check_export( :return: The age of the current dataset in hours, if any """ # Make the request - response = cls.head( - request, - f"/sources/api/ppm/{provider.value}/{ppm_id}/export", - {"age": age}, - raw=True, - ) + response = cls.head(request, f"/sources/api/ppm/{provider.value}/{ppm_id}/export", {"age": age}, raw=True,) if response: return response.ok @@ -770,9 +701,7 @@ def download_export(cls, request, ppm_id, provider=ExportProviders.Participant): :return: The user's entire dataset """ # Make the request - response = cls.get( - request, f"/sources/api/ppm/{provider.value}/{ppm_id}/export", raw=True - ) + response = cls.get(request, f"/sources/api/ppm/{provider.value}/{ppm_id}/export", raw=True) if response: return response.content @@ -788,18 +717,14 @@ def download_data(cls, request, ppm_id, provider=ExportProviders.Participant): :return: The user's entire dataset """ # Make the request - response = cls.get( - request, f"/sources/api/ppm/{provider.value}/{ppm_id}/download", raw=True - ) + response = cls.get(request, f"/sources/api/ppm/{provider.value}/{ppm_id}/download", raw=True) if response: return response.content return None @classmethod - def download_data_notify( - cls, request, ppm_id, recipients, provider=ExportProviders.Participant - ): + def download_data_notify(cls, request, ppm_id, recipients, provider=ExportProviders.Participant): """ Downloads the PPM dataset for the passed user :param request: The original Django request object diff --git a/ppmutils/ppm.py b/ppmutils/ppm.py index 8a992fd..0ca4512 100644 --- a/ppmutils/ppm.py +++ b/ppmutils/ppm.py @@ -28,10 +28,7 @@ def enum(cls, enum): return item # Compare titles - if ( - item.value in dict(cls.choices()) - and dict(cls.choices())[item.value] == enum - ): + if item.value in dict(cls.choices()) and dict(cls.choices())[item.value] == enum: return item raise ValueError('Value "{}" is not a valid {}'.format(enum, cls.__name__)) @@ -71,11 +68,7 @@ def title(cls, enum): value = cls.get(enum).value # Try choices - return ( - dict(cls.choices())[value] - if value in dict(cls.choices()) - else cls.get(enum).name - ) + return dict(cls.choices())[value] if value in dict(cls.choices()) else cls.get(enum).name @classmethod def choices(cls): @@ -125,15 +118,9 @@ def is_tester(email): :param email: The user's email address :return: bool """ - if ( - hasattr(settings, "TEST_EMAIL_PATTERNS") - and type(getattr(settings, "TEST_EMAIL_PATTERNS")) is str - ): + if hasattr(settings, "TEST_EMAIL_PATTERNS") and type(getattr(settings, "TEST_EMAIL_PATTERNS")) is str: testers = settings.TEST_EMAIL_PATTERNS.split(",") - elif ( - hasattr(settings, "TEST_EMAIL_PATTERNS") - and type(getattr(settings, "TEST_EMAIL_PATTERNS")) is list - ): + elif hasattr(settings, "TEST_EMAIL_PATTERNS") and type(getattr(settings, "TEST_EMAIL_PATTERNS")) is list: testers = settings.TEST_EMAIL_PATTERNS else: return False @@ -204,9 +191,7 @@ def enum(cls, enum): # Check edge case if enum == "ppm-asd" or enum == "asd": # An edge case from change in study naming - logger.warning( - 'PPM.Study deprecated study identifier used: "{}"'.format(enum) - ) + logger.warning('PPM.Study deprecated study identifier used: "{}"'.format(enum)) return PPM.Study.ASD raise ValueError('Value "{}" is not a valid {}'.format(enum, cls.__name__)) @@ -273,12 +258,7 @@ def dashboard(cls, study, environment): steps = None if _study is PPM.Study.ASD: steps = [ - { - "step": "email-confirm", - "blocking": True, - "required": True, - "enabled": True, - }, + {"step": "email-confirm", "blocking": True, "required": True, "enabled": True,}, { "step": "registration", "blocking": True, @@ -302,54 +282,27 @@ def dashboard(cls, study, environment): "post_enrollment": PPM.Enrollment.Proposed.value, "enabled": True, }, - { - "step": "approval", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "questionnaire", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "twitter", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "fitbit", - "blocking": False, - "required": False, - "enabled": True, - }, + {"step": "approval", "blocking": True, "required": True, "enabled": True,}, + {"step": "questionnaire", "blocking": True, "required": True, "enabled": True,}, + {"step": "twitter", "blocking": False, "required": False, "enabled": True,}, + {"step": "fitbit", "blocking": False, "required": False, "enabled": True,}, { "step": "facebook", "blocking": False, "required": False, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, { "step": "ehr", "blocking": False, "required": False, "multiple": True, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, ] elif _study is PPM.Study.EXAMPLE: steps = [ - { - "step": "email-confirm", - "blocking": True, - "required": True, - "enabled": True, - }, + {"step": "email-confirm", "blocking": True, "required": True, "enabled": True,}, { "step": "registration", "blocking": True, @@ -372,66 +325,29 @@ def dashboard(cls, study, environment): "post_enrollment": PPM.Enrollment.Proposed.value, "enabled": True, }, - { - "step": "approval", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "poc", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "research-studies", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "twitter", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "fitbit", - "blocking": False, - "required": False, - "enabled": True, - }, + {"step": "approval", "blocking": True, "required": True, "enabled": True,}, + {"step": "poc", "blocking": True, "required": True, "enabled": True,}, + {"step": "research-studies", "blocking": False, "required": False, "enabled": True,}, + {"step": "twitter", "blocking": False, "required": False, "enabled": True,}, + {"step": "fitbit", "blocking": False, "required": False, "enabled": True,}, { "step": "facebook", "blocking": False, "required": False, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, { "step": "ehr", "blocking": False, "required": False, "multiple": True, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, - }, - { - "step": "picnichealth", - "blocking": False, - "required": True, - "enabled": True, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, + {"step": "picnichealth", "blocking": False, "required": True, "enabled": True,}, ] elif _study is PPM.Study.NEER: steps = [ - { - "step": "email-confirm", - "blocking": True, - "required": True, - "enabled": True, - }, + {"step": "email-confirm", "blocking": True, "required": True, "enabled": True,}, { "step": "registration", "blocking": True, @@ -455,66 +371,29 @@ def dashboard(cls, study, environment): "post_enrollment": PPM.Enrollment.Proposed.value, "enabled": True, }, - { - "step": "approval", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "poc", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "research-studies", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "twitter", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "fitbit", - "blocking": False, - "required": False, - "enabled": True, - }, + {"step": "approval", "blocking": True, "required": True, "enabled": True,}, + {"step": "poc", "blocking": True, "required": True, "enabled": True,}, + {"step": "research-studies", "blocking": False, "required": False, "enabled": True,}, + {"step": "twitter", "blocking": False, "required": False, "enabled": True,}, + {"step": "fitbit", "blocking": False, "required": False, "enabled": True,}, { "step": "facebook", "blocking": False, "required": False, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, { "step": "ehr", "blocking": False, "required": False, "multiple": True, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, - }, - { - "step": "picnichealth", - "blocking": False, - "required": True, - "enabled": True, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, + {"step": "picnichealth", "blocking": False, "required": True, "enabled": True,}, ] elif _study is PPM.Study.RANT: steps = [ - { - "step": "email-confirm", - "blocking": True, - "required": True, - "enabled": True, - }, + {"step": "email-confirm", "blocking": True, "required": True, "enabled": True,}, { "step": "registration", "blocking": True, @@ -538,57 +417,25 @@ def dashboard(cls, study, environment): "post_enrollment": PPM.Enrollment.Proposed.value, "enabled": True, }, - { - "step": "approval", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "poc", - "blocking": True, - "required": True, - "enabled": True, - }, - { - "step": "research-studies", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "twitter", - "blocking": False, - "required": False, - "enabled": True, - }, - { - "step": "fitbit", - "blocking": False, - "required": False, - "enabled": True, - }, + {"step": "approval", "blocking": True, "required": True, "enabled": True,}, + {"step": "poc", "blocking": True, "required": True, "enabled": True,}, + {"step": "research-studies", "blocking": False, "required": False, "enabled": True,}, + {"step": "twitter", "blocking": False, "required": False, "enabled": True,}, + {"step": "fitbit", "blocking": False, "required": False, "enabled": True,}, { "step": "facebook", "blocking": False, "required": False, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, { "step": "ehr", "blocking": False, "required": False, "multiple": True, - "enabled": PPM.Environment.get(environment) - is not PPM.Environment.Prod, - }, - { - "step": "picnichealth", - "blocking": False, - "required": True, - "enabled": True, + "enabled": PPM.Environment.get(environment) is not PPM.Environment.Prod, }, + {"step": "picnichealth", "blocking": False, "required": True, "enabled": True,}, ] return steps @@ -603,11 +450,7 @@ def is_dashboard_step_enabled(step, study, environment): :param environment: The current PPM environment :return: bool """ - step_dict = next( - s - for s in PPM.Study.dashboard(study, environment) - if s["step"] == step.lower() - ) + step_dict = next(s for s in PPM.Study.dashboard(study, environment) if s["step"] == step.lower()) # Check if enabled if step_dict.get("enabled"): @@ -661,12 +504,7 @@ def __lt__(self, other): def enum(cls, enum): """Accepts any form of an enum and returns the enum""" for item in cls: - if ( - enum is item - or enum == item.name - or enum == item.value - or enum == cls.title(item) - ): + if enum is item or enum == item.name or enum == item.value or enum == cls.title(item): return item raise ValueError('Value "{}" is not a valid {}'.format(enum, cls.__name__)) @@ -691,11 +529,7 @@ def choices(cls): @classmethod def active_choices(cls): - return ( - choice - for choice in PPM.Enrollment.choices() - if PPM.Enrollment.is_active(choice[0]) - ) + return (choice for choice in PPM.Enrollment.choices() if PPM.Enrollment.is_active(choice[0])) @classmethod def title(cls, enrollment): @@ -749,12 +583,7 @@ class Communication(PPMEnum): def enum(cls, enum): """Accepts any form of an enum and returns the enum""" for item in cls: - if ( - enum is item - or enum == item.name - or enum == item.value - or enum == cls.title(item) - ): + if enum is item or enum == item.name or enum == item.value or enum == cls.title(item): return item raise ValueError('Value "{}" is not a valid {}'.format(enum, cls.__name__)) @@ -771,18 +600,13 @@ def choices(cls): (PPM.Communication.ParticipantPending.value, "Participant Pending"), (PPM.Communication.ParticipantIneligible.value, "Participant Queued"), (PPM.Communication.ParticipantAccepted.value, "Participant Accepted"), - ( - PPM.Communication.PicnicHealthRegistration.value, - "PicnicHealth Registration", - ), + (PPM.Communication.PicnicHealthRegistration.value, "PicnicHealth Registration",), ) @classmethod def title(cls, communication): """Returns the value to be used as the communication's title""" - return dict(PPM.Communication.choices())[ - PPM.Communication.get(communication).value - ] + return dict(PPM.Communication.choices())[PPM.Communication.get(communication).value] class Questionnaire(PPMEnum): @@ -930,30 +754,21 @@ def exceptions(questionnaire_id): "question-5": "702475000", } - elif ( - questionnaire_id - == PPM.Questionnaire.ASDConsentIndividualSignatureQuestionnaire.value - ): + elif questionnaire_id == PPM.Questionnaire.ASDConsentIndividualSignatureQuestionnaire.value: return { "question-1": "225098009", "question-2": "284036006", "question-3": "702475000", } - elif ( - questionnaire_id - == PPM.Questionnaire.ASDGuardianConsentQuestionnaire.value - ): + elif questionnaire_id == PPM.Questionnaire.ASDGuardianConsentQuestionnaire.value: return { "question-1": "225098009", "question-2": "284036006", "question-3": "702475000", } - elif ( - questionnaire_id - == PPM.Questionnaire.ASDConsentIndividualSignatureQuestionnaire.value - ): + elif questionnaire_id == PPM.Questionnaire.ASDConsentIndividualSignatureQuestionnaire.value: return { "question-1": "225098009", "question-2": "284036006", @@ -987,12 +802,7 @@ class Provider(Enum): def enum(cls, enum): """Accepts any form of an enum and returns the enum""" for item in cls: - if ( - enum is item - or enum == item.name - or enum == item.value - or enum == cls.title(item) - ): + if enum is item or enum == item.name or enum == item.value or enum == cls.title(item): return item raise ValueError('Value "{}" is not a valid {}'.format(enum, cls.__name__)) @@ -1033,12 +843,7 @@ class TrackedItem(Enum): def enum(cls, enum): """Accepts any form of an enum and returns the enum""" for item in cls: - if ( - enum is item - or enum == item.name - or enum == item.value - or enum == cls.title(item) - ): + if enum is item or enum == item.name or enum == item.value or enum == cls.title(item): return item raise ValueError('Value "{}" is not a valid {}'.format(enum, cls.__name__)) @@ -1066,9 +871,7 @@ def title(cls, tracked_item): :return: The item's title :rtype: str """ - return dict(PPM.TrackedItem.choices())[ - PPM.TrackedItem.get(tracked_item).value - ] + return dict(PPM.TrackedItem.choices())[PPM.TrackedItem.get(tracked_item).value] @staticmethod def devices(study=None): @@ -1089,10 +892,7 @@ def devices(study=None): PPM.TrackedItem.uBiomeFecalSampleKit.value, PPM.TrackedItem.BloodSampleKit.value, ], - PPM.Study.ASD.value: [ - PPM.TrackedItem.Fitbit.value, - PPM.TrackedItem.SalivaSampleKit.value, - ], + PPM.Study.ASD.value: [PPM.TrackedItem.Fitbit.value, PPM.TrackedItem.SalivaSampleKit.value,], PPM.Study.EXAMPLE.value: [ PPM.TrackedItem.Fitbit.value, PPM.TrackedItem.uBiomeFecalSampleKit.value, @@ -1131,11 +931,7 @@ def _build_url(cls, path): # Log the filter if len(segments) < len(url.path.segments): - logger.debug( - "Path filtered: /{} -> /{}".format( - "/".join(url.path.segments), "/".join(segments) - ) - ) + logger.debug("Path filtered: /{} -> /{}".format("/".join(url.path.segments), "/".join(segments))) # Set it url.path.segments = segments @@ -1149,9 +945,7 @@ def service_url(cls): names = ["###_URL", "DBMI_###_URL", "###_API_URL", "###_BASE_URL"] for name in names: if hasattr(settings, name.replace("###", cls.service.upper())): - service_url = getattr( - settings, name.replace("###", cls.service.upper()) - ) + service_url = getattr(settings, name.replace("###", cls.service.upper())) # We want only the domain and no paths, as those should be # specified in the calls so strip any included paths and queries @@ -1167,9 +961,7 @@ def service_url(cls): if environment and cls.default_url_for_env(environment): return cls.default_url_for_env(environment) - raise ValueError( - "Service URL not defined in settings".format(cls.service.upper()) - ) + raise ValueError("Service URL not defined in settings".format(cls.service.upper())) @classmethod def default_url_for_env(cls, environment): @@ -1179,10 +971,7 @@ def default_url_for_env(cls, environment): :param environment: The DBMI_ENV string :return: A URL, if any """ - logger.warning( - f"Class PPM does not return a default URL for " - f"environment: {environment}" - ) + logger.warning(f"Class PPM does not return a default URL for " f"environment: {environment}") return None @classmethod @@ -1198,9 +987,7 @@ def headers(cls, request=None, content_type="application/json"): # Use JWT return { - "Authorization": "{} {}".format( - cls.jwt_authorization_prefix, cls.get_jwt(request) - ), + "Authorization": "{} {}".format(cls.jwt_authorization_prefix, cls.get_jwt(request)), "Content-Type": content_type, } @@ -1211,9 +998,7 @@ def headers(cls, request=None, content_type="application/json"): # Check for specified prefix prefix = getattr( - settings, - "{}_AUTH_PREFIX".format(cls.service.upper()), - cls.token_authorization_prefix, + settings, "{}_AUTH_PREFIX".format(cls.service.upper()), cls.token_authorization_prefix, ) # Use token @@ -1238,14 +1023,11 @@ def get_jwt(cls, request): elif ( hasattr(request, "META") and request.META.get("HTTP_AUTHORIZATION") - and cls.jwt_authorization_prefix - in request.META.get("HTTP_AUTHORIZATION") + and cls.jwt_authorization_prefix in request.META.get("HTTP_AUTHORIZATION") ): # Remove prefix and return the token - return request.META.get("HTTP_AUTHORIZATION").replace( - "{} ".format(cls.jwt_authorization_prefix), "" - ) + return request.META.get("HTTP_AUTHORIZATION").replace("{} ".format(cls.jwt_authorization_prefix), "") return None @@ -1268,9 +1050,7 @@ def head(cls, request=None, path="/", data=None, raw=False): try: # Prepare the request. - response = requests.head( - cls._build_url(path), headers=cls.headers(request), params=data - ) + response = requests.head(cls._build_url(path), headers=cls.headers(request), params=data) # Check response type if raw: @@ -1280,9 +1060,7 @@ def head(cls, request=None, path="/", data=None, raw=False): except Exception as e: logger.exception( - "{} error: {}".format(cls.service, e), - exc_info=True, - extra={"data": data, "path": path,}, + "{} error: {}".format(cls.service, e), exc_info=True, extra={"data": data, "path": path,}, ) return None @@ -1306,9 +1084,7 @@ def get(cls, request=None, path="/", data=None, raw=False): try: # Prepare the request. - response = requests.get( - cls._build_url(path), headers=cls.headers(request), params=data - ) + response = requests.get(cls._build_url(path), headers=cls.headers(request), params=data) # Check response type if raw: @@ -1318,9 +1094,7 @@ def get(cls, request=None, path="/", data=None, raw=False): except Exception as e: logger.exception( - "{} error: {}".format(cls.service, e), - exc_info=True, - extra={"data": data, "path": path,}, + "{} error: {}".format(cls.service, e), exc_info=True, extra={"data": data, "path": path,}, ) return None @@ -1344,11 +1118,7 @@ def post(cls, request=None, path="/", data=None, raw=False): try: # Prepare the request. - response = requests.post( - cls._build_url(path), - headers=cls.headers(request), - data=json.dumps(data), - ) + response = requests.post(cls._build_url(path), headers=cls.headers(request), data=json.dumps(data),) # Check response type if raw: @@ -1358,9 +1128,7 @@ def post(cls, request=None, path="/", data=None, raw=False): except Exception as e: logger.exception( - "{} error: {}".format(cls.service, e), - exc_info=True, - extra={"data": data, "path": path,}, + "{} error: {}".format(cls.service, e), exc_info=True, extra={"data": data, "path": path,}, ) return None @@ -1384,11 +1152,7 @@ def put(cls, request=None, path="/", data=None, raw=False): try: # Prepare the request. - response = requests.put( - cls._build_url(path), - headers=cls.headers(request), - data=json.dumps(data), - ) + response = requests.put(cls._build_url(path), headers=cls.headers(request), data=json.dumps(data),) # Check response type if raw: @@ -1398,9 +1162,7 @@ def put(cls, request=None, path="/", data=None, raw=False): except Exception as e: logger.exception( - "{} error: {}".format(cls.service, e), - exc_info=True, - extra={"data": data, "path": path,}, + "{} error: {}".format(cls.service, e), exc_info=True, extra={"data": data, "path": path,}, ) return None @@ -1424,11 +1186,7 @@ def patch(cls, request=None, path="/", data=None, raw=False): try: # Prepare the request. - response = requests.patch( - cls._build_url(path), - headers=cls.headers(request), - data=json.dumps(data), - ) + response = requests.patch(cls._build_url(path), headers=cls.headers(request), data=json.dumps(data),) # Check response type if raw: @@ -1438,9 +1196,7 @@ def patch(cls, request=None, path="/", data=None, raw=False): except Exception as e: logger.exception( - "{} error: {}".format(cls.service, e), - exc_info=True, - extra={"data": data, "path": path,}, + "{} error: {}".format(cls.service, e), exc_info=True, extra={"data": data, "path": path,}, ) return False @@ -1464,11 +1220,7 @@ def delete(cls, request=None, path="/", data=None, raw=False): try: # Prepare the request. - response = requests.delete( - cls._build_url(path), - headers=cls.headers(request), - data=json.dumps(data), - ) + response = requests.delete(cls._build_url(path), headers=cls.headers(request), data=json.dumps(data),) # Check response type if raw: @@ -1478,9 +1230,7 @@ def delete(cls, request=None, path="/", data=None, raw=False): except Exception as e: logger.exception( - "{} error: {}".format(cls.service, e), - exc_info=True, - extra={"path": path,}, + "{} error: {}".format(cls.service, e), exc_info=True, extra={"path": path,}, ) return False @@ -1532,12 +1282,7 @@ def request(cls, verb, request=None, path="/", data=None, check=True): logger.exception( "{} {} error: {}".format(cls.service, verb.upper(), e), exc_info=True, - extra={ - "path": path, - "verb": verb, - "data": data, - "response": response, - }, + extra={"path": path, "verb": verb, "data": data, "response": response,}, ) return False diff --git a/ppmutils/tests/test_fhir.py b/ppmutils/tests/test_fhir.py index 2246b6f..0155743 100644 --- a/ppmutils/tests/test_fhir.py +++ b/ppmutils/tests/test_fhir.py @@ -38,29 +38,18 @@ def test_query_patient(self): # Build a patient with a lastname patient = FHIRData.patient( - email, - firstname="User", - lastname="Patient", - street2="Unit 500", - contact_email="user@email.org", + email, firstname="User", lastname="Patient", street2="Unit 500", contact_email="user@email.org", ) study = FHIRData.research_study(PPM.Study.NEER) - subject = FHIRData.research_subject( - "Patient/{}".format(patient["id"]), PPM.Study.NEER - ) + subject = FHIRData.research_subject("Patient/{}".format(patient["id"]), PPM.Study.NEER) enrollment = FHIRData.enrollment_flag("Patient/{}".format(patient["id"])) # Put them in a bundle - bundle = FHIRData.create_bundle( - [patient, study, subject, enrollment], self.fhir_url - ) + bundle = FHIRData.create_bundle([patient, study, subject, enrollment], self.fhir_url) # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient.*"), - json=bundle, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient.*"), json=bundle, status=200, ) # Do the query @@ -78,11 +67,7 @@ def test_query_patient_id(self): # Build a patient with a lastname patient = FHIRData.patient( - email, - firstname="User", - lastname="Patient", - street2="Unit 500", - contact_email="user@email.org", + email, firstname="User", lastname="Patient", street2="Unit 500", contact_email="user@email.org", ) # Put them in a bundle @@ -90,10 +75,7 @@ def test_query_patient_id(self): # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient.*"), - json=bundle, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient.*"), json=bundle, status=200, ) # Do the query @@ -111,29 +93,18 @@ def test_query_participant(self): # Build a patient with a lastname patient = FHIRData.patient( - email, - firstname="User", - lastname="Patient", - street2="Unit 500", - contact_email="user@email.org", + email, firstname="User", lastname="Patient", street2="Unit 500", contact_email="user@email.org", ) study = FHIRData.research_study(PPM.Study.NEER) - subject = FHIRData.research_subject( - "Patient/{}".format(patient["id"]), PPM.Study.NEER - ) + subject = FHIRData.research_subject("Patient/{}".format(patient["id"]), PPM.Study.NEER) enrollment = FHIRData.enrollment_flag("Patient/{}".format(patient["id"])) # Put them in a bundle - bundle = FHIRData.create_bundle( - [patient, study, subject, enrollment], self.fhir_url - ) + bundle = FHIRData.create_bundle([patient, study, subject, enrollment], self.fhir_url) # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient.*"), - json=bundle, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient.*"), json=bundle, status=200, ) # Do the query @@ -153,31 +124,18 @@ def test_query_enrollment_status(self): # Build a patient with a lastname patient = FHIRData.patient( - email, - firstname="User", - lastname="Patient", - street2="Unit 500", - contact_email="user@email.org", + email, firstname="User", lastname="Patient", street2="Unit 500", contact_email="user@email.org", ) study = FHIRData.research_study(PPM.Study.NEER) - subject = FHIRData.research_subject( - "Patient/{}".format(patient["id"]), PPM.Study.NEER - ) - enrollment = FHIRData.enrollment_flag( - "Patient/{}".format(patient["id"]), PPM.Enrollment.Proposed.value - ) + subject = FHIRData.research_subject("Patient/{}".format(patient["id"]), PPM.Study.NEER) + enrollment = FHIRData.enrollment_flag("Patient/{}".format(patient["id"]), PPM.Enrollment.Proposed.value) # Put them in a bundle - bundle = FHIRData.create_bundle( - [patient, study, subject, enrollment], self.fhir_url - ) + bundle = FHIRData.create_bundle([patient, study, subject, enrollment], self.fhir_url) # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Flag.*"), - json=bundle, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Flag.*"), json=bundle, status=200, ) # Do the query @@ -195,10 +153,7 @@ def test_query_participants(self): # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient.*"), - json=data.bundle, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient.*"), json=data.bundle, status=200, ) # Do the query @@ -211,27 +166,20 @@ def test_query_participants(self): # Check some properties self.assertTrue(PPM.Study.enum(participants[2]["study"]) in PPM.Study) self.assertTrue(participants[1]["email"] is not None) - self.assertTrue( - PPM.Enrollment.enum(participants[2]["enrollment"]) in PPM.Enrollment - ) + self.assertTrue(PPM.Enrollment.enum(participants[2]["enrollment"]) in PPM.Enrollment) @responses.activate def test_patient_update_lastname(self): # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", firstname="User", lastname="Patient" - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient") # Set an example form form = {"firstname": "Newer", "lastname": None} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -261,19 +209,14 @@ def update_callback(request): def test_patient_update_add_address2(self): # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", firstname="User", lastname="Patient" - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient") # Set an example form form = {"street_address1": "3100 Some Address", "street_address2": "Unit 401"} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -303,22 +246,14 @@ def update_callback(request): def test_patient_update_remove_address2(self): # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", - firstname="User", - lastname="Patient", - street2="Unit 500", - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient", street2="Unit 500",) # Set an example form form = {"street_address1": "3100 Some Address", "street_address2": None} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -348,22 +283,14 @@ def update_callback(request): def test_patient_update_add_contact_email(self): # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", - firstname="User", - lastname="Patient", - street2="Unit 500", - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient", street2="Unit 500",) # Set an example form form = {"contact_email": "user@email.org"} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -387,16 +314,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure last name was removed - self.assertTrue( - next( - ( - telecom - for telecom in payload["telecom"] - if telecom["system"] == "email" - ), - False, - ) - ) + self.assertTrue(next((telecom for telecom in payload["telecom"] if telecom["system"] == "email"), False,)) @responses.activate def test_patient_update_contact_email(self): @@ -405,22 +323,14 @@ def test_patient_update_contact_email(self): contact_email = "somenewemail@email.com" # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", - firstname="User", - lastname="Patient", - street2="Unit 500", - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient", street2="Unit 500",) # Set an example form form = {"contact_email": contact_email} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -445,12 +355,7 @@ def update_callback(request): # Ensure last name was removed self.assertEqual( - next( - telecom - for telecom in payload["telecom"] - if telecom["system"] == "email" - )["value"], - contact_email, + next(telecom for telecom in payload["telecom"] if telecom["system"] == "email")["value"], contact_email, ) @responses.activate @@ -470,10 +375,7 @@ def test_patient_update_remove_contact_email(self): # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -497,37 +399,20 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure last name was removed - self.assertFalse( - next( - ( - telecom - for telecom in payload["telecom"] - if telecom["system"] == "email" - ), - False, - ) - ) + self.assertFalse(next((telecom for telecom in payload["telecom"] if telecom["system"] == "email"), False,)) @responses.activate def test_patient_update_add_referral(self): # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", - firstname="User", - lastname="Patient", - street2="Unit 500", - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient", street2="Unit 500",) # Set an example form form = {"how_did_you_hear_about_us": "I was referred by John Smith"} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -551,16 +436,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure last name was removed - self.assertTrue( - next( - ( - e - for e in payload["extension"] - if e["url"] == FHIR.referral_extension_url - ), - False, - ) - ) + self.assertTrue(next((e for e in payload["extension"] if e["url"] == FHIR.referral_extension_url), False,)) @responses.activate def test_patient_update_referral(self): @@ -569,25 +445,15 @@ def test_patient_update_referral(self): referral = "new_referral" # Build a patient with a lastname - patient = FHIRData.patient( - "patient@email.org", - firstname="User", - lastname="Patient", - street2="Unit 500", - ) - patient.setdefault("extension", []).append( - {"url": FHIR.referral_extension_url, "valueString": "old_referral"} - ) + patient = FHIRData.patient("patient@email.org", firstname="User", lastname="Patient", street2="Unit 500",) + patient.setdefault("extension", []).append({"url": FHIR.referral_extension_url, "valueString": "old_referral"}) # Set an example form form = {"how_did_you_hear_about_us": referral} # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -612,14 +478,7 @@ def update_callback(request): # Ensure last name was removed self.assertEqual( - next( - ( - e - for e in payload["extension"] - if e["url"] == FHIR.referral_extension_url - ), - False, - )["valueString"], + next((e for e in payload["extension"] if e["url"] == FHIR.referral_extension_url), False,)["valueString"], referral, ) @@ -640,10 +499,7 @@ def test_patient_update_remove_referral(self): # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -667,16 +523,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure last name was removed - self.assertFalse( - next( - ( - e - for e in payload["extension"] - if e["url"] == FHIR.referral_extension_url - ), - False, - ) - ) + self.assertFalse(next((e for e in payload["extension"] if e["url"] == FHIR.referral_extension_url), False,)) @responses.activate def test_patient_update_requirements(self): @@ -695,10 +542,7 @@ def test_patient_update_requirements(self): # Build the response handler responses.add( - responses.GET, - re.compile(self.fhir_url + r"/Patient/.*"), - json=patient, - status=200, + responses.GET, re.compile(self.fhir_url + r"/Patient/.*"), json=patient, status=200, ) def update_callback(request): @@ -724,25 +568,11 @@ def update_callback(request): # Ensure properties still exist self.assertTrue(len(payload["name"][0]["given"]) > 0) self.assertTrue( - next( - ( - id["value"] - for id in payload["identifier"] - if id["system"] == "http://schema.org/email" - ), - False, - ) + next((id["value"] for id in payload["identifier"] if id["system"] == "http://schema.org/email"), False,) ) self.assertTrue(payload["address"][0].get("city", False)) self.assertTrue( - next( - ( - telecom["value"] - for telecom in payload["telecom"] - if telecom["system"] == "phone" - ), - False, - ) + next((telecom["value"] for telecom in payload["telecom"] if telecom["system"] == "phone"), False,) ) @responses.activate @@ -752,9 +582,7 @@ def test_update_patient_deceased_1(self): deceased = datetime.now() # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def update_callback(request): @@ -787,9 +615,7 @@ def test_update_patient_deceased_2(self): deceased = None # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def update_callback(request): @@ -821,9 +647,7 @@ def test_update_patient_deceased_3(self): deceased = datetime.now() # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def update_callback(request): @@ -860,9 +684,7 @@ def test_update_patient_deceased_4(self): deceased = None # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def update_callback(request): @@ -899,9 +721,7 @@ def test_update_patient_twitter_1(self): uses_twitter = None # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -932,17 +752,11 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Get telecom - telecom = next( - t - for t in payload["telecom"] - if t["system"] == FHIR.patient_twitter_telecom_system - ) + telecom = next(t for t in payload["telecom"] if t["system"] == FHIR.patient_twitter_telecom_system) self.assertEqual(telecom["value"], "https://twitter.com/" + handle) # Get extension - extension = next( - e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url - ) + extension = next(e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url) self.assertEqual(extension["valueBoolean"], True) @responses.activate @@ -953,9 +767,7 @@ def test_update_patient_twitter_2(self): uses_twitter = None # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -986,21 +798,11 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Get telecom - telecom = next( - ( - t - for t in payload["telecom"] - if t["system"] == FHIR.patient_twitter_telecom_system - ), - None, - ) + telecom = next((t for t in payload["telecom"] if t["system"] == FHIR.patient_twitter_telecom_system), None,) self.assertIsNone(telecom) # Get extension - extension = next( - (e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url), - None, - ) + extension = next((e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url), None,) self.assertIsNone(extension) @responses.activate @@ -1011,9 +813,7 @@ def test_update_patient_twitter_3(self): uses_twitter = False # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1044,21 +844,11 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Get telecom - telecom = next( - ( - t - for t in payload["telecom"] - if t["system"] == FHIR.patient_twitter_telecom_system - ), - None, - ) + telecom = next((t for t in payload["telecom"] if t["system"] == FHIR.patient_twitter_telecom_system), None,) self.assertIsNone(telecom) # Get extension - extension = next( - (e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url), - None, - ) + extension = next((e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url), None,) self.assertEqual(extension["valueBoolean"], uses_twitter) @responses.activate @@ -1069,20 +859,13 @@ def test_update_patient_twitter_4(self): uses_twitter = False # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Set an initial handle and extension patient["telecom"].append( - { - "system": FHIR.patient_twitter_telecom_system, - "value": "https://twitter.com/somehandle", - } - ) - patient["extension"].append( - {"url": FHIR.twitter_extension_url, "valueBoolean": True} + {"system": FHIR.patient_twitter_telecom_system, "value": "https://twitter.com/somehandle",} ) + patient["extension"].append({"url": FHIR.twitter_extension_url, "valueBoolean": True}) # Build the response handler responses.add( @@ -1110,21 +893,11 @@ def test_update_patient_twitter_4(self): payload = json.loads(responses.calls[1].request.body) # Get telecom - telecom = next( - ( - t - for t in payload["telecom"] - if t["system"] == FHIR.patient_twitter_telecom_system - ), - None, - ) + telecom = next((t for t in payload["telecom"] if t["system"] == FHIR.patient_twitter_telecom_system), None,) self.assertIsNone(telecom) # Get extension - extension = next( - (e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url), - None, - ) + extension = next((e for e in payload["extension"] if e["url"] == FHIR.twitter_extension_url), None,) self.assertEqual(extension["valueBoolean"], uses_twitter) @responses.activate @@ -1135,9 +908,7 @@ def test_update_patient_extension_1(self): value = False # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1179,9 +950,7 @@ def test_update_patient_extension_2(self): value = False # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Set an initial handle and extension patient["extension"].append({"url": url, "valueBoolean": not value}) @@ -1226,9 +995,7 @@ def test_update_patient_extension_3(self): value = "somestring" # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Set an initial handle and extension patient["extension"].append({"url": url, "valueString": "someotherstring"}) @@ -1273,9 +1040,7 @@ def test_update_patient_picnichealth_1(self): value = False # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1317,9 +1082,7 @@ def test_update_patient_picnichealth_2(self): value = True # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Set an initial handle and extension patient["extension"].append({"url": url, "valueBoolean": not value}) @@ -1363,9 +1126,7 @@ def test_update_enrollment_1(self): enrollment = PPM.Enrollment.Consented.value # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1396,9 +1157,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure properties still exist - self.assertEqual( - payload["subject"]["reference"], "Patient/{}".format(patient["id"]) - ) + self.assertEqual(payload["subject"]["reference"], "Patient/{}".format(patient["id"])) self.assertEqual(payload["code"]["coding"][0]["code"], enrollment) self.assertEqual(payload["code"]["text"], PPM.Enrollment.title(enrollment)) self.assertEqual(payload["status"], "inactive") @@ -1411,9 +1170,7 @@ def test_update_enrollment_2(self): enrollment = PPM.Enrollment.Proposed.value # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1444,9 +1201,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure properties still exist - self.assertEqual( - payload["subject"]["reference"], "Patient/{}".format(patient["id"]) - ) + self.assertEqual(payload["subject"]["reference"], "Patient/{}".format(patient["id"])) self.assertEqual(payload["code"]["coding"][0]["code"], enrollment) self.assertEqual(payload["code"]["text"], PPM.Enrollment.title(enrollment)) self.assertNotEqual(payload["status"], "active") @@ -1459,9 +1214,7 @@ def test_update_enrollment_3(self): enrollment = PPM.Enrollment.Accepted.value # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1492,9 +1245,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure properties still exist - self.assertEqual( - payload["subject"]["reference"], "Patient/{}".format(patient["id"]) - ) + self.assertEqual(payload["subject"]["reference"], "Patient/{}".format(patient["id"])) self.assertEqual(payload["code"]["coding"][0]["code"], enrollment) self.assertEqual(payload["code"]["text"], PPM.Enrollment.title(enrollment)) self.assertEqual(payload["status"], "active") @@ -1508,9 +1259,7 @@ def test_update_enrollment_4(self): enrollment = PPM.Enrollment.Terminated.value # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1541,9 +1290,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure properties still exist - self.assertEqual( - payload["subject"]["reference"], "Patient/{}".format(patient["id"]) - ) + self.assertEqual(payload["subject"]["reference"], "Patient/{}".format(patient["id"])) self.assertEqual(payload["code"]["coding"][0]["code"], enrollment) self.assertEqual(payload["code"]["text"], PPM.Enrollment.title(enrollment)) self.assertEqual(payload["status"], "inactive") @@ -1557,9 +1304,7 @@ def test_update_enrollment_5(self): enrollment = PPM.Enrollment.Completed.value # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1590,9 +1335,7 @@ def update_callback(request): payload = json.loads(responses.calls[1].request.body) # Ensure properties still exist - self.assertEqual( - payload["subject"]["reference"], "Patient/{}".format(patient["id"]) - ) + self.assertEqual(payload["subject"]["reference"], "Patient/{}".format(patient["id"])) self.assertEqual(payload["code"]["coding"][0]["code"], enrollment) self.assertEqual(payload["code"]["text"], PPM.Enrollment.title(enrollment)) self.assertEqual(payload["status"], "inactive") @@ -1606,9 +1349,7 @@ def test_update_research_subject_1(self): end = datetime.now() # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def update_callback(request): @@ -1622,9 +1363,7 @@ def update_callback(request): ) # Do the update - updated = FHIR.update_research_subject( - patient["id"], research_subject["id"], end=end - ) + updated = FHIR.update_research_subject(patient["id"], research_subject["id"], end=end) # Check it self.assertGreaterEqual(len(responses.calls), 1) @@ -1643,9 +1382,7 @@ def test_update_research_subject_2(self): end = None # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def update_callback(request): @@ -1659,9 +1396,7 @@ def update_callback(request): ) # Do the update - updated = FHIR.update_research_subject( - patient["id"], research_subject["id"], end=end - ) + updated = FHIR.update_research_subject(patient["id"], research_subject["id"], end=end) # Check it self.assertGreaterEqual(len(responses.calls), 1) @@ -1679,9 +1414,7 @@ def test_update_ppm_research_subject_1(self): end = datetime.now() # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1703,9 +1436,7 @@ def update_callback(request): ) # Do the update - updated = FHIR.update_ppm_research_subject( - patient["id"], PPM.Study.NEER.value, end=end - ) + updated = FHIR.update_ppm_research_subject(patient["id"], PPM.Study.NEER.value, end=end) # Check it self.assertGreaterEqual(len(responses.calls), 2) @@ -1724,9 +1455,7 @@ def test_update_ppm_research_subject_2(self): end = None # Start a data set - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Build the response handler responses.add( @@ -1748,9 +1477,7 @@ def update_callback(request): ) # Do the update - updated = FHIR.update_ppm_research_subject( - patient["id"], PPM.Study.NEER.value, end=end - ) + updated = FHIR.update_ppm_research_subject(patient["id"], PPM.Study.NEER.value, end=end) # Check it self.assertGreaterEqual(len(responses.calls), 2) @@ -1765,9 +1492,7 @@ def update_callback(request): def test_delete_participant(self): # Create our participant to be deleted - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) # Collect IDs resource_ids = [ @@ -1780,9 +1505,7 @@ def test_delete_participant(self): responses.add( responses.GET, re.compile(self.fhir_url + r"/Patient.*"), - json=FHIRData.create_bundle( - [patient, flag, research_subject, research_study], self.fhir_url - ), + json=FHIRData.create_bundle([patient, flag, research_subject, research_study], self.fhir_url), status=200, ) @@ -1818,9 +1541,7 @@ def update_callback(request): def test_delete_patient(self): # Create our participant to be deleted - research_study, patient, flag, research_subject = FHIRData.participant( - PPM.Study.NEER - ) + research_study, patient, flag, research_subject = FHIRData.participant(PPM.Study.NEER) def delete_callback(request): @@ -1861,18 +1582,11 @@ def __init__(self, fhir_url, participants=1, study=None): # If not study, randomize it if not study: study = PPM.Study( - list(dict(PPM.Study.choices()).keys())[ - random.randint(1, len(PPM.Study.choices())) - 1 - ] + list(dict(PPM.Study.choices()).keys())[random.randint(1, len(PPM.Study.choices())) - 1] ) # Create the resources - ( - research_study, - patient, - enrollment, - research_subject, - ) = FHIRData.participant(study) + (research_study, patient, enrollment, research_subject,) = FHIRData.participant(study) # Add the study if not already there if not self.bundle: @@ -1880,22 +1594,14 @@ def __init__(self, fhir_url, participants=1, study=None): # Start it self.bundle = FHIRData.create_bundle([research_study], fhir_url) - elif not next( - r - for r in self.bundle["entry"] - if r["resource"]["id"] == research_study["id"] - ): + elif not next(r for r in self.bundle["entry"] if r["resource"]["id"] == research_study["id"]): # Add it - self.bundle["entry"].extend( - FHIRData.create_bundle([research_study], fhir_url)["entry"] - ) + self.bundle["entry"].extend(FHIRData.create_bundle([research_study], fhir_url)["entry"]) # Add remaining resources self.bundle["entry"].extend( - FHIRData.create_bundle( - [patient, enrollment, research_subject], fhir_url - )["entry"] + FHIRData.create_bundle([patient, enrollment, research_subject], fhir_url)["entry"] ) def add_participant(self, patient, enrollment, research_subject, research_study): @@ -1913,25 +1619,14 @@ def add_participant(self, patient, enrollment, research_subject, research_study) # Start it self.bundle = FHIRData.create_bundle([research_study], self.fhir_url) - elif not next( - ( - r - for r in self.bundle["entry"] - if r["resource"]["id"] == research_study["id"] - ), - None, - ): + elif not next((r for r in self.bundle["entry"] if r["resource"]["id"] == research_study["id"]), None,): # Add it - self.bundle["entry"].extend( - FHIRData.create_bundle([research_study], self.fhir_url)["entry"] - ) + self.bundle["entry"].extend(FHIRData.create_bundle([research_study], self.fhir_url)["entry"]) # Add remaining resources self.bundle["entry"].extend( - FHIRData.create_bundle( - [patient, enrollment, research_subject], self.fhir_url - )["entry"] + FHIRData.create_bundle([patient, enrollment, research_subject], self.fhir_url)["entry"] ) @staticmethod @@ -1963,12 +1658,8 @@ def participant(study, enrollment=PPM.Enrollment.Registered): contact_email=f"patient-{ppm_id}-{PPM.Study.enum(study).value}@email.org", ) research_study = FHIRData.research_study(study) - research_subject = FHIRData.research_subject( - "Patient/{}".format(patient["id"]), study - ) - enrollment_flag = FHIRData.enrollment_flag( - "Patient/{}".format(patient["id"]), enrollment.value - ) + research_subject = FHIRData.research_subject("Patient/{}".format(patient["id"]), study) + enrollment_flag = FHIRData.enrollment_flag("Patient/{}".format(patient["id"]), enrollment.value) return research_study, patient, enrollment_flag, research_subject @@ -1988,11 +1679,7 @@ def create_bundle(resources, fhir_url): "total": len(resources), "link": [{"relation": "self", "url": fhir_url}], "entry": [ - { - "resource": resource, - "fullUrl": f'{fhir_url}/{resource["resourceType"]}/' - f'{resource["id"]}', - } + {"resource": resource, "fullUrl": f'{fhir_url}/{resource["resourceType"]}/' f'{resource["id"]}',} for resource in resources ], } @@ -2041,17 +1728,13 @@ def patient( # Replace/set some additional data data["id"] = f"{random.randint(1, 9999)}" data["identifier"].append( - { - "system": "https://peoplepoweredmedicine.org/fhir/patient", - "value": identifier, - } + {"system": "https://peoplepoweredmedicine.org/fhir/patient", "value": identifier,} ) # Toggle Twitter usage data["extension"].append( { - "url": "https://p2m2.dbmi.hms.harvard.edu/fhir/" - "StructureDefinition/uses-twitter", + "url": "https://p2m2.dbmi.hms.harvard.edu/fhir/" "StructureDefinition/uses-twitter", "valueBoolean": twitter is not None, } ) @@ -2104,10 +1787,7 @@ def research_subject(patient, study, start=None, end=None): @staticmethod def enrollment_flag( - patient, - enrollment=PPM.Enrollment.Registered.value, - start=datetime.now(), - end=None, + patient, enrollment=PPM.Enrollment.Registered.value, start=datetime.now(), end=None, ): """ Initializes and returns a test ResearchSubject resource using the @@ -2117,9 +1797,7 @@ def enrollment_flag( :rtype: dict """ # Build initial object - data = FHIR.Resources.enrollment_flag( - patient, status=enrollment, start=start, end=end - ) + data = FHIR.Resources.enrollment_flag(patient, status=enrollment, start=start, end=end) # Set random id data["id"] = f"{random.randint(1, 9999)}" diff --git a/ppmutils/tests/test_ppm.py b/ppmutils/tests/test_ppm.py index 02ba86b..d0849be 100644 --- a/ppmutils/tests/test_ppm.py +++ b/ppmutils/tests/test_ppm.py @@ -79,21 +79,15 @@ def test_url_build_4(self, mock_service_url): def test_ppm_study_meta_1(self): # Compare methods for determining study meta - self.assertEqual( - PPM.Study.title(PPM.Study.NEER), PPM.Study.title(PPM.Study.NEER.value) - ) - self.assertEqual( - PPM.Study.title(PPM.Study.ASD), PPM.Study.title(PPM.Study.ASD.value) - ) + self.assertEqual(PPM.Study.title(PPM.Study.NEER), PPM.Study.title(PPM.Study.NEER.value)) + self.assertEqual(PPM.Study.title(PPM.Study.ASD), PPM.Study.title(PPM.Study.ASD.value)) # Use FHIR codes self.assertEqual( - PPM.Study.title(PPM.Study.ASD), - PPM.Study.title(PPM.Study.fhir_id(PPM.Study.ASD)), + PPM.Study.title(PPM.Study.ASD), PPM.Study.title(PPM.Study.fhir_id(PPM.Study.ASD)), ) self.assertEqual( - PPM.Study.title(PPM.Study.ASD), - PPM.Study.title(PPM.Study.fhir_id(PPM.Study.ASD.value)), + PPM.Study.title(PPM.Study.ASD), PPM.Study.title(PPM.Study.fhir_id(PPM.Study.ASD.value)), ) def test_ppm_study_meta_2(self): diff --git a/pyproject.toml b/pyproject.toml index 99c1f89..23144b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,9 @@ +[build-system] +requires = [ "setuptools >= 35.0.2", "wheel"] +build-backend = "setuptools.build_meta" + [tool.black] -line-length = 88 +line-length = 120 include = '\.pyi?$' exclude = ''' /( @@ -13,4 +17,26 @@ exclude = ''' | build | dist )/ +''' + +[tool.tox] +legacy_tox_ini = ''' +[tox] +envlist = py{36,37,38}-django{111,22,30} +install_command = pip install {opts} "{packages}" + +[testenv] +basepython = + py36: python3.6 + py37: python3.7 + py38: python3.8 + +commands = + nosetests {posargs} + +deps = + -rrequirements-test.txt + django111: Django>=1.11,<1.12 + django22: Django>=2.2,<2.3 + django30: Django>=3.0,<4.0 ''' \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index ee60250..49fe9ad 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1 +1 @@ -pre-commit==2.3.0 \ No newline at end of file +pre-commit==2.* \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4cc7853..78a054a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -django>=1.11,<4.0 -python-dateutil==2.7.5 -fhirclient==3.2.0 -requests==2.21.0 -furl==2.1.0 +django +python-dateutil==2.* +fhirclient==3.* +requests==2.* +furl==2.* diff --git a/setup.cfg b/setup.cfg index d2d485b..2d79402 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,7 @@ verbosity=2 [flake8] # Recommend matching the black line length (default 88), # rather than using the flake8 default of 79: -max-line-length = 88 +max-line-length = 120 extend-ignore = # See https://github.com/PyCQA/pycodestyle/issues/373 E203, E231 diff --git a/setup.py b/setup.py index 40a873c..af61708 100644 --- a/setup.py +++ b/setup.py @@ -16,10 +16,7 @@ def get_version(package): Return package version as listed in `__version__` in `init.py`. """ init_py = open( - os.path.join( - os.path.dirname(os.path.abspath(__file__)), - os.path.join(package, "__init__.py"), - ) + os.path.join(os.path.dirname(os.path.abspath(__file__)), os.path.join(package, "__init__.py"),) ).read() return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1)