def _remote_survey_url_vioscreen(transaction, account_id, source_id,
                                 language_tag, survey_redirect_url,
                                 vioscreen_ext_sample_id):
    # assumes an instance of Transaction is already available
    acct_repo = AccountRepo(transaction)
    survey_template_repo = SurveyTemplateRepo(transaction)

    if vioscreen_ext_sample_id:
        # User is about to start a vioscreen survey for this sample
        # record this in the database.
        db_vioscreen_id = survey_template_repo.create_vioscreen_id(
            account_id, source_id, vioscreen_ext_sample_id)
    else:
        raise ValueError("Vioscreen Template requires "
                         "vioscreen_ext_sample_id parameter.")

    (birth_year, gender, height, weight) = \
        survey_template_repo.fetch_user_basic_physiology(
        account_id, source_id)

    account = acct_repo.get_account(account_id)
    country_code = account.address.country_code

    url = vioscreen.gen_survey_url(db_vioscreen_id,
                                   language_tag,
                                   survey_redirect_url,
                                   birth_year=birth_year,
                                   gender=gender,
                                   height=height,
                                   weight=weight,
                                   country_code=country_code)

    return url
def _remote_survey_url_polyphenol_ffq(transaction, account_id, source_id,
                                      language_tag):
    st_repo = SurveyTemplateRepo(transaction)

    # right now, ID won't exist
    # future plans to allow surveys to behave more flexibly will use this
    # functionality to allow participants to re-join in-progress surveys
    polyphenol_ffq_id, study = \
        st_repo.get_polyphenol_ffq_id_if_exists(account_id, source_id)

    if polyphenol_ffq_id is None:
        # The Polyphenol FFQ belongs to Danone and they're interested in
        # tracking results that come from their sponsored studies
        # separately from other samples. We pass 'THDMI' as the study for
        # THDMI samples and 'Microsetta' for all other samples. Therefore,
        # we need to determine if the source has any THDMI-associated samples.
        # Without investing significant developer effort to build a category
        # system around projects, a basic text search is the best compromise.
        study = 'Microsetta'
        sample_repo = SampleRepo(transaction)
        samples = sample_repo.get_samples_by_source(account_id, source_id)
        for s in samples:
            for s_p in s.sample_projects:
                if s_p.startswith('THDMI'):
                    study = 'THDMI'
                    break

        polyphenol_ffq_id = st_repo.create_polyphenol_ffq_entry(
            account_id, source_id, language_tag, study)

    return polyphenol_ffq.gen_ffq_url(polyphenol_ffq_id, study, language_tag)
Beispiel #3
0
def read_survey_templates(account_id, source_id, language_tag, token_info):
    _validate_account_access(token_info, account_id)

    # TODO: I don't think surveys have names... only survey groups have names.
    #  So what can I pass down to the user that will make any sense here?

    # Note to future maintainers,
    # 2/21/20: currently the only way to figure this out
    # is to look through the "surveys" and "survey_group" tables, try:
    # select survey_id, american from surveys left join survey_group on
    # survey_group = group_order;

    with Transaction() as t:
        source_repo = SourceRepo(t)
        source = source_repo.get_source(account_id, source_id)
        if source is None:
            return jsonify(code=404, message="No source found"), 404
        template_repo = SurveyTemplateRepo(t)
        if source.source_type == Source.SOURCE_TYPE_HUMAN:
            return jsonify([
                template_repo.get_survey_template_link_info(x)
                for x in [1, 3, 4, 5, 6, SurveyTemplateRepo.VIOSCREEN_ID]
            ]), 200
        elif source.source_type == Source.SOURCE_TYPE_ANIMAL:
            return jsonify([
                template_repo.get_survey_template_link_info(x) for x in [2]
            ]), 200
        else:
            return jsonify([]), 200
Beispiel #4
0
 def test_get_polyphenol_ffq_id_if_exists_false(self):
     with Transaction() as t:
         template_repo = SurveyTemplateRepo(t)
         obs = \
             template_repo.get_polyphenol_ffq_id_if_exists(TEST1_ACCOUNT_ID,
                                                           TEST1_SOURCE_ID)
         self.assertEqual(obs, (None, None))
Beispiel #5
0
 def test_create_vioscreen_id_valid(self):
     with Transaction() as t:
         template_repo = SurveyTemplateRepo(t)
         obs = template_repo.create_vioscreen_id(TEST2_ACCOUNT_ID,
                                                 TEST2_SOURCE_ID,
                                                 TEST2_SAMPLE_ID)
         self.assertTrue(obs is not None)
         t.rollback()
