def test_overdue_stats(self):
        self.promote_user(user=self.test_user, role_name=ROLE.PATIENT.value)

        rp = ResearchProtocol(name='proto')
        with SessionScope(db):
            db.session.add(rp)
            db.session.commit()
        rp = db.session.merge(rp)
        rp_id = rp.id

        crv = Organization(name='CRV')
        crv.research_protocols.append(rp)
        epic26 = self.add_questionnaire(name='epic26')
        with SessionScope(db):
            db.session.add(crv)
            db.session.commit()
        crv, epic26 = map(db.session.merge, (crv, epic26))
        crv_id = crv.id

        bank = QuestionnaireBank(
            name='CRV', research_protocol_id=rp_id,
            start='{"days": 1}',
            overdue='{"days": 2}',
            expired='{"days": 90}')
        qbq = QuestionnaireBankQuestionnaire(
            questionnaire_id=epic26.id,
            rank=0)
        bank.questionnaires.append(qbq)

        with SessionScope(db):
            db.session.add(bank)
            db.session.commit()

        self.test_user = db.session.merge(self.test_user)
        self.test_user.organizations.append(crv)
        self.consent_with_org(org_id=crv_id)
        self.test_user = db.session.merge(self.test_user)

        # test user with status = 'Expired' (should not show up)
        a_s = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert a_s.overall_status == OverallStatus.expired

        ostats = self.get_ostats()
        assert len(ostats) == 0

        # test user with status = 'Overdue' (should show up)
        self.consent_with_org(org_id=crv_id, backdate=relativedelta(days=18))
        with SessionScope(db):
            db.session.add(bank)
            db.session.commit()
        crv, self.test_user = map(db.session.merge, (crv, self.test_user))

        invalidate_users_QBT(self.test_user.id)
        a_s = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert a_s.overall_status == OverallStatus.overdue

        ostats = self.get_ostats()
        assert len(ostats) == 1
        assert ostats[(crv.id, crv.name)] == [(15, TEST_USER_ID)]
    def test_overdue_stats(self):
        self.promote_user(user=self.test_user, role_name=ROLE.PATIENT.value)

        rp = ResearchProtocol(name='proto')
        with SessionScope(db):
            db.session.add(rp)
            db.session.commit()
        rp = db.session.merge(rp)
        rp_id = rp.id

        crv = Organization(name='CRV')
        crv.research_protocols.append(rp)
        epic26 = self.add_questionnaire(name='epic26')
        with SessionScope(db):
            db.session.add(crv)
            db.session.commit()
        crv, epic26 = map(db.session.merge, (crv, epic26))
        crv_id = crv.id

        bank = QuestionnaireBank(name='CRV',
                                 research_protocol_id=rp_id,
                                 start='{"days": 1}',
                                 overdue='{"days": 2}',
                                 expired='{"days": 90}')
        qbq = QuestionnaireBankQuestionnaire(questionnaire_id=epic26.id,
                                             rank=0)
        bank.questionnaires.append(qbq)

        with SessionScope(db):
            db.session.add(bank)
            db.session.commit()

        self.test_user = db.session.merge(self.test_user)
        self.test_user.organizations.append(crv)
        self.consent_with_org(org_id=crv_id)
        self.test_user = db.session.merge(self.test_user)

        # test user with status = 'Expired' (should not show up)
        a_s = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert a_s.overall_status == OverallStatus.expired

        ostats = self.get_ostats()
        assert len(ostats) == 0

        # test user with status = 'Overdue' (should show up)
        self.consent_with_org(org_id=crv_id, backdate=relativedelta(days=18))
        with SessionScope(db):
            db.session.add(bank)
            db.session.commit()
        crv, self.test_user = map(db.session.merge, (crv, self.test_user))

        invalidate_users_QBT(self.test_user.id)
        a_s = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert a_s.overall_status == OverallStatus.overdue

        ostats = self.get_ostats()
        assert len(ostats) == 1
        assert ostats[(crv.id, crv.name)] == [(15, TEST_USER_ID)]
