def _do_update_for_organization(organization_id, submission_date, person_file):
    """
  deferred task: creates EhrReceipt and updates ParticipantSummary objects from a person.csv file
  """
    updated_datetime = datetime.datetime.combine(submission_date,
                                                 datetime.datetime.min.time())

    org_dao = OrganizationDao()
    summary_dao = ParticipantSummaryDao()
    receipt_dao = EhrReceiptDao()

    org_external_id = organization_id.upper()
    org = org_dao.get_by_external_id(org_external_id)
    if org is None:
        LOG.info("Organization not found with external_id: {}".format(
            org_external_id))

    receipt = EhrReceipt(organizationId=org.organizationId,
                         receiptTime=updated_datetime)
    receipt_dao.insert(receipt)

    for participant_id in _get_participant_ids_from_person_file(person_file):
        summary = summary_dao.get(participant_id)
        if summary is None:
            LOG.info("Participant not found with participant_id: {}".format(
                participant_id))
            continue
        summary_dao.update_ehr_status(summary, updated_datetime)
        summary_dao.update(summary)
class ParticipantSummaryDaoTest(NdbTestBase):
    def setUp(self):
        super(ParticipantSummaryDaoTest, self).setUp(use_mysql=True)
        self.dao = ParticipantSummaryDao()
        self.participant_dao = ParticipantDao()
        self.no_filter_query = Query([], None, 2, None)
        self.one_filter_query = Query(
            [FieldFilter("participantId", Operator.EQUALS, 1)], None, 2, None)
        self.two_filter_query = Query([
            FieldFilter("participantId", Operator.EQUALS, 1),
            FieldFilter("hpoId", Operator.EQUALS, PITT_HPO_ID)
        ], None, 2, None)
        self.ascending_biobank_id_query = Query([], OrderBy("biobankId", True),
                                                2, None)
        self.descending_biobank_id_query = Query([],
                                                 OrderBy("biobankId",
                                                         False), 2, None)
        self.enrollment_status_order_query = Query([],
                                                   OrderBy(
                                                       "enrollmentStatus",
                                                       True), 2, None)
        self.hpo_id_order_query = Query([], OrderBy("hpoId", True), 2, None)
        self.first_name_order_query = Query([], OrderBy("firstName", True), 2,
                                            None)

    def assert_no_results(self, query):
        results = self.dao.query(query)
        self.assertEquals([], results.items)
        self.assertIsNone(results.pagination_token)

    def assert_results(self, query, items, pagination_token=None):
        results = self.dao.query(query)
        self.assertListAsDictEquals(items, results.items)
        self.assertEquals(
            pagination_token, results.pagination_token,
            "Pagination tokens don't match; decoded = %s, %s" %
            (_decode_token(pagination_token),
             _decode_token(results.pagination_token)))

    def test_query_with_total(self):
        num_participants = 5
        query = Query([], None, 10, None, include_total=True)
        results = self.dao.query(query)
        self.assertEqual(results.total, 0)
        for i in range(num_participants):
            participant = Participant(participantId=i, biobankId=i)
            self._insert(participant)
        results = self.dao.query(query)
        self.assertEqual(results.total, num_participants)

    def testQuery_noSummaries(self):
        self.assert_no_results(self.no_filter_query)
        self.assert_no_results(self.one_filter_query)
        self.assert_no_results(self.two_filter_query)
        self.assert_no_results(self.ascending_biobank_id_query)
        self.assert_no_results(self.descending_biobank_id_query)

    def _insert(self, participant, first_name=None, last_name=None):
        self.participant_dao.insert(participant)
        summary = self.participant_summary(participant)
        if first_name:
            summary.firstName = first_name
        if last_name:
            summary.lastName = last_name
        self.dao.insert(summary)
        return participant

    def testQuery_oneSummary(self):
        participant = Participant(participantId=1, biobankId=2)
        self._insert(participant)
        summary = self.dao.get(1)
        self.assert_results(self.no_filter_query, [summary])
        self.assert_results(self.one_filter_query, [summary])
        self.assert_no_results(self.two_filter_query)
        self.assert_results(self.ascending_biobank_id_query, [summary])
        self.assert_results(self.descending_biobank_id_query, [summary])

    def testUnicodeNameRoundTrip(self):
        name = self.fake.first_name()
        with self.assertRaises(UnicodeEncodeError):
            str(name)  # sanity check that the name contains non-ASCII
        participant = self._insert(Participant(participantId=1, biobankId=2))
        summary = self.dao.get(participant.participantId)
        summary.firstName = name
        self.dao.update(summary)
        fetched_summary = self.dao.get(participant.participantId)
        self.assertEquals(name, fetched_summary.firstName)

    def testQuery_twoSummaries(self):
        participant_1 = Participant(participantId=1, biobankId=2)
        self._insert(participant_1, 'Alice', 'Smith')
        participant_2 = Participant(participantId=2, biobankId=1)
        self._insert(participant_2, 'Zed', 'Zebra')
        ps_1 = self.dao.get(1)
        ps_2 = self.dao.get(2)
        self.assert_results(self.no_filter_query, [ps_1, ps_2])
        self.assert_results(self.one_filter_query, [ps_1])
        self.assert_no_results(self.two_filter_query)
        self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_1])
        self.assert_results(self.descending_biobank_id_query, [ps_1, ps_2])

    def testQuery_threeSummaries_paginate(self):
        participant_1 = Participant(participantId=1, biobankId=4)
        self._insert(participant_1, 'Alice', 'Aardvark')
        participant_2 = Participant(participantId=2, biobankId=1)
        self._insert(participant_2, 'Bob', 'Builder')
        participant_3 = Participant(participantId=3, biobankId=3)
        self._insert(participant_3, 'Chad', 'Caterpillar')
        ps_1 = self.dao.get(1)
        ps_2 = self.dao.get(2)
        ps_3 = self.dao.get(3)
        self.assert_results(
            self.no_filter_query, [ps_1, ps_2],
            _make_pagination_token(['Builder', 'Bob', None, 2]))
        self.assert_results(self.one_filter_query, [ps_1])
        self.assert_no_results(self.two_filter_query)
        self.assert_results(
            self.ascending_biobank_id_query, [ps_2, ps_3],
            _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3]))
        self.assert_results(
            self.descending_biobank_id_query, [ps_1, ps_3],
            _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3]))

        self.assert_results(
            _with_token(self.no_filter_query,
                        _make_pagination_token(['Builder', 'Bob', None, 2])),
            [ps_3])
        self.assert_results(
            _with_token(
                self.ascending_biobank_id_query,
                _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])),
            [ps_1])
        self.assert_results(
            _with_token(
                self.descending_biobank_id_query,
                _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])),
            [ps_2])

    def testQuery_fourFullSummaries_paginate(self):
        participant_1 = Participant(participantId=1, biobankId=4)
        self._insert(participant_1, 'Bob', 'Jones')
        participant_2 = Participant(participantId=2, biobankId=1)
        self._insert(participant_2, 'Bob', 'Jones')
        participant_3 = Participant(participantId=3, biobankId=3)
        self._insert(participant_3, 'Bob', 'Jones')
        participant_4 = Participant(participantId=4, biobankId=2)
        self._insert(participant_4, 'Bob', 'Jones')
        ps_1 = self.dao.get(1)
        ps_2 = self.dao.get(2)
        ps_3 = self.dao.get(3)
        ps_4 = self.dao.get(4)

        ps_1.lastName = 'Jones'
        ps_1.firstName = 'Bob'
        ps_1.dateOfBirth = datetime.date(1978, 10, 9)
        ps_1.hpoId = PITT_HPO_ID
        self.dao.update(ps_1)

        ps_2.lastName = 'Aardvark'
        ps_2.firstName = 'Bob'
        ps_2.dateOfBirth = datetime.date(1978, 10, 10)
        ps_2.enrollmentStatus = EnrollmentStatus.MEMBER
        self.dao.update(ps_2)

        ps_3.lastName = 'Jones'
        ps_3.firstName = 'Bob'
        ps_3.dateOfBirth = datetime.date(1978, 10, 10)
        ps_3.hpoId = PITT_HPO_ID
        ps_3.enrollmentStatus = EnrollmentStatus.MEMBER
        self.dao.update(ps_3)

        ps_4.lastName = 'Jones'
        ps_4.enrollmentStatus = EnrollmentStatus.FULL_PARTICIPANT
        self.dao.update(ps_4)

        self.assert_results(self.no_filter_query, [ps_2, ps_4],
                            _make_pagination_token(['Jones', 'Bob', None, 4]))
        self.assert_results(self.one_filter_query, [ps_1])
        self.assert_results(self.two_filter_query, [ps_1])
        self.assert_results(
            self.ascending_biobank_id_query, [ps_2, ps_4],
            _make_pagination_token([2, 'Jones', 'Bob', None, 4]))
        self.assert_results(
            self.descending_biobank_id_query, [ps_1, ps_3],
            _make_pagination_token(
                [3, 'Jones', 'Bob',
                 datetime.date(1978, 10, 10), 3]))
        self.assert_results(
            self.hpo_id_order_query, [ps_2, ps_4],
            _make_pagination_token([0, 'Jones', 'Bob', None, 4]))
        self.assert_results(
            self.enrollment_status_order_query, [ps_1, ps_2],
            _make_pagination_token(
                ['MEMBER', 'Aardvark', 'Bob',
                 datetime.date(1978, 10, 10), 2]))

        self.assert_results(
            _with_token(self.no_filter_query,
                        _make_pagination_token(['Jones', 'Bob', None, 4])),
            [ps_1, ps_3])
        self.assert_results(
            _with_token(self.ascending_biobank_id_query,
                        _make_pagination_token([2, 'Jones', 'Bob', None, 4])),
            [ps_3, ps_1])
        self.assert_results(
            _with_token(
                self.descending_biobank_id_query,
                _make_pagination_token(
                    [3, 'Jones', 'Bob',
                     datetime.date(1978, 10, 10), 3])), [ps_4, ps_2])
        self.assert_results(
            _with_token(self.hpo_id_order_query,
                        _make_pagination_token([0, 'Jones', 'Bob', None, 4])),
            [ps_1, ps_3])
        self.assert_results(
            _with_token(
                self.enrollment_status_order_query,
                _make_pagination_token([
                    'MEMBER', 'Aardvark', 'Bob',
                    datetime.date(1978, 10, 10), 2
                ])), [ps_3, ps_4])

    def test_update_from_samples(self):
        # baseline_tests = ['BASELINE1', 'BASELINE2']
        baseline_tests = ["1PST8", "2PST8"]

        config.override_setting(config.BASELINE_SAMPLE_TEST_CODES,
                                baseline_tests)
        self.dao.update_from_biobank_stored_samples()  # safe noop

        p_baseline_samples = self._insert(
            Participant(participantId=1, biobankId=11))
        p_mixed_samples = self._insert(
            Participant(participantId=2, biobankId=22))
        p_no_samples = self._insert(Participant(participantId=3, biobankId=33))
        p_unconfirmed = self._insert(Participant(participantId=4,
                                                 biobankId=44))
        self.assertEquals(
            self.dao.get(
                p_baseline_samples.participantId).numBaselineSamplesArrived, 0)

        def get_p_baseline_last_modified():
            return self.dao.get(p_baseline_samples.participantId).lastModified

        p_baseline_last_modified1 = get_p_baseline_last_modified()

        sample_dao = BiobankStoredSampleDao()

        def add_sample(participant, test_code, sample_id):
            TIME = datetime.datetime(2018, 3, 2)
            sample_dao.insert(
                BiobankStoredSample(biobankStoredSampleId=sample_id,
                                    biobankId=participant.biobankId,
                                    biobankOrderIdentifier='KIT',
                                    test=test_code,
                                    confirmed=TIME))

        add_sample(p_baseline_samples, baseline_tests[0], '11111')
        add_sample(p_baseline_samples, baseline_tests[1], '22223')
        add_sample(p_mixed_samples, baseline_tests[0], '11112')
        add_sample(p_mixed_samples, 'NOT1', '44441')
        # add unconfirmed sample
        sample_dao.insert(
            BiobankStoredSample(biobankStoredSampleId=55555,
                                biobankId=p_unconfirmed.biobankId,
                                biobankOrderIdentifier='KIT',
                                test=baseline_tests[1],
                                confirmed=None))
        # sleep 1 sec to make lastModified different
        time.sleep(1)
        self.dao.update_from_biobank_stored_samples()

        p_baseline_last_modified2 = get_p_baseline_last_modified()
        self.assertNotEquals(p_baseline_last_modified2,
                             p_baseline_last_modified1)

        self.assertEquals(
            self.dao.get(
                p_baseline_samples.participantId).numBaselineSamplesArrived, 2)
        self.assertEquals(
            self.dao.get(
                p_mixed_samples.participantId).numBaselineSamplesArrived, 1)
        self.assertEquals(
            self.dao.get(p_no_samples.participantId).numBaselineSamplesArrived,
            0)
        self.assertEquals(
            self.dao.get(
                p_unconfirmed.participantId).numBaselineSamplesArrived, 0)

        M_baseline_samples = self._insert(
            Participant(participantId=9, biobankId=99))
        add_sample(M_baseline_samples, baseline_tests[0], '999')
        M_first_update = self.dao.get(M_baseline_samples.participantId)
        # sleep 1 sec to make lastModified different
        time.sleep(1)
        self.dao.update_from_biobank_stored_samples()
        add_sample(M_baseline_samples, baseline_tests[1], '9999')
        M_second_update = self.dao.get(M_baseline_samples.participantId)
        # sleep 1 sec to make lastModified different
        time.sleep(1)
        self.dao.update_from_biobank_stored_samples()

        self.assertNotEqual(M_first_update.lastModified,
                            M_second_update.lastModified)
        self.assertEquals(get_p_baseline_last_modified(),
                          p_baseline_last_modified2)

    def test_update_from_samples_changed_tests(self):
        baseline_tests = ["1PST8", "2PST8"]
        config.override_setting(config.BASELINE_SAMPLE_TEST_CODES,
                                baseline_tests)
        self.dao.update_from_biobank_stored_samples()  # safe noop

        participant = self._insert(Participant(participantId=1, biobankId=11))
        self.assertEquals(
            self.dao.get(participant.participantId).numBaselineSamplesArrived,
            0)

        sample_dao = BiobankStoredSampleDao()

        def add_sample(test_code, sample_id):
            TIME = datetime.datetime(2018, 3, 2)
            sample_dao.insert(
                BiobankStoredSample(biobankStoredSampleId=sample_id,
                                    biobankId=participant.biobankId,
                                    biobankOrderIdentifier='KIT',
                                    test=test_code,
                                    confirmed=TIME))

        add_sample(baseline_tests[0], '11111')
        add_sample(baseline_tests[1], '22223')
        self.dao.update_from_biobank_stored_samples()
        summary = self.dao.get(participant.participantId)
        init_last_modified = summary.lastModified
        self.assertEquals(summary.numBaselineSamplesArrived, 2)
        # sleep 1 sec to make lastModified different
        time.sleep(1)
        # Simulate removal of one of the baseline tests from config.json.
        baseline_tests.pop()
        config.override_setting(config.BASELINE_SAMPLE_TEST_CODES,
                                baseline_tests)
        self.dao.update_from_biobank_stored_samples()

        summary = self.dao.get(participant.participantId)
        self.assertEquals(summary.numBaselineSamplesArrived, 1)
        self.assertNotEqual(init_last_modified, summary.lastModified)

    def test_only_update_dna_sample(self):
        dna_tests = ["1ED10", "1SAL2"]

        config.override_setting(config.DNA_SAMPLE_TEST_CODES, dna_tests)
        self.dao.update_from_biobank_stored_samples()  # safe noop

        p_dna_samples = self._insert(Participant(participantId=1,
                                                 biobankId=11))

        self.assertEquals(
            self.dao.get(p_dna_samples.participantId).samplesToIsolateDNA,
            None)
        self.assertEquals(
            self.dao.get(p_dna_samples.participantId).
            enrollmentStatusCoreStoredSampleTime, None)
        self.assertEquals(
            self.dao.get(p_dna_samples.participantId).
            enrollmentStatusCoreOrderedSampleTime, None)

        sample_dao = BiobankStoredSampleDao()

        def add_sample(participant, test_code, sample_id, confirmed_time):
            sample_dao.insert(
                BiobankStoredSample(biobankStoredSampleId=sample_id,
                                    biobankId=participant.biobankId,
                                    biobankOrderIdentifier='KIT',
                                    test=test_code,
                                    confirmed=confirmed_time))

        confirmed_time_0 = datetime.datetime(2018, 3, 1)
        add_sample(p_dna_samples, dna_tests[0], '11111', confirmed_time_0)

        self.dao.update_from_biobank_stored_samples()

        self.assertEquals(
            self.dao.get(p_dna_samples.participantId).samplesToIsolateDNA,
            SampleStatus.RECEIVED)
        # only update dna sample will not update enrollmentStatusCoreStoredSampleTime
        self.assertEquals(
            self.dao.get(p_dna_samples.participantId).
            enrollmentStatusCoreStoredSampleTime, None)
        self.assertEquals(
            self.dao.get(p_dna_samples.participantId).
            enrollmentStatusCoreOrderedSampleTime, None)

    def test_calculate_enrollment_status(self):
        self.assertEquals(
            EnrollmentStatus.FULL_PARTICIPANT,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED))
        self.assertEquals(
            EnrollmentStatus.MEMBER,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES - 1,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED))
        self.assertEquals(
            EnrollmentStatus.MEMBER,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.UNSET, SampleStatus.RECEIVED))
        self.assertEquals(
            EnrollmentStatus.MEMBER,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.UNSET))
        self.assertEquals(
            EnrollmentStatus.INTERESTED,
            self.dao.calculate_enrollment_status(
                False, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED))

    def testUpdateEnrollmentStatus(self):
        ehr_consent_time = datetime.datetime(2018, 3, 1)
        summary = ParticipantSummary(
            participantId=1,
            biobankId=2,
            consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
            consentForElectronicHealthRecords=QuestionnaireStatus.SUBMITTED,
            consentForElectronicHealthRecordsTime=ehr_consent_time,
            enrollmentStatus=EnrollmentStatus.INTERESTED)
        self.dao.update_enrollment_status(summary)
        self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus)
        self.assertEquals(ehr_consent_time, summary.enrollmentStatusMemberTime)