Beispiel #6
0
    def test_create_myfoodrepo_id(self):
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)
            obs = template_repo.create_myfoodrepo_entry(
                TEST2_ACCOUNT_ID, TEST2_SOURCE_ID)
            self.assertTrue(obs)

            t.rollback()
Beispiel #7
0
    def test_set_myfoodrepo_no_slot(self):
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)

            with self.assertRaises(KeyError):
                template_repo.set_myfoodrepo_id(TEST2_ACCOUNT_ID,
                                                TEST2_SOURCE_ID, "asubject")
            t.rollback()
def read_myfoodrepo_available_slots():
    with Transaction() as t:
        st_repo = SurveyTemplateRepo(t)
        available = st_repo.myfoodrepo_slots_available()
        total = st_repo.myfoodrepo_slots_total()

    resp = jsonify(code=200,
                   number_of_available_slots=available,
                   total_number_of_slots=total)
    return resp, 200
Beispiel #9
0
 def test_create_polyphenol_ffq_entry_valid(self):
     with Transaction() as t:
         template_repo = SurveyTemplateRepo(t)
         obs = template_repo.create_polyphenol_ffq_entry(
             TEST1_ACCOUNT_ID, TEST1_SOURCE_ID, 'en_US', 'THDMI')
         try:
             uuid.UUID(obs)
             valid_uuid_returned = True
         except ValueError:
             valid_uuid_returned = False
         self.assertTrue(valid_uuid_returned)
Beispiel #10
0
def read_survey_template(account_id, source_id, survey_template_id,
                         language_tag, token_info, survey_redirect_url=None):
    _validate_account_access(token_info, account_id)

    # TODO: can we get rid of source_id?  I don't have anything useful to do
    #  with it...  I guess I could check if the source is a dog before giving
    #  out a pet information survey?

    with Transaction() as t:
        survey_template_repo = SurveyTemplateRepo(t)
        info = survey_template_repo.get_survey_template_link_info(
            survey_template_id)

        # For external surveys, we generate links pointing out
        if survey_template_id == SurveyTemplateRepo.VIOSCREEN_ID:

            url = vioscreen.gen_survey_url(
                language_tag, survey_redirect_url
            )
            # TODO FIXME HACK: This field's contents are not specified!
            info.survey_template_text = {
                "url": url
            }
            return jsonify(info), 200

        # For local surveys, we generate the json representing the survey
        survey_template = survey_template_repo.get_survey_template(
            survey_template_id, language_tag)
        info.survey_template_text = vue_adapter.to_vue_schema(survey_template)

        # TODO FIXME HACK: We need a better way to enforce validation on fields
        #  that need it, can this be stored adjacent to the survey questions?
        client_side_validation = {
            "108": {
                # Height
                "inputType": "number",
                "validator": "number",
                "min": 0,
                "max": None
            },
            "113": {
                # Weight
                "inputType": "number",
                "validator": "number",
                "min": 0,
                "max": None
            }
        }
        for group in info.survey_template_text.groups:
            for field in group.fields:
                if field.id in client_side_validation:
                    field.set(**client_side_validation[field.id])

        return jsonify(info), 200
Beispiel #11
0
 def test_get_polyphenol_ffq_id_if_exists_true(self):
     with Transaction() as t:
         template_repo = SurveyTemplateRepo(t)
         test_pffq_id = \
             template_repo.create_polyphenol_ffq_entry(TEST1_ACCOUNT_ID,
                                                       TEST1_SOURCE_ID,
                                                       'en_US',
                                                       'THDMI')
         obs = \
             template_repo.get_polyphenol_ffq_id_if_exists(TEST1_ACCOUNT_ID,
                                                           TEST1_SOURCE_ID)
         self.assertEqual((test_pffq_id, 'THDMI'), obs)
Beispiel #12
0
def _get_session_by_account_details(account_id, source_id, sample_id):
    with Transaction() as t:
        surv_temp = SurveyTemplateRepo(t)
        vio_sess = VioscreenSessionRepo(t)

        vio_username = surv_temp.get_vioscreen_id_if_exists(
            account_id, source_id, sample_id)
        if vio_username is None:
            return True, (jsonify(code=404, message="Username not found"), 404)

        vioscreen_session = vio_sess.get_sessions_by_username(vio_username)
        if vioscreen_session is None:
            return True, (jsonify(code=404, message="Session not found"), 404)

        return False, vioscreen_session
