class ParticipantSummaryDao(UpdatableDao):
    def __init__(self):
        super(ParticipantSummaryDao,
              self).__init__(ParticipantSummary,
                             order_by_ending=_ORDER_BY_ENDING)
        self.hpo_dao = HPODao()
        self.code_dao = CodeDao()

    def get_id(self, obj):
        return obj.participantId

    def get_by_email(self, email):
        with self.session() as session:
            return session.query(ParticipantSummary).filter(
                ParticipantSummary.email == email).all()

    def _validate_update(self, session, obj, existing_obj):
        """Participant summaries don't have a version value; drop it from validation logic."""
        if not existing_obj:
            raise NotFound('%s with id %s does not exist' %
                           (self.model_type.__name__, id))

    def _has_withdrawn_filter(self, query):
        for field_filter in query.field_filters:
            if (field_filter.field_name == 'withdrawalStatus'
                    and field_filter.value == WithdrawalStatus.NO_USE):
                return True
            if field_filter.field_name == 'withdrawalTime' and field_filter.value is not None:
                return True
        return False

    def _get_non_withdrawn_filter_field(self, query):
        """Returns the first field referenced in query filters which isn't in
    WITHDRAWN_PARTICIPANT_FIELDS."""
        for field_filter in query.field_filters:
            if not field_filter.field_name in WITHDRAWN_PARTICIPANT_FIELDS:
                return field_filter.field_name
        return None

    def _initialize_query(self, session, query_def):
        non_withdrawn_field = self._get_non_withdrawn_filter_field(query_def)
        if self._has_withdrawn_filter(query_def):
            if non_withdrawn_field:
                raise BadRequest(
                    "Can't query on %s for withdrawn participants" %
                    non_withdrawn_field)
            # When querying for withdrawn participants, ensure that the only fields being filtered on or
            # ordered by are in WITHDRAWN_PARTICIPANT_FIELDS.
            return super(ParticipantSummaryDao,
                         self)._initialize_query(session, query_def)
        else:
            query = super(ParticipantSummaryDao,
                          self)._initialize_query(session, query_def)
            if non_withdrawn_field:
                # When querying on fields that aren't available for withdrawn participants,
                # ensure that we only return participants
                # who have not withdrawn or withdrew in the past 48 hours.
                withdrawn_visible_start = clock.CLOCK.now(
                ) - WITHDRAWN_PARTICIPANT_VISIBILITY_TIME
                return query.filter(
                    or_(
                        ParticipantSummary.withdrawalStatus !=
                        WithdrawalStatus.NO_USE,
                        ParticipantSummary.withdrawalTime >=
                        withdrawn_visible_start))
            else:
                # When querying on fields that are available for withdrawn participants, return everybody;
                # withdrawn participants will have all but WITHDRAWN_PARTICIPANT_FIELDS cleared out 48
                # hours after withdrawing.
                return query

    def _get_order_by_ending(self, query):
        if self._has_withdrawn_filter(query):
            return _WITHDRAWN_ORDER_BY_ENDING
        return self.order_by_ending

    def _add_order_by(self, query, order_by, field_names, fields):
        if order_by.field_name in _CODE_FILTER_FIELDS:
            return super(ParticipantSummaryDao, self)._add_order_by(
                query, OrderBy(order_by.field_name + 'Id', order_by.ascending),
                field_names, fields)
        return super(ParticipantSummaryDao,
                     self)._add_order_by(query, order_by, field_names, fields)

    def make_query_filter(self, field_name, value):
        """Handle HPO and code values when parsing filter values."""
        if field_name == 'hpoId':
            hpo = self.hpo_dao.get_by_name(value)
            if not hpo:
                raise BadRequest('No HPO found with name %s' % value)
            return super(ParticipantSummaryDao,
                         self).make_query_filter(field_name, hpo.hpoId)
        if field_name in _CODE_FILTER_FIELDS:
            if value == UNSET:
                return super(ParticipantSummaryDao,
                             self).make_query_filter(field_name + 'Id', None)
            # Note: we do not at present support querying for UNMAPPED code values.
            code = self.code_dao.get_code(PPI_SYSTEM, value)
            if not code:
                raise BadRequest('No code found: %s' % value)
            return super(ParticipantSummaryDao,
                         self).make_query_filter(field_name + 'Id',
                                                 code.codeId)
        return super(ParticipantSummaryDao,
                     self).make_query_filter(field_name, value)

    def update_from_biobank_stored_samples(self, participant_id=None):
        """Rewrites sample-related summary data. Call this after updating BiobankStoredSamples.
    If participant_id is provided, only that participant will have their summary updated."""
        baseline_tests_sql, baseline_tests_params = get_sql_and_params_for_array(
            config.getSettingList(config.BASELINE_SAMPLE_TEST_CODES),
            'baseline')
        dna_tests_sql, dna_tests_params = get_sql_and_params_for_array(
            config.getSettingList(config.DNA_SAMPLE_TEST_CODES), 'dna')
        sample_sql, sample_params = _get_sample_sql_and_params()
        sql = """
    UPDATE
      participant_summary
    SET
      num_baseline_samples_arrived = (
        SELECT
          COUNT(*)
        FROM
          biobank_stored_sample
        WHERE
          biobank_stored_sample.biobank_id = participant_summary.biobank_id
          AND biobank_stored_sample.test IN %s
      ),
      samples_to_isolate_dna = (
          CASE WHEN EXISTS(SELECT * FROM biobank_stored_sample
                           WHERE biobank_stored_sample.biobank_id = participant_summary.biobank_id
                           AND biobank_stored_sample.test IN %s)
          THEN :received ELSE :unset END
      ) %s""" % (baseline_tests_sql, dna_tests_sql, sample_sql)
        params = {
            'received': int(SampleStatus.RECEIVED),
            'unset': int(SampleStatus.UNSET)
        }
        params.update(baseline_tests_params)
        params.update(dna_tests_params)
        params.update(sample_params)
        enrollment_status_params = {
            'submitted': int(QuestionnaireStatus.SUBMITTED),
            'num_baseline_ppi_modules': self._get_num_baseline_ppi_modules(),
            'completed': int(PhysicalMeasurementsStatus.COMPLETED),
            'received': int(SampleStatus.RECEIVED),
            'full_participant': int(EnrollmentStatus.FULL_PARTICIPANT),
            'member': int(EnrollmentStatus.MEMBER),
            'interested': int(EnrollmentStatus.INTERESTED)
        }
        enrollment_status_sql = _ENROLLMENT_STATUS_SQL
        # If participant_id is provided, add the participant ID filter to both update statements.
        if participant_id:
            sql += _PARTICIPANT_ID_FILTER
            params['participant_id'] = participant_id
            enrollment_status_sql += _PARTICIPANT_ID_FILTER
            enrollment_status_params['participant_id'] = participant_id

        with self.session() as session:
            session.execute(sql, params)
            session.execute(enrollment_status_sql, enrollment_status_params)

    def _get_num_baseline_ppi_modules(self):
        return len(
            config.getSettingList(config.BASELINE_PPI_QUESTIONNAIRE_FIELDS))

    def update_enrollment_status(self, summary):
        """Updates the enrollment status field on the provided participant summary to
    the correct value based on the other fields on it. Called after
    a questionnaire response or physical measurements are submitted."""
        consent = (summary.consentForStudyEnrollment
                   == QuestionnaireStatus.SUBMITTED
                   and summary.consentForElectronicHealthRecords
                   == QuestionnaireStatus.SUBMITTED)
        enrollment_status = self.calculate_enrollment_status(
            consent, summary.numCompletedBaselinePPIModules,
            summary.physicalMeasurementsStatus, summary.samplesToIsolateDNA)
        summary.enrollment_status = enrollment_status

    def calculate_enrollment_status(self, consent_for_study_enrollment_and_ehr,
                                    num_completed_baseline_ppi_modules,
                                    physical_measurements_status,
                                    samples_to_isolate_dna):
        if consent_for_study_enrollment_and_ehr:
            if (num_completed_baseline_ppi_modules
                    == self._get_num_baseline_ppi_modules()
                    and physical_measurements_status
                    == PhysicalMeasurementsStatus.COMPLETED
                    and samples_to_isolate_dna == SampleStatus.RECEIVED):
                return EnrollmentStatus.FULL_PARTICIPANT
            return EnrollmentStatus.MEMBER
        return EnrollmentStatus.INTERESTED

    def to_client_json(self, model):
        result = model.asdict()
        # Participants that withdrew more than 48 hours ago should have fields other than
        # WITHDRAWN_PARTICIPANT_FIELDS cleared.
        if (model.withdrawalStatus == WithdrawalStatus.NO_USE
                and model.withdrawalTime <
                clock.CLOCK.now() - WITHDRAWN_PARTICIPANT_VISIBILITY_TIME):
            result = {k: result.get(k) for k in WITHDRAWN_PARTICIPANT_FIELDS}

        result['participantId'] = to_client_participant_id(model.participantId)
        biobank_id = result.get('biobankId')
        if biobank_id:
            result['biobankId'] = to_client_biobank_id(biobank_id)
        date_of_birth = result.get('dateOfBirth')
        if date_of_birth:
            result['ageRange'] = get_bucketed_age(date_of_birth,
                                                  clock.CLOCK.now())
        else:
            result['ageRange'] = UNSET
        format_json_hpo(result, self.hpo_dao, 'hpoId')
        _initialize_field_type_sets()
        for fieldname in _DATE_FIELDS:
            format_json_date(result, fieldname)
        for fieldname in _CODE_FIELDS:
            format_json_code(result, self.code_dao, fieldname)
        for fieldname in _ENUM_FIELDS:
            format_json_enum(result, fieldname)
        if (model.withdrawalStatus == WithdrawalStatus.NO_USE
                or model.suspensionStatus == SuspensionStatus.NO_CONTACT):
            result['recontactMethod'] = 'NO_CONTACT'
        # Strip None values.
        result = {k: v for k, v in result.iteritems() if v is not None}

        return result
 def __init__(self):
     super(ParticipantSummaryDao,
           self).__init__(ParticipantSummary,
                          order_by_ending=_ORDER_BY_ENDING)
     self.hpo_dao = HPODao()
     self.code_dao = CodeDao()