def mock_qr(
        instrument_id, status='completed', timestamp=None, qb=None,
        doc_id=None, iteration=None, user_id=TEST_USER_ID, entry_method=None):
    if not doc_id:
        doc_id = ''.join(choice(ascii_letters) for _ in range(10))
    timestamp = timestamp or datetime.utcnow()
    qr_document = {
        "questionnaire": {
            "display": "Additional questions",
            "reference":
                "https://{}/api/questionnaires/{}".format(
                    'SERVER_NAME', instrument_id)},
        "identifier": {
            "use": "official",
            "label": "cPRO survey session ID",
            "value": doc_id,
            "system": "https://stg-ae.us.truenth.org/eproms-demo"}
    }

    enc = Encounter(
        status='planned', auth_method='url_authenticated', user_id=user_id,
        start_time=timestamp)
    if entry_method:
        enc.type.append(entry_method.codings[0])
    with SessionScope(db):
        db.session.add(enc)
        db.session.commit()
    enc = db.session.merge(enc)
    if not qb:
        qstats = QB_Status(get_user(user_id), timestamp)
        qbd = qstats.current_qbd()
        qb, iteration = qbd.questionnaire_bank, qbd.iteration

    qr = QuestionnaireResponse(
        subject_id=user_id,
        status=status,
        authored=timestamp,
        document=qr_document,
        encounter_id=enc.id,
        questionnaire_bank=qb,
        qb_iteration=iteration)
    with SessionScope(db):
        db.session.add(qr)
        db.session.commit()
    invalidate_users_QBT(user_id=user_id)
Exemple #4
0
    def add_user(
            self, username, first_name="", last_name="", image_url=None,
            password=DEFAULT_PASSWORD, email=None):
        """Create a user and add to test db, and return it"""
        # Hash the password
        password = self.app.user_manager.hash_password(password)

        test_user = User(
            username=username, first_name=first_name, last_name=last_name,
            image_url=image_url, password=password)
        if email is not None:
            test_user.email = email
        with SessionScope(db):
            db.session.add(test_user)
            db.session.commit()
        test_user = db.session.merge(test_user)
        # Avoid testing cached/stale data
        invalidate_users_QBT(test_user.id)
        return test_user
Exemple #5
0
def mock_qr(
        instrument_id, status='completed', timestamp=None, qb=None,
        doc_id=None, iteration=None, user_id=TEST_USER_ID):
    if not doc_id:
        doc_id = ''.join(choice(ascii_letters) for _ in range(10))
    timestamp = timestamp or datetime.utcnow()
    qr_document = {
        "questionnaire": {
            "display": "Additional questions",
            "reference":
                "https://{}/api/questionnaires/{}".format(
                    'SERVER_NAME', instrument_id)},
        "identifier": {
            "use": "official",
            "label": "cPRO survey session ID",
            "value": doc_id,
            "system": "https://stg-ae.us.truenth.org/eproms-demo"}
    }

    enc = Encounter(
        status='planned', auth_method='url_authenticated', user_id=user_id,
        start_time=timestamp)
    with SessionScope(db):
        db.session.add(enc)
        db.session.commit()
    enc = db.session.merge(enc)
    if not qb:
        qstats = QB_Status(get_user(user_id), timestamp)
        qbd = qstats.current_qbd()
        qb, iteration = qbd.questionnaire_bank, qbd.iteration

    qr = QuestionnaireResponse(
        subject_id=user_id,
        status=status,
        authored=timestamp,
        document=qr_document,
        encounter_id=enc.id,
        questionnaire_bank=qb,
        qb_iteration=iteration)
    with SessionScope(db):
        db.session.add(qr)
        db.session.commit()
    invalidate_users_QBT(user_id=user_id)