Beispiel #13
0
    def test_create_vioscreen_id_idempotent(self):
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)
            obs1 = template_repo.create_vioscreen_id(TEST2_ACCOUNT_ID,
                                                     TEST2_SOURCE_ID,
                                                     TEST2_SAMPLE_ID)
            obs2 = template_repo.create_vioscreen_id(TEST2_ACCOUNT_ID,
                                                     TEST2_SOURCE_ID,
                                                     TEST2_SAMPLE_ID)
            self.assertEqual(obs1, obs2)

            obs = template_repo.create_vioscreen_id(TEST1_ACCOUNT_ID,
                                                    TEST1_SOURCE_ID,
                                                    TEST1_SAMPLE_ID)
            self.assertEqual(obs, TEST1_VIO_ID)
            t.rollback()
Beispiel #14
0
def read_answered_surveys(account_id, source_id, language_tag, token_info):
    _validate_account_access(token_info, account_id)

    with Transaction() as t:
        survey_answers_repo = SurveyAnswersRepo(t)
        survey_template_repo = SurveyTemplateRepo(t)
        answered_surveys = survey_answers_repo.list_answered_surveys(
            account_id, source_id)
        api_objs = []
        for ans in answered_surveys:
            template_id = survey_answers_repo.find_survey_template_id(ans)
            if template_id is None:
                continue
            o = survey_template_repo.get_survey_template_link_info(template_id)
            api_objs.append(o.to_api(ans))
        return jsonify(api_objs), 200
Beispiel #15
0
    def test_create_myfoodrepo_id_no_slots(self):
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)

            # insert 1 less than the available slots
            slots = SERVER_CONFIG['myfoodrepo_slots']
            cur = t.cursor()
            cur.execute(
                """INSERT INTO ag.myfoodrepo_registry
                           (account_id, source_id)
                           SELECT account_id, id as source_id
                                 FROM ag.source
                                 WHERE id NOT IN %s
                                 LIMIT %s""",
                ((TEST1_SOURCE_ID, TEST2_SOURCE_ID), slots - 1))

            # our next insertion should work
            obs = template_repo.create_myfoodrepo_entry(
                TEST1_ACCOUNT_ID, TEST1_SOURCE_ID)
            self.assertTrue(obs)

            # we should now be at the maximum number of slots, so our final
            # insertion should fail
            obs = template_repo.create_myfoodrepo_entry(
                TEST2_ACCOUNT_ID, TEST2_SOURCE_ID)
            self.assertFalse(obs)

            # update some of our creation timestamps
            cur.execute(
                """UPDATE ag.myfoodrepo_registry
                           SET creation_timestamp=NOW() - INTERVAL '30 days'
                           WHERE source_id IN (
                               SELECT source_id
                               FROM ag.myfoodrepo_registry
                               WHERE source_id != %s
                               LIMIT 5
                           )""", (TEST2_SOURCE_ID, ))

            # we now have slots, so we should be successful creating an entry
            obs = template_repo.create_myfoodrepo_entry(
                TEST2_ACCOUNT_ID, TEST2_SOURCE_ID)
            self.assertTrue(obs)
Beispiel #16
0
def read_answered_survey_associations(account_id, source_id, sample_id,
                                      token_info):
    _validate_account_access(token_info, account_id)

    with Transaction() as t:
        answers_repo = SurveyAnswersRepo(t)
        template_repo = SurveyTemplateRepo(t)
        answered_surveys = answers_repo.list_answered_surveys_by_sample(
            account_id, source_id, sample_id)

        resp_obj = []
        for answered_survey in answered_surveys:
            template_id = answers_repo.find_survey_template_id(answered_survey)
            if template_id is None:
                continue
            info = template_repo.get_survey_template_link_info(template_id)
            resp_obj.append(info.to_api(answered_survey))

        t.commit()
        return jsonify(resp_obj), 200