class ParticipantSummaryDao(UpdatableDao):
    def __init__(self):
        super(ParticipantSummaryDao,
              self).__init__(ParticipantSummary,
                             order_by_ending=_ORDER_BY_ENDING)
        self.hpo_dao = HPODao()
        self.code_dao = CodeDao()
        self.site_dao = SiteDao()
        self.organization_dao = OrganizationDao()

    def get_id(self, obj):
        return obj.participantId

    def _validate_update(self, session, obj, existing_obj):  # pylint: disable=unused-argument
        """Participant summaries don't have a version value; drop it from validation logic."""
        if not existing_obj:
            raise NotFound('%s with id %s does not exist' %
                           (self.model_type.__name__, id))

    def _has_withdrawn_filter(self, query):
        for field_filter in query.field_filters:
            if (field_filter.field_name == 'withdrawalStatus'
                    and field_filter.value == WithdrawalStatus.NO_USE):
                return True
            if field_filter.field_name == 'withdrawalTime' and field_filter.value is not None:
                return True
        return False

    def _get_non_withdrawn_filter_field(self, query):
        """Returns the first field referenced in query filters which isn't in
    WITHDRAWN_PARTICIPANT_FIELDS."""
        for field_filter in query.field_filters:
            if not field_filter.field_name in WITHDRAWN_PARTICIPANT_FIELDS:
                return field_filter.field_name
        return None

    def _initialize_query(self, session, query_def):
        non_withdrawn_field = self._get_non_withdrawn_filter_field(query_def)
        if self._has_withdrawn_filter(query_def):
            if non_withdrawn_field:
                raise BadRequest(
                    "Can't query on %s for withdrawn participants" %
                    non_withdrawn_field)
            # When querying for withdrawn participants, ensure that the only fields being filtered on or
            # ordered by are in WITHDRAWN_PARTICIPANT_FIELDS.
            return super(ParticipantSummaryDao,
                         self)._initialize_query(session, query_def)
        else:
            query = super(ParticipantSummaryDao,
                          self)._initialize_query(session, query_def)
            if non_withdrawn_field:
                # When querying on fields that aren't available for withdrawn participants,
                # ensure that we only return participants
                # who have not withdrawn or withdrew in the past 48 hours.
                withdrawn_visible_start = clock.CLOCK.now(
                ) - WITHDRAWN_PARTICIPANT_VISIBILITY_TIME
                return query.filter(
                    or_(
                        ParticipantSummary.withdrawalStatus !=
                        WithdrawalStatus.NO_USE,
                        ParticipantSummary.withdrawalTime >=
                        withdrawn_visible_start))
            else:
                # When querying on fields that are available for withdrawn participants, return everybody;
                # withdrawn participants will have all but WITHDRAWN_PARTICIPANT_FIELDS cleared out 48
                # hours after withdrawing.
                return query

    def _get_order_by_ending(self, query):
        if self._has_withdrawn_filter(query):
            return _WITHDRAWN_ORDER_BY_ENDING
        return self.order_by_ending

    def _add_order_by(self, query, order_by, field_names, fields):
        if order_by.field_name in _CODE_FILTER_FIELDS:
            return super(ParticipantSummaryDao, self)._add_order_by(
                query, OrderBy(order_by.field_name + 'Id', order_by.ascending),
                field_names, fields)
        return super(ParticipantSummaryDao,
                     self)._add_order_by(query, order_by, field_names, fields)

    def make_query_filter(self, field_name, value):
        """Handle HPO and code values when parsing filter values."""
        if field_name == 'biobankId':
            value = from_client_biobank_id(value, log_exception=True)
        if field_name == 'hpoId' or field_name == 'awardee':
            hpo = self.hpo_dao.get_by_name(value)
            if not hpo:
                raise BadRequest('No HPO found with name %s' % value)
            if field_name == 'awardee':
                field_name = 'hpoId'
            return super(ParticipantSummaryDao,
                         self).make_query_filter(field_name, hpo.hpoId)
        if field_name == 'organization':
            organization = self.organization_dao.get_by_external_id(value)
            if not organization:
                raise BadRequest('No organization found with name %s' % value)
            return super(ParticipantSummaryDao,
                         self).make_query_filter(field_name + 'Id',
                                                 organization.organizationId)
        if field_name in _SITE_FIELDS:
            if value == UNSET:
                return super(ParticipantSummaryDao,
                             self).make_query_filter(field_name + 'Id', None)
            site = self.site_dao.get_by_google_group(value)
            if not site:
                raise BadRequest('No site found with google group %s' % value)
            return super(ParticipantSummaryDao,
                         self).make_query_filter(field_name + 'Id',
                                                 site.siteId)
        if field_name in _CODE_FILTER_FIELDS:
            if value == UNSET:
                return super(ParticipantSummaryDao,
                             self).make_query_filter(field_name + 'Id', None)
            # Note: we do not at present support querying for UNMAPPED code values.
            code = self.code_dao.get_code(PPI_SYSTEM, value)
            if not code:
                raise BadRequest('No code found: %s' % value)
            return super(ParticipantSummaryDao,
                         self).make_query_filter(field_name + 'Id',
                                                 code.codeId)
        return super(ParticipantSummaryDao,
                     self).make_query_filter(field_name, value)

    def update_from_biobank_stored_samples(self, participant_id=None):
        """Rewrites sample-related summary data. Call this after updating BiobankStoredSamples.
    If participant_id is provided, only that participant will have their summary updated."""
        now = clock.CLOCK.now()
        sample_sql, sample_params = _get_sample_sql_and_params(now)

        baseline_tests_sql, baseline_tests_params = _get_baseline_sql_and_params(
        )
        dna_tests_sql, dna_tests_params = _get_dna_isolates_sql_and_params()

        sample_status_time_sql = _get_sample_status_time_sql_and_params()
        sample_status_time_params = {}

        counts_sql = """
    UPDATE
      participant_summary
    SET
      num_baseline_samples_arrived = {baseline_tests_sql},
      samples_to_isolate_dna = {dna_tests_sql},
      last_modified = :now
    WHERE
      num_baseline_samples_arrived != {baseline_tests_sql} OR
      samples_to_isolate_dna != {dna_tests_sql}
    """.format(baseline_tests_sql=baseline_tests_sql,
               dna_tests_sql=dna_tests_sql)
        counts_params = {'now': now}
        counts_params.update(baseline_tests_params)
        counts_params.update(dna_tests_params)

        enrollment_status_sql = _ENROLLMENT_STATUS_SQL
        enrollment_status_params = {
            'submitted': int(QuestionnaireStatus.SUBMITTED),
            'unset': int(QuestionnaireStatus.UNSET),
            'num_baseline_ppi_modules': self._get_num_baseline_ppi_modules(),
            'completed': int(PhysicalMeasurementsStatus.COMPLETED),
            'received': int(SampleStatus.RECEIVED),
            'full_participant': int(EnrollmentStatus.FULL_PARTICIPANT),
            'member': int(EnrollmentStatus.MEMBER),
            'interested': int(EnrollmentStatus.INTERESTED),
            'now': now
        }

        # If participant_id is provided, add the participant ID filter to all update statements.
        if participant_id:
            sample_sql += ' AND participant_id = :participant_id'
            sample_params['participant_id'] = participant_id
            counts_sql += ' AND participant_id = :participant_id'
            counts_params['participant_id'] = participant_id
            enrollment_status_sql += ' AND participant_id = :participant_id'
            enrollment_status_params['participant_id'] = participant_id
            sample_status_time_sql += ' AND a.participant_id = :participant_id'
            sample_status_time_params['participant_id'] = participant_id

        sample_sql = replace_null_safe_equals(sample_sql)
        counts_sql = replace_null_safe_equals(counts_sql)

        with self.session() as session:
            session.execute(sample_sql, sample_params)
            session.execute(counts_sql, counts_params)
            session.execute(enrollment_status_sql, enrollment_status_params)
            # TODO: Change this to the optimized sql in _update_dv_stored_samples()
            session.execute(sample_status_time_sql, sample_status_time_params)

    def _get_num_baseline_ppi_modules(self):
        return len(
            config.getSettingList(config.BASELINE_PPI_QUESTIONNAIRE_FIELDS))

    def update_enrollment_status(self, summary):
        """Updates the enrollment status field on the provided participant summary to
    the correct value based on the other fields on it. Called after
    a questionnaire response or physical measurements are submitted."""
        consent = (summary.consentForStudyEnrollment == QuestionnaireStatus.SUBMITTED and
                   summary.consentForElectronicHealthRecords == QuestionnaireStatus.SUBMITTED) or \
                  (summary.consentForStudyEnrollment == QuestionnaireStatus.SUBMITTED and
                   summary.consentForElectronicHealthRecords is None and
                   summary.consentForDvElectronicHealthRecordsSharing == QuestionnaireStatus.SUBMITTED)

        enrollment_status = self.calculate_enrollment_status(
            consent, summary.numCompletedBaselinePPIModules,
            summary.physicalMeasurementsStatus, summary.samplesToIsolateDNA)
        summary.enrollmentStatusMemberTime = self.calculate_member_time(
            consent, summary)
        summary.enrollmentStatusCoreOrderedSampleTime = self.calculate_core_ordered_sample_time(
            consent, summary)
        summary.enrollmentStatusCoreStoredSampleTime = self.calculate_core_stored_sample_time(
            consent, summary)

        # Update last modified date if status changes
        if summary.enrollmentStatus != enrollment_status:
            summary.lastModified = clock.CLOCK.now()

        summary.enrollmentStatus = enrollment_status

    def calculate_enrollment_status(self, consent,
                                    num_completed_baseline_ppi_modules,
                                    physical_measurements_status,
                                    samples_to_isolate_dna):
        if consent:
            if (num_completed_baseline_ppi_modules
                    == self._get_num_baseline_ppi_modules()
                    and physical_measurements_status
                    == PhysicalMeasurementsStatus.COMPLETED
                    and samples_to_isolate_dna == SampleStatus.RECEIVED):
                return EnrollmentStatus.FULL_PARTICIPANT
            return EnrollmentStatus.MEMBER
        return EnrollmentStatus.INTERESTED

    def calculate_member_time(self, consent, participant_summary):
        if consent and participant_summary.enrollmentStatusMemberTime is not None:
            return participant_summary.enrollmentStatusMemberTime
        elif consent:
            if participant_summary.consentForElectronicHealthRecords is None and \
              participant_summary.consentForDvElectronicHealthRecordsSharing == \
              QuestionnaireStatus.SUBMITTED:
                return participant_summary.consentForDvElectronicHealthRecordsSharingTime
            return participant_summary.consentForElectronicHealthRecordsTime
        else:
            return None

    def calculate_core_stored_sample_time(self, consent, participant_summary):
        if consent and \
          participant_summary.numCompletedBaselinePPIModules == \
          self._get_num_baseline_ppi_modules() and \
          participant_summary.physicalMeasurementsStatus == PhysicalMeasurementsStatus.COMPLETED and \
          participant_summary.samplesToIsolateDNA == SampleStatus.RECEIVED:

            max_core_sample_time = self.calculate_max_core_sample_time(
                participant_summary, field_name_prefix='sampleStatus')

            if max_core_sample_time and participant_summary.enrollmentStatusCoreStoredSampleTime:
                return participant_summary.enrollmentStatusCoreStoredSampleTime
            else:
                return max_core_sample_time
        else:
            return None

    def calculate_core_ordered_sample_time(self, consent, participant_summary):
        if consent and \
          participant_summary.numCompletedBaselinePPIModules == \
          self._get_num_baseline_ppi_modules() and \
          participant_summary.physicalMeasurementsStatus == PhysicalMeasurementsStatus.COMPLETED:

            max_core_sample_time = self.calculate_max_core_sample_time(
                participant_summary, field_name_prefix='sampleOrderStatus')

            if max_core_sample_time and participant_summary.enrollmentStatusCoreOrderedSampleTime:
                return participant_summary.enrollmentStatusCoreOrderedSampleTime
            else:
                return max_core_sample_time
        else:
            return None

    def calculate_max_core_sample_time(self,
                                       participant_summary,
                                       field_name_prefix='sampleStatus'):

        keys = [
            field_name_prefix + '%sTime' % test
            for test in config.getSettingList(config.DNA_SAMPLE_TEST_CODES)
        ]
        sample_time_list = \
          [v for k, v in participant_summary if k in keys and v is not None]

        sample_time = min(sample_time_list) if sample_time_list else None

        if sample_time is not None:
            return max([
                sample_time, participant_summary.enrollmentStatusMemberTime,
                participant_summary.questionnaireOnTheBasicsTime,
                participant_summary.questionnaireOnLifestyleTime,
                participant_summary.questionnaireOnOverallHealthTime,
                participant_summary.physicalMeasurementsFinalizedTime
            ])
        else:
            return None

    def calculate_distinct_visits(self,
                                  pid,
                                  finalized_time,
                                  id_,
                                  amendment=False):
        """ Participants may get PM or biobank samples on same day. This should be considered as
    a single visit in terms of program payment to participant.
    return Boolean: true if there has not been an order on same date."""
        from dao.biobank_order_dao import BiobankOrderDao
        from dao.physical_measurements_dao import PhysicalMeasurementsDao

        day_has_order, day_has_measurement = False, False
        existing_orders = BiobankOrderDao().get_biobank_orders_for_participant(
            pid)
        ordered_samples = BiobankOrderDao(
        ).get_ordered_samples_for_participant(pid)
        existing_measurements = PhysicalMeasurementsDao(
        ).get_measuremnets_for_participant(pid)

        order_id_to_finalized_date = {
            sample.biobankOrderId: sample.finalized.date()
            for sample in ordered_samples if sample.finalized
        }

        if existing_orders and finalized_time:
            for order in existing_orders:
                order_finalized_date = order_id_to_finalized_date.get(
                    order.biobankOrderId)
                if order_finalized_date == finalized_time.date() and order.biobankOrderId != id_ and \
                  order.orderStatus != BiobankOrderStatus.CANCELLED:
                    day_has_order = True
                elif order.biobankOrderId == id_ and order.orderStatus == BiobankOrderStatus.AMENDED:
                    day_has_order = True
        elif not finalized_time and amendment:
            day_has_order = True

        if existing_measurements and finalized_time:
            for measurement in existing_measurements:
                if not measurement.finalized:
                    continue
                if measurement.finalized.date() == finalized_time.date() and measurement.physicalMeasurementsId\
                  != id_:
                    day_has_measurement = True

        is_distinct_visit = not (day_has_order or day_has_measurement)
        return is_distinct_visit

    def to_client_json(self, model):
        result = model.asdict()
        # Participants that withdrew more than 48 hours ago should have fields other than
        # WITHDRAWN_PARTICIPANT_FIELDS cleared.
        if (model.withdrawalStatus == WithdrawalStatus.NO_USE and
            (model.withdrawalTime is None or model.withdrawalTime <
             clock.CLOCK.now() - WITHDRAWN_PARTICIPANT_VISIBILITY_TIME)):
            result = {k: result.get(k) for k in WITHDRAWN_PARTICIPANT_FIELDS}

        elif model.withdrawalStatus != WithdrawalStatus.NO_USE and \
          model.suspensionStatus == SuspensionStatus.NO_CONTACT:
            for i in SUSPENDED_PARTICIPANT_FIELDS:
                result[i] = UNSET

        result['participantId'] = to_client_participant_id(model.participantId)
        biobank_id = result.get('biobankId')
        if biobank_id:
            result['biobankId'] = to_client_biobank_id(biobank_id)
        date_of_birth = result.get('dateOfBirth')
        if date_of_birth:
            result['ageRange'] = get_bucketed_age(date_of_birth,
                                                  clock.CLOCK.now())
        else:
            result['ageRange'] = UNSET

        if result.get('primaryLanguage') is None:
            result['primaryLanguage'] = UNSET

        if 'organizationId' in result:
            result['organization'] = result['organizationId']
            del result['organizationId']
            format_json_org(result, self.organization_dao, 'organization')

        format_json_hpo(result, self.hpo_dao, 'hpoId')
        result['awardee'] = result['hpoId']
        _initialize_field_type_sets()
        for fieldname in _DATE_FIELDS:
            format_json_date(result, fieldname)
        for fieldname in _CODE_FIELDS:
            format_json_code(result, self.code_dao, fieldname)
        for fieldname in _ENUM_FIELDS:
            format_json_enum(result, fieldname)
        for fieldname in _SITE_FIELDS:
            format_json_site(result, self.site_dao, fieldname)
        if (model.withdrawalStatus == WithdrawalStatus.NO_USE
                or model.suspensionStatus == SuspensionStatus.NO_CONTACT):
            result['recontactMethod'] = 'NO_CONTACT'
        # Strip None values.
        result = {k: v for k, v in result.iteritems() if v is not None}

        return result

    def _decode_token(self, query_def, fields):
        """ If token exists in participant_summary api, decode and use lastModified to add a buffer
    of 60 seconds. This ensures when a _sync link is used no one is missed. This will return
    at a minimum, the last participant and any more that have been modified in the previous 60
    seconds. Duplicate participants returned should be handled on the client side."""
        decoded_vals = super(ParticipantSummaryDao,
                             self)._decode_token(query_def, fields)
        if query_def.order_by and (query_def.order_by.field_name
                                   == 'lastModified'
                                   and query_def.always_return_token is True
                                   and query_def.backfill_sync is True):
            decoded_vals[0] = decoded_vals[0] - datetime.timedelta(
                seconds=config.LAST_MODIFIED_BUFFER_SECONDS)

        return decoded_vals

    @staticmethod
    def update_ehr_status(summary, update_time):
        summary.ehrStatus = EhrStatus.PRESENT
        if not summary.ehrReceiptTime:
            summary.ehrReceiptTime = update_time
        summary.ehrUpdateTime = update_time
        return summary
  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)
    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)
      participant = ParticipantDao().validate_participant_reference(session, questionnaire_response)
      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:
              # 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)
      session.merge(participant_summary)
 def setUp(self):
     super(CodeDaoTest, self).setUp()
     self.code_book_dao = CodeBookDao()
     self.code_dao = CodeDao()
     self.code_history_dao = CodeHistoryDao()