Exemple #6
0
    def test_st_undone(self):
        # Symptom Tracker QB with incompleted should generate communication
        mock_communication_request('symptom_tracker', '{"days": 30}')

        self.app.config['NO_CHALLENGE_WO_DATA'] = False
        self.promote_user(role_name=ROLE.PATIENT.value)
        self.login()
        self.add_required_clinical_data(backdate=relativedelta(days=31))
        self.test_user = db.session.merge(self.test_user)
        self.test_user.birthdate = '1969-07-16'

        # Confirm test user qualifies for ST QB
        qstats = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert qstats.enrolled_in_classification('baseline')

        # With most q's undone, should generate a message
        mock_qr(instrument_id='epic26')
        invalidate_users_QBT(TEST_USER_ID)
        self.test_user = db.session.merge(self.test_user)
        qstats = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert OverallStatus.in_progress == qstats.overall_status
        update_patient_loop(update_cache=False, queue_messages=True)
        expected = Communication.query.first()
        assert expected
    def test_st_undone(self):
        # Symptom Tracker QB with incompleted should generate communication
        mock_communication_request('symptom_tracker', '{"days": 30}')

        self.app.config['NO_CHALLENGE_WO_DATA'] = False
        self.promote_user(role_name=ROLE.PATIENT.value)
        self.login()
        self.add_required_clinical_data(backdate=relativedelta(days=31))
        self.test_user = db.session.merge(self.test_user)
        self.test_user.birthdate = '1969-07-16'

        # Confirm test user qualifies for ST QB
        qstats = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert qstats.enrolled_in_classification('baseline')

        # With most q's undone, should generate a message
        mock_qr(instrument_id='epic26')
        invalidate_users_QBT(TEST_USER_ID)
        self.test_user = db.session.merge(self.test_user)
        qstats = QB_Status(self.test_user, as_of_date=datetime.utcnow())
        assert OverallStatus.in_progress == qstats.overall_status
        update_patient_loop(update_cache=False, queue_messages=True)
        expected = Communication.query.first()
        assert expected