Beispiel #17
0
def read_answered_survey(account_id, source_id, survey_id, language_tag,
                         token_info):
    _validate_account_access(token_info, account_id)

    with Transaction() as t:
        survey_answers_repo = SurveyAnswersRepo(t)
        survey_answers = survey_answers_repo.get_answered_survey(
            account_id, source_id, survey_id, language_tag)
        if not survey_answers:
            return jsonify(code=404, message="No survey answers found"), 404

        template_id = survey_answers_repo.find_survey_template_id(survey_id)
        if template_id is None:
            return jsonify(code=422, message="No answers in survey"), 422

        template_repo = SurveyTemplateRepo(t)
        link_info = template_repo.get_survey_template_link_info(template_id)
        link_info.survey_id = survey_id
        link_info.survey_text = survey_answers
        return jsonify(link_info), 200
Beispiel #18
0
    def test_create_myfoodrepo_id_bad_source_or_account(self):
        # transaction needs to be created each time as the FK violation
        # disrupts the active transaction
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)
            with self.assertRaises(ForeignKeyViolation):
                template_repo.create_myfoodrepo_entry(str(uuid.uuid4()),
                                                      TEST2_SOURCE_ID)

        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)
            with self.assertRaises(ForeignKeyViolation):
                template_repo.create_myfoodrepo_entry(TEST2_ACCOUNT_ID,
                                                      str(uuid.uuid4()))
def _remote_survey_url_myfoodrepo(transaction, account_id, source_id,
                                  language_tag):
    # assumes an instance of Transaction is already available
    st_repo = SurveyTemplateRepo(transaction)

    # do we already have an id?
    mfr_id, created = st_repo.get_myfoodrepo_id_if_exists(
        account_id, source_id)

    if mfr_id is None:
        # we need an ID so let's try and get one
        if created is None:
            # we need a slot and an id
            slot = st_repo.create_myfoodrepo_entry(account_id, source_id)
            if not slot:
                # we could not obtain a slot
                raise NotFound("Sorry, but the annotators are all allocated")

            mfr_id = myfoodrepo.create_subj()
        else:
            # we have a slot but no id
            mfr_id = myfoodrepo.create_subj()

        st_repo.set_myfoodrepo_id(account_id, source_id, mfr_id)
    else:
        # we already have an ID then just return the URL
        pass

    return myfoodrepo.gen_survey_url(mfr_id)
Beispiel #20
0
    def test_set_myfoodrepo_cannot_assign_new_id(self):
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)

            obs = template_repo.create_myfoodrepo_entry(
                TEST2_ACCOUNT_ID, TEST2_SOURCE_ID)
            self.assertTrue(obs)

            template_repo.set_myfoodrepo_id(TEST2_ACCOUNT_ID, TEST2_SOURCE_ID,
                                            "asubject")

            with self.assertRaises(KeyError):
                template_repo.set_myfoodrepo_id(TEST2_ACCOUNT_ID,
                                                TEST2_SOURCE_ID,
                                                "adifferentsubject")
            t.rollback()
Beispiel #21
0
    def test_get_myfoodrepo_id_if_exists(self):
        with Transaction() as t:
            template_repo = SurveyTemplateRepo(t)
            obs = template_repo.get_myfoodrepo_id_if_exists(
                TEST1_ACCOUNT_ID, TEST1_SOURCE_ID)
            self.assertEqual(obs, (None, None))

            obs = template_repo.create_myfoodrepo_entry(
                TEST1_ACCOUNT_ID, TEST1_SOURCE_ID)
            self.assertTrue(obs)

            template_repo.set_myfoodrepo_id(TEST1_ACCOUNT_ID, TEST1_SOURCE_ID,
                                            "asubject")
            obs = template_repo.get_myfoodrepo_id_if_exists(
                TEST1_ACCOUNT_ID, TEST1_SOURCE_ID)
            self.assertEqual(obs[0], "asubject")
            self.assertTrue(obs[1] is not None)
Beispiel #22
0
 def test_create_polyphenol_ffq_entry_invalid(self):
     with Transaction() as t:
         template_repo = SurveyTemplateRepo(t)
         with self.assertRaises(InvalidTextRepresentation):
             template_repo.create_polyphenol_ffq_entry(
                 '', TEST1_SOURCE_ID, '', '')