class ParticipantSummaryDaoTest(NdbTestBase):

  def setUp(self):
    super(ParticipantSummaryDaoTest, self).setUp(use_mysql=True)
    self.dao = ParticipantSummaryDao()
    self.order_dao = BiobankOrderDao()
    self.measurement_dao = PhysicalMeasurementsDao()
    self.participant_dao = ParticipantDao()
    self.no_filter_query = Query([], None, 2, None)
    self.one_filter_query = Query([FieldFilter("participantId", Operator.EQUALS, 1)],
                                  None, 2, None)
    self.two_filter_query = Query([FieldFilter("participantId", Operator.EQUALS, 1),
                                   FieldFilter("hpoId", Operator.EQUALS, PITT_HPO_ID)],
                                  None, 2, None)
    self.ascending_biobank_id_query = Query([], OrderBy("biobankId", True), 2, None)
    self.descending_biobank_id_query = Query([], OrderBy("biobankId", False), 2, None)
    self.enrollment_status_order_query = Query([], OrderBy("enrollmentStatus", True), 2, None)
    self.hpo_id_order_query = Query([], OrderBy("hpoId", True), 2, None)
    self.first_name_order_query = Query([], OrderBy("firstName", True), 2, None)

  def assert_no_results(self, query):
    results = self.dao.query(query)
    self.assertEquals([], results.items)
    self.assertIsNone(results.pagination_token)

  def assert_results(self, query, items, pagination_token=None):
    results = self.dao.query(query)
    self.assertListAsDictEquals(items, results.items)
    self.assertEquals(pagination_token, results.pagination_token,
                      "Pagination tokens don't match; decoded = %s, %s" %
                      (_decode_token(pagination_token), _decode_token(results.pagination_token)))

  def test_query_with_total(self):
    num_participants = 5
    query = Query([], None, 10, None, include_total=True)
    results = self.dao.query(query)
    self.assertEqual(results.total, 0)
    for i in range(num_participants):
      participant = Participant(participantId=i, biobankId=i)
      self._insert(participant)
    results = self.dao.query(query)
    self.assertEqual(results.total, num_participants)

  def testQuery_noSummaries(self):
    self.assert_no_results(self.no_filter_query)
    self.assert_no_results(self.one_filter_query)
    self.assert_no_results(self.two_filter_query)
    self.assert_no_results(self.ascending_biobank_id_query)
    self.assert_no_results(self.descending_biobank_id_query)

  def _insert(self, participant, first_name=None, last_name=None):
    self.participant_dao.insert(participant)
    summary = self.participant_summary(participant)
    if first_name:
      summary.firstName = first_name
    if last_name:
      summary.lastName = last_name
    self.dao.insert(summary)
    return participant

  def testQuery_oneSummary(self):
    participant = Participant(participantId=1, biobankId=2)
    self._insert(participant)
    summary = self.dao.get(1)
    self.assert_results(self.no_filter_query, [summary])
    self.assert_results(self.one_filter_query, [summary])
    self.assert_no_results(self.two_filter_query)
    self.assert_results(self.ascending_biobank_id_query, [summary])
    self.assert_results(self.descending_biobank_id_query, [summary])

  def testUnicodeNameRoundTrip(self):
    name = self.fake.first_name()
    with self.assertRaises(UnicodeEncodeError):
      str(name)  # sanity check that the name contains non-ASCII
    participant = self._insert(Participant(participantId=1, biobankId=2))
    summary = self.dao.get(participant.participantId)
    summary.firstName = name
    self.dao.update(summary)
    fetched_summary = self.dao.get(participant.participantId)
    self.assertEquals(name, fetched_summary.firstName)

  def testQuery_twoSummaries(self):
    participant_1 = Participant(participantId=1, biobankId=2)
    self._insert(participant_1, 'Alice', 'Smith')
    participant_2 = Participant(participantId=2, biobankId=1)
    self._insert(participant_2, 'Zed', 'Zebra')
    ps_1 = self.dao.get(1)
    ps_2 = self.dao.get(2)
    self.assert_results(self.no_filter_query, [ps_1, ps_2])
    self.assert_results(self.one_filter_query, [ps_1])
    self.assert_no_results(self.two_filter_query)
    self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_1])
    self.assert_results(self.descending_biobank_id_query, [ps_1, ps_2])

  def testQuery_threeSummaries_paginate(self):
    participant_1 = Participant(participantId=1, biobankId=4)
    self._insert(participant_1, 'Alice', 'Aardvark')
    participant_2 = Participant(participantId=2, biobankId=1)
    self._insert(participant_2, 'Bob', 'Builder')
    participant_3 = Participant(participantId=3, biobankId=3)
    self._insert(participant_3, 'Chad', 'Caterpillar')
    ps_1 = self.dao.get(1)
    ps_2 = self.dao.get(2)
    ps_3 = self.dao.get(3)
    self.assert_results(self.no_filter_query, [ps_1, ps_2],
                        _make_pagination_token(['Builder', 'Bob', None, 2]))
    self.assert_results(self.one_filter_query, [ps_1])
    self.assert_no_results(self.two_filter_query)
    self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_3],
                        _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3]))
    self.assert_results(self.descending_biobank_id_query, [ps_1, ps_3],
                        _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3]))

    self.assert_results(_with_token(self.no_filter_query,
                                    _make_pagination_token(['Builder', 'Bob', None, 2])), [ps_3])
    self.assert_results(_with_token(self.ascending_biobank_id_query,
                                    _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])),
                        [ps_1])
    self.assert_results(_with_token(self.descending_biobank_id_query,
                                    _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])),
                        [ps_2])

  def testQuery_fourFullSummaries_paginate(self):
    participant_1 = Participant(participantId=1, biobankId=4)
    self._insert(participant_1, 'Bob', 'Jones')
    participant_2 = Participant(participantId=2, biobankId=1)
    self._insert(participant_2, 'Bob', 'Jones')
    participant_3 = Participant(participantId=3, biobankId=3)
    self._insert(participant_3, 'Bob', 'Jones')
    participant_4 = Participant(participantId=4, biobankId=2)
    self._insert(participant_4, 'Bob', 'Jones')
    ps_1 = self.dao.get(1)
    ps_2 = self.dao.get(2)
    ps_3 = self.dao.get(3)
    ps_4 = self.dao.get(4)

    ps_1.lastName = 'Jones'
    ps_1.firstName = 'Bob'
    ps_1.dateOfBirth = datetime.date(1978, 10, 9)
    ps_1.hpoId = PITT_HPO_ID
    self.dao.update(ps_1)

    ps_2.lastName = 'Aardvark'
    ps_2.firstName = 'Bob'
    ps_2.dateOfBirth = datetime.date(1978, 10, 10)
    ps_2.enrollmentStatus = EnrollmentStatus.MEMBER
    self.dao.update(ps_2)

    ps_3.lastName = 'Jones'
    ps_3.firstName = 'Bob'
    ps_3.dateOfBirth = datetime.date(1978, 10, 10)
    ps_3.hpoId = PITT_HPO_ID
    ps_3.enrollmentStatus = EnrollmentStatus.MEMBER
    self.dao.update(ps_3)

    ps_4.lastName = 'Jones'
    ps_4.enrollmentStatus = EnrollmentStatus.FULL_PARTICIPANT
    self.dao.update(ps_4)

    self.assert_results(self.no_filter_query, [ps_2, ps_4],
                        _make_pagination_token(['Jones', 'Bob', None, 4]))
    self.assert_results(self.one_filter_query, [ps_1])
    self.assert_results(self.two_filter_query, [ps_1])
    self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_4],
                        _make_pagination_token([2, 'Jones', 'Bob', None, 4]))
    self.assert_results(self.descending_biobank_id_query, [ps_1, ps_3],
                        _make_pagination_token([3, 'Jones', 'Bob', datetime.date(1978, 10, 10), 3]))
    self.assert_results(self.hpo_id_order_query, [ps_2, ps_4],
                        _make_pagination_token([0, 'Jones', 'Bob', None, 4]))
    self.assert_results(self.enrollment_status_order_query, [ps_1, ps_2],
                        _make_pagination_token(['MEMBER', 'Aardvark', 'Bob',
                                                datetime.date(1978, 10, 10), 2]))

    self.assert_results(_with_token(self.no_filter_query,
                                    _make_pagination_token(['Jones', 'Bob', None, 4])),
                        [ps_1, ps_3])
    self.assert_results(_with_token(self.ascending_biobank_id_query,
                                    _make_pagination_token([2, 'Jones', 'Bob', None, 4])),
                        [ps_3, ps_1])
    self.assert_results(_with_token(self.descending_biobank_id_query,
                                    _make_pagination_token([3, 'Jones', 'Bob',
                                                            datetime.date(1978, 10, 10), 3])),
                        [ps_4, ps_2])
    self.assert_results(_with_token(self.hpo_id_order_query,
                                    _make_pagination_token([0, 'Jones', 'Bob', None, 4])),
                        [ps_1, ps_3])
    self.assert_results(_with_token(self.enrollment_status_order_query,
                                    _make_pagination_token(['MEMBER', 'Aardvark', 'Bob',
                                                datetime.date(1978, 10, 10), 2])),
                        [ps_3, ps_4])

  def test_update_from_samples(self):
    # baseline_tests = ['BASELINE1', 'BASELINE2']
    baseline_tests = ["1PST8", "2PST8"]

    config.override_setting(config.BASELINE_SAMPLE_TEST_CODES, baseline_tests)
    self.dao.update_from_biobank_stored_samples()  # safe noop

    p_baseline_samples = self._insert(Participant(participantId=1, biobankId=11))
    p_mixed_samples = self._insert(Participant(participantId=2, biobankId=22))
    p_no_samples = self._insert(Participant(participantId=3, biobankId=33))
    p_unconfirmed = self._insert(Participant(participantId=4, biobankId=44))
    self.assertEquals(self.dao.get(p_baseline_samples.participantId).numBaselineSamplesArrived, 0)

    def get_p_baseline_last_modified():
      return self.dao.get(p_baseline_samples.participantId).lastModified
    p_baseline_last_modified1 = get_p_baseline_last_modified()

    sample_dao = BiobankStoredSampleDao()

    def add_sample(participant, test_code, sample_id):
      TIME = datetime.datetime(2018, 3, 2)
      sample_dao.insert(BiobankStoredSample(
          biobankStoredSampleId=sample_id, biobankId=participant.biobankId,
        biobankOrderIdentifier='KIT', test=test_code, confirmed=TIME))

    add_sample(p_baseline_samples, baseline_tests[0], '11111')
    add_sample(p_baseline_samples, baseline_tests[1], '22223')
    add_sample(p_mixed_samples, baseline_tests[0], '11112')
    add_sample(p_mixed_samples, 'NOT1', '44441')
    # add unconfirmed sample
    sample_dao.insert(BiobankStoredSample(biobankStoredSampleId=55555,
                                          biobankId=p_unconfirmed.biobankId,
                                          biobankOrderIdentifier='KIT', test=baseline_tests[1],
                                          confirmed=None))
    # sleep 1 sec to make lastModified different
    time.sleep(1)
    self.dao.update_from_biobank_stored_samples()

    p_baseline_last_modified2 = get_p_baseline_last_modified()
    self.assertNotEquals(p_baseline_last_modified2, p_baseline_last_modified1)

    self.assertEquals(self.dao.get(p_baseline_samples.participantId).numBaselineSamplesArrived, 2)
    self.assertEquals(self.dao.get(p_mixed_samples.participantId).numBaselineSamplesArrived, 1)
    self.assertEquals(self.dao.get(p_no_samples.participantId).numBaselineSamplesArrived, 0)
    self.assertEquals(self.dao.get(p_unconfirmed.participantId).numBaselineSamplesArrived, 0)

    M_baseline_samples = self._insert(Participant(participantId=9, biobankId=99))
    add_sample(M_baseline_samples, baseline_tests[0], '999')
    M_first_update = self.dao.get(M_baseline_samples.participantId)
    # sleep 1 sec to make lastModified different
    time.sleep(1)
    self.dao.update_from_biobank_stored_samples()
    add_sample(M_baseline_samples, baseline_tests[1], '9999')
    M_second_update = self.dao.get(M_baseline_samples.participantId)
    # sleep 1 sec to make lastModified different
    time.sleep(1)
    self.dao.update_from_biobank_stored_samples()

    self.assertNotEqual(M_first_update.lastModified, M_second_update.lastModified)
    self.assertEquals(get_p_baseline_last_modified(), p_baseline_last_modified2)

  def test_update_from_samples_changed_tests(self):
    baseline_tests = ["1PST8", "2PST8"]
    config.override_setting(config.BASELINE_SAMPLE_TEST_CODES, baseline_tests)
    self.dao.update_from_biobank_stored_samples()  # safe noop

    participant = self._insert(Participant(participantId=1, biobankId=11))
    self.assertEquals(self.dao.get(participant.participantId).numBaselineSamplesArrived, 0)

    sample_dao = BiobankStoredSampleDao()
    def add_sample(test_code, sample_id):
      TIME = datetime.datetime(2018, 3, 2)
      sample_dao.insert(BiobankStoredSample(
          biobankStoredSampleId=sample_id, biobankId=participant.biobankId,
          biobankOrderIdentifier='KIT', test=test_code, confirmed=TIME))

    add_sample(baseline_tests[0], '11111')
    add_sample(baseline_tests[1], '22223')
    self.dao.update_from_biobank_stored_samples()
    summary = self.dao.get(participant.participantId)
    init_last_modified = summary.lastModified
    self.assertEquals(summary.numBaselineSamplesArrived, 2)
    # sleep 1 sec to make lastModified different
    time.sleep(1)
    # Simulate removal of one of the baseline tests from config.json.
    baseline_tests.pop()
    config.override_setting(config.BASELINE_SAMPLE_TEST_CODES, baseline_tests)
    self.dao.update_from_biobank_stored_samples()

    summary = self.dao.get(participant.participantId)
    self.assertEquals(summary.numBaselineSamplesArrived, 1)
    self.assertNotEqual(init_last_modified, summary.lastModified)

  def test_only_update_dna_sample(self):
    dna_tests = ["1ED10", "1SAL2"]

    config.override_setting(config.DNA_SAMPLE_TEST_CODES, dna_tests)
    self.dao.update_from_biobank_stored_samples()  # safe noop

    p_dna_samples = self._insert(Participant(participantId=1, biobankId=11))

    self.assertEquals(self.dao.get(p_dna_samples.participantId).samplesToIsolateDNA, None)
    self.assertEquals(
      self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreStoredSampleTime, None)
    self.assertEquals(
      self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreOrderedSampleTime, None)

    sample_dao = BiobankStoredSampleDao()

    def add_sample(participant, test_code, sample_id, confirmed_time):
      sample_dao.insert(BiobankStoredSample(
          biobankStoredSampleId=sample_id, biobankId=participant.biobankId,
          biobankOrderIdentifier='KIT', test=test_code, confirmed=confirmed_time))

    confirmed_time_0 = datetime.datetime(2018, 3, 1)
    add_sample(p_dna_samples, dna_tests[0], '11111', confirmed_time_0)

    self.dao.update_from_biobank_stored_samples()

    self.assertEquals(self.dao.get(p_dna_samples.participantId).samplesToIsolateDNA,
                      SampleStatus.RECEIVED)
    # only update dna sample will not update enrollmentStatusCoreStoredSampleTime
    self.assertEquals(
      self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreStoredSampleTime, None)
    self.assertEquals(
      self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreOrderedSampleTime, None)

  def test_calculate_enrollment_status(self):
    self.assertEquals(EnrollmentStatus.FULL_PARTICIPANT,
                      self.dao.calculate_enrollment_status(True,
                                                           NUM_BASELINE_PPI_MODULES,
                                                           PhysicalMeasurementsStatus.COMPLETED,
                                                           SampleStatus.RECEIVED))
    self.assertEquals(EnrollmentStatus.MEMBER,
                      self.dao.calculate_enrollment_status(True,
                                                           NUM_BASELINE_PPI_MODULES - 1,
                                                           PhysicalMeasurementsStatus.COMPLETED,
                                                           SampleStatus.RECEIVED))
    self.assertEquals(EnrollmentStatus.MEMBER,
                      self.dao.calculate_enrollment_status(True,
                                                           NUM_BASELINE_PPI_MODULES,
                                                           PhysicalMeasurementsStatus.UNSET,
                                                           SampleStatus.RECEIVED))
    self.assertEquals(EnrollmentStatus.MEMBER,
                      self.dao.calculate_enrollment_status(True,
                                                           NUM_BASELINE_PPI_MODULES,
                                                           PhysicalMeasurementsStatus.COMPLETED,
                                                           SampleStatus.UNSET))
    self.assertEquals(EnrollmentStatus.INTERESTED,
                      self.dao.calculate_enrollment_status(False,
                                                           NUM_BASELINE_PPI_MODULES,
                                                           PhysicalMeasurementsStatus.COMPLETED,
                                                           SampleStatus.RECEIVED))

  def testUpdateEnrollmentStatus(self):
    ehr_consent_time = datetime.datetime(2018, 3, 1)
    summary = ParticipantSummary(
        participantId=1,
        biobankId=2,
        consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED,
        consentForElectronicHealthRecords=QuestionnaireStatus.SUBMITTED,
        consentForElectronicHealthRecordsTime=ehr_consent_time,
        enrollmentStatus=EnrollmentStatus.INTERESTED)
    self.dao.update_enrollment_status(summary)
    self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus)
    self.assertEquals(ehr_consent_time, summary.enrollmentStatusMemberTime)

  def testUpdateEnrollmentStatusLastModified(self):
    """DA-631: enrollment_status update should update last_modified."""
    participant = self._insert(Participant(participantId=6, biobankId=66))
    # collect current modified and enrollment status
    summary = self.dao.get(participant.participantId)
    test_dt = datetime.datetime(2018, 11, 1)

    def reset_summary():
      # change summary so enrollment status will be changed from INTERESTED to MEMBER.
      summary.enrollmentStatus = EnrollmentStatus.INTERESTED
      summary.lastModified = test_dt
      summary.consentForStudyEnrollment = QuestionnaireStatus.SUBMITTED
      summary.consentForElectronicHealthRecords = QuestionnaireStatus.SUBMITTED
      summary.physicalMeasurementsStatus = PhysicalMeasurementsStatus.COMPLETED
      summary.samplesToIsolateDNA = SampleStatus.RECEIVED
      self.dao.update(summary)

    ## Test Step 1: Validate update_from_biobank_stored_samples() changes lastModified.
    reset_summary()

    # Update and reload summary record
    self.dao.update_from_biobank_stored_samples(participant_id=participant.participantId)
    summary = self.dao.get(participant.participantId)

    # Test that status has changed and lastModified is also different
    self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus)
    self.assertNotEqual(test_dt, summary.lastModified)

    ## Test Step 2: Validate that update_enrollment_status() changes the lastModified property.
    reset_summary()
    summary = self.dao.get(participant.participantId)

    self.assertEqual(test_dt, summary.lastModified)

    # update_enrollment_status() does not touch the db, it only modifies object properties.
    self.dao.update_enrollment_status(summary)

    self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus)
    self.assertNotEqual(test_dt, summary.lastModified)

  def testNumberDistinctVisitsCounts(self):
    self.participant = self._insert(Participant(participantId=7, biobankId=77))
    # insert biobank order
    order = self.order_dao.insert(self._make_biobank_order())
    summary = self.dao.get(self.participant.participantId)
    self.assertEquals(summary.numberDistinctVisits, 1)
    cancel_request = cancel_biobank_order()
    # cancel biobank order
    self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version)
    summary = self.dao.get(self.participant.participantId)
    # distinct count should be 0
    self.assertEquals(summary.numberDistinctVisits, 0)

    self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                             TIME_1.isoformat()))
    # insert physical measurement
    measurement = self.measurement_dao.insert(self._make_physical_measurements())
    summary = self.dao.get(self.participant.participantId)
    # count should be 1
    self.assertEquals(summary.numberDistinctVisits, 1)

    # cancel the measurement
    cancel_measurement = get_restore_or_cancel_info()
    with self.measurement_dao.session() as session:
      self.measurement_dao.update_with_patch(measurement.physicalMeasurementsId, session,
                                             cancel_measurement)

    summary = self.dao.get(self.participant.participantId)
    # count should be 0
    self.assertEquals(summary.numberDistinctVisits, 0)

    with clock.FakeClock(TIME_1):
      self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[
        BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample(
                                                        biobankOrderId='2',
                                                        test=BIOBANK_TESTS[0],
                                                        description='description',
                                                        processingRequired=True)]))
    with clock.FakeClock(TIME_2):
      self.measurement_dao.insert(self._make_physical_measurements(
        physicalMeasurementsId=2))
      summary = self.dao.get(self.participant.participantId)

      # A PM on another day should add a new distinct count.
      self.assertEquals(summary.numberDistinctVisits, 2)

    with clock.FakeClock(TIME_3):
      self.order_dao.insert(self._make_biobank_order(biobankOrderId='3', identifiers=[
        BiobankOrderIdentifier(system='s', value='s')], samples=[BiobankOrderedSample(
        biobankOrderId ='3',
        finalized=TIME_3,
        test=BIOBANK_TESTS[1],
        description='another description',
        processingRequired=False)]))

      # a physical measurement on same day as biobank order does not add distinct visit.
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=6))

      # another biobank order on the same day should also not add a distinct visit
      self.order_dao.insert(self._make_biobank_order(biobankOrderId='7', identifiers=[
          BiobankOrderIdentifier(system='x', value='x')], samples=[BiobankOrderedSample(
              biobankOrderId ='7',
              finalized=TIME_3,
              test=BIOBANK_TESTS[1],
              description='another description',
              processingRequired=False)]))

      summary = self.dao.get(self.participant.participantId)
      # 1 from each of TIME_1 TIME_2 TIME_3
      self.assertEquals(summary.numberDistinctVisits, 3)


  def test_qa_scenarios_for_pmb_visits(self):
    """ PDR at https://docs.google.com/document/d/1sL54f-I91RvhjIprrdbwD8TlR9Jq91MX2ELf1EtJdxc/edit#heading=h.bqo8kt3igsrw<Paste> """
    self.participant = self._insert(Participant(participantId=6, biobankId=66))

    # test scenario 1
    with clock.FakeClock(TIME_4):
      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_4.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=666,
                                                                   participantId=self.participant.participantId,
                                                                   finalized=TIME_4))
      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

    with clock.FakeClock(TIME_5):
      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_5.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=669,
                                                                   finalized=TIME_5))
      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 2)

    # test scenario 2
    with clock.FakeClock(TIME_6):
      self.participant = self._insert(Participant(participantId=9, biobankId=13))
      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_6.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=8,
                                                                   finalized=TIME_6))
      self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[
        BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample(
                                                        biobankOrderId='2',
                                                        finalized=TIME_7,
                                                        test=BIOBANK_TESTS[0],
                                                        description='description',
                                                        processingRequired=True)]))


      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 2
      self.assertEquals(summary.numberDistinctVisits, 2)

    # test scenario 3
    with clock.FakeClock(TIME_6):
      self.participant = self._insert(Participant(participantId=66, biobankId=42))
      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_6.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=12,
                                                                   createdSiteId=2,
                                                                   finalized=TIME_6))

      self.order_dao.insert(self._make_biobank_order(biobankOrderId='18', finalizedSiteId=1, identifiers=[
          BiobankOrderIdentifier(system='x', value='y')], samples=[BiobankOrderedSample(
              biobankOrderId='18',
              finalized=TIME_6,
              test=BIOBANK_TESTS[0],
              description='description',
              processingRequired=True)]))


      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 1
      self.assertEquals(summary.numberDistinctVisits, 1)

    # test scenario 4
    with clock.FakeClock(TIME_8):
      self.participant = self._insert(Participant(participantId=6613, biobankId=142))
      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_8.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=129,
                                                                   finalized=TIME_8))

      order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='999', identifiers=[
          BiobankOrderIdentifier(system='s', value='s')], samples=[BiobankOrderedSample(
              biobankOrderId='999',
              finalized=TIME_8,
              test=BIOBANK_TESTS[1],
              description='description',
              processingRequired=True)]))
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 1
      self.assertEquals(summary.numberDistinctVisits, 1)

     # change finalized time, recalculating count
      with self.order_dao.session() as session:
        existing_order = copy.deepcopy(order)
        order.samples[0].finalized = TIME_9
        self.order_dao._do_update(session, order, existing_order)

      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

     # change test, should not change count.
      with self.order_dao.session() as session:
        existing_order = copy.deepcopy(order)
        order.samples[0].test = BIOBANK_TESTS[0]
        self.order_dao._do_update(session, order, existing_order)

      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

    # test scenario 5
    with clock.FakeClock(TIME_12):
      self.participant = self._insert(Participant(participantId=3000, biobankId=2019))

      self.order_dao.insert(self._make_biobank_order(biobankOrderId='700', identifiers=[
          BiobankOrderIdentifier(system='n', value='s')], samples=[BiobankOrderedSample(
              biobankOrderId='700',
              finalized=TIME_10,
              test=BIOBANK_TESTS[1],
              description='description',
              processingRequired=True)]))
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 1
      self.assertEquals(summary.numberDistinctVisits, 1)

      other_order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='701', identifiers=[
          BiobankOrderIdentifier(system='n', value='t')], samples=[BiobankOrderedSample(
              biobankOrderId='701',
              finalized=TIME_11,
              test=BIOBANK_TESTS[1],
              description='description',
              processingRequired=True)]))
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 2
      self.assertEquals(summary.numberDistinctVisits, 2)

      order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='702', identifiers=[
          BiobankOrderIdentifier(system='n', value='u')], samples=[BiobankOrderedSample(
              biobankOrderId='702',
              finalized=TIME_12,
              test=BIOBANK_TESTS[1],
              description='description',
              processingRequired=True)]))
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 3
      self.assertEquals(summary.numberDistinctVisits, 3)

      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_12.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=120,
                                                                   finalized=TIME_12))

      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 3
      self.assertEquals(summary.numberDistinctVisits, 3)
      cancel_request = cancel_biobank_order()
      # cancel biobank order with PM on same day
      self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version)
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 3 (the PM on same day still counts)
      self.assertEquals(summary.numberDistinctVisits, 3)

      self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                               TIME_1.isoformat()))
      self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=150,
                                                                   finalized=TIME_1))
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 4
      self.assertEquals(summary.numberDistinctVisits, 4)
      # cancel order with pm on different day
      self.order_dao.update_with_patch(other_order.biobankOrderId, cancel_request, order.version)
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 3
      self.assertEquals(summary.numberDistinctVisits, 3)

  def test_pm_restore_cancel_biobank_restore_cancel(self):
    self.participant = self._insert(Participant(participantId=9, biobankId=13))
    self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId,
                                                             TIME_4.isoformat()))
    measurement = self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=669,
                                                                 finalized=TIME_4))
    summary = self.dao.get(self.participant.participantId)
    self.assertEquals(summary.numberDistinctVisits, 1)

    with clock.FakeClock(TIME_5):
      order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[
        BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample(
                                                        biobankOrderId='2',
                                                        finalized=TIME_5,
                                                        test=BIOBANK_TESTS[0],
                                                        description='description',
                                                        processingRequired=True)]))


    with clock.FakeClock(TIME_7):
      summary = self.dao.get(self.participant.participantId)
      # distinct count should be 2
      self.assertEquals(summary.numberDistinctVisits, 2)

      # cancel the measurement
      cancel_measurement = get_restore_or_cancel_info()
      with self.measurement_dao.session() as session:
        self.measurement_dao.update_with_patch(measurement.physicalMeasurementsId, session,
                                               cancel_measurement)

      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

    with clock.FakeClock(TIME_7):
      restore_measurement = get_restore_or_cancel_info(status='restored')
      with self.measurement_dao.session() as session:
        self.measurement_dao.update_with_patch(measurement.physicalMeasurementsId, session,
                                               restore_measurement)

      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 2)


      cancel_request = cancel_biobank_order()
      order = self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version)

      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

      restore_order = get_restore_or_cancel_info(status='restored')
      restore_order['amendedReason'] = 'some reason'
      self.order_dao.update_with_patch(order.biobankOrderId, restore_order, order.version)
      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 2)

  def test_amending_biobank_order_distinct_visit_count(self):
    self.participant = self._insert(Participant(participantId=9, biobankId=13))
    with clock.FakeClock(TIME_5):
      order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[
        BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample(
                                                        biobankOrderId='2',
                                                        finalized=TIME_5,
                                                        test=BIOBANK_TESTS[0],
                                                        description='description',
                                                        processingRequired=True)]))

      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

    with clock.FakeClock(TIME_7):
      amend_order = self._get_amended_info(order)
      with self.order_dao.session() as session:
        self.order_dao._do_update(session, amend_order, order)

      # Shouldn't change on a simple amendment (unless finalized time on samples change)
      summary = self.dao.get(self.participant.participantId)
      self.assertEquals(summary.numberDistinctVisits, 1)

    with clock.FakeClock(TIME_7_5):
      cancel_request = cancel_biobank_order()
      order = self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version)

    # A cancelled order (even after amending) should reduce count (unless some other valid order on same day)
    summary = self.dao.get(self.participant.participantId)
    self.assertEquals(summary.numberDistinctVisits, 0)

  @staticmethod
  def _get_amended_info(order):
    amendment = dict(amendedReason='I had to change something', amendedInfo={
      "author": {
        "system": "https://www.pmi-ops.org/healthpro-username",
        "value": "*****@*****.**"
      },
      "site": {
        "system": "https://www.pmi-ops.org/site-id",
        "value": "hpo-site-monroeville"
      }
    })

    order.amendedReason = amendment['amendedReason']
    order.amendedInfo = amendment['amendedInfo']
    return order



  def _make_biobank_order(self, **kwargs):
    """Makes a new BiobankOrder (same values every time) with valid/complete defaults.

    Kwargs pass through to BiobankOrder constructor, overriding defaults.
    """
    for k, default_value in (
        ('biobankOrderId', '1'),
        ('created', clock.CLOCK.now()),
        ('participantId', self.participant.participantId),
        ('sourceSiteId', 1),
        ('sourceUsername', '*****@*****.**'),
        ('collectedSiteId', 1),
        ('collectedUsername', '*****@*****.**'),
        ('processedSiteId', 1),
        ('processedUsername', '*****@*****.**'),
        ('finalizedSiteId', 2),
        ('finalizedUsername', '*****@*****.**'),
        ('identifiers', [BiobankOrderIdentifier(system='a', value='c')]),
        ('samples', [BiobankOrderedSample(
            biobankOrderId='1',
            test=BIOBANK_TESTS[0],
            description='description',
            finalized=TIME_1,
            processingRequired=True)])):
      if k not in kwargs:
        kwargs[k] = default_value
    return BiobankOrder(**kwargs)

  def _make_physical_measurements(self, **kwargs):
    """Makes a new PhysicalMeasurements (same values every time) with valid/complete defaults.

    Kwargs pass through to PM constructor, overriding defaults.
    """
    for k, default_value in (
        ('physicalMeasurementsId', 1),
        ('participantId', self.participant.participantId),
        ('resource', self.measurement_json),
        ('createdSiteId', 1),
        ('finalized', TIME_3),
        ('finalizedSiteId', 2)):
      if k not in kwargs:
        kwargs[k] = default_value
    return PhysicalMeasurements(**kwargs)