def _query_and_write_reports(exporter, now, report_type, path_received,
                             path_missing, path_modified, path_withdrawals):
    """Runs the reconciliation MySQL queries and writes result rows to the given CSV writers.

  Note that due to syntax differences, the query runs on MySQL only (not SQLite in unit tests).
  """

    report_cover_range = 10
    if report_type == 'monthly':
        report_cover_range = 60

    # Gets all sample/order pairs where everything arrived, within the past n days.
    received_predicate = lambda result: (
        result[_RECEIVED_TEST_INDEX] and result[_SENT_COUNT_INDEX] <=
        result[_RECEIVED_COUNT_INDEX] and in_past_n_days(
            result, now, report_cover_range))

    # Gets samples or orders where something has gone missing within the past n days, and if an order
    # was placed, it was placed at least 36 hours ago.
    missing_predicate = lambda result: (
        (result[_SENT_COUNT_INDEX] != result[_RECEIVED_COUNT_INDEX] or
         (result[_SENT_FINALIZED_INDEX] and not result[_RECEIVED_TEST_INDEX]
          )) and in_past_n_days(result,
                                now,
                                report_cover_range,
                                ordered_before=now - _THIRTY_SIX_HOURS_AGO) and
        result[_EDITED_CANCELLED_RESTORED_STATUS_FLAG_INDEX] != 'cancelled')

    # Gets samples or orders where something has modified within the past n days.
    modified_predicate = lambda result: (result[
        _EDITED_CANCELLED_RESTORED_STATUS_FLAG_INDEX] and in_past_n_days(
            result, now, report_cover_range))

    code_dao = CodeDao()
    race_question_code = code_dao.get_code(PPI_SYSTEM, RACE_QUESTION_CODE)
    native_american_race_code = code_dao.get_code(PPI_SYSTEM, RACE_AIAN_CODE)

    # break into three steps to avoid OOM issue
    report_paths = [path_received, path_missing, path_modified]
    report_predicates = [
        received_predicate, missing_predicate, modified_predicate
    ]

    for report_path, report_predicate in zip(report_paths, report_predicates):
        with exporter.open_writer(report_path,
                                  report_predicate) as report_writer:
            exporter.run_export_with_writer(
                report_writer,
                replace_isodate(_RECONCILIATION_REPORT_SQL), {
                    'race_question_code_id':
                    race_question_code.codeId,
                    'native_american_race_code_id':
                    native_american_race_code.codeId,
                    'biobank_id_prefix':
                    get_biobank_id_prefix(),
                    'pmi_ops_system':
                    _PMI_OPS_SYSTEM,
                    'kit_id_system':
                    _KIT_ID_SYSTEM,
                    'tracking_number_system':
                    _TRACKING_NUMBER_SYSTEM,
                    'n_days_ago':
                    now - datetime.timedelta(days=(report_cover_range + 1))
                },
                backup=True)

    # Now generate the withdrawal report, within the past n days.
    exporter.run_export(
        path_withdrawals,
        replace_isodate(_WITHDRAWAL_REPORT_SQL), {
            'race_question_code_id': race_question_code.codeId,
            'native_american_race_code_id': native_american_race_code.codeId,
            'n_days_ago': now - datetime.timedelta(days=report_cover_range),
            'biobank_id_prefix': get_biobank_id_prefix()
        },
        backup=True)
Ejemplo n.º 7
0
 def setUp(self):
     super(CheckPpiDataApiTest, self).setUp()
     CodeDao().insert(email_code())
     CodeDao().insert(first_name_code())
def _get_validation_result(key, codes_to_answers):
    result = _ValidationResult()
    with ParticipantSummaryDao().session() as session:
        # Get summary by email or phone
        if '@' not in key:
            summaries = session.query(ParticipantSummary).\
                            filter(ParticipantSummary.loginPhoneNumber == key).all()
        else:
            summaries = session.query(ParticipantSummary).\
                            filter(ParticipantSummary.email == key).all()

    if not summaries:
        result.add_error('No ParticipantSummary found for %r.' % key)
        return result
    if len(summaries) > 1:
        result.add_error('%d ParticipantSummary values found for %r.' %
                         (len(summaries), key))
        return result
    participant_id = summaries[0].participantId

    code_dao = CodeDao()
    qra_dao = QuestionnaireResponseAnswerDao()
    with qra_dao.session() as session:
        for code_string, answer_string in codes_to_answers.iteritems():
            result.tests_count += 1

            question_code = code_dao.get_code(PPI_SYSTEM, code_string)
            if not question_code:
                result.add_error(
                    'Could not find question code %r, skipping answer %r.' %
                    (code_string, answer_string))
                continue
            if question_code.codeType != CodeType.QUESTION:
                result.add_error(
                    'Code %r type is %s, not QUESTION; skipping.' %
                    (code_string, question_code.codeType))
                continue

            qras = qra_dao.get_current_answers_for_concepts(
                session, participant_id, [question_code.codeId])
            qra_values = set()
            for qra in qras:
                try:
                    qra_values.add(
                        _get_value_for_qra(qra, question_code, code_dao,
                                           session))
                except ValueError as e:
                    result.add_error(e.message)
                    continue

            if answer_string:
                expected_values = set(
                    _boolean_to_lower(v.strip())
                    for v in answer_string.split('|'))
            else:
                expected_values = set()
            if expected_values != qra_values:
                result.add_error(
                    '%s: Expected %s, found %s.' %
                    (question_code.value, _format_values(expected_values),
                     _format_values(qra_values)))
    return result