def read_survey_template(account_id,
                         source_id,
                         survey_template_id,
                         language_tag,
                         token_info,
                         survey_redirect_url=None,
                         vioscreen_ext_sample_id=None):
    _validate_account_access(token_info, account_id)

    with Transaction() as t:
        survey_template_repo = SurveyTemplateRepo(t)
        info = survey_template_repo.get_survey_template_link_info(
            survey_template_id)
        remote_surveys = set(survey_template_repo.remote_surveys())

        # For external surveys, we generate links pointing out
        if survey_template_id in remote_surveys:
            if survey_template_id == SurveyTemplateRepo.VIOSCREEN_ID:
                url = _remote_survey_url_vioscreen(t, account_id, source_id,
                                                   language_tag,
                                                   survey_redirect_url,
                                                   vioscreen_ext_sample_id)
            elif survey_template_id == SurveyTemplateRepo.MYFOODREPO_ID:
                url = _remote_survey_url_myfoodrepo(t, account_id, source_id,
                                                    language_tag)
            elif survey_template_id == SurveyTemplateRepo.POLYPHENOL_FFQ_ID:
                url = _remote_survey_url_polyphenol_ffq(
                    t, account_id, source_id, language_tag)
            else:
                raise ValueError(f"Cannot generate URL for survey "
                                 f"{survey_template_id}")

            # TODO FIXME HACK: This field's contents are not specified!
            info.survey_template_text = {"url": url}
            t.commit()
            return jsonify(info), 200

        # For local surveys, we generate the json representing the survey
        survey_template = survey_template_repo.get_survey_template(
            survey_template_id, language_tag)
        info.survey_template_text = vue_adapter.to_vue_schema(survey_template)

        # TODO FIXME HACK: We need a better way to enforce validation on fields
        #  that need it, can this be stored adjacent to the survey questions?
        client_side_validation = {
            "108": {
                # Height
                "inputType": "number",
                "validator": "integer",
                "min": 0,
                "max": None
            },
            "113": {
                # Weight
                "inputType": "number",
                "validator": "integer",
                "min": 0,
                "max": None
            }
        }
        for group in info.survey_template_text.groups:
            for field in group.fields:
                if field.id in client_side_validation:
                    field.set(**client_side_validation[field.id])

        return jsonify(info), 200
Beispiel #24
0
    def test_fetch_user_basic_physiology(self):
        with Transaction() as t:
            tr = SurveyTemplateRepo(t)

            # year and gender already set for this survey
            # weight and height are scrambled in the test
            # database as they're remarked as free text
            with t.cursor() as cur:
                cur.execute("""UPDATE ag.survey_answers_other
                               SET response='["254"]'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 108))  # height_cm
                cur.execute("""UPDATE ag.survey_answers_other
                               SET response='["100"]'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 113))  # weight_kg

                tr = SurveyTemplateRepo(t)

                obs = tr.fetch_user_basic_physiology(TEST1_ACCOUNT_ID,
                                                     TEST1_SOURCE_ID)
                exp = (1973, 'Male', 100, 220.462)
                self.assertEqual(obs, exp)

                cur.execute("""UPDATE ag.survey_answers_other
                               SET response='["100"]'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 108))  # height_cm
                cur.execute("""UPDATE ag.survey_answers
                               SET response='inches'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 109))  # height_units
                cur.execute("""UPDATE ag.survey_answers
                               SET response='pounds'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 114))  # weight_units

                obs = tr.fetch_user_basic_physiology(TEST1_ACCOUNT_ID,
                                                     TEST1_SOURCE_ID)
                exp = (1973, 'Male', 100, 100)
                self.assertEqual(obs, exp)

                # equiv of Unspecified for height
                cur.execute("""UPDATE ag.survey_answers_other
                               SET response='[""]'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 108))  # height_cm

                obs = tr.fetch_user_basic_physiology(TEST1_ACCOUNT_ID,
                                                     TEST1_SOURCE_ID)
                exp = (1973, 'Male', None, 100)
                self.assertEqual(obs, exp)

                # equiv of Unspecified for weight
                cur.execute("""UPDATE ag.survey_answers_other
                               SET response='[""]'
                               WHERE survey_id=%s AND survey_question_id=%s""",
                            (TEST1_SURVEY_ID, 113))  # weight_kg

                obs = tr.fetch_user_basic_physiology(TEST1_ACCOUNT_ID,
                                                     TEST1_SOURCE_ID)
                exp = (1973, 'Male', None, None)
                self.assertEqual(obs, exp)
Beispiel #25
0
 def test_get_vioscreen_id_if_exists_invalid(self):
     with Transaction() as t:
         template_repo = SurveyTemplateRepo(t)
         obs = template_repo.get_vioscreen_id_if_exists(
             TEST2_ACCOUNT_ID, TEST2_SOURCE_ID, TEST2_SAMPLE_ID)
         self.assertEqual(obs, None)
