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)
Esempio n. 2
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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