def map_answers(reader):
  """Emit (participantId, date|<metric>.<answer>) for each answer.

  Metric names are taken from the field name in code_constants.

  Code and string answers are accepted.

  Incoming rows are expected to be sorted by participant ID, start time, and question code,
  such that repeated answers for the same question are next to each other.
  """
  last_participant_id = None
  last_start_time = None
  race_code_values = []
  code_dao = CodeDao()
  for participant_id, start_time, question_code, answer_code, answer_string in reader:

    # Multiple race answer values for the participant at a single time
    # are combined into a single race enum.
    if race_code_values and (last_participant_id != participant_id or
                             last_start_time != start_time or
                             question_code != RACE_QUESTION_CODE):
      race_codes = [code_dao.get_code(PPI_SYSTEM, value) for value in race_code_values]
      race = get_race(race_codes)
      yield(last_participant_id, make_tuple(last_start_time,
                                               make_metric(RACE_METRIC, str(race))))
      race_code_values = []
    last_participant_id = participant_id
    last_start_time = start_time
    if question_code == RACE_QUESTION_CODE:
      race_code_values.append(answer_code)
      continue
    if question_code == EHR_CONSENT_QUESTION_CODE:
      metric = EHR_CONSENT_ANSWER_METRIC
      answer_value = answer_code
    else:
      question_field = QUESTION_CODE_TO_FIELD[question_code]
      metric = transform_participant_summary_field(question_field[0])
      if question_field[1] == FieldType.CODE:
        answer_value = answer_code
        if metric == 'state':
          state_val = answer_code[len(answer_code) - 2:]
          census_region = census_regions.get(state_val) or UNSET
          yield(participant_id, make_tuple(start_time, make_metric(CENSUS_REGION_METRIC,
                                                                   census_region)))
        elif question_field[1] == FieldType.STRING:
          answer_value = answer_string
      else:
        raise AssertionError("Invalid field type: %s" % question_field[1])
    yield(participant_id, make_tuple(start_time, make_metric(metric, answer_value)))

  # Emit race for the last participant if we saved some values for it.
  if race_code_values:
    race_codes = [code_dao.get_code(PPI_SYSTEM, value) for value in race_code_values]
    race = get_race(race_codes)
    yield(last_participant_id, make_tuple(last_start_time,
                                             make_metric(RACE_METRIC, str(race))))
