def create_computed_questionnaire_response(record: REDCapRecord, patient_reference: dict, encounter_reference: dict, birthdate: datetime, encounter_date: datetime) -> Optional[dict]: """ Returns a FHIR Questionnaire Response resource entry for a computed questionnaire. This "computed questionnaire" produces answers that do not appear directly in an actual REDCap survey. For example, a computed question captures the participant's age on the date of the encounter. """ # A birthdate of None will return a falsy relativedelta() object delta = relativedelta(encounter_date, birthdate) if not delta: age = None else: # Age Ceiling age = age_ceiling(delta.years) record['age'] = age integer_questions = [ 'age' ] question_categories = { 'valueInteger': integer_questions } return create_questionnaire_response( record = record, question_categories = question_categories, patient_reference = patient_reference, encounter_reference = encounter_reference, system_identifier = INTERNAL_SYSTEM)
def determine_questionnaire_items(record: dict) -> List[dict]: """ Returns a list of FHIR Questionnaire Response answer items """ items: Dict[str, Any] = {} if record["age"]: items["age"] = [{'valueInteger': age_ceiling(int(record["age"]))}] questionnaire_items: List[dict] = [] for key, value in items.items(): questionnaire_items.append( create_questionnaire_response_item(question_id=key, answers=value)) return questionnaire_items
def create_questionnaire_response(record: dict, patient_reference: dict, encounter_reference: dict) -> Optional[dict]: """ Returns a FHIR Questionnaire Response resource entry. """ def create_custom_coding_key(coded_question: str, record: dict) -> Optional[List]: """ Handles the 'race' edge case by combining "select all that apply"-type responses into one list. """ coded_keys = list(filter(lambda r: grab_coding_keys(coded_question, r), record)) coded_names = list(map(lambda k: record[k], coded_keys)) if coded_question == 'race': return race(coded_names) return None def grab_coding_keys(coded_question: str, key: str) -> Optional[Match[str]]: if record[key] == '': return None return re.match(f'{coded_question}___[0-9]+$', key) def build_questionnaire_items(question: str) -> Optional[dict]: return questionnaire_item(record, question, category) coding_questions = [ 'race', # 'insurance', # TODO address these non-essential coded questions later # 'how_hear_sfs', # 'poc_behaviors', ] boolean_questions = [ 'ethnicity', 'barcode_confirm', 'travel_states', 'travel_countries', ] integer_questions = [ 'age', 'age_months', ] string_questions = [ 'education', 'doctor_3e8fae', 'samp_process_date', 'house_members', 'shelter_members', 'where_sick', 'antiviral_0', 'acute_symptom_onset', 'doctor_1week', 'antiviral_1', ] question_categories = { 'valueCoding': coding_questions, 'valueBoolean': boolean_questions, 'valueInteger': integer_questions, 'valueString': string_questions, } # Do some pre-processing record['race'] = create_custom_coding_key('race', record) record['age'] = age_ceiling(int(record['age'])) record['age_months'] = age_ceiling(int(record['age_months']) / 12) * 12 items: List[dict] = [] for category in question_categories: category_items = list(map(build_questionnaire_items, question_categories[category])) for item in category_items: if item: items.append(item) # Handle edge cases vaccine_item = vaccine(record) if vaccine_item: items.append(vaccine_item) if items: questionnaire_reseponse_resource = create_questionnaire_response_resource( patient_reference, encounter_reference, items ) full_url = generate_full_url_uuid() return create_resource_entry(questionnaire_reseponse_resource, full_url) return None
def questionnaire_item(record: dict, question_id: str, response_type: str) -> Optional[dict]: """ Creates a QuestionnaireResponse internal item from a REDCap record. """ response = record.get(question_id) if not response: return None # Age ceiling if question_id in {'age', 'age_months'}: try: value = int(response) if question_id == 'age': response = age_ceiling(value) if question_id == 'age_months': response = age_ceiling(value / 12) * 12 except ValueError: return None def cast_to_coding(string: str): """ Currently the only QuestionnaireItem we code is race. """ return create_coding( system=f"{INTERNAL_SYSTEM}/race", code=string, ) def cast_to_string(string: str) -> Optional[str]: if string != '': return string.strip() return None def cast_to_integer(string: str) -> Optional[int]: try: return int(string) except ValueError: return None def cast_to_boolean(string: str) -> Optional[bool]: if string == 'yes': return True elif string == 'no': return False return None def build_response_answers(response: Union[str, List]) -> List: answers = [] if not isinstance(response, list): response = [response] for item in response: type_casted_item = casting_functions[response_type](item) # cast_to_boolean can return False, so must be `is not None` if type_casted_item is not None: answers.append({response_type: type_casted_item}) return answers casting_functions: Mapping[str, Callable[[str], Any]] = { 'valueCoding': cast_to_coding, 'valueInteger': cast_to_integer, 'valueBoolean': cast_to_boolean, 'valueString': cast_to_string, 'valueDate': cast_to_string, } answers = build_response_answers(response) if answers: return create_questionnaire_response_item(question_id, answers) return None
def determine_all_questionnaire_items(redcap_record: dict) -> List[dict]: """ Given a *redcap_record*, determine answers for all core questions """ items: Dict[str, Any] = {} if redcap_record['age']: items['age'] = [{ 'valueInteger': age_ceiling(int(redcap_record['age'])) }] items['age_months'] = [{ 'valueInteger': int(age_ceiling(float(redcap_record['age_months']) / 12) * 12) }] if redcap_record['acute_symptom_onset']: items['acute_symptom_onset'] = [{ 'valueString': redcap_record['acute_symptom_onset'] }] # Participant can select multiple insurance types, so create # a separate answer for each selection insurance_responses = find_selected_options('insurance___', redcap_record) insurances = determine_insurance_type(insurance_responses) if insurances: items['insurance'] = [{ 'valueString': insurance } for insurance in insurances] # Participant can select multiple races, so create # a separate answer for each selection race_responses = find_selected_options('race___', redcap_record) if 'Prefer not to say' not in race_responses: races = race(race_responses) items['race'] = [{'valueString': race} for race in races] if redcap_record['hispanic'] != 'Prefer not to say': items['ethnicity'] = [{ 'valueBoolean': redcap_record['hispanic'] == 'Yes' }] items['travel_countries'] = [{ 'valueBoolean': redcap_record['travel_countries'] == 'Yes' }] items['travel_states'] = [{ 'valueBoolean': redcap_record['travel_states'] == 'Yes' }] # Only include vaccine status if known vaccine_status = map_vaccine(redcap_record['vaccine']) # Only include vaccine status if known if vaccine_status is not None: items['vaccine'] = [{'valueBoolean': vaccine_status}] immunization_date = determine_vaccine_date( vaccine_year=redcap_record['vaccine_year'], vaccine_month=redcap_record['vaccine_month']) if vaccine_status and immunization_date: items['vaccine'].append({'valueDate': immunization_date}) response_items = [] for item in items: response_items.append( create_questionnaire_response_item(question_id=item, answers=items[item])) return response_items