class ParticipantCountsOverTimeApiTest(FlaskTestBase):

  def setUp(self):
    super(ParticipantCountsOverTimeApiTest, self).setUp(use_mysql=True)
    self.dao = ParticipantDao()
    self.ps_dao = ParticipantSummaryDao()
    self.ps = ParticipantSummary()
    self.calendar_dao = CalendarDao()
    self.hpo_dao = HPODao()

    # Needed by ParticipantCountsOverTimeApi
    self.hpo_dao.insert(HPO(hpoId=TEST_HPO_ID, name=TEST_HPO_NAME, displayName='Test',
                       organizationType=OrganizationType.UNSET))

    self.time1 = datetime.datetime(2017, 12, 31)
    self.time2 = datetime.datetime(2018, 1, 1)
    self.time3 = datetime.datetime(2018, 1, 2)
    self.time4 = datetime.datetime(2018, 1, 3)

    # Insert 2 weeks of dates
    curr_date = datetime.date(2017, 12, 22)
    for _ in xrange(0, 14):
      calendar_day = Calendar(day=curr_date )
      CalendarDao().insert(calendar_day)
      curr_date = curr_date + datetime.timedelta(days=1)

  def _insert(self, participant, first_name=None, last_name=None, hpo_name=None,
              unconsented=False, time_int=None, time_mem=None, time_fp=None):
    """
    Create a participant in a transient test database.

    :param participant: Participant object
    :param first_name: First name
    :param last_name: Last name
    :param hpo_name: HPO name (one of PITT or AZ_TUCSON)
    :param time_int: Time that participant fulfilled INTERESTED criteria
    :param time_mem: Time that participant fulfilled MEMBER criteria
    :param time_fp: Time that participant fulfilled FULL_PARTICIPANT criteria
    :return: Participant object
    """

    if unconsented is True:
      enrollment_status = None
    elif time_mem is None:
      enrollment_status = EnrollmentStatus.INTERESTED
    elif time_fp is None:
      enrollment_status = EnrollmentStatus.MEMBER
    else:
      enrollment_status = EnrollmentStatus.FULL_PARTICIPANT

    with FakeClock(time_int):
      self.dao.insert(participant)

    participant.providerLink = make_primary_provider_link_for_name(hpo_name)
    with FakeClock(time_mem):
      self.dao.update(participant)

    if enrollment_status is None:
      return None

    summary = self.participant_summary(participant)

    if first_name:
      summary.firstName = first_name
    if last_name:
      summary.lastName = last_name

    summary.dateOfBirth = datetime.date(1978, 10, 10)

    summary.enrollmentStatus = enrollment_status

    summary.hpoId = self.hpo_dao.get_by_name(hpo_name).hpoId

    if time_mem is not None:
      with FakeClock(time_mem):
        summary.consentForElectronicHealthRecordsTime = time_mem

    if time_fp is not None:
      with FakeClock(time_fp):
        summary.consentForElectronicHealthRecordsTime = time_fp
        summary.questionnaireOnTheBasicsTime = time_fp
        summary.questionnaireOnLifestyleTime = time_fp
        summary.questionnaireOnOverallHealthTime = time_fp
        summary.physicalMeasurementsFinalizedTime = time_fp
        summary.sampleOrderStatus1ED04Time = time_fp
        summary.sampleOrderStatus1SALTime = time_fp

    self.ps_dao.insert(summary)

    return summary

  def test_get_counts_with_default_parameters(self):
    # The most basic test in this class

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    # TODO: remove bucketSize from these parameters in all tests
    qs = """
      bucketSize=1
      &stratification=ENROLLMENT_STATUS
      &startDate=2017-12-30
      &endDate=2018-01-04
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    # You can debug API responses easily by uncommenting the lines below
    # print('response')
    # print(response)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 1)

  def test_get_counts_with_single_awardee_filter(self):
    # Does the awardee filter work?

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p1 = Participant(participantId=2, biobankId=5)
    self._insert(p1, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p1 = Participant(participantId=3, biobankId=6)
    self._insert(p1, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)

    # enrollmentStatus param left blank to test we can handle it
    qs = """
      bucketSize=1
      &stratification=ENROLLMENT_STATUS
      &startDate=2017-12-30
      &endDate=2018-01-04
      &awardee=PITT
      &enrollmentStatus=
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 1)

    qs = """
      bucketSize=1
      &stratification=ENROLLMENT_STATUS
      &startDate=2017-12-30
      &endDate=2018-01-04
      &awardee=AZ_TUCSON
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 2)

  def test_get_counts_with_single_awardee_filter(self):
    # Does the awardee filter work when passed a single awardee?

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p1 = Participant(participantId=2, biobankId=5)
    self._insert(p1, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p1 = Participant(participantId=3, biobankId=6)
    self._insert(p1, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)

    qs = """
        bucketSize=1
        &stratification=ENROLLMENT_STATUS
        &startDate=2017-12-30
        &endDate=2018-01-04
        &awardee=PITT
        """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 1)

    qs = """
        bucketSize=1
        &stratification=ENROLLMENT_STATUS
        &startDate=2017-12-30
        &endDate=2018-01-04
        &awardee=AZ_TUCSON
        """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 2)

  def test_get_counts_with_multiple_awardee_filters(self):
    # Does the awardee filter work when passed more than one awardee?

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)

    qs = """
        bucketSize=1
        &stratification=ENROLLMENT_STATUS
        &startDate=2017-12-30
        &endDate=2018-01-04
        &awardee=PITT,AZ_TUCSON
        &enrollmentStatus=
        """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 3)

  def test_get_counts_with_enrollment_status_member_filter(self):

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1, time_mem=self.time2)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1, time_mem=self.time2)

    p4 = Participant(participantId=4, biobankId=7)
    self._insert(p4, 'Debra', 'Dinosaur', 'PITT', time_int=self.time1, time_mem=self.time3)

    # awardee param intentionally left blank to test we can handle it
    qs = """
      bucketSize=1
      &stratification=ENROLLMENT_STATUS
      &startDate=2017-12-30
      &endDate=2018-01-04
      &awardee=
      &enrollmentStatus=MEMBER
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    member_count_day_1 = response[0]['metrics']['MEMBER']
    member_count_day_2 = response[1]['metrics']['MEMBER']
    member_count_day_3 = response[2]['metrics']['MEMBER']
    member_count_day_4 = response[3]['metrics']['MEMBER']
    interested_count_day_4 = response[1]['metrics']['INTERESTED']

    self.assertEquals(member_count_day_1, 0)
    self.assertEquals(member_count_day_2, 0)
    self.assertEquals(member_count_day_3, 2)
    self.assertEquals(member_count_day_4, 3)
    self.assertEquals(interested_count_day_4, 0)

    qs = """
      bucketSize=1
      &stratification=TOTAL
      &startDate=2017-12-30
      &endDate=2018-01-04
      &enrollmentStatus=MEMBER
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    total_count_day_1 = response[0]['metrics']['TOTAL']
    total_count_day_2 = response[1]['metrics']['TOTAL']

    self.assertEquals(total_count_day_1, 0)
    self.assertEquals(total_count_day_2, 3)

  def test_get_counts_with_enrollment_status_full_participant_filter(self):

    # MEMBER @ time 1
    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1,
                 time_mem=self.time1)

    # FULL PARTICIPANT @ time 2
    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1,
                 time_mem=self.time1, time_fp=self.time2)

    # FULL PARTICIPANT @ time 2
    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1,
                 time_mem=self.time1, time_fp=self.time2)

    # FULL PARTICIPANT @ time 3
    p4 = Participant(participantId=4, biobankId=7)
    self._insert(p4, 'Debra', 'Dinosaur', 'PITT', time_int=self.time1,
                 time_mem=self.time1, time_fp=self.time3)

    qs = """
      bucketSize=1
      &stratification=ENROLLMENT_STATUS
      &startDate=2017-12-30
      &endDate=2018-01-04
      &enrollmentStatus=FULL_PARTICIPANT
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    full_participant_count_day_1 = response[0]['metrics']['FULL_PARTICIPANT']
    full_participant_count_day_2 = response[1]['metrics']['FULL_PARTICIPANT']
    full_participant_count_day_3 = response[2]['metrics']['FULL_PARTICIPANT']
    full_participant_count_day_4 = response[3]['metrics']['FULL_PARTICIPANT']
    member_count_day_4 = response[4]['metrics']['MEMBER']

    self.assertEquals(full_participant_count_day_1, 0)
    self.assertEquals(full_participant_count_day_2, 0)
    self.assertEquals(full_participant_count_day_3, 2)
    self.assertEquals(full_participant_count_day_4, 3)
    self.assertEquals(member_count_day_4, 0)  # Excluded per enrollmentStatus parameter

  def test_get_counts_with_total_enrollment_status_full_participant_filter(self):
    # When filtering with TOTAL stratification, filtered participants are
    # returned by their sign up date, not the date they reached their highest
    # enrollment status.

    # MEMBER @ time 1
    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1,
                 time_mem=self.time1)

    # FULL PARTICIPANT @ time 2
    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1,
                 time_mem=self.time1, time_fp=self.time2)

    # FULL PARTICIPANT @ time 2
    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1,
                 time_mem=self.time1, time_fp=self.time2)

    # FULL PARTICIPANT @ time 3
    p4 = Participant(participantId=4, biobankId=7)
    self._insert(p4, 'Debra', 'Dinosaur', 'PITT', time_int=self.time1,
                 time_mem=self.time1, time_fp=self.time3)

    qs = """
      bucketSize=1
      &stratification=TOTAL
      &startDate=2017-12-30
      &endDate=2018-01-04
      &enrollmentStatus=FULL_PARTICIPANT
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    total_count_day_1 = response[0]['metrics']['TOTAL']
    total_count_day_2 = response[1]['metrics']['TOTAL']
    total_count_day_3 = response[2]['metrics']['TOTAL']
    total_count_day_4 = response[3]['metrics']['TOTAL']

    self.assertEquals(total_count_day_1, 0)
    self.assertEquals(total_count_day_2, 3)
    self.assertEquals(total_count_day_3, 3)
    self.assertEquals(total_count_day_4, 3)

  def test_get_counts_with_single_various_filters(self):
    # Do the awardee and enrollment status filters work when passed single values?

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1,
                 time_mem=self.time1)

    p4 = Participant(participantId=4, biobankId=7)
    self._insert(p4, 'Debra', 'Dinosaur', 'PITT', time_int=self.time1,
                 time_mem=self.time1)

    qs = """
      bucketSize=1
      &stratification=ENROLLMENT_STATUS
      &startDate=2017-12-30
      &endDate=2018-01-04
      &awardee=PITT
      &enrollmentStatus=MEMBER
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']
    member_count_day_2 = response[1]['metrics']['MEMBER']

    self.assertEquals(interested_count_day_1, 0)

    # We requested data for only MEMBERs, so no INTERESTEDs should be returned
    self.assertEquals(interested_count_day_2, 0)

    # We requested data for only MEMBERs in PITT, so no MEMBERs in AZ_TUCSON should be returned
    self.assertEquals(member_count_day_2, 1)

  def test_get_counts_with_multiple_various_filters(self):
    # Do the awardee and enrollment status filters work when passed multiple values?

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)

    qs = """
        bucketSize=1
        &stratification=ENROLLMENT_STATUS
        &startDate=2017-12-30
        &endDate=2018-01-04
        &awardee=AZ_TUCSON,PITT
        &enrollmentStatus=INTERESTED,MEMBER,FULL_PARTICIPANT
        """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_1 = response[0]['metrics']['INTERESTED']
    interested_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(interested_count_day_1, 0)
    self.assertEquals(interested_count_day_2, 3)

  def test_get_counts_with_total_stratification_unfiltered(self):

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)

    qs = """
      bucketSize=1
      &stratification=TOTAL
      &startDate=2017-12-30
      &endDate=2018-01-04
      """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    total_count_day_1 = response[0]['metrics']['TOTAL']
    total_count_day_2 = response[1]['metrics']['TOTAL']

    self.assertEquals(total_count_day_1, 0)
    self.assertEquals(total_count_day_2, 3)

  def test_get_counts_excluding_interested_participants(self):
    # When filtering only for MEMBER, no INTERESTED (neither consented nor unconsented) should be counted

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'UNSET', unconsented=True, time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1, time_mem=self.time1)

    qs = """
        bucketSize=1
        &stratification=ENROLLMENT_STATUS
        &startDate=2017-12-30
        &endDate=2018-01-04
        &enrollmentStatus=MEMBER
        """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    interested_count_day_2 = response[1]['metrics']['INTERESTED']
    member_count_day_2 = response[1]['metrics']['MEMBER']

    self.assertEquals(interested_count_day_2, 0)
    self.assertEquals(member_count_day_2, 1)

  def test_get_counts_excluding_withdrawn_participants(self):
    # Withdrawn participants should not appear in counts

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'PITT', time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    ps3 = self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)
    ps3.withdrawalStatus = WithdrawalStatus.NO_USE  # Chad withdrew from the study
    self.ps_dao.update(ps3)

    qs = """
        bucketSize=1
        &stratification=TOTAL
        &startDate=2017-12-30
        &endDate=2018-01-04
        """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    total_count_day_1 = response[0]['metrics']['TOTAL']
    total_count_day_2 = response[1]['metrics']['TOTAL']

    self.assertEquals(total_count_day_1, 0)
    self.assertEquals(total_count_day_2, 2)

  def test_get_counts_for_unconsented_individuals(self):
    # Those who have signed up but not consented should be INTERESTED

    p1 = Participant(participantId=1, biobankId=4)
    self._insert(p1, 'Alice', 'Aardvark', 'UNSET', unconsented=True, time_int=self.time1)

    p2 = Participant(participantId=2, biobankId=5)
    self._insert(p2, 'Bob', 'Builder', 'AZ_TUCSON', time_int=self.time1)

    p3 = Participant(participantId=3, biobankId=6)
    self._insert(p3, 'Chad', 'Caterpillar', 'AZ_TUCSON', time_int=self.time1)

    qs = """
          bucketSize=1
          &stratification=ENROLLMENT_STATUS
          &startDate=2017-12-30
          &endDate=2018-01-04
          """

    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs)

    total_count_day_1 = response[0]['metrics']['INTERESTED']
    total_count_day_2 = response[1]['metrics']['INTERESTED']

    self.assertEquals(total_count_day_1, 0)
    self.assertEquals(total_count_day_2, 3)

  def test_url_parameter_validation_for_date_range(self):
    # Ensure requests for very long date ranges are marked BAD REQUEST

    qs = """
        bucketSize=1
        &stratification=TOTAL
        &startDate=2017-12-30
        &endDate=2217-12-30
        """
    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs,
                             expected_status=httplib.BAD_REQUEST)
    self.assertEquals(response, None)

  def test_url_parameter_validation_for_stratifications(self):
    # Ensure requests invalid stratifications are marked BAD REQUEST

    qs = """
          bucketSize=1
          &stratification=FOOBAR
          &startDate=2017-12-30
          &endDate=2018-01-04
          """
    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs,
                             expected_status=httplib.BAD_REQUEST)
    self.assertEquals(response, None)

  def test_url_parameter_validation_for_awardee(self):
    # Ensure requests invalid awardee are marked BAD REQUEST

    qs = """
            bucketSize=1
            &stratification=ENROLLMENT_STATUS
            &startDate=2017-12-30
            &endDate=2018-01-04
            &awardee=FOOBAR
            """
    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs,
                             expected_status=httplib.BAD_REQUEST)
    self.assertEquals(response, None)

  def test_url_parameter_validation_for_enrollment_status(self):
    # Ensure requests invalid enrollment status are marked BAD REQUEST

    qs = """
            bucketSize=1
            &stratification=ENROLLMENT_STATUS
            &startDate=2017-12-30
            &endDate=2018-01-04
            &enrollmentStatus=FOOBAR
            """
    qs = ''.join(qs.split())  # Remove all whitespace

    response = self.send_get('ParticipantCountsOverTime', query_string=qs,
                             expected_status=httplib.BAD_REQUEST)
    self.assertEquals(response, None)