Example #2
0
    def _update_participant_summary(self, session, questionnaire_response,
                                    code_ids, questions, questionnaire_history,
                                    resource_json):
        """Updates the participant summary based on questions answered and modules completed
    in the questionnaire response.

    If no participant summary exists already, only a response to the study enrollment consent
    questionnaire can be submitted, and it must include first and last name and e-mail address.
    """

        # Block on other threads modifying the participant or participant summary.
        participant = ParticipantDao().get_for_update(
            session, questionnaire_response.participantId)

        if participant is None:
            raise BadRequest('Participant with ID %d is not found.' %
                             questionnaire_response.participantId)

        participant_summary = participant.participantSummary

        code_ids.extend(
            [concept.codeId for concept in questionnaire_history.concepts])

        code_dao = CodeDao()

        something_changed = False
        # If no participant summary exists, make sure this is the study enrollment consent.
        if not participant_summary:
            consent_code = code_dao.get_code(
                PPI_SYSTEM, CONSENT_FOR_STUDY_ENROLLMENT_MODULE)
            if not consent_code:
                raise BadRequest(
                    'No study enrollment consent code found; import codebook.')
            if not consent_code.codeId in code_ids:
                raise BadRequest(
                    "Can't submit order for participant %s without consent" %
                    questionnaire_response.participantId)
            raise_if_withdrawn(participant)
            participant_summary = ParticipantDao.create_summary_for_participant(
                participant)
            something_changed = True
        else:
            raise_if_withdrawn(participant_summary)

        # Fetch the codes for all questions and concepts
        codes = code_dao.get_with_ids(code_ids)

        code_map = {
            code.codeId: code
            for code in codes if code.system == PPI_SYSTEM
        }
        question_map = {
            question.questionnaireQuestionId: question
            for question in questions
        }
        race_code_ids = []
        ehr_consent = False
        dvehr_consent = QuestionnaireStatus.SUBMITTED_NO_CONSENT
        # Set summary fields for answers that have questions with codes found in QUESTION_CODE_TO_FIELD
        for answer in questionnaire_response.answers:
            question = question_map.get(answer.questionId)
            if question:
                code = code_map.get(question.codeId)
                if code:
                    summary_field = QUESTION_CODE_TO_FIELD.get(code.value)
                    if summary_field:
                        if something_changed:
                            self._update_field(participant_summary,
                                               summary_field[0],
                                               summary_field[1], answer)
                        else:
                            something_changed = self._update_field(
                                participant_summary, summary_field[0],
                                summary_field[1], answer)
                    elif code.value == RACE_QUESTION_CODE:
                        race_code_ids.append(answer.valueCodeId)

                    elif code.value == DVEHR_SHARING_QUESTION_CODE:
                        code = code_dao.get(answer.valueCodeId)
                        if code and code.value == DVEHRSHARING_CONSENT_CODE_YES:
                            dvehr_consent = QuestionnaireStatus.SUBMITTED
                        elif code and code.value == DVEHRSHARING_CONSENT_CODE_NOT_SURE:
                            dvehr_consent = QuestionnaireStatus.SUBMITTED_NOT_SURE
                    elif code.value == EHR_CONSENT_QUESTION_CODE:
                        code = code_dao.get(answer.valueCodeId)
                        if code and code.value == CONSENT_PERMISSION_YES_CODE:
                            ehr_consent = True
                    elif code.value == CABOR_SIGNATURE_QUESTION_CODE:
                        if answer.valueUri or answer.valueString:
                            # TODO: validate the URI? [DA-326]
                            if not participant_summary.consentForCABoR:
                                participant_summary.consentForCABoR = True
                                participant_summary.consentForCABoRTime = questionnaire_response.created
                                something_changed = True

        # If race was provided in the response in one or more answers, set the new value.
        if race_code_ids:
            race_codes = [code_dao.get(code_id) for code_id in race_code_ids]
            race = get_race(race_codes)
            if race != participant_summary.race:
                participant_summary.race = race
                something_changed = True

        # Set summary fields to SUBMITTED for questionnaire concepts that are found in
        # QUESTIONNAIRE_MODULE_CODE_TO_FIELD
        module_changed = False
        for concept in questionnaire_history.concepts:
            code = code_map.get(concept.codeId)
            if code:
                summary_field = QUESTIONNAIRE_MODULE_CODE_TO_FIELD.get(
                    code.value)
                if summary_field:
                    new_status = QuestionnaireStatus.SUBMITTED
                    if code.value == CONSENT_FOR_ELECTRONIC_HEALTH_RECORDS_MODULE and not ehr_consent:
                        new_status = QuestionnaireStatus.SUBMITTED_NO_CONSENT
                    elif code.value == CONSENT_FOR_DVEHR_MODULE:
                        new_status = dvehr_consent
                    elif code.value == CONSENT_FOR_STUDY_ENROLLMENT_MODULE:
                        # set language of consent to participant summary
                        for extension in resource_json.get('extension', []):
                            if extension.get('url') == _LANGUAGE_EXTENSION and \
                              extension.get('valueCode') in LANGUAGE_OF_CONSENT:
                                if participant_summary.primaryLanguage != extension.get(
                                        'valueCode'):
                                    participant_summary.primaryLanguage = extension.get(
                                        'valueCode')
                                    something_changed = True
                                break
                            elif extension.get('url') == _LANGUAGE_EXTENSION and \
                              extension.get('valueCode') not in LANGUAGE_OF_CONSENT:
                                logging.warn(
                                    'consent language %s not recognized.' %
                                    extension.get('valueCode'))
                    if getattr(participant_summary,
                               summary_field) != new_status:
                        setattr(participant_summary, summary_field, new_status)
                        setattr(participant_summary, summary_field + 'Time',
                                questionnaire_response.created)
                        something_changed = True
                        module_changed = True
        if module_changed:
            participant_summary.numCompletedBaselinePPIModules = \
                count_completed_baseline_ppi_modules(participant_summary)
            participant_summary.numCompletedPPIModules = \
                count_completed_ppi_modules(participant_summary)

        if something_changed:
            first_last = (participant_summary.firstName,
                          participant_summary.lastName)
            email_phone = (participant_summary.email,
                           participant_summary.loginPhoneNumber)
            if not all(first_last):
                raise BadRequest(
                    'First name (%s), and last name (%s) required for consenting.'
                    % tuple([
                        'present' if part else 'missing' for part in first_last
                    ]))
            if not any(email_phone):
                raise BadRequest(
                    'Email address (%s), or phone number (%s) required for consenting.'
                    % tuple([
                        'present' if part else 'missing'
                        for part in email_phone
                    ]))

            ParticipantSummaryDao().update_enrollment_status(
                participant_summary)
            participant_summary.lastModified = clock.CLOCK.now()
            session.merge(participant_summary)

            # switch account to test account if the phone number is start with 444
            # this is a requirement from PTSC
            if participant_summary.loginPhoneNumber is not None and \
              participant_summary.loginPhoneNumber.startswith(TEST_LOGIN_PHONE_NUMBER_PREFIX):
                ParticipantDao().switch_to_test_account(
                    session, participant_summary.participantId)
  def _update_participant_summary(
      self, session, questionnaire_response, code_ids, questions, questionnaire_history):
    """Updates the participant summary based on questions answered and modules completed
    in the questionnaire response.

    If no participant summary exists already, only a response to the study enrollment consent
    questionnaire can be submitted, and it must include first and last name and e-mail address.
    """
    # Block on other threads modifying the participant or participant summary.
    participant = ParticipantDao().get_for_update(session, questionnaire_response.participantId)

    if participant is None:
      raise BadRequest('Participant with ID %d is not found.' %
                        questionnaire_response.participantId)

    participant_summary = participant.participantSummary

    code_ids.extend([concept.codeId for concept in questionnaire_history.concepts])

    code_dao = CodeDao()

    something_changed = False
    # If no participant summary exists, make sure this is the study enrollment consent.
    if not participant_summary:
      consent_code = code_dao.get_code(PPI_SYSTEM, CONSENT_FOR_STUDY_ENROLLMENT_MODULE)
      if not consent_code:
        raise BadRequest('No study enrollment consent code found; import codebook.')
      if not consent_code.codeId in code_ids:
        raise BadRequest("Can't submit order for participant %s without consent" %
                         questionnaire_response.participantId)
      raise_if_withdrawn(participant)
      participant_summary = ParticipantDao.create_summary_for_participant(participant)
      something_changed = True
    else:
      raise_if_withdrawn(participant_summary)

    # Fetch the codes for all questions and concepts
    codes = code_dao.get_with_ids(code_ids)

    code_map = {code.codeId: code for code in codes if code.system == PPI_SYSTEM}
    question_map = {question.questionnaireQuestionId: question for question in questions}
    race_code_ids = []
    ehr_consent = False
    # Set summary fields for answers that have questions with codes found in QUESTION_CODE_TO_FIELD
    for answer in questionnaire_response.answers:
      question = question_map.get(answer.questionId)
      if question:
        code = code_map.get(question.codeId)
        if code:
          summary_field = QUESTION_CODE_TO_FIELD.get(code.value)
          if summary_field:
            something_changed = self._update_field(participant_summary, summary_field[0],
                                                   summary_field[1], answer)
          elif code.value == RACE_QUESTION_CODE:
            race_code_ids.append(answer.valueCodeId)
          elif code.value == EHR_CONSENT_QUESTION_CODE:
            code = code_dao.get(answer.valueCodeId)
            if code and code.value == CONSENT_PERMISSION_YES_CODE:
              ehr_consent = True
          elif code.value == CABOR_SIGNATURE_QUESTION_CODE:
            if answer.valueUri or answer.valueString:
              # TODO: validate the URI? [DA-326]
              if not participant_summary.consentForCABoR:
                participant_summary.consentForCABoR = True
                participant_summary.consentForCABoRTime = questionnaire_response.created
                something_changed = True

    # If race was provided in the response in one or more answers, set the new value.
    if race_code_ids:
      race_codes = [code_dao.get(code_id) for code_id in race_code_ids]
      race = get_race(race_codes)
      if race != participant_summary.race:
        participant_summary.race = race
        something_changed = True

    # Set summary fields to SUBMITTED for questionnaire concepts that are found in
    # QUESTIONNAIRE_MODULE_CODE_TO_FIELD
    module_changed = False
    for concept in questionnaire_history.concepts:
      code = code_map.get(concept.codeId)
      if code:
        summary_field = QUESTIONNAIRE_MODULE_CODE_TO_FIELD.get(code.value)
        if summary_field:
          new_status = QuestionnaireStatus.SUBMITTED
          if code.value == CONSENT_FOR_ELECTRONIC_HEALTH_RECORDS_MODULE and not ehr_consent:
            new_status = QuestionnaireStatus.SUBMITTED_NO_CONSENT
          if getattr(participant_summary, summary_field) != new_status:
            setattr(participant_summary, summary_field, new_status)
            setattr(participant_summary, summary_field + 'Time', questionnaire_response.created)
            something_changed = True
            module_changed = True
    if module_changed:
      participant_summary.numCompletedBaselinePPIModules = \
          count_completed_baseline_ppi_modules(participant_summary)
      participant_summary.numCompletedPPIModules = \
          count_completed_ppi_modules(participant_summary)

    if something_changed:
      first_last_email = (
          participant_summary.firstName, participant_summary.lastName, participant_summary.email)
      if not all(first_last_email):
        raise BadRequest(
            'First name (%s), last name (%s), and email address (%s) required for consenting.'
            % tuple(['present' if part else 'missing' for part in first_last_email]))
      ParticipantSummaryDao().update_enrollment_status(participant_summary)
      participant_summary.lastModified = clock.CLOCK.now()
      session.merge(participant_summary)