def patch_deceased_user_consents(uid):
    user = User.query.get(uid)
    user_consents = UserConsent.query.filter(
        UserConsent.user_id == uid).order_by(UserConsent.id).all()

    # if no user_consents exist for user, we're done
    if not user_consents:
        return

    # acquire the deceased date
    dd = user.deceased.timestamp

    # if the most recent consent looks correct, assume we're done
    if user_consents[-1].status == 'suspended' and not (
            user_consents[-1].options & SEND_REMINDERS_MASK):
        return

    admin = User.query.filter_by(email='__system__').first()

    log.info("Patching broken consents for {}, with deceased date {}".format(
        user, dd))
    keeper = user_consents.pop(0)
    log.info(" keeping original user_consent {} {} {} {}".format(
        keeper.id, keeper.status, keeper.options, keeper.acceptance_date))
    obsolete_audits, obsolete_consents = [], []
    for uc in user_consents:
        log.info(" deleting user_consent {} {} {} {}".format(
            uc.id, uc.status, uc.options, uc.acceptance_date))
        a = Audit.query.get(uc.audit_id)
        log.info("  and the related {}".format(a))
        obsolete_audits.append(a.id)
        obsolete_consents.append(uc.id)

    # out of loop, delete the unwanted
    db.session.query(UserConsent).filter(
        UserConsent.id.in_(obsolete_consents)).delete(
            synchronize_session=False)
    db.session.query(Audit).filter(
        Audit.id.in_(obsolete_audits)).delete(synchronize_session=False)
    db.session.commit()

    # now add back in the corrected consent, from the initial
    deceased_options = STAFF_EDITABLE_MASK | INCLUDE_IN_REPORTS_MASK
    deceased_consent = UserConsent(user_id=uid,
                                   organization_id=keeper.organization_id,
                                   agreement_url=keeper.agreement_url,
                                   acceptance_date=dd,
                                   options=deceased_options,
                                   status='suspended')

    user.update_consents(consent_list=[deceased_consent], acting_user=admin)

    # The updated consent may have altered the cached assessment
    # status - invalidate this user's data at this time.
    invalidate_users_QBT(user_id=user.id)
    db.session.commit()

    log.info(" Done, results:")
    for uc in UserConsent.query.filter(UserConsent.user_id == uid):
        log.info("  {} {} {} {}".format(uc, uc.acceptance_date, uc.status,
                                        uc.options))
    def test_outdated_done_indef(self):
        """Confirm completed indefinite counts after RP switch"""

        # boiler plate to create baseline and indef with retired RP
        yesterday = now - timedelta(days=1)
        weekago = now - timedelta(weeks=1)
        org, rp2, rp2_id = self.setup_org_n_rp(
            org_name='testorg', rp_name='v2', retired_as_of=yesterday)
        org, rp3, rp3_id = self.setup_org_n_rp(org=org, rp_name='v3')
        org_id = org.id
        self.promote_user(role_name=ROLE.PATIENT.value)
        self.test_user = db.session.merge(self.test_user)
        self.test_user.organizations.append(org)
        audit = Audit(user_id=TEST_USER_ID, subject_id=TEST_USER_ID)
        uc = UserConsent(
            user_id=TEST_USER_ID, organization_id=org_id,
            audit=audit, agreement_url='http://no.com',
            acceptance_date=weekago)
        with SessionScope(db):
            db.session.add(audit)
            db.session.add(uc)
            db.session.commit()

        self.setup_qb(
            questionnaire_name='epic23', qb_name='baseline v2',
            classification='baseline', rp_id=rp2_id)
        self.setup_qb(
            questionnaire_name='epic26', qb_name='baseline v3',
            classification='baseline', rp_id=rp3_id)
        qb2_indef = self.setup_qb(
            questionnaire_name='irondemog', qb_name='indef v2',
            classification='indefinite', rp_id=rp2_id)
        self.setup_qb(
            questionnaire_name='irondemog_v3', qb_name='indef v3',
            classification='indefinite', rp_id=rp3_id)

        # for today, should get the v3 baseline
        user = db.session.merge(self.test_user)
        a_s = QB_Status(user=user, as_of_date=now)
        assert (['epic26', 'irondemog_v3'] ==
                a_s.instruments_needing_full_assessment(classification='all'))

        # create done QNR for indefinite dated prior to rp transition
        # belonging to older qb - confirm that clears indef work as of then
        mock_qr('irondemog', timestamp=weekago, qb=qb2_indef)
        user = db.session.merge(self.test_user)
        invalidate_users_QBT(user_id=TEST_USER_ID)
        a_s = QB_Status(user=user, as_of_date=weekago)
        assert (a_s.instruments_needing_full_assessment(
            classification='indefinite') == [])

        # move forward in time; user should no longer need indefinite, even
        # tho RP changed
        qb2_indef = db.session.merge(qb2_indef)
        a_s = QB_Status(user=user, as_of_date=now)
        assert qb2_indef == a_s.current_qbd(
            classification='indefinite').questionnaire_bank
        assert (a_s.instruments_needing_full_assessment(
            classification='indefinite') == [])
        assert (a_s.instruments_needing_full_assessment(
            classification='all') == ['epic26'])
    def test_outdated_inprogress_qb(self):
        # create base QB/RP
        org, rp, rp_id = self.setup_org_n_rp(org_name='testorg')
        qn = self.add_questionnaire(name='epic26')
        qn, org, self.test_user = map(
            db.session.merge, (qn, org, self.test_user))
        org_id = org.id
        qb = QuestionnaireBank(
            name='Test Questionnaire Bank',
            classification='baseline',
            research_protocol_id=rp_id,
            start='{"days": 0}',
            overdue='{"days": 7}',
            expired='{"days": 90}')
        qbq = QuestionnaireBankQuestionnaire(questionnaire=qn, rank=0)
        qb.questionnaires.append(qbq)

        self.test_user.organizations.append(org)
        self.promote_user(role_name=ROLE.PATIENT.value)

        audit = Audit(user_id=TEST_USER_ID, subject_id=TEST_USER_ID)
        uc = UserConsent(
            user_id=TEST_USER_ID, organization=org,
            audit=audit, agreement_url='http://no.com',
            acceptance_date=now)

        with SessionScope(db):
            db.session.add(qb)
            db.session.add(self.test_user)
            db.session.add(audit)
            db.session.add(uc)
            db.session.commit()
        qb = db.session.merge(qb)

        # create in-progress QNR for User/QB/RP
        mock_qr(
            instrument_id='epic26', status='in-progress',
            timestamp=now, qb=qb)

        # User associated with CRV org should generate appropriate
        # questionnaires
        self.test_user = db.session.merge(self.test_user)
        qb_stat = QB_Status(self.test_user, now)
        qb = qb_stat.current_qbd().questionnaire_bank
        assert qb.research_protocol.name == 'proto'

        # Pointing the User's org to a new QB/RP
        # marking the old RP as retired as of yesterday
        old_rp = OrganizationResearchProtocol.query.filter(
            OrganizationResearchProtocol.organization_id == org_id,
            OrganizationResearchProtocol.research_protocol_id == rp_id).one()
        old_rp.retired_as_of = now - timedelta(days=1)
        rp2 = ResearchProtocol(name='new_proto')
        qn2 = self.add_questionnaire(name='epic27')
        with SessionScope(db):
            db.session.add(rp2)
            db.session.commit()
        rp2 = db.session.merge(rp2)
        rp2_id = rp2.id

        qn2, org = map(db.session.merge, (qn2, org))
        qb2 = QuestionnaireBank(
            name='Test Questionnaire Bank 2',
            classification='baseline',
            research_protocol_id=rp2_id,
            start='{"days": 0}',
            overdue='{"days": 7}',
            expired='{"days": 90}')
        qbq2 = QuestionnaireBankQuestionnaire(questionnaire=qn2, rank=0)
        qb2.questionnaires.append(qbq2)

        org.research_protocols.append(rp2)

        with SessionScope(db):
            db.session.add(qb2)
            db.session.add(org)
            db.session.commit()
        qb2 = db.session.merge(qb2)

        # outdated QB/RP should be used as long as User has in-progress QNR
        self.test_user = db.session.merge(self.test_user)
        qb_stat = QB_Status(self.test_user, now)
        qb = qb_stat.current_qbd().questionnaire_bank
        assert qb.name == 'Test Questionnaire Bank'
        assert qb.research_protocol.name == 'proto'

        # completing QNR should result in completed status
        # shouldn't pick up new protocol till next iteration
        mock_qr(
            instrument_id='epic26', status='completed',
            timestamp=now, qb=qb)

        self.test_user = db.session.merge(self.test_user)
        invalidate_users_QBT(TEST_USER_ID)
        qb_stat = QB_Status(self.test_user, now)
        qb = qb_stat.current_qbd().questionnaire_bank
        assert qb.name == 'Test Questionnaire Bank'
        assert qb_stat.overall_status == OverallStatus.completed