Beispiel #26
0
def read_survey_template(account_id,
                         source_id,
                         survey_template_id,
                         language_tag,
                         token_info,
                         survey_redirect_url=None,
                         vioscreen_ext_sample_id=None):
    _validate_account_access(token_info, account_id)

    # TODO: can we get rid of source_id?  I don't have anything useful to do
    #  with it...  I guess I could check if the source is a dog before giving
    #  out a pet information survey?

    with Transaction() as t:
        survey_template_repo = SurveyTemplateRepo(t)
        info = survey_template_repo.get_survey_template_link_info(
            survey_template_id)

        # For external surveys, we generate links pointing out
        if survey_template_id == SurveyTemplateRepo.VIOSCREEN_ID:
            if vioscreen_ext_sample_id:
                # User is about to start a vioscreen survey for this sample
                # record this in the database.
                db_vioscreen_id = survey_template_repo.create_vioscreen_id(
                    account_id, source_id, vioscreen_ext_sample_id)
            else:
                raise ValueError("Vioscreen Template requires "
                                 "vioscreen_ext_sample_id parameter.")
            (birth_year, gender
             ) = survey_template_repo.fetch_user_birth_year_gender(account_id)
            url = vioscreen.gen_survey_url(db_vioscreen_id,
                                           language_tag,
                                           survey_redirect_url,
                                           birth_year=birth_year,
                                           gender=gender)
            # TODO FIXME HACK: This field's contents are not specified!
            info.survey_template_text = {"url": url}
            t.commit()
            return jsonify(info), 200

        # For local surveys, we generate the json representing the survey
        survey_template = survey_template_repo.get_survey_template(
            survey_template_id, language_tag)
        info.survey_template_text = vue_adapter.to_vue_schema(survey_template)

        # TODO FIXME HACK: We need a better way to enforce validation on fields
        #  that need it, can this be stored adjacent to the survey questions?
        client_side_validation = {
            "108": {
                # Height
                "inputType": "number",
                "validator": "number",
                "min": 0,
                "max": None
            },
            "113": {
                # Weight
                "inputType": "number",
                "validator": "number",
                "min": 0,
                "max": None
            }
        }
        for group in info.survey_template_text.groups:
            for field in group.fields:
                if field.id in client_side_validation:
                    field.set(**client_side_validation[field.id])

        return jsonify(info), 200
    def submit_answered_survey(self,
                               ag_login_id,
                               source_id,
                               language_tag,
                               survey_template_id,
                               survey_model,
                               survey_answers_id=None):
        # note that "ag_login_id" is the same as account_id

        # This is actually pretty complicated in the current schema:
        #   We need to filter the model down to questions that are in the
        #       template
        #   We need to ensure that the account has access to write the given
        #       participant
        #   We need to generate a survey_answer id
        #   We need to log that the user submitted this survey
        #   We need to write each answer to one or more rows

        # TODO: We need to ensure that the account has access to write the
        #  given participant!?!

        if survey_answers_id is None:
            survey_answers_id = str(uuid.uuid4())

        survey_template_repo = SurveyTemplateRepo(self._transaction)
        survey_template = survey_template_repo.get_survey_template(
            survey_template_id, language_tag)

        with self._transaction.cursor() as cur:

            # Log that the user submitted this survey
            cur.execute(
                "INSERT INTO ag_login_surveys "
                "(ag_login_id, survey_id, source_id) "
                "VALUES(%s, %s, %s)",
                (ag_login_id, survey_answers_id, source_id))

            # Write each answer
            for survey_template_group in survey_template.groups:
                for survey_question in survey_template_group.questions:
                    survey_question_id = survey_question.id
                    q_type = survey_question.response_type

                    # TODO FIXME HACK: Modify DB to make this unnecessary!
                    # We MUST record at least ONE answer for each survey
                    # (even if the user answered nothing)
                    #  or we can't properly track the survey template id later.
                    # Therefore, if the user answered NOTHING, store an empty
                    # string for the first string or text question in the
                    # survey, just so something is recorded.
                    if len(survey_model) == 0 and \
                            (q_type == "STRING" or q_type == "TEXT"):
                        survey_model[str(survey_question_id)] = ""

                    if str(survey_question_id) not in survey_model:
                        # TODO: Is this supposed to leave the question blank
                        #  or write Unspecified?
                        continue
                    answer = survey_model[str(survey_question_id)]

                    if q_type == "SINGLE":
                        # Normalize localized answer
                        normalized_answer = self._unlocalize(
                            answer, language_tag)

                        try:
                            cur.execute(
                                "INSERT INTO survey_answers "
                                "(survey_id, "
                                "survey_question_id, "
                                "response) "
                                "VALUES(%s, %s, %s)",
                                (survey_answers_id, survey_question_id,
                                 normalized_answer))
                        except psycopg2.errors.ForeignKeyViolation:
                            raise BadRequest("Invalid survey response: %s" %
                                             answer)

                    if q_type == "MULTIPLE":
                        for ans in answer:
                            normalized_answer = self._unlocalize(
                                ans, language_tag)
                            try:
                                cur.execute(
                                    "INSERT INTO survey_answers "
                                    "(survey_id, "
                                    "survey_question_id, "
                                    "response) "
                                    "VALUES(%s, %s, %s)",
                                    (survey_answers_id, survey_question_id,
                                     normalized_answer))
                            except psycopg2.errors.ForeignKeyViolation:
                                raise BadRequest(
                                    "Invalid survey response: %s" % ans)

                    if q_type == "STRING" or q_type == "TEXT":
                        # Note:  Can't convert language on free text...
                        cur.execute(
                            "INSERT INTO survey_answers_other "
                            "(survey_id, "
                            "survey_question_id, "
                            "response) "
                            "VALUES(%s, %s, %s)",
                            (survey_answers_id, survey_question_id, answer))

        if len(survey_model) == 0:
            # we should not have gotten to the end without recording at least
            # ONE answer (even an empty one) ... but it could happen if this
            # survey template includes NO text or string questions AND the
            # user doesn't answer any of the questions it does include. Not
            # worth making the code robust to this case, as this whole "include
            # one empty answer" is a temporary hack, but at least ensure we
            # know this problem occurred if it ever does
            raise RepoException("No answers provided for survey template %s "
                                "and not able to add an empty string default" %
                                survey_template_id)

        return survey_answers_id