Ejemplo n.º 9
0
    def get_metrics_cache_sql(self):

        race_code_dict = {
            'Race_WhatRaceEthnicity': 193,
            'WhatRaceEthnicity_Hispanic': 207,
            'WhatRaceEthnicity_Black': 259,
            'WhatRaceEthnicity_White': 220,
            'WhatRaceEthnicity_AIAN': 252,
            'WhatRaceEthnicity_RaceEthnicityNoneOfThese': 235,
            'WhatRaceEthnicity_Asian': 194,
            'PMI_PreferNotToAnswer': 924,
            'WhatRaceEthnicity_MENA': 274,
            'PMI_Skip': 930,
            'WhatRaceEthnicity_NHPI': 237
        }

        for k in race_code_dict:
            code = CodeDao().get_code(PPI_SYSTEM, k)
            if code is not None:
                race_code_dict[k] = code.codeId

        sql = """
          insert into metrics_race_cache
            SELECT
              :date_inserted as date_inserted,
              hpo_id,
              name AS hpo_name,
              day,
              SUM(American_Indian_Alaska_Native) AS American_Indian_Alaska_Native,
              SUM(Asian) AS Asian,
              SUM(Black_African_American) AS Black_African_American,
              SUM(Middle_Eastern_North_African) AS Middle_Eastern_North_African,
              SUM(Native_Hawaiian_other_Pacific_Islander) AS Native_Hawaiian_other_Pacific_Islander,
              SUM(White) AS White,
              SUM(Hispanic_Latino_Spanish) AS Hispanic_Latino_Spanish,
              SUM(None_Of_These_Fully_Describe_Me) AS None_Of_These_Fully_Describe_Me,
              SUM(Prefer_Not_To_Answer) AS Prefer_Not_To_Answer,
              SUM(Multi_Ancestry) AS Multi_Ancestry,
              SUM(No_Ancestry_Checked) AS No_Ancestry_Checked
              FROM
              (
                SELECT p.hpo_id,
                       hpo.name,
                       day,
                       CASE WHEN WhatRaceEthnicity_AIAN=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS American_Indian_Alaska_Native,
                       CASE WHEN WhatRaceEthnicity_Asian=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS Asian,
                       CASE WHEN WhatRaceEthnicity_Black=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS Black_African_American,
                       CASE WHEN WhatRaceEthnicity_MENA=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS Middle_Eastern_North_African,
                       CASE WHEN WhatRaceEthnicity_NHPI=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS Native_Hawaiian_other_Pacific_Islander,
                       CASE WHEN WhatRaceEthnicity_White=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS White,
                       CASE WHEN WhatRaceEthnicity_Hispanic=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS Hispanic_Latino_Spanish,
                       CASE WHEN WhatRaceEthnicity_RaceEthnicityNoneOfThese=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS None_Of_These_Fully_Describe_Me,
                       CASE WHEN PMI_PreferNotToAnswer=1 AND Number_of_Answer=1 THEN 1 ELSE 0 END AS Prefer_Not_To_Answer,
                       CASE
                         WHEN (WhatRaceEthnicity_Hispanic + WhatRaceEthnicity_Black + WhatRaceEthnicity_White + WhatRaceEthnicity_AIAN + WhatRaceEthnicity_Asian + WhatRaceEthnicity_MENA + WhatRaceEthnicity_NHPI) >
                              1
                           THEN 1
                         ELSE 0
                       END AS Multi_Ancestry,
                       CASE
                         WHEN (PMI_Skip = 1 AND Number_of_Answer=1) OR UNSET = 1
                           THEN 1
                         ELSE 0
                       END AS No_Ancestry_Checked
                FROM (
                       SELECT participant_id,
                              hpo_id,
                              sign_up_time,
                              MAX(WhatRaceEthnicity_Hispanic)                 AS WhatRaceEthnicity_Hispanic,
                              MAX(WhatRaceEthnicity_Black)                    AS WhatRaceEthnicity_Black,
                              MAX(WhatRaceEthnicity_White)                    AS WhatRaceEthnicity_White,
                              MAX(WhatRaceEthnicity_AIAN)                     AS WhatRaceEthnicity_AIAN,
                              MAX(UNSET)                                      AS UNSET,
                              MAX(WhatRaceEthnicity_RaceEthnicityNoneOfThese) AS WhatRaceEthnicity_RaceEthnicityNoneOfThese,
                              MAX(WhatRaceEthnicity_Asian)                    AS WhatRaceEthnicity_Asian,
                              MAX(PMI_PreferNotToAnswer)                      AS PMI_PreferNotToAnswer,
                              MAX(WhatRaceEthnicity_MENA)                     AS WhatRaceEthnicity_MENA,
                              MAX(PMI_Skip)                                   AS PMI_Skip,
                              MAX(WhatRaceEthnicity_NHPI)                     AS WhatRaceEthnicity_NHPI,
                              COUNT(*) as Number_of_Answer
                       FROM (
                              SELECT p.participant_id,
                                     p.hpo_id,
                                     p.sign_up_time,
                                     CASE WHEN q.code_id = {1} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_Hispanic,
                                     CASE WHEN q.code_id = {2} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_Black,
                                     CASE WHEN q.code_id = {3} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_White,
                                     CASE WHEN q.code_id = {4} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_AIAN,
                                     CASE WHEN q.code_id IS NULL THEN 1 ELSE 0 END AS UNSET,
                                     CASE WHEN q.code_id = {5} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_RaceEthnicityNoneOfThese,
                                     CASE WHEN q.code_id = {6} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_Asian,
                                     CASE WHEN q.code_id = {7} THEN 1 ELSE 0 END   AS PMI_PreferNotToAnswer,
                                     CASE WHEN q.code_id = {8} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_MENA,
                                     CASE WHEN q.code_id = {9} THEN 1 ELSE 0 END   AS PMI_Skip,
                                     CASE WHEN q.code_id = {10} THEN 1 ELSE 0 END   AS WhatRaceEthnicity_NHPI
                              FROM participant p
                                     INNER JOIN participant_summary ps ON p.participant_id = ps.participant_id
                                     LEFT JOIN
                                   (
                                     SELECT qr.participant_id, qra.value_code_id as code_id
                                     FROM questionnaire_question qq,
                                          questionnaire_response_answer qra,
                                          questionnaire_response qr
                                     WHERE qq.questionnaire_question_id = qra.question_id
                                       AND qq.code_id = {0}
                                       AND qra.questionnaire_response_id = qr.questionnaire_response_id
                                   ) q ON p.participant_id = q.participant_id
                              WHERE p.hpo_id=:hpo_id AND p.hpo_id <> :test_hpo_id
                                AND (ps.email IS NULL OR NOT ps.email LIKE :test_email_pattern)
                                AND p.withdrawal_status = :not_withdraw
                                AND p.is_ghost_id IS NOT TRUE
                                AND ps.questionnaire_on_the_basics = 1
                            ) x
                       GROUP BY participant_id, hpo_id, sign_up_time
                     ) p,
                     calendar,
                     hpo
                WHERE p.hpo_id = hpo.hpo_id
                  AND calendar.day >= :start_date
                  AND calendar.day <= :end_date
                  AND calendar.day >= Date(p.sign_up_time)
              ) y
              GROUP BY day, hpo_id, name
              ;
        """.format(
            race_code_dict['Race_WhatRaceEthnicity'],
            race_code_dict['WhatRaceEthnicity_Hispanic'],
            race_code_dict['WhatRaceEthnicity_Black'],
            race_code_dict['WhatRaceEthnicity_White'],
            race_code_dict['WhatRaceEthnicity_AIAN'],
            race_code_dict['WhatRaceEthnicity_RaceEthnicityNoneOfThese'],
            race_code_dict['WhatRaceEthnicity_Asian'],
            race_code_dict['PMI_PreferNotToAnswer'],
            race_code_dict['WhatRaceEthnicity_MENA'],
            race_code_dict['PMI_Skip'],
            race_code_dict['WhatRaceEthnicity_NHPI'])
        return sql
Ejemplo n.º 10
0
  def test_insert(self):
    participant_id = self.create_participant()
    questionnaire_id = self.create_questionnaire('questionnaire1.json')
    with open(data_path('questionnaire_response3.json')) as fd:
      resource = json.load(fd)

    # Sending response with the dummy participant id in the file is an error
    self.send_post(_questionnaire_response_url('{participant_id}'), resource,
                   expected_status=httplib.NOT_FOUND)

    # Fixing participant id but not the questionnaire id is also an error
    resource['subject']['reference'] = \
        resource['subject']['reference'].format(participant_id=participant_id)
    self.send_post(_questionnaire_response_url(participant_id), resource,
                   expected_status=httplib.BAD_REQUEST)

    # Fix the reference
    resource['questionnaire']['reference'] = \
        resource['questionnaire']['reference'].format(questionnaire_id=questionnaire_id)

    # Sending the response before the consent is an error.
    self.send_post(_questionnaire_response_url(participant_id), resource,
                   expected_status=httplib.BAD_REQUEST)

    # After consent, the post succeeds
    self.send_consent(participant_id)
    response = self.send_post(_questionnaire_response_url(participant_id), resource)
    resource['id'] = response['id']
    # The resource gets rewritten to include the version
    resource['questionnaire']['reference'] = 'Questionnaire/%s/_history/1' % questionnaire_id
    self.assertJsonResponseMatches(resource, response)

    # Do a get to fetch the questionnaire
    get_response = self.send_get(_questionnaire_response_url(participant_id) + "/" + response['id'])
    self.assertJsonResponseMatches(resource, get_response)

    code_dao = CodeDao()

    # Ensure we didn't create codes in the extra system
    self.assertIsNone(code_dao.get_code(PPI_EXTRA_SYSTEM, 'IgnoreThis'))

    name_of_child = code_dao.get_code("sys", "nameOfChild")
    birth_weight = code_dao.get_code("sys", "birthWeight")
    birth_length = code_dao.get_code("sys", "birthLength")
    vitamin_k_dose_1 = code_dao.get_code("sys", "vitaminKDose1")
    vitamin_k_dose_2 = code_dao.get_code("sys", "vitaminKDose2")
    hep_b_given = code_dao.get_code("sys", "hepBgiven")
    abnormalities_at_birth = code_dao.get_code("sys", "abnormalitiesAtBirth")
    answer_dao = QuestionnaireResponseAnswerDao()
    with answer_dao.session() as session:
      code_ids = [code.codeId for code in
                  [name_of_child, birth_weight, birth_length, vitamin_k_dose_1, vitamin_k_dose_2,
                  hep_b_given, abnormalities_at_birth]]
      current_answers = answer_dao.get_current_answers_for_concepts(session,\
          from_client_participant_id(participant_id), code_ids)
    self.assertEquals(7, len(current_answers))
    questionnaire = QuestionnaireDao().get_with_children(questionnaire_id)
    question_id_to_answer = {answer.questionId : answer for answer in current_answers}
    code_id_to_answer = {question.codeId:
                         question_id_to_answer.get(question.questionnaireQuestionId)
                         for question in questionnaire.questions}
    self.assertEquals("Cathy Jones", code_id_to_answer[name_of_child.codeId].valueString)
    self.assertEquals(3.25, code_id_to_answer[birth_weight.codeId].valueDecimal)
    self.assertEquals(44.3, code_id_to_answer[birth_length.codeId].valueDecimal)
    self.assertEquals(44, code_id_to_answer[birth_length.codeId].valueInteger)
    self.assertEquals(True, code_id_to_answer[hep_b_given.codeId].valueBoolean)
    self.assertEquals(0, code_id_to_answer[abnormalities_at_birth.codeId].valueInteger)
    self.assertEquals(datetime.date(1972, 11, 30),
                      code_id_to_answer[vitamin_k_dose_1.codeId].valueDate)
    self.assertEquals(datetime.datetime(1972, 11, 30, 12, 34, 42),
                      code_id_to_answer[vitamin_k_dose_2.codeId].valueDateTime)