Example #5
0
class ParticipantSummaryDaoTest(NdbTestBase):
    def setUp(self):
        super(ParticipantSummaryDaoTest, self).setUp()
        self.dao = ParticipantSummaryDao()
        self.participant_dao = ParticipantDao()
        self.no_filter_query = Query([], None, 2, None)
        self.one_filter_query = Query(
            [FieldFilter("participantId", Operator.EQUALS, 1)], None, 2, None)
        self.two_filter_query = Query([
            FieldFilter("participantId", Operator.EQUALS, 1),
            FieldFilter("hpoId", Operator.EQUALS, PITT_HPO_ID)
        ], None, 2, None)
        self.ascending_biobank_id_query = Query([], OrderBy("biobankId", True),
                                                2, None)
        self.descending_biobank_id_query = Query([],
                                                 OrderBy("biobankId",
                                                         False), 2, None)
        self.enrollment_status_order_query = Query([],
                                                   OrderBy(
                                                       "enrollmentStatus",
                                                       True), 2, None)
        self.hpo_id_order_query = Query([], OrderBy("hpoId", True), 2, None)
        self.first_name_order_query = Query([], OrderBy("firstName", True), 2,
                                            None)

    def assert_no_results(self, query):
        results = self.dao.query(query)
        self.assertEquals([], results.items)
        self.assertIsNone(results.pagination_token)

    def assert_results(self, query, items, pagination_token=None):
        results = self.dao.query(query)
        self.assertListAsDictEquals(items, results.items)
        self.assertEquals(
            pagination_token, results.pagination_token,
            "Pagination tokens don't match; decoded = %s, %s" %
            (_decode_token(pagination_token),
             _decode_token(results.pagination_token)))

    def testQuery_noSummaries(self):
        self.assert_no_results(self.no_filter_query)
        self.assert_no_results(self.one_filter_query)
        self.assert_no_results(self.two_filter_query)
        self.assert_no_results(self.ascending_biobank_id_query)
        self.assert_no_results(self.descending_biobank_id_query)

    def _insert(self, participant, first_name=None, last_name=None):
        self.participant_dao.insert(participant)
        summary = self.participant_summary(participant)
        if first_name:
            summary.firstName = first_name
        if last_name:
            summary.lastName = last_name
        self.dao.insert(summary)
        return participant

    def testQuery_oneSummary(self):
        participant = Participant(participantId=1, biobankId=2)
        self._insert(participant)
        summary = self.dao.get(1)
        self.assert_results(self.no_filter_query, [summary])
        self.assert_results(self.one_filter_query, [summary])
        self.assert_no_results(self.two_filter_query)
        self.assert_results(self.ascending_biobank_id_query, [summary])
        self.assert_results(self.descending_biobank_id_query, [summary])

    def testUnicodeNameRoundTrip(self):
        name = self.fake.first_name()
        with self.assertRaises(UnicodeEncodeError):
            str(name)  # sanity check that the name contains non-ASCII
        participant = self._insert(Participant(participantId=1, biobankId=2))
        summary = self.dao.get(participant.participantId)
        summary.firstName = name
        self.dao.update(summary)
        fetched_summary = self.dao.get(participant.participantId)
        self.assertEquals(name, fetched_summary.firstName)

    def testQuery_twoSummaries(self):
        participant_1 = Participant(participantId=1, biobankId=2)
        self._insert(participant_1, 'Alice', 'Smith')
        participant_2 = Participant(participantId=2, biobankId=1)
        self._insert(participant_2, 'Zed', 'Zebra')
        ps_1 = self.dao.get(1)
        ps_2 = self.dao.get(2)
        self.assert_results(self.no_filter_query, [ps_1, ps_2])
        self.assert_results(self.one_filter_query, [ps_1])
        self.assert_no_results(self.two_filter_query)
        self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_1])
        self.assert_results(self.descending_biobank_id_query, [ps_1, ps_2])

    def testQuery_threeSummaries_paginate(self):
        participant_1 = Participant(participantId=1, biobankId=4)
        self._insert(participant_1, 'Alice', 'Aardvark')
        participant_2 = Participant(participantId=2, biobankId=1)
        self._insert(participant_2, 'Bob', 'Builder')
        participant_3 = Participant(participantId=3, biobankId=3)
        self._insert(participant_3, 'Chad', 'Caterpillar')
        ps_1 = self.dao.get(1)
        ps_2 = self.dao.get(2)
        ps_3 = self.dao.get(3)
        self.assert_results(
            self.no_filter_query, [ps_1, ps_2],
            _make_pagination_token(['Builder', 'Bob', None, 2]))
        self.assert_results(self.one_filter_query, [ps_1])
        self.assert_no_results(self.two_filter_query)
        self.assert_results(
            self.ascending_biobank_id_query, [ps_2, ps_3],
            _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3]))
        self.assert_results(
            self.descending_biobank_id_query, [ps_1, ps_3],
            _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3]))

        self.assert_results(
            _with_token(self.no_filter_query,
                        _make_pagination_token(['Builder', 'Bob', None, 2])),
            [ps_3])
        self.assert_results(
            _with_token(
                self.ascending_biobank_id_query,
                _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])),
            [ps_1])
        self.assert_results(
            _with_token(
                self.descending_biobank_id_query,
                _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])),
            [ps_2])

    def testQuery_fourFullSummaries_paginate(self):
        participant_1 = Participant(participantId=1, biobankId=4)
        self._insert(participant_1, 'Bob', 'Jones')
        participant_2 = Participant(participantId=2, biobankId=1)
        self._insert(participant_2, 'Bob', 'Jones')
        participant_3 = Participant(participantId=3, biobankId=3)
        self._insert(participant_3, 'Bob', 'Jones')
        participant_4 = Participant(participantId=4, biobankId=2)
        self._insert(participant_4, 'Bob', 'Jones')
        ps_1 = self.dao.get(1)
        ps_2 = self.dao.get(2)
        ps_3 = self.dao.get(3)
        ps_4 = self.dao.get(4)

        ps_1.lastName = 'Jones'
        ps_1.firstName = 'Bob'
        ps_1.dateOfBirth = datetime.date(1978, 10, 9)
        ps_1.hpoId = PITT_HPO_ID
        self.dao.update(ps_1)

        ps_2.lastName = 'Aardvark'
        ps_2.firstName = 'Bob'
        ps_2.dateOfBirth = datetime.date(1978, 10, 10)
        ps_2.enrollmentStatus = EnrollmentStatus.MEMBER
        self.dao.update(ps_2)

        ps_3.lastName = 'Jones'
        ps_3.firstName = 'Bob'
        ps_3.dateOfBirth = datetime.date(1978, 10, 10)
        ps_3.hpoId = PITT_HPO_ID
        ps_3.enrollmentStatus = EnrollmentStatus.MEMBER
        self.dao.update(ps_3)

        ps_4.lastName = 'Jones'
        ps_4.enrollmentStatus = EnrollmentStatus.FULL_PARTICIPANT
        self.dao.update(ps_4)

        self.assert_results(self.no_filter_query, [ps_2, ps_4],
                            _make_pagination_token(['Jones', 'Bob', None, 4]))
        self.assert_results(self.one_filter_query, [ps_1])
        self.assert_results(self.two_filter_query, [ps_1])
        self.assert_results(
            self.ascending_biobank_id_query, [ps_2, ps_4],
            _make_pagination_token([2, 'Jones', 'Bob', None, 4]))
        self.assert_results(
            self.descending_biobank_id_query, [ps_1, ps_3],
            _make_pagination_token(
                [3, 'Jones', 'Bob',
                 datetime.date(1978, 10, 10), 3]))
        self.assert_results(
            self.hpo_id_order_query, [ps_2, ps_4],
            _make_pagination_token([0, 'Jones', 'Bob', None, 4]))
        self.assert_results(
            self.enrollment_status_order_query, [ps_1, ps_2],
            _make_pagination_token(
                ['MEMBER', 'Aardvark', 'Bob',
                 datetime.date(1978, 10, 10), 2]))

        self.assert_results(
            _with_token(self.no_filter_query,
                        _make_pagination_token(['Jones', 'Bob', None, 4])),
            [ps_1, ps_3])
        self.assert_results(
            _with_token(self.ascending_biobank_id_query,
                        _make_pagination_token([2, 'Jones', 'Bob', None, 4])),
            [ps_3, ps_1])
        self.assert_results(
            _with_token(
                self.descending_biobank_id_query,
                _make_pagination_token(
                    [3, 'Jones', 'Bob',
                     datetime.date(1978, 10, 10), 3])), [ps_4, ps_2])
        self.assert_results(
            _with_token(self.hpo_id_order_query,
                        _make_pagination_token([0, 'Jones', 'Bob', None, 4])),
            [ps_1, ps_3])
        self.assert_results(
            _with_token(
                self.enrollment_status_order_query,
                _make_pagination_token([
                    'MEMBER', 'Aardvark', 'Bob',
                    datetime.date(1978, 10, 10), 2
                ])), [ps_3, ps_4])

    def test_update_from_samples(self):
        baseline_tests = ['BASELINE1', 'BASELINE2']
        config.override_setting(config.BASELINE_SAMPLE_TEST_CODES,
                                baseline_tests)
        self.dao.update_from_biobank_stored_samples()  # safe noop

        p_baseline_samples = self._insert(
            Participant(participantId=1, biobankId=11))
        p_mixed_samples = self._insert(
            Participant(participantId=2, biobankId=22))
        p_no_samples = self._insert(Participant(participantId=3, biobankId=33))
        self.assertEquals(
            self.dao.get(
                p_baseline_samples.participantId).numBaselineSamplesArrived, 0)

        sample_dao = BiobankStoredSampleDao()

        def add_sample(participant, test_code, sample_id):
            sample_dao.insert(
                BiobankStoredSample(biobankStoredSampleId=sample_id,
                                    biobankId=participant.biobankId,
                                    test=test_code))

        add_sample(p_baseline_samples, baseline_tests[0], '11111')
        add_sample(p_baseline_samples, baseline_tests[1], '22223')
        add_sample(p_mixed_samples, baseline_tests[0], '11112')
        add_sample(p_mixed_samples, 'NOT1', '44441')

        self.dao.update_from_biobank_stored_samples()
        self.assertEquals(
            self.dao.get(
                p_baseline_samples.participantId).numBaselineSamplesArrived, 2)
        self.assertEquals(
            self.dao.get(
                p_mixed_samples.participantId).numBaselineSamplesArrived, 1)
        self.assertEquals(
            self.dao.get(p_no_samples.participantId).numBaselineSamplesArrived,
            0)

    def test_calculate_enrollment_status(self):
        self.assertEquals(
            EnrollmentStatus.FULL_PARTICIPANT,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED))
        self.assertEquals(
            EnrollmentStatus.MEMBER,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES - 1,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED))
        self.assertEquals(
            EnrollmentStatus.MEMBER,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.UNSET, SampleStatus.RECEIVED))
        self.assertEquals(
            EnrollmentStatus.MEMBER,
            self.dao.calculate_enrollment_status(
                True, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.UNSET))
        self.assertEquals(
            EnrollmentStatus.INTERESTED,
            self.dao.calculate_enrollment_status(
                False, NUM_BASELINE_PPI_MODULES,
                PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED))