Exemple #11
0
    def test_outdated_done_indef(self):
        """Confirm completed indefinite counts after RP switch"""

        # boiler plate to create baseline and indef with retired RP
        yesterday = now - timedelta(days=1)
        weekago = now - timedelta(weeks=1)
        org, rp2, rp2_id = self.setup_org_n_rp(org_name='testorg',
                                               rp_name='v2',
                                               retired_as_of=yesterday)
        org, rp3, rp3_id = self.setup_org_n_rp(org=org, rp_name='v3')
        org_id = org.id
        self.promote_user(role_name=ROLE.PATIENT.value)
        self.test_user = db.session.merge(self.test_user)
        self.test_user.organizations.append(org)
        audit = Audit(user_id=TEST_USER_ID, subject_id=TEST_USER_ID)
        uc = UserConsent(user_id=TEST_USER_ID,
                         organization_id=org_id,
                         audit=audit,
                         agreement_url='http://no.com',
                         acceptance_date=weekago)
        with SessionScope(db):
            db.session.add(audit)
            db.session.add(uc)
            db.session.commit()

        self.setup_qb(questionnaire_name='epic23',
                      qb_name='baseline v2',
                      classification='baseline',
                      rp_id=rp2_id)
        self.setup_qb(questionnaire_name='epic26',
                      qb_name='baseline v3',
                      classification='baseline',
                      rp_id=rp3_id)
        qb2_indef = self.setup_qb(questionnaire_name='irondemog',
                                  qb_name='indef v2',
                                  classification='indefinite',
                                  rp_id=rp2_id)
        self.setup_qb(questionnaire_name='irondemog_v3',
                      qb_name='indef v3',
                      classification='indefinite',
                      rp_id=rp3_id)

        # for today, should get the v3 baseline
        user = db.session.merge(self.test_user)
        a_s = QB_Status(user=user, as_of_date=now)
        assert ([
            'epic26', 'irondemog_v3'
        ] == a_s.instruments_needing_full_assessment(classification='all'))

        # create done QNR for indefinite dated prior to rp transition
        # belonging to older qb - confirm that clears indef work as of then
        mock_qr('irondemog', timestamp=weekago, qb=qb2_indef)
        user = db.session.merge(self.test_user)
        invalidate_users_QBT(user_id=TEST_USER_ID)
        a_s = QB_Status(user=user, as_of_date=weekago)
        assert (a_s.instruments_needing_full_assessment(
            classification='indefinite') == [])

        # move forward in time; user should no longer need indefinite, even
        # tho RP changed
        qb2_indef = db.session.merge(qb2_indef)
        a_s = QB_Status(user=user, as_of_date=now)
        assert qb2_indef == a_s.current_qbd(
            classification='indefinite').questionnaire_bank
        assert (a_s.instruments_needing_full_assessment(
            classification='indefinite') == [])
        assert (a_s.instruments_needing_full_assessment(
            classification='all') == ['epic26'])