Ejemplo n.º 11
0
    def get_metrics_cache_sql(self):

        gender_code_dict = {
            'GenderIdentity_Woman': 354,
            'GenderIdentity_Man': 356,
            'GenderIdentity_Transgender': 355,
            'PMI_Skip': 930,
            'GenderIdentity_NonBinary': 358,
            'GenderIdentity_AdditionalOptions': 357,
            'PMI_PreferNotToAnswer': 924
        }

        for k in gender_code_dict:
            code = CodeDao().get_code(PPI_SYSTEM, k)
            if code is not None:
                gender_code_dict[k] = code.codeId

        sql = """insert into metrics_gender_cache """
        gender_names = [
            'UNSET', 'Woman', 'Man', 'Transgender', 'PMI_Skip', 'Non-Binary',
            'Other/Additional Options', 'Prefer not to say'
        ]
        gender_conditions = [
            ' ps.gender_identity_id IS NULL ',
            ' ps.gender_identity_id=' +
            str(gender_code_dict['GenderIdentity_Woman']) + ' ',
            ' ps.gender_identity_id=' +
            str(gender_code_dict['GenderIdentity_Man']) + ' ',
            ' ps.gender_identity_id=' +
            str(gender_code_dict['GenderIdentity_Transgender']) + ' ',
            ' ps.gender_identity_id=' + str(gender_code_dict['PMI_Skip']) +
            ' ',
            ' ps.gender_identity_id=' +
            str(gender_code_dict['GenderIdentity_NonBinary']) + ' ',
            ' ps.gender_identity_id=' +
            str(gender_code_dict['GenderIdentity_AdditionalOptions']) + ' ',
            ' ps.gender_identity_id=' +
            str(gender_code_dict['PMI_PreferNotToAnswer']) + ' ',
        ]
        sub_queries = []
        sql_template = """
      SELECT
        :date_inserted AS date_inserted,
        :hpo_id AS hpo_id,
        (SELECT name FROM hpo WHERE hpo_id=:hpo_id) AS hpo_name,
        c.day AS date,
        '{0}' AS gender_name,  
        IFNULL((
          SELECT SUM(results.gender_count)
          FROM
          (
            SELECT DATE(p.sign_up_time) as day,
                   COUNT(*) gender_count
            FROM participant p
                   LEFT JOIN participant_summary ps ON p.participant_id = ps.participant_id
                   LEFT JOIN hpo ON p.hpo_id=hpo.hpo_id
            WHERE p.hpo_id = :hpo_id AND p.hpo_id <> :test_hpo_id
              AND p.is_ghost_id IS NOT TRUE
              AND (ps.email IS NULL OR NOT ps.email LIKE :test_email_pattern)
              AND p.withdrawal_status = :not_withdraw
              AND {1}
            GROUP BY DATE(p.sign_up_time)
          ) AS results
          WHERE results.day <= c.day
        ),0) AS gender_count
      FROM calendar c
      WHERE c.day BETWEEN :start_date AND :end_date
    """
        for gender_name, gender_condition in zip(gender_names,
                                                 gender_conditions):
            sub_query = sql_template.format(gender_name, gender_condition)
            sub_queries.append(sub_query)

        sql += ' union '.join(sub_queries)

        return sql
  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 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)
      if race == Race.SKIPPED:
        race = PMI_SKIP_CODE
      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':
          if answer_code == PMI_SKIP_CODE:
            census_region = PMI_SKIP_CODE
          else:
            # The last two letters of the answer code should be a state abbreviation,
            # e.g. TN, AZ, etc
            state_abbr = answer_code[-2:]
            census_region = census_regions.get(state_abbr, 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)
    if race == Race.SKIPPED:
      race = PMI_SKIP_CODE
    yield (last_participant_id, make_tuple(last_start_time, make_metric(RACE_METRIC, str(race))))
    def setUp(self):
        super(QuestionnaireResponseDaoTest, self).setUp()
        self.code_dao = CodeDao()
        self.participant_dao = ParticipantDao()
        self.questionnaire_dao = QuestionnaireDao()
        self.questionnaire_response_dao = QuestionnaireResponseDao()
        self.questionnaire_response_answer_dao = QuestionnaireResponseAnswerDao(
        )
        self.participant_summary_dao = ParticipantSummaryDao()
        self.CODE_1 = Code(codeId=1,
                           system=PPI_SYSTEM,
                           value=GENDER_IDENTITY_QUESTION_CODE,
                           display=u'c',
                           topic=u'd',
                           codeType=CodeType.QUESTION,
                           mapped=True)
        self.CODE_2 = Code(codeId=2,
                           system='a',
                           value='x',
                           display=u'y',
                           codeType=CodeType.QUESTION,
                           mapped=False)
        self.CODE_3 = Code(codeId=3,
                           system='a',
                           value='c',
                           codeType=CodeType.ANSWER,
                           mapped=True,
                           parentId=1)
        self.CODE_4 = Code(codeId=4,
                           system='a',
                           value='d',
                           codeType=CodeType.ANSWER,
                           mapped=True,
                           parentId=2)
        self.CODE_5 = Code(codeId=5,
                           system='a',
                           value='e',
                           codeType=CodeType.ANSWER,
                           mapped=False,
                           parentId=1)
        self.CODE_6 = Code(codeId=6,
                           system='a',
                           value='f',
                           codeType=CodeType.ANSWER,
                           mapped=True,
                           parentId=1)
        self.MODULE_CODE_7 = Code(codeId=7,
                                  system=PPI_SYSTEM,
                                  value=THE_BASICS_PPI_MODULE,
                                  codeType=CodeType.MODULE,
                                  mapped=True)
        self.CONCEPT_1 = QuestionnaireConcept(codeId=7)
        self.CODE_1_QUESTION_1 = QuestionnaireQuestion(linkId='a',
                                                       codeId=1,
                                                       repeats=False)
        self.CODE_2_QUESTION = QuestionnaireQuestion(linkId='d',
                                                     codeId=2,
                                                     repeats=True)
        # Same code as question 1
        self.CODE_1_QUESTION_2 = QuestionnaireQuestion(linkId='x',
                                                       codeId=1,
                                                       repeats=False)

        self.skip_code = Code(codeId=8,
                              system=PPI_SYSTEM,
                              value=PMI_SKIP_CODE,
                              mapped=True,
                              codeType=CodeType.ANSWER)

        config.override_setting(config.CONSENT_PDF_BUCKET, [_FAKE_BUCKET])
class CodeDaoTest(SqlTestBase):
    def setUp(self):
        super(CodeDaoTest, self).setUp()
        self.code_book_dao = CodeBookDao()
        self.code_dao = CodeDao()
        self.code_history_dao = CodeHistoryDao()

    def test_get_before_insert(self):
        self.assertIsNone(self.code_book_dao.get(1))
        self.assertIsNone(self.code_dao.get(1))
        self.assertIsNone(self.code_history_dao.get(1))

    def test_insert_without_codebook_or_parent(self):
        code = Code(system="a",
                    value="b",
                    display=u"c",
                    topic=u"d",
                    codeType=CodeType.MODULE,
                    mapped=True)
        with FakeClock(TIME):
            self.code_dao.insert(code)

        expected_code = Code(codeId=1,
                             system="a",
                             value="b",
                             display=u"c",
                             topic=u"d",
                             codeType=CodeType.MODULE,
                             mapped=True,
                             created=TIME)
        self.assertEquals(expected_code.asdict(),
                          self.code_dao.get(1).asdict())

        expected_code_history = CodeHistory(codeHistoryId=1,
                                            codeId=1,
                                            system="a",
                                            value="b",
                                            display=u"c",
                                            topic=u"d",
                                            codeType=CodeType.MODULE,
                                            mapped=True,
                                            created=TIME)
        self.assertEquals(expected_code_history.asdict(),
                          self.code_history_dao.get(1).asdict())

    def test_insert_with_codebook_and_parent(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="a")
        with FakeClock(TIME):
            self.code_book_dao.insert(code_book_1)
        expected_code_book = CodeBook(codeBookId=1,
                                      latest=True,
                                      created=TIME,
                                      name="pmi",
                                      version="v1",
                                      system="a")
        self.assertEquals(expected_code_book.asdict(),
                          self.code_book_dao.get(1).asdict())

        code_1 = Code(codeBookId=1,
                      system="a",
                      value="b",
                      display=u"c",
                      topic=u"d",
                      codeType=CodeType.MODULE,
                      mapped=True)
        with FakeClock(TIME_2):
            self.code_dao.insert(code_1)

        expected_code = Code(codeBookId=1,
                             codeId=1,
                             system="a",
                             value="b",
                             display=u"c",
                             topic=u"d",
                             codeType=CodeType.MODULE,
                             mapped=True,
                             created=TIME_2)
        self.assertEquals(expected_code.asdict(),
                          self.code_dao.get(1).asdict())

        expected_code_history = CodeHistory(codeBookId=1,
                                            codeHistoryId=1,
                                            codeId=1,
                                            system="a",
                                            value=u"b",
                                            display=u"c",
                                            topic=u"d",
                                            codeType=CodeType.MODULE,
                                            mapped=True,
                                            created=TIME_2)
        self.assertEquals(expected_code_history.asdict(),
                          self.code_history_dao.get(1).asdict())

        code_2 = Code(codeBookId=1,
                      system="x",
                      value="y",
                      display=u"z",
                      topic=u"q",
                      codeType=CodeType.QUESTION,
                      mapped=False,
                      parentId=1)
        with FakeClock(TIME_3):
            self.code_dao.insert(code_2)

        expected_code_2 = Code(codeBookId=1,
                               codeId=2,
                               system="x",
                               value="y",
                               display=u"z",
                               topic=u"q",
                               codeType=CodeType.QUESTION,
                               mapped=False,
                               created=TIME_3,
                               parentId=1)
        self.assertEquals(expected_code_2.asdict(),
                          self.code_dao.get(2).asdict())

    def test_insert_second_codebook_same_system(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="a")
        with FakeClock(TIME):
            self.code_book_dao.insert(code_book_1)

        code_book_2 = CodeBook(name="pmi", version="v2", system="a")
        with FakeClock(TIME_2):
            self.code_book_dao.insert(code_book_2)

        expected_code_book = CodeBook(codeBookId=1,
                                      latest=False,
                                      created=TIME,
                                      name="pmi",
                                      version="v1",
                                      system="a")
        self.assertEquals(expected_code_book.asdict(),
                          self.code_book_dao.get(1).asdict())

        expected_code_book_2 = CodeBook(codeBookId=2,
                                        latest=True,
                                        created=TIME_2,
                                        name="pmi",
                                        version="v2",
                                        system="a")
        self.assertEquals(expected_code_book_2.asdict(),
                          self.code_book_dao.get(2).asdict())

    def test_insert_second_codebook_different_system(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="a")
        with FakeClock(TIME):
            self.code_book_dao.insert(code_book_1)

        code_book_2 = CodeBook(name="pmi", version="v2", system="b")
        with FakeClock(TIME_2):
            self.code_book_dao.insert(code_book_2)

        expected_code_book = CodeBook(codeBookId=1,
                                      latest=True,
                                      created=TIME,
                                      name="pmi",
                                      version="v1",
                                      system="a")
        self.assertEquals(expected_code_book.asdict(),
                          self.code_book_dao.get(1).asdict())

        expected_code_book_2 = CodeBook(codeBookId=2,
                                        latest=True,
                                        created=TIME_2,
                                        name="pmi",
                                        version="v2",
                                        system="b")
        self.assertEquals(expected_code_book_2.asdict(),
                          self.code_book_dao.get(2).asdict())

    def test_insert_second_codebook_same_system_same_version(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="a")
        self.code_book_dao.insert(code_book_1)

        code_book_2 = CodeBook(name="pmi", version="v1", system="a")
        with self.assertRaises(BadRequest):
            self.code_book_dao.insert(code_book_2)

    def test_update_codes_no_codebook_id(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="c")
        with FakeClock(TIME):
            self.code_book_dao.insert(code_book_1)
        code_1 = Code(codeBookId=1,
                      system="a",
                      value="b",
                      display=u"c",
                      topic=u"d",
                      codeType=CodeType.MODULE,
                      mapped=True)
        with FakeClock(TIME_2):
            self.code_dao.insert(code_1)

        new_code_1 = Code(codeId=1,
                          system="x",
                          value="b",
                          display=u"c",
                          topic=u"d",
                          codeType=CodeType.MODULE,
                          mapped=True)
        with self.assertRaises(BadRequest):
            self.code_dao.update(new_code_1)

    def test_update_codes_same_codebook_id(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="c")
        with FakeClock(TIME):
            self.code_book_dao.insert(code_book_1)
        code_1 = Code(codeBookId=1,
                      system="a",
                      value="b",
                      display=u"c",
                      topic=u"d",
                      codeType=CodeType.MODULE,
                      mapped=True)
        with FakeClock(TIME_2):
            self.code_dao.insert(code_1)

        new_code_1 = Code(codeBookId=1,
                          codeId=1,
                          system="x",
                          value="b",
                          display=u"c",
                          topic=u"d",
                          codeType=CodeType.MODULE,
                          mapped=True)
        with self.assertRaises(BadRequest):
            self.code_dao.update(new_code_1)

    def test_update_codes_new_codebook_id(self):
        code_book_1 = CodeBook(name="pmi", version="v1", system="a")
        with FakeClock(TIME):
            self.code_book_dao.insert(code_book_1)
        code_1 = Code(codeBookId=1,
                      system="a",
                      value="b",
                      display=u"c",
                      topic=u"d",
                      codeType=CodeType.MODULE,
                      mapped=True)
        with FakeClock(TIME_2):
            self.code_dao.insert(code_1)

        code_book_2 = CodeBook(name="pmi", version="v2", system="a")
        with FakeClock(TIME_3):
            self.code_book_dao.insert(code_book_2)

        new_code_1 = Code(codeBookId=2,
                          codeId=1,
                          system="x",
                          value="b",
                          display=u"c",
                          topic=u"d",
                          codeType=CodeType.MODULE,
                          mapped=True)
        with FakeClock(TIME_4):
            self.code_dao.update(new_code_1)

        expected_code = Code(codeBookId=2,
                             codeId=1,
                             system="x",
                             value="b",
                             display=u"c",
                             topic=u"d",
                             codeType=CodeType.MODULE,
                             mapped=True,
                             created=TIME_2)
        self.assertEquals(expected_code.asdict(),
                          self.code_dao.get(1).asdict())

        expected_code_history = CodeHistory(codeBookId=1,
                                            codeHistoryId=1,
                                            codeId=1,
                                            system="a",
                                            value="b",
                                            display=u"c",
                                            topic=u"d",
                                            codeType=CodeType.MODULE,
                                            mapped=True,
                                            created=TIME_2)
        self.assertEquals(expected_code_history.asdict(),
                          self.code_history_dao.get(1).asdict())

        expected_code_history_2 = CodeHistory(codeHistoryId=2,
                                              codeBookId=2,
                                              codeId=1,
                                              system="x",
                                              value="b",
                                              display=u"c",
                                              topic=u"d",
                                              codeType=CodeType.MODULE,
                                              mapped=True,
                                              created=TIME_2)
        self.assertEquals(expected_code_history_2.asdict(),
                          self.code_history_dao.get(2).asdict())

    def test_import_codebook(self):
        answer_1 = _make_concept(u"t1", "Answer", "c1", u"d1")
        answer_2 = _make_concept(u"t2", "Answer", "c2", u"d2")
        answer_3 = _make_concept(u"t2", "Answer", "c3", u"d3")
        question_1 = _make_concept(u"t1", "Question", "q1", u"d4", [answer_1])
        question_2 = _make_concept(u"t2", "Question", "q2", u"d5",
                                   [answer_2, answer_3])
        topic_1 = _make_concept(u"t1", "Topic", "t1", u"d6", [question_1])
        module_1 = _make_concept(u"mt1", "Module Name", "m1", u"d7", [topic_1])
        module_2 = _make_concept(u"mt2", "Module Name", "m2", u"d8",
                                 [question_2])
        system = 'http://blah/foo'
        codebook = {
            'name': 'pmi',
            'version': 'v1',
            'url': system,
            'concept': [module_1, module_2]
        }
        with FakeClock(TIME):
            self.code_book_dao.import_codebook(codebook)

        expectedCodeBook = CodeBook(codeBookId=1,
                                    latest=True,
                                    created=TIME,
                                    name="pmi",
                                    version="v1",
                                    system=system)
        self.assertEquals(expectedCodeBook.asdict(),
                          self.code_book_dao.get(1).asdict())

        expectedModule1 = Code(codeBookId=1,
                               codeId=1,
                               system=system,
                               value="m1",
                               shortValue="m1",
                               display=u"d7",
                               topic=u"mt1",
                               codeType=CodeType.MODULE,
                               mapped=True,
                               created=TIME)
        self.assertEquals(expectedModule1.asdict(),
                          self.code_dao.get(1).asdict())

        expectedModuleHistory1 = CodeHistory(codeHistoryId=1,
                                             codeBookId=1,
                                             codeId=1,
                                             system=system,
                                             value="m1",
                                             shortValue="m1",
                                             display=u"d7",
                                             topic=u"mt1",
                                             codeType=CodeType.MODULE,
                                             mapped=True,
                                             created=TIME)
        self.assertEquals(expectedModuleHistory1.asdict(),
                          self.code_history_dao.get(1).asdict())

        expectedTopic1 = Code(codeBookId=1,
                              codeId=2,
                              system=system,
                              value="t1",
                              shortValue="t1",
                              display=u"d6",
                              topic=u"t1",
                              codeType=CodeType.TOPIC,
                              mapped=True,
                              created=TIME,
                              parentId=1)
        self.assertEquals(expectedTopic1.asdict(),
                          self.code_dao.get(2).asdict())

        expectedQuestion1 = Code(codeBookId=1,
                                 codeId=3,
                                 system=system,
                                 value="q1",
                                 shortValue="q1",
                                 display=u"d4",
                                 topic=u"t1",
                                 codeType=CodeType.QUESTION,
                                 mapped=True,
                                 created=TIME,
                                 parentId=2)
        self.assertEquals(expectedQuestion1.asdict(),
                          self.code_dao.get(3).asdict())

        expectedAnswer1 = Code(codeBookId=1,
                               codeId=4,
                               system=system,
                               value="c1",
                               shortValue="c1",
                               display=u"d1",
                               topic=u"t1",
                               codeType=CodeType.ANSWER,
                               mapped=True,
                               created=TIME,
                               parentId=3)
        self.assertEquals(expectedAnswer1.asdict(),
                          self.code_dao.get(4).asdict())