Example #6
0
class MetricsEhrApiTestBase(FlaskTestBase):
    def setUp(self, **kwargs):
        super(MetricsEhrApiTestBase, self).setUp(use_mysql=True, **kwargs)
        self.dao = ParticipantDao()
        self.ps_dao = ParticipantSummaryDao()
        self.ehr_receipt_dao = EhrReceiptDao()
        self.ps = ParticipantSummary()
        self.calendar_dao = CalendarDao()
        self.site_dao = SiteDao()
        self.hpo_dao = HPODao()
        self.org_dao = OrganizationDao()

        self.hpo_test = self._make_hpo(hpoId=TEST_HPO_ID,
                                       name=TEST_HPO_NAME,
                                       displayName='Test',
                                       organizationType=OrganizationType.UNSET)

        self.hpo_foo = self._make_hpo(hpoId=10, name='FOO', displayName='Foo')
        self.hpo_bar = self._make_hpo(hpoId=11, name='BAR', displayName='Bar')

        self.org_foo_a = self._make_org(organizationId=10,
                                        externalId='FOO_A',
                                        displayName='Foo A',
                                        hpoId=self.hpo_foo.hpoId)
        self.org_bar_a = self._make_org(organizationId=11,
                                        externalId='BAR_A',
                                        displayName='Bar A',
                                        hpoId=self.hpo_bar.hpoId)

    def _make_hpo(self, **kwargs):
        hpo = HPO(**kwargs)
        self.hpo_dao.insert(hpo)
        return hpo

    def _make_org(self, **kwargs):
        org = Organization(**kwargs)
        self.org_dao.insert(org)
        return org

    def _make_participant(self,
                          participant,
                          first_name=None,
                          last_name=None,
                          hpo=None,
                          organization=None,
                          unconsented=False,
                          time_int=None,
                          time_study=None,
                          time_mem=None,
                          time_fp=None,
                          time_fp_stored=None,
                          gender_id=None,
                          dob=None,
                          state_id=None):
        """
    Create a participant in a transient test database.

    Note: copied from ParticipantCountsOverTimeApiTest

    :param participant: Participant object
    :param first_name: First name
    :param last_name: Last name
    :param time_int: Time that participant fulfilled INTERESTED criteria
    :param time_mem: Time that participant fulfilled MEMBER criteria
    :param time_fp: Time that participant fulfilled FULL_PARTICIPANT criteria
    :return: Participant object
    """

        participant.hpoId = hpo.hpoId
        participant.organizationId = organization.organizationId

        if unconsented is True:
            enrollment_status = None
        elif time_mem is None:
            enrollment_status = EnrollmentStatus.INTERESTED
        elif time_fp is None:
            enrollment_status = EnrollmentStatus.MEMBER
        else:
            enrollment_status = EnrollmentStatus.FULL_PARTICIPANT

        with FakeClock(time_int):
            self.dao.insert(participant)

        participant.providerLink = make_primary_provider_link_for_name(
            hpo.name)
        with FakeClock(time_mem):
            self.dao.update(participant)

        if enrollment_status is None:
            return None

        summary = self.participant_summary(participant)

        if first_name:
            summary.firstName = first_name
        if last_name:
            summary.lastName = last_name

        if gender_id:
            summary.genderIdentityId = gender_id
        if dob:
            summary.dateOfBirth = dob
        else:
            summary.dateOfBirth = datetime.date(1978, 10, 10)
        if state_id:
            summary.stateId = state_id

        summary.enrollmentStatus = enrollment_status

        summary.enrollmentStatusMemberTime = time_mem
        summary.enrollmentStatusCoreOrderedSampleTime = time_fp
        summary.enrollmentStatusCoreStoredSampleTime = time_fp_stored

        summary.hpoId = hpo.hpoId
        summary.organizationId = organization.organizationId

        if time_study is not None:
            with FakeClock(time_mem):
                summary.consentForStudyEnrollment = QuestionnaireStatus.SUBMITTED
                summary.consentForStudyEnrollmentTime = time_study

        if time_mem is not None:
            with FakeClock(time_mem):
                summary.consentForElectronicHealthRecords = QuestionnaireStatus.SUBMITTED
                summary.consentForElectronicHealthRecordsTime = time_mem

        if time_fp is not None:
            with FakeClock(time_fp):
                if not summary.consentForElectronicHealthRecords:
                    summary.consentForElectronicHealthRecords = QuestionnaireStatus.SUBMITTED
                    summary.consentForElectronicHealthRecordsTime = time_fp
                summary.questionnaireOnTheBasicsTime = time_fp
                summary.questionnaireOnLifestyleTime = time_fp
                summary.questionnaireOnOverallHealthTime = time_fp
                summary.physicalMeasurementsFinalizedTime = time_fp
                summary.physicalMeasurementsTime = time_fp
                summary.sampleOrderStatus1ED04Time = time_fp
                summary.sampleOrderStatus1SALTime = time_fp
                summary.sampleStatus1ED04Time = time_fp
                summary.sampleStatus1SALTime = time_fp
                summary.biospecimenOrderTime = time_fp
                summary.numCompletedBaselinePPIModules = REQUIRED_PPI_MODULE_COUNT

        self.ps_dao.insert(summary)

        return summary

    def _update_ehr(self, participant_summary, update_time):
        receipt = EhrReceipt(organizationId=participant_summary.organizationId,
                             receiptTime=update_time)
        self.ehr_receipt_dao.insert(receipt)
        self.ps_dao.update_ehr_status(participant_summary, update_time)
        self.ps_dao.update(participant_summary)