Exemple #12
0
    def test_outdated_inprogress_qb(self):
        # create base QB/RP
        org, rp, rp_id = self.setup_org_n_rp(org_name='testorg')
        qn = self.add_questionnaire(name='epic26')
        qn, org, self.test_user = map(db.session.merge,
                                      (qn, org, self.test_user))
        org_id = org.id
        qb = QuestionnaireBank(name='Test Questionnaire Bank',
                               classification='baseline',
                               research_protocol_id=rp_id,
                               start='{"days": 0}',
                               overdue='{"days": 7}',
                               expired='{"days": 90}')
        qbq = QuestionnaireBankQuestionnaire(questionnaire=qn, rank=0)
        qb.questionnaires.append(qbq)

        self.test_user.organizations.append(org)
        self.promote_user(role_name=ROLE.PATIENT.value)

        audit = Audit(user_id=TEST_USER_ID, subject_id=TEST_USER_ID)
        uc = UserConsent(user_id=TEST_USER_ID,
                         organization=org,
                         audit=audit,
                         agreement_url='http://no.com',
                         acceptance_date=now)

        with SessionScope(db):
            db.session.add(qb)
            db.session.add(self.test_user)
            db.session.add(audit)
            db.session.add(uc)
            db.session.commit()
        qb = db.session.merge(qb)

        # create in-progress QNR for User/QB/RP
        mock_qr(instrument_id='epic26',
                status='in-progress',
                timestamp=now,
                qb=qb)

        # User associated with CRV org should generate appropriate
        # questionnaires
        self.test_user = db.session.merge(self.test_user)
        qb_stat = QB_Status(self.test_user, now)
        qb = qb_stat.current_qbd().questionnaire_bank
        assert qb.research_protocol.name == 'proto'

        # Pointing the User's org to a new QB/RP
        # marking the old RP as retired as of yesterday
        old_rp = OrganizationResearchProtocol.query.filter(
            OrganizationResearchProtocol.organization_id == org_id,
            OrganizationResearchProtocol.research_protocol_id == rp_id).one()
        old_rp.retired_as_of = now - timedelta(days=1)
        rp2 = ResearchProtocol(name='new_proto')
        qn2 = self.add_questionnaire(name='epic27')
        with SessionScope(db):
            db.session.add(rp2)
            db.session.commit()
        rp2 = db.session.merge(rp2)
        rp2_id = rp2.id

        qn2, org = map(db.session.merge, (qn2, org))
        qb2 = QuestionnaireBank(name='Test Questionnaire Bank 2',
                                classification='baseline',
                                research_protocol_id=rp2_id,
                                start='{"days": 0}',
                                overdue='{"days": 7}',
                                expired='{"days": 90}')
        qbq2 = QuestionnaireBankQuestionnaire(questionnaire=qn2, rank=0)
        qb2.questionnaires.append(qbq2)

        org.research_protocols.append(rp2)

        with SessionScope(db):
            db.session.add(qb2)
            db.session.add(org)
            db.session.commit()
        qb2 = db.session.merge(qb2)

        # outdated QB/RP should be used as long as User has in-progress QNR
        self.test_user = db.session.merge(self.test_user)
        qb_stat = QB_Status(self.test_user, now)
        qb = qb_stat.current_qbd().questionnaire_bank
        assert qb.name == 'Test Questionnaire Bank'
        assert qb.research_protocol.name == 'proto'

        # completing QNR should result in completed status
        # shouldn't pick up new protocol till next iteration
        mock_qr(instrument_id='epic26',
                status='completed',
                timestamp=now,
                qb=qb)

        self.test_user = db.session.merge(self.test_user)
        invalidate_users_QBT(TEST_USER_ID)
        qb_stat = QB_Status(self.test_user, now)
        qb = qb_stat.current_qbd().questionnaire_bank
        assert qb.name == 'Test Questionnaire Bank'
        assert qb_stat.overall_status == OverallStatus.completed