Ejemplo n.º 16
0
class QuestionnaireResponseDaoTest(FlaskTestBase):
    def setUp(self):
        super(QuestionnaireResponseDaoTest, self).setUp()
        self.code_dao = CodeDao()
        self.participant_dao = ParticipantDao()
        self.questionnaire_dao = QuestionnaireDao()
        self.questionnaire_response_dao = QuestionnaireResponseDao()
        self.questionnaire_response_answer_dao = QuestionnaireResponseAnswerDao(
        )
        self.participant_summary_dao = ParticipantSummaryDao()
        self.CODE_1 = Code(codeId=1,
                           system=PPI_SYSTEM,
                           value=GENDER_IDENTITY_QUESTION_CODE,
                           display=u'c',
                           topic=u'd',
                           codeType=CodeType.QUESTION,
                           mapped=True)
        self.CODE_2 = Code(codeId=2,
                           system='a',
                           value='x',
                           display=u'y',
                           codeType=CodeType.QUESTION,
                           mapped=False)
        self.CODE_3 = Code(codeId=3,
                           system='a',
                           value='c',
                           codeType=CodeType.ANSWER,
                           mapped=True,
                           parentId=1)
        self.CODE_4 = Code(codeId=4,
                           system='a',
                           value='d',
                           codeType=CodeType.ANSWER,
                           mapped=True,
                           parentId=2)
        self.CODE_5 = Code(codeId=5,
                           system='a',
                           value='e',
                           codeType=CodeType.ANSWER,
                           mapped=False,
                           parentId=1)
        self.CODE_6 = Code(codeId=6,
                           system='a',
                           value='f',
                           codeType=CodeType.ANSWER,
                           mapped=True,
                           parentId=1)
        self.MODULE_CODE_7 = Code(codeId=7,
                                  system=PPI_SYSTEM,
                                  value=THE_BASICS_PPI_MODULE,
                                  codeType=CodeType.MODULE,
                                  mapped=True)
        self.CONCEPT_1 = QuestionnaireConcept(codeId=7)
        self.CODE_1_QUESTION_1 = QuestionnaireQuestion(linkId='a',
                                                       codeId=1,
                                                       repeats=False)
        self.CODE_2_QUESTION = QuestionnaireQuestion(linkId='d',
                                                     codeId=2,
                                                     repeats=True)
        # Same code as question 1
        self.CODE_1_QUESTION_2 = QuestionnaireQuestion(linkId='x',
                                                       codeId=1,
                                                       repeats=False)

        self.skip_code = Code(codeId=8,
                              system=PPI_SYSTEM,
                              value=PMI_SKIP_CODE,
                              mapped=True,
                              codeType=CodeType.ANSWER)

        config.override_setting(config.CONSENT_PDF_BUCKET, [_FAKE_BUCKET])

    def _setup_questionnaire(self):
        q = Questionnaire(resource=QUESTIONNAIRE_RESOURCE)
        q.concepts.append(self.CONCEPT_1)
        q.concepts.append(QuestionnaireConcept(codeId=self.consent_code_id))
        q.questions.append(self.CODE_1_QUESTION_1)
        q.questions.append(self.CODE_2_QUESTION)
        q.questions.append(self.FN_QUESTION)
        q.questions.append(self.LN_QUESTION)
        q.questions.append(self.EMAIL_QUESTION)
        q.questions.append(self.LOGIN_PHONE_NUMBER_QUESTION)
        return self.questionnaire_dao.insert(q)

    def insert_codes(self):
        self.code_dao.insert(self.CODE_1)
        self.code_dao.insert(self.CODE_2)
        self.code_dao.insert(self.CODE_3)
        self.code_dao.insert(self.CODE_4)
        self.code_dao.insert(self.CODE_5)
        self.code_dao.insert(self.CODE_6)
        self.code_dao.insert(self.MODULE_CODE_7)
        self.code_dao.insert(self.skip_code)
        self.consent_code_id = self.code_dao.insert(consent_code()).codeId
        self.first_name_code_id = self.code_dao.insert(
            first_name_code()).codeId
        self.last_name_code_id = self.code_dao.insert(last_name_code()).codeId
        self.email_code_id = self.code_dao.insert(email_code()).codeId
        self.login_phone_number_code_id = self.code_dao.insert(
            login_phone_number_code()).codeId
        self.FN_QUESTION = QuestionnaireQuestion(
            linkId='fn', codeId=self.first_name_code_id, repeats=False)
        self.LN_QUESTION = QuestionnaireQuestion(linkId='ln',
                                                 codeId=self.last_name_code_id,
                                                 repeats=False)
        self.EMAIL_QUESTION = QuestionnaireQuestion(linkId='email',
                                                    codeId=self.email_code_id,
                                                    repeats=False)
        self.LOGIN_PHONE_NUMBER_QUESTION = QuestionnaireQuestion(
            linkId='lpn',
            codeId=self.login_phone_number_code_id,
            repeats=False)
        self.first_name = self.fake.first_name()
        self.last_name = self.fake.last_name()
        self.email = self.fake.email()
        self.login_phone_number = self.fake.phone_number()
        self.FN_ANSWER = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=3,
            questionnaireResponseId=1,
            questionId=3,
            valueString=self.first_name)
        self.LN_ANSWER = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=4,
            questionnaireResponseId=1,
            questionId=4,
            valueString=self.last_name)
        self.EMAIL_ANSWER = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=5,
            questionnaireResponseId=1,
            questionId=5,
            valueString=self.email)
        self.LOGIN_PHONE_NUMBER_ANSWER = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=6,
            questionnaireResponseId=1,
            questionId=6,
            valueString=self.login_phone_number)

    def check_response(self, expected_qr):
        qr = self.questionnaire_response_dao.get_with_children(
            expected_qr.questionnaireResponseId)
        self.assertEquals(expected_qr.asdict(follow=ANSWERS),
                          qr.asdict(follow=ANSWERS))

    def _names_and_email_answers(self):
        return [self.FN_ANSWER, self.LN_ANSWER, self.EMAIL_ANSWER]

    def _names_and_login_phone_number_answers(self):
        return [self.FN_ANSWER, self.LN_ANSWER, self.LOGIN_PHONE_NUMBER_ANSWER]

    def test_get_before_insert(self):
        self.assertIsNone(self.questionnaire_response_dao.get(1))
        self.assertIsNone(self.questionnaire_response_dao.get_with_children(1))
        self.assertIsNone(self.questionnaire_response_answer_dao.get(1))

    def test_insert_questionnaire_not_found(self):
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_participant_not_found(self):
        self.insert_codes()
        q = Questionnaire(resource=QUESTIONNAIRE_RESOURCE)
        q.concepts.append(QuestionnaireConcept(codeId=self.consent_code_id))
        self.questionnaire_dao.insert(q)
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_email_answers())
        # Answers are there but the participant is not.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_participant_not_found2(self):
        self.insert_codes()
        p = Participant(participantId=1,
                        biobankId=2,
                        withdrawalStatus=WithdrawalStatus.NOT_WITHDRAWN)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=2,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_email_answers())
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_participant_withdrawn(self):
        self.insert_codes()
        p = Participant(participantId=1,
                        biobankId=2,
                        withdrawalStatus=WithdrawalStatus.NO_USE)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_email_answers())
        with self.assertRaises(Forbidden):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_not_name_answers(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(
            QuestionnaireResponseAnswer(questionnaireResponseAnswerId=2,
                                        questionnaireResponseId=1,
                                        questionId=2,
                                        valueSystem='c',
                                        valueCodeId=4))
        # Both first and last name are required.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_first_name_only(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.FN_ANSWER)
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_last_name_only(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.LN_ANSWER)
        # Both first and last name are required.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_names_only(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.FN_ANSWER)
        qr.answers.append(self.LN_ANSWER)
        # Email is required.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_email_only(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.EMAIL_ANSWER)
        # First and last name are required.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_login_phone_number_only(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.LOGIN_PHONE_NUMBER_ANSWER)
        # First and last name are required.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_both_email_and_login_phone_number_without_names(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.EMAIL_ANSWER)
        qr.answers.append(self.LOGIN_PHONE_NUMBER_ANSWER)
        # First and last name are required.
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    def test_insert_both_names_and_login_phone_number(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_login_phone_number_answers())
        time = datetime.datetime(2016, 1, 1)
        with FakeClock(time):
            qr.authored = time
            self.questionnaire_response_dao.insert(qr)

        expected_qr = QuestionnaireResponse(
            questionnaireResponseId=1,
            questionnaireId=1,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE, 1),
            created=time,
            authored=time)
        expected_qr.answers.extend(
            self._names_and_login_phone_number_answers())
        qr2 = self.questionnaire_response_dao.get(1)
        self.assertEquals(expected_qr.asdict(), qr2.asdict())
        self.check_response(expected_qr)

    def test_insert_both_names_and_email(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_email_answers())
        time = datetime.datetime(2016, 1, 1)
        with FakeClock(time):
            qr.authored = time
            self.questionnaire_response_dao.insert(qr)

        expected_qr = QuestionnaireResponse(
            questionnaireResponseId=1,
            questionnaireId=1,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE, 1),
            created=time,
            authored=time)
        expected_qr.answers.extend(self._names_and_email_answers())
        qr2 = self.questionnaire_response_dao.get(1)
        self.assertEquals(expected_qr.asdict(), qr2.asdict())
        self.check_response(expected_qr)

    def test_insert_both_names_and_email_and_login_phone_number(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.append(self.FN_ANSWER)
        qr.answers.append(self.LN_ANSWER)
        qr.answers.append(self.EMAIL_ANSWER)
        qr.answers.append(self.LOGIN_PHONE_NUMBER_ANSWER)
        time = datetime.datetime(2016, 1, 1)
        with FakeClock(time):
            qr.authored = time
            self.questionnaire_response_dao.insert(qr)

        expected_qr = QuestionnaireResponse(
            questionnaireResponseId=1,
            questionnaireId=1,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE, 1),
            created=time,
            authored=time)
        expected_qr.answers.append(self.FN_ANSWER)
        expected_qr.answers.append(self.LN_ANSWER)
        expected_qr.answers.append(self.EMAIL_ANSWER)
        expected_qr.answers.append(self.LOGIN_PHONE_NUMBER_ANSWER)
        qr2 = self.questionnaire_response_dao.get(1)
        self.assertEquals(expected_qr.asdict(), qr2.asdict())
        self.check_response(expected_qr)

    def test_insert_duplicate(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_email_answers())
        self.questionnaire_response_dao.insert(qr)
        qr2 = QuestionnaireResponse(questionnaireResponseId=1,
                                    questionnaireId=1,
                                    questionnaireVersion=1,
                                    participantId=1,
                                    resource=QUESTIONNAIRE_RESPONSE_RESOURCE_2)
        qr2.answers.append(
            QuestionnaireResponseAnswer(questionnaireResponseAnswerId=2,
                                        questionnaireResponseId=1,
                                        questionId=2,
                                        valueSystem='c',
                                        valueCodeId=4))
        with self.assertRaises(IntegrityError):
            self.questionnaire_response_dao.insert(qr2)

    def test_insert_skip_codes(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        with FakeClock(TIME):
            self.participant_dao.insert(p)
        self._setup_questionnaire()

        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)

        answer_1 = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=1,
            questionnaireResponseId=1,
            questionId=1,
            valueSystem='a',
            valueCodeId=self.skip_code.codeId)

        answer_2 = QuestionnaireResponseAnswer(questionnaireResponseAnswerId=2,
                                               questionnaireResponseId=1,
                                               questionId=2,
                                               valueSystem='c',
                                               valueCodeId=4)

        qr.answers.extend([answer_1, answer_2])
        qr.answers.extend(self._names_and_email_answers())

        with FakeClock(TIME_2):
            self.questionnaire_response_dao.insert(qr)

        expected_qr = QuestionnaireResponse(
            questionnaireResponseId=1,
            questionnaireId=1,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE, 1),
            created=TIME_2,
            authored=TIME_2)

        qr2 = self.questionnaire_response_dao.get(1)
        self.assertEquals(expected_qr.asdict(), qr2.asdict())

        expected_qr.answers.extend([answer_1, answer_2])
        expected_qr.answers.extend(self._names_and_email_answers())
        self.check_response(expected_qr)

        expected_ps = self._participant_summary_with_defaults(
            genderIdentityId=self.skip_code.codeId,
            participantId=1,
            biobankId=2,
            signUpTime=TIME,
            numCompletedBaselinePPIModules=1,
            numCompletedPPIModules=1,
            questionnaireOnTheBasics=QuestionnaireStatus.SUBMITTED,
            questionnaireOnTheBasicsTime=TIME_2,
            questionnaireOnTheBasicsAuthored=TIME_2,
            consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
            consentForStudyEnrollmentTime=TIME_2,
            consentForStudyEnrollmentAuthored=TIME_2,
            firstName=self.first_name,
            lastName=self.last_name,
            email=self.email,
            lastModified=TIME_2,
        )
        self.assertEquals(expected_ps.asdict(),
                          self.participant_summary_dao.get(1).asdict())

    def test_from_client_json_raises_BadRequest_for_excessively_long_value_string(
            self):
        self.insert_codes()
        q_id = self.create_questionnaire('questionnaire1.json')
        p_id = self.create_participant()
        self.send_consent(p_id)

        # First check that the normal case actually writes out correctly
        string = 'a' * QuestionnaireResponseAnswer.VALUE_STRING_MAXLEN
        string_answers = [["nameOfChild", string]]
        resource = make_questionnaire_response_json(
            p_id, q_id, string_answers=string_answers)
        qr = self.questionnaire_response_dao.from_client_json(
            resource, participant_id=int(p_id[1:]))
        with self.questionnaire_response_answer_dao.session() as session:
            self.questionnaire_response_dao.insert(qr)
            all_strings_query = session.query(
                QuestionnaireResponseAnswer.valueString).all()
            all_strings = [obj.valueString for obj in all_strings_query]
            self.assertTrue(string in all_strings)

        # Now check that the incorrect case throws
        string = 'a' * (QuestionnaireResponseAnswer.VALUE_STRING_MAXLEN + 1)
        string_answers = [["nameOfChild", string]]
        resource = make_questionnaire_response_json(
            p_id, q_id, string_answers=string_answers)
        with self.assertRaises(BadRequest):
            qr = self.questionnaire_response_dao.from_client_json(
                resource, participant_id=int(p_id[1:]))

    def test_get_after_withdrawal_fails(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        qr.answers.extend(self._names_and_email_answers())
        self.questionnaire_response_dao.insert(qr)
        p.withdrawalStatus = WithdrawalStatus.NO_USE
        self.participant_dao.update(p)
        with self.assertRaises(Forbidden):
            self.questionnaire_response_dao.get(qr.questionnaireResponseId)

    def test_insert_with_answers(self):
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        with FakeClock(TIME):
            self.participant_dao.insert(p)
        self._setup_questionnaire()
        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        answer_1 = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=1,
            questionnaireResponseId=1,
            questionId=1,
            valueSystem='a',
            valueCodeId=3,
            valueDecimal=123,
            valueString=self.fake.first_name(),
            valueDate=datetime.date.today())
        answer_2 = QuestionnaireResponseAnswer(questionnaireResponseAnswerId=2,
                                               questionnaireResponseId=1,
                                               questionId=2,
                                               valueSystem='c',
                                               valueCodeId=4)
        qr.answers.append(answer_1)
        qr.answers.append(answer_2)
        names_and_email_answers = self._names_and_email_answers()
        qr.answers.extend(names_and_email_answers)
        with FakeClock(TIME_2):
            self.questionnaire_response_dao.insert(qr)

        expected_qr = QuestionnaireResponse(
            questionnaireResponseId=1,
            questionnaireId=1,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE, 1),
            created=TIME_2,
            authored=TIME_2)
        qr2 = self.questionnaire_response_dao.get(1)
        self.assertEquals(expected_qr.asdict(), qr2.asdict())

        expected_qr.answers.append(answer_1)
        expected_qr.answers.append(answer_2)
        expected_qr.answers.extend(names_and_email_answers)
        self.check_response(expected_qr)

        expected_ps = self._participant_summary_with_defaults(
            participantId=1,
            biobankId=2,
            genderIdentityId=3,
            signUpTime=TIME,
            numCompletedBaselinePPIModules=1,
            numCompletedPPIModules=1,
            questionnaireOnTheBasics=QuestionnaireStatus.SUBMITTED,
            questionnaireOnTheBasicsTime=TIME_2,
            questionnaireOnTheBasicsAuthored=TIME_2,
            consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
            consentForStudyEnrollmentTime=TIME_2,
            consentForStudyEnrollmentAuthored=TIME_2,
            lastModified=TIME_2,
            firstName=self.first_name,
            lastName=self.last_name,
            email=self.email)
        self.assertEquals(expected_ps.asdict(),
                          self.participant_summary_dao.get(1).asdict())

    def test_insert_qr_three_times(self):
        """Adds three questionnaire responses for the same participant.

    The latter two responses are for the same questionnaire, answering a question that has the
    same concept code and system as a question found on the first (different) questionnaire.

    Verifies that new answers set endTime on answers for questions with the same concept for the
    same participant, whether on the same questionnaire or a different questionnaire,
    without affecting other answers.
    """
        self.insert_codes()
        p = Participant(participantId=1, biobankId=2)
        with FakeClock(TIME):
            self.participant_dao.insert(p)
        self._setup_questionnaire()
        q2 = Questionnaire(resource=QUESTIONNAIRE_RESOURCE_2)
        # The question on the second questionnaire has the same concept as the first question on the
        # first questionnaire; answers to it will thus set endTime for answers to the first question.
        q2.questions.append(self.CODE_1_QUESTION_2)

        self.questionnaire_dao.insert(q2)

        qr = QuestionnaireResponse(questionnaireResponseId=1,
                                   questionnaireId=1,
                                   questionnaireVersion=1,
                                   participantId=1,
                                   resource=QUESTIONNAIRE_RESPONSE_RESOURCE)
        answer_1 = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=1,
            questionnaireResponseId=1,
            questionId=1,
            valueSystem='a',
            valueCodeId=3,
            valueDecimal=123,
            valueString=self.fake.first_name(),
            valueDate=datetime.date.today())
        answer_2 = QuestionnaireResponseAnswer(questionnaireResponseAnswerId=2,
                                               questionnaireResponseId=1,
                                               questionId=2,
                                               valueSystem='c',
                                               valueCodeId=4)
        qr.answers.append(answer_1)
        qr.answers.append(answer_2)
        qr.answers.extend(self._names_and_email_answers())
        with FakeClock(TIME_2):
            self.questionnaire_response_dao.insert(qr)

        expected_ps = self._participant_summary_with_defaults(
            participantId=1,
            biobankId=2,
            genderIdentityId=3,
            signUpTime=TIME,
            numCompletedBaselinePPIModules=1,
            numCompletedPPIModules=1,
            questionnaireOnTheBasics=QuestionnaireStatus.SUBMITTED,
            questionnaireOnTheBasicsTime=TIME_2,
            questionnaireOnTheBasicsAuthored=TIME_2,
            consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
            consentForStudyEnrollmentTime=TIME_2,
            consentForStudyEnrollmentAuthored=TIME_2,
            lastModified=TIME_2,
            firstName=self.first_name,
            lastName=self.last_name,
            email=self.email)
        self.assertEquals(expected_ps.asdict(),
                          self.participant_summary_dao.get(1).asdict())

        qr2 = QuestionnaireResponse(questionnaireResponseId=2,
                                    questionnaireId=2,
                                    questionnaireVersion=1,
                                    participantId=1,
                                    resource=QUESTIONNAIRE_RESPONSE_RESOURCE_2)
        answer_3 = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=6,
            questionnaireResponseId=2,
            questionId=7,
            valueSystem='x',
            valueCodeId=5,
            valueDecimal=123,
            valueString=self.fake.last_name(),
            valueDate=datetime.date.today())
        qr2.answers.append(answer_3)
        with FakeClock(TIME_3):
            qr2.authored = TIME_3
            self.questionnaire_response_dao.insert(qr2)

        expected_qr = QuestionnaireResponse(
            questionnaireResponseId=1,
            questionnaireId=1,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE, 1),
            created=TIME_2,
            authored=TIME_2)
        # Answer one on the original response should be marked as ended, since a question with
        # the same concept was answered. Answer two should be left alone.
        answer_1.endTime = TIME_3
        expected_qr.answers.append(answer_1)
        expected_qr.answers.append(answer_2)
        expected_qr.answers.extend(self._names_and_email_answers())
        self.check_response(expected_qr)

        # The new questionnaire response should be there, too.
        expected_qr2 = QuestionnaireResponse(
            questionnaireResponseId=2,
            questionnaireId=2,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE_2, 2),
            created=TIME_3,
            authored=TIME_3)
        expected_qr2.answers.append(answer_3)
        self.check_response(expected_qr2)

        expected_ps2 = self._participant_summary_with_defaults(
            participantId=1,
            biobankId=2,
            genderIdentityId=5,
            signUpTime=TIME,
            numCompletedBaselinePPIModules=1,
            numCompletedPPIModules=1,
            questionnaireOnTheBasics=QuestionnaireStatus.SUBMITTED,
            questionnaireOnTheBasicsTime=TIME_2,
            questionnaireOnTheBasicsAuthored=TIME_2,
            lastModified=TIME_3,
            consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
            consentForStudyEnrollmentTime=TIME_2,
            consentForStudyEnrollmentAuthored=TIME_2,
            firstName=self.first_name,
            lastName=self.last_name,
            email=self.email)
        # The participant summary should be updated with the new gender identity, but nothing else
        # changes.
        self.assertEquals(expected_ps2.asdict(),
                          self.participant_summary_dao.get(1).asdict())

        qr3 = QuestionnaireResponse(questionnaireResponseId=3,
                                    questionnaireId=2,
                                    questionnaireVersion=1,
                                    participantId=1,
                                    resource=QUESTIONNAIRE_RESPONSE_RESOURCE_3)
        answer_4 = QuestionnaireResponseAnswer(
            questionnaireResponseAnswerId=7,
            questionnaireResponseId=3,
            questionId=7,
            valueSystem='z',
            valueCodeId=6,
            valueDecimal=456,
            valueString=self.fake.last_name(),
            valueDate=datetime.date.today())
        qr3.answers.append(answer_4)
        with FakeClock(TIME_4):
            qr3.authored = TIME_4
            self.questionnaire_response_dao.insert(qr3)

        # The first questionnaire response hasn't changed.
        self.check_response(expected_qr)

        # The second questionnaire response's answer should have had an endTime set.
        answer_3.endTime = TIME_4
        self.check_response(expected_qr2)

        # The third questionnaire response should be there.
        expected_qr3 = QuestionnaireResponse(
            questionnaireResponseId=3,
            questionnaireId=2,
            questionnaireVersion=1,
            participantId=1,
            resource=with_id(QUESTIONNAIRE_RESPONSE_RESOURCE_3, 3),
            created=TIME_4,
            authored=TIME_4)
        expected_qr3.answers.append(answer_4)
        self.check_response(expected_qr3)

        expected_ps3 = self._participant_summary_with_defaults(
            participantId=1,
            biobankId=2,
            genderIdentityId=6,
            signUpTime=TIME,
            numCompletedBaselinePPIModules=1,
            numCompletedPPIModules=1,
            questionnaireOnTheBasics=QuestionnaireStatus.SUBMITTED,
            questionnaireOnTheBasicsTime=TIME_2,
            questionnaireOnTheBasicsAuthored=TIME_2,
            consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
            consentForStudyEnrollmentTime=TIME_2,
            consentForStudyEnrollmentAuthored=TIME_2,
            lastModified=TIME_4,
            firstName=self.first_name,
            lastName=self.last_name,
            email=self.email)
        # The participant summary should be updated with the new gender identity, but nothing else
        # changes.
        self.assertEquals(expected_ps3.asdict(),
                          self.participant_summary_dao.get(1).asdict())

    def _get_questionnaire_response_with_consents(self, *consent_paths):
        self.insert_codes()
        questionnaire = self._setup_questionnaire()
        participant = Participant(participantId=1, biobankId=2)
        self.participant_dao.insert(participant)
        resource = test_data.load_questionnaire_response_with_consents(
            questionnaire.questionnaireId, participant.participantId,
            self.FN_QUESTION.linkId, self.LN_QUESTION.linkId,
            self.EMAIL_QUESTION.linkId, consent_paths)

        # We need to remove the unused answers in the resource so we don't trigger an unused
        # link id exception.
        res_len = len(resource['group']['question']) - 1
        for idx in range(res_len, -1, -1):
            if resource['group']['question'][idx]['linkId'].isdigit() is True:
                del resource['group']['question'][idx]

        questionnaire_response = self.questionnaire_response_dao.from_client_json(
            resource, participant.participantId)

        return questionnaire_response

    @mock.patch('dao.questionnaire_response_dao._raise_if_gcloud_file_missing')
    def test_consent_pdf_valid_leading_slash(self, mock_gcloud_check):
        consent_pdf_path = '/Participant/xyz/consent.pdf'
        questionnaire_response = self._get_questionnaire_response_with_consents(
            consent_pdf_path)
        # This should pass validation (not raise exceptions).
        self.questionnaire_response_dao.insert(questionnaire_response)
        mock_gcloud_check.assert_called_with('/%s%s' %
                                             (_FAKE_BUCKET, consent_pdf_path))

    @mock.patch('dao.questionnaire_response_dao._raise_if_gcloud_file_missing')
    def test_consent_pdf_valid_no_leading_slash(self, mock_gcloud_check):
        consent_pdf_path = 'Participant/xyz/consent.pdf'
        questionnaire_response = self._get_questionnaire_response_with_consents(
            consent_pdf_path)
        # This should pass validation (not raise exceptions).
        self.questionnaire_response_dao.insert(questionnaire_response)
        mock_gcloud_check.assert_called_with('/%s/%s' %
                                             (_FAKE_BUCKET, consent_pdf_path))

    @mock.patch('dao.questionnaire_response_dao._raise_if_gcloud_file_missing')
    def test_consent_pdf_file_invalid(self, mock_gcloud_check):
        mock_gcloud_check.side_effect = BadRequest('Test should raise this.')
        qr = self._get_questionnaire_response_with_consents(
            '/nobucket/no/file.pdf')
        with self.assertRaises(BadRequest):
            self.questionnaire_response_dao.insert(qr)

    @mock.patch('dao.questionnaire_response_dao._raise_if_gcloud_file_missing')
    def test_consent_pdf_checks_multiple_extensions(self, mock_gcloud_check):
        qr = self._get_questionnaire_response_with_consents(
            '/Participant/one.pdf', '/Participant/two.pdf')
        self.questionnaire_response_dao.insert(qr)
        self.assertEquals(mock_gcloud_check.call_count, 2)
def _sanity_check_codebook():
    if not CodeDao().get_code(PPI_SYSTEM, EMAIL_QUESTION_CODE):
        raise RuntimeError('No question code found for %s; import codebook.' %
                           EMAIL_QUESTION_CODE)