class EhrReceiptDaoTest(SqlTestBase):
    def setUp(self, with_data=True, use_mysql=True):
        super(EhrReceiptDaoTest, self).setUp(with_data=with_data,
                                             use_mysql=use_mysql)
        self.setup_fake()
        self.calendar_dao = CalendarDao()
        self.org_dao = OrganizationDao()
        self.hpo_dao = HPODao()
        self.participant_dao = ParticipantDao()
        self.summary_dao = ParticipantSummaryDao()
        self.ehr_receipt_dao = EhrReceiptDao()
        self._setup_initial_data()

    @staticmethod
    def _iter_dates_in_range(start, end):
        current = start
        while current <= end:
            yield current
            current += datetime.timedelta(days=1)

    def _fill_calendar_range(self, start, end):
        for date in self._iter_dates_in_range(start, end):
            self.calendar_dao.insert(Calendar(day=date))

    def _make_hpo(self, int_id, string_id):
        hpo = HPO(hpoId=int_id, name=string_id)
        self.hpo_dao.insert(hpo)
        return hpo

    def _make_org(self, **kwargs):
        org = Organization(**kwargs)
        self.org_dao.insert(org)
        return org

    def _make_participant(self, org, int_id):
        participant = self._participant_with_defaults(participantId=int_id,
                                                      biobankId=int_id)
        participant.hpoId = org.hpoId
        participant.organizationId = org.organizationId
        self.participant_dao.insert(participant)
        summary = self.participant_summary(participant)
        summary.hpoId = participant.hpoId
        summary.organizationId = participant.organizationId
        self.summary_dao.insert(summary)
        return participant, summary

    def _update_ehr(self, participant_summary, update_time):
        self.summary_dao.update_ehr_status(participant_summary, update_time)
        self.summary_dao.update(participant_summary)

    def _save_ehr_receipt(self, org, receipt_time):
        receipt = EhrReceipt(organizationId=org.organizationId,
                             receiptTime=receipt_time)
        self.ehr_receipt_dao.insert(receipt)

    def _setup_initial_data(self):
        self.hpo_foo = self._make_hpo(int_id=10, string_id='hpo_foo')
        self.hpo_bar = self._make_hpo(int_id=11, string_id='hpo_bar')

        self.org_foo_a = self._make_org(organizationId=10,
                                        externalId='FOO_A',
                                        displayName='Foo A',
                                        hpoId=self.hpo_foo.hpoId)
        self.org_bar_a = self._make_org(organizationId=11,
                                        externalId='BAR_A',
                                        displayName='Bar A',
                                        hpoId=self.hpo_bar.hpoId)

        participant_and_summary_pairs = [
            self._make_participant(org=self.org_foo_a, int_id=11),
            self._make_participant(org=self.org_foo_a, int_id=12),
            self._make_participant(org=self.org_bar_a, int_id=13),
            self._make_participant(org=self.org_bar_a, int_id=14),
        ]
        self.participants = {
            participant.participantId: participant
            for participant, summary in participant_and_summary_pairs
        }
        self.summaries = {
            participant.participantId: summary
            for participant, summary in participant_and_summary_pairs
        }

    def test_get_active_organization_counts_in_interval_day(self):
        self._fill_calendar_range(datetime.date(2019, 1, 1),
                                  datetime.date(2019, 3, 1))

        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 2, 2))
        self._save_ehr_receipt(org=self.org_bar_a,
                               receipt_time=datetime.datetime(2019, 2, 2))
        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 2, 4))

        results = self.ehr_receipt_dao.get_active_organization_counts_in_interval(
            start_date=datetime.datetime(2019, 2, 1),
            end_date=datetime.datetime(2019, 2, 7),
            interval=INTERVAL_DAY)

        self.assertEqual([(r['start_date'], r['active_organization_count'])
                          for r in results], [
                              (datetime.date(2019, 2, 1), 0L),
                              (datetime.date(2019, 2, 2), 2L),
                              (datetime.date(2019, 2, 3), 0L),
                              (datetime.date(2019, 2, 4), 1L),
                              (datetime.date(2019, 2, 5), 0L),
                              (datetime.date(2019, 2, 6), 0L),
                              (datetime.date(2019, 2, 7), 0L),
                          ])

    def test_get_active_organization_counts_in_interval_week(self):
        self._fill_calendar_range(datetime.date(2019, 1, 1),
                                  datetime.date(2019, 3, 1))

        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 2, 4))
        self._save_ehr_receipt(org=self.org_bar_a,
                               receipt_time=datetime.datetime(2019, 2, 4))
        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 2, 18))

        results = self.ehr_receipt_dao.get_active_organization_counts_in_interval(
            start_date=datetime.datetime(2019, 2, 1),
            end_date=datetime.datetime(2019, 3, 1),
            interval=INTERVAL_WEEK)

        self.assertEqual([(r['start_date'], r['active_organization_count'])
                          for r in results], [
                              (datetime.date(2019, 1, 27), 0L),
                              (datetime.date(2019, 2, 3), 2L),
                              (datetime.date(2019, 2, 10), 0L),
                              (datetime.date(2019, 2, 17), 1L),
                              (datetime.date(2019, 2, 24), 0L),
                          ])

    def test_get_active_organization_counts_in_interval_month(self):
        self._fill_calendar_range(datetime.date(2018, 12, 1),
                                  datetime.date(2019, 7, 1))

        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 2, 1))
        self._save_ehr_receipt(org=self.org_bar_a,
                               receipt_time=datetime.datetime(2019, 2, 1))
        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 4, 1))

        results = self.ehr_receipt_dao.get_active_organization_counts_in_interval(
            start_date=datetime.datetime(2019, 1, 1),
            end_date=datetime.datetime(2019, 5, 1),
            interval=INTERVAL_MONTH)

        self.assertEqual([(r['start_date'], r['active_organization_count'])
                          for r in results], [
                              (datetime.date(2019, 1, 1), 0L),
                              (datetime.date(2019, 2, 1), 2L),
                              (datetime.date(2019, 3, 1), 0L),
                              (datetime.date(2019, 4, 1), 1L),
                              (datetime.date(2019, 5, 1), 0L),
                          ])

    def test_get_active_organization_counts_in_interval_quarter(self):
        self._fill_calendar_range(datetime.date(2018, 12, 1),
                                  datetime.date(2020, 1, 1))

        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 5, 1))
        self._save_ehr_receipt(org=self.org_bar_a,
                               receipt_time=datetime.datetime(2019, 5, 1))
        self._save_ehr_receipt(org=self.org_foo_a,
                               receipt_time=datetime.datetime(2019, 11, 1))

        results = self.ehr_receipt_dao.get_active_organization_counts_in_interval(
            start_date=datetime.datetime(2019, 1, 1),
            end_date=datetime.datetime(2020, 1, 1),
            interval=INTERVAL_QUARTER)

        self.assertEqual([(r['start_date'], r['active_organization_count'])
                          for r in results], [
                              (datetime.date(2019, 1, 1), 0L),
                              (datetime.date(2019, 4, 1), 2L),
                              (datetime.date(2019, 7, 1), 0L),
                              (datetime.date(2019, 10, 1), 1L),
                              (datetime.date(2020, 1, 1), 0L),
                          ])