Beispiel #28
0
def per_sample(project, barcodes, strip_sampleid):
    summaries = []
    with Transaction() as t:
        admin_repo = AdminRepo(t)
        sample_repo = SampleRepo(t)
        template_repo = SurveyTemplateRepo(t)
        vs_repo = VioscreenSessionRepo(t)

        if project is not None:
            project_barcodes = admin_repo.get_project_barcodes(project)
        else:
            project = 'Unspecified'

        if barcodes is None:
            barcodes = project_barcodes

        for barcode in barcodes:
            diag = admin_repo.retrieve_diagnostics_by_barcode(barcode)
            if diag is None:
                raise NotFound(f"Barcode not found: {barcode}")

            sample = diag['sample']
            account = diag['account']
            source = diag['source']

            account_email = None if account is None else account.email
            source_email = None
            source_type = None if source is None else source.source_type
            vio_id = None

            if source is not None and source_type == Source.SOURCE_TYPE_HUMAN:
                source_email = source.source_data.email

                vio_id = template_repo.get_vioscreen_id_if_exists(account.id,
                                                                  source.id,
                                                                  sample.id)

            # at least one sample has been observed that "is_microsetta",
            # described in the barcodes.project_barcode table, but which is
            # unexpectedly not present in ag.ag_kit_barcodes
            if sample is None:
                sample_status = None
                sample_site = None
                ffq_complete = None
                ffq_taken = None
            else:
                sample_status = sample_repo.get_sample_status(
                    sample.barcode,
                    sample._latest_scan_timestamp
                )
                sample_site = sample.site
                ffq_complete, ffq_taken, _ = vs_repo.get_ffq_status_by_sample(
                    sample.id
                )

            summary = {
                "sampleid": None if strip_sampleid else barcode,
                "project": project,
                "source-type": source_type,
                "site-sampled": sample_site,
                "source-email": source_email,
                "account-email": account_email,
                "vioscreen_username": vio_id,
                "ffq-taken": ffq_taken,
                "ffq-complete": ffq_complete,
                "sample-status": sample_status,
                "sample-received": sample_status is not None
            }

            for status in ["sample-is-valid",
                           "no-associated-source",
                           "no-registered-account",
                           "no-collection-info",
                           "sample-has-inconsistencies",
                           "received-unknown-validity"]:
                summary[status] = sample_status == status

            summaries.append(summary)
    return summaries