def patch_deceased_user_consents(uid):
    user = User.query.get(uid)
    user_consents = UserConsent.query.filter(
        UserConsent.user_id == uid).order_by(UserConsent.id).all()

    # if no user_consents exist for user, we're done
    if not user_consents:
        return

    # acquire the deceased date
    dd = user.deceased.timestamp

    # if the most recent consent looks correct, assume we're done
    if user_consents[-1].status == 'suspended' and not(
            user_consents[-1].options & SEND_REMINDERS_MASK):
        return

    admin = User.query.filter_by(email='__system__').first()

    log.info("Patching broken consents for {}, with deceased date {}".format(
        user, dd))
    keeper = user_consents.pop(0)
    log.info(" keeping original user_consent {} {} {} {}".format(
        keeper.id, keeper.status, keeper.options, keeper.acceptance_date))
    obsolete_audits, obsolete_consents = [], []
    for uc in user_consents:
        log.info(" deleting user_consent {} {} {} {}".format(
            uc.id, uc.status, uc.options, uc.acceptance_date))
        a = Audit.query.get(uc.audit_id)
        log.info("  and the related {}".format(a))
        obsolete_audits.append(a.id)
        obsolete_consents.append(uc.id)

    # out of loop, delete the unwanted
    db.session.query(UserConsent).filter(UserConsent.id.in_(
        obsolete_consents)).delete(synchronize_session=False)
    db.session.query(Audit).filter(Audit.id.in_(
        obsolete_audits)).delete(synchronize_session=False)
    db.session.commit()

    # now add back in the corrected consent, from the initial
    deceased_options = STAFF_EDITABLE_MASK | INCLUDE_IN_REPORTS_MASK
    deceased_consent = UserConsent(
        user_id=uid,
        organization_id=keeper.organization_id,
        agreement_url=keeper.agreement_url,
        acceptance_date=dd,
        options=deceased_options,
        status='suspended')

    user.update_consents(
        consent_list=[deceased_consent], acting_user=admin)

    # The updated consent may have altered the cached assessment
    # status - invalidate this user's data at this time.
    invalidate_users_QBT(user_id=user.id)
    db.session.commit()

    log.info(" Done, results:")
    for uc in UserConsent.query.filter(UserConsent.user_id == uid):
        log.info("  {} {} {} {}".format(
            uc, uc.acceptance_date, uc.status, uc.options))
def upgrade():
    # Tricky task to locate the errors.  One time expensive process,
    # loop through patients identifying problems.

    admin = admin_id()
    qb_map = qb_pairs_by_rp()
    if not qb_map:
        return  # nothing to do on systems w/o RP changes
    fix_map = dict()

    for patient in active_patients(include_test_role=True).order_by(User.id):
        qnrs = QuestionnaireResponse.query.filter(
            QuestionnaireResponse.subject_id == patient.id).order_by(
            QuestionnaireResponse.authored)
        if not qnrs.count():
            # Only care if the user submitted QNRs:
            continue

        for qnr in qnrs:

            # Obtain qb_timeline row at the time of QNR.authored
            as_of_date = qnr.authored
            qbt = QBT.query.filter(QBT.user_id == patient.id).filter(
                QBT.at <= as_of_date).order_by(
                QBT.at.desc(), QBT.id.desc()).first()

            if qnr.questionnaire_bank_id is None:
                print(
                    "qnr {} user_id {} authored {} {}"
                    " with NO qb_id set".format(
                        qnr.id, patient.id, as_of_date,
                        qnr.document['questionnaire']['reference']))
                continue

            if qbt and qbt.status != OverallStatus.withdrawn:
                recorded_qb = QuestionnaireBank.query.get(
                    qnr.questionnaire_bank_id)
                qbd = qbt.qbd()
                expected_qb = qbd.questionnaire_bank
                if recorded_qb.classification == 'indefinite':
                    # indefinite case is handled differently...
                    qb_status = QB_Status(patient, as_of_date)
                    expected_qb = QuestionnaireBank.query.get(
                        qb_status._current_indef.qb_id)

                if recorded_qb != expected_qb:
                    replacement = qb_map.get(recorded_qb.id)
                    if replacement == expected_qb.id:
                        # retain the change request, as altering it inline
                        # interrupts the iteration
                        patched_qbd = QBD(
                            relative_start=qbd.relative_start, iteration=qbd.iteration,
                            recur=qbd.recur, qb_id=replacement)
                        fix_map[qnr.id] = (expected_qb.id, visit_name(patched_qbd))
                        print(
                            "qnr {} replacing incorrect qb_id {} with {} "
                            "for user {}".format(
                                qnr.id, recorded_qb.id, expected_qb.id,
                                patient.id))

                    else:
                        print(
                            "qnr {} PROBLEM: recorded qb_id {}, "
                            "expected {} doesn't match replacement {} "
                            "for user_id {}".format(
                                qnr.id, recorded_qb.id, expected_qb.id,
                                replacement, patient.id))

    for qnr_id, row in fix_map.items():
        qb_id, visit = row
        qnr = QuestionnaireResponse.query.get(qnr_id)
        comment = (
            "Migration correct qnr.id {} ({}) questionnaire_bank_id"
            " from {} to {}".format(
                qnr.id, visit, qnr.questionnaire_bank_id, qb_id))
        audit = Audit(
            user_id=admin, subject_id=qnr.subject_id,
            context='assessment', comment=comment)
        db.session.add(audit)
        qnr.questionnaire_bank_id = qb_id

        # clear out cached qb_timeline data for user as it
        # needs to be recalculated given the change
        invalidate_users_QBT(qnr.subject_id)

    db.session.commit()
def upgrade():
    # Tricky task to locate the errors.  One time expensive process,
    # loop through patients identifying problems.

    admin = admin_id()
    qb_map = qb_pairs_by_rp()
    if not qb_map:
        return  # nothing to do on systems w/o RP changes
    fix_map = dict()

    raise RuntimeError(
        "Unsupported migration; code has changed - not maintained")
    for patient in active_patients(include_test_role=True).order_by(User.id):
        qnrs = QuestionnaireResponse.query.filter(
            QuestionnaireResponse.subject_id == patient.id).order_by(
            QuestionnaireResponse.authored)
        if not qnrs.count():
            # Only care if the user submitted QNRs:
            continue

        for qnr in qnrs:

            # Obtain qb_timeline row at the time of QNR.authored
            as_of_date = qnr.authored
            qbt = QBT.query.filter(QBT.user_id == patient.id).filter(
                QBT.at <= as_of_date).order_by(
                QBT.at.desc(), QBT.id.desc()).first()

            if qnr.questionnaire_bank_id is None:
                print(
                    "qnr {} user_id {} authored {} {}"
                    " with NO qb_id set".format(
                        qnr.id, patient.id, as_of_date,
                        qnr.document['questionnaire']['reference']))
                continue

            if qbt and qbt.status != OverallStatus.withdrawn:
                recorded_qb = QuestionnaireBank.query.get(
                    qnr.questionnaire_bank_id)
                qbd = qbt.qbd()
                expected_qb = qbd.questionnaire_bank
                if recorded_qb.classification == 'indefinite':
                    # indefinite case is handled differently...
                    qb_status = QB_Status(patient, as_of_date)
                    expected_qb = QuestionnaireBank.query.get(
                        qb_status._current_indef.qb_id)

                if recorded_qb != expected_qb:
                    replacement = qb_map.get(recorded_qb.id)
                    if replacement == expected_qb.id:
                        # retain the change request, as altering it inline
                        # interrupts the iteration
                        patched_qbd = QBD(
                            relative_start=qbd.relative_start, iteration=qbd.iteration,
                            recur=qbd.recur, qb_id=replacement)
                        fix_map[qnr.id] = (expected_qb.id, visit_name(patched_qbd))
                        print(
                            "qnr {} replacing incorrect qb_id {} with {} "
                            "for user {}".format(
                                qnr.id, recorded_qb.id, expected_qb.id,
                                patient.id))

                    else:
                        print(
                            "qnr {} PROBLEM: recorded qb_id {}, "
                            "expected {} doesn't match replacement {} "
                            "for user_id {}".format(
                                qnr.id, recorded_qb.id, expected_qb.id,
                                replacement, patient.id))

    for qnr_id, row in fix_map.items():
        qb_id, visit = row
        qnr = QuestionnaireResponse.query.get(qnr_id)
        comment = (
            "Migration correct qnr.id {} ({}) questionnaire_bank_id"
            " from {} to {}".format(
                qnr.id, visit, qnr.questionnaire_bank_id, qb_id))
        audit = Audit(
            user_id=admin, subject_id=qnr.subject_id,
            context='assessment', comment=comment)
        db.session.add(audit)
        qnr.questionnaire_bank_id = qb_id

        # clear out cached qb_timeline data for user as it
        # needs to be recalculated given the change
        invalidate_users_QBT(qnr.subject_id)

    db.session.commit()