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)
def update_sample_association(account_id, source_id, sample_id, body, token_info): _validate_account_access(token_info, account_id) # TODO: API layer doesn't understand that BadRequest can be thrown, # but that looks to be the right result if sample_site bad. # Need to update the api layer if we want to specify 400s. # (Or we leave api as is and say 400's can always be thrown if your # request is bad) with Transaction() as t: sample_repo = SampleRepo(t) source_repo = SourceRepo(t) source = source_repo.get_source(account_id, source_id) if source is None: return jsonify(code=404, message="No such source"), 404 needs_sample_site = source.source_type in [ Source.SOURCE_TYPE_HUMAN, Source.SOURCE_TYPE_ANIMAL ] precludes_sample_site = source.source_type == \ Source.SOURCE_TYPE_ENVIRONMENT sample_site_present = "sample_site" in body and \ body["sample_site"] is not None if needs_sample_site and not sample_site_present: # Human/Animal sources require sample_site to be set raise BadRequest("human/animal samples require sample_site") if precludes_sample_site and sample_site_present: raise BadRequest("environmental samples cannot specify " "sample_site") sample_datetime = body['sample_datetime'] try: sample_datetime = fromisotime(sample_datetime) except ValueError: raise BadRequest("Invalid sample_datetime") curdate = datetime.now(sample_datetime.tzinfo) lower_limit = curdate + relativedelta(years=-10) upper_limit = curdate + relativedelta(months=+1) if sample_datetime < lower_limit or sample_datetime > upper_limit: raise BadRequest('Invalid sample date') # sample_site will not be present if its environmental. this will # default to None if the key is not present sample_site = body.get('sample_site') sample_info = SampleInfo(sample_id, sample_datetime, sample_site, body["sample_notes"]) is_admin = token_grants_admin_access(token_info) sample_repo.update_info(account_id, source_id, sample_info, override_locked=is_admin) final_sample = sample_repo.get_sample(account_id, source_id, sample_id) t.commit() return jsonify(final_sample), 200
def test_scrub_bad_sample(self): samp1 = 'd8592c74-85f0-2135-e040-8a80115d6401' # 000001766 with Transaction() as t: acct1, src1 = self._get_source_from_sample(t, samp1) sr = SampleRepo(t) with self.assertRaises(RepoException): sr.scrub(acct1, src1, src1)
def dissociate_sample(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample_repo.dissociate_sample(account_id, source_id, sample_id) t.commit() return '', 204
def read_sample_associations(account_id, source_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) samples = sample_repo.get_samples_by_source(account_id, source_id) api_samples = [x.to_api() for x in samples] return jsonify(api_samples), 200
def read_sample_association(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample = sample_repo.get_sample(account_id, source_id, sample_id) if sample is None: return jsonify(code=404, message="Sample not found"), 404 return jsonify(sample.to_api()), 200
def delete_account(account_id, token_info): validate_admin_access(token_info) with Transaction() as t: acct_repo = AccountRepo(t) src_repo = SourceRepo(t) samp_repo = SampleRepo(t) sar_repo = SurveyAnswersRepo(t) acct = acct_repo.get_account(account_id) if acct is None: return jsonify(message="Account not found", code=404), 404 else: # the account is already scrubbed so let's stop early if acct.account_type == 'deleted': return None, 204 sample_count = 0 sources = src_repo.get_sources_in_account(account_id) for source in sources: samples = samp_repo.get_samples_by_source(account_id, source.id) has_samples = len(samples) > 0 sample_count += len(samples) for sample in samples: # we scrub rather than disassociate in the event that the # sample is in our freezers but not with an up-to-date scan samp_repo.scrub(account_id, source.id, sample.id) surveys = sar_repo.list_answered_surveys(account_id, source.id) if has_samples: # if we have samples, we need to scrub survey / source # free text for survey_id in surveys: sar_repo.scrub(account_id, source.id, survey_id) src_repo.scrub(account_id, source.id) else: # if we do not have associated samples, then the source # is safe to delete for survey_id in surveys: sar_repo.delete_answered_survey(account_id, survey_id) src_repo.delete_source(account_id, source.id) # an account is safe to delete if there are no associated samples if sample_count > 0: acct_repo.scrub(account_id) else: acct_repo.delete_account(account_id) t.commit() return None, 204
def associate_sample(account_id, source_id, body, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample_repo.associate_sample(account_id, source_id, body['sample_id']) t.commit() response = flask.Response() response.status_code = 201 response.headers['Location'] = '/api/accounts/%s/sources/%s/samples/%s' % \ (account_id, source_id, body['sample_id']) return response
def delete_dummy_accts(): all_sample_ids = [] acct_ids = [ACCT_ID_1, ACCT_ID_2] with Transaction() as t: acct_repo = AccountRepo(t) source_repo = SourceRepo(t) survey_answers_repo = SurveyAnswersRepo(t) sample_repo = SampleRepo(t) for curr_acct_id in acct_ids: sources = source_repo.get_sources_in_account(curr_acct_id) for curr_source in sources: source_samples = sample_repo.get_samples_by_source( curr_acct_id, curr_source.id) sample_ids = [x.id for x in source_samples] all_sample_ids.extend(sample_ids) # Dissociate all samples linked to this source from all # answered surveys linked to this source, then delete all # answered surveys delete_dummy_answered_surveys_from_source_with_t( t, curr_acct_id, curr_source.id, sample_ids, survey_answers_repo) # Now dissociate all the samples from this source for curr_sample_id in sample_ids: sample_repo.dissociate_sample(curr_acct_id, curr_source.id, curr_sample_id) # Finally, delete the source source_repo.delete_source(curr_acct_id, curr_source.id) # Delete the account acct_repo.delete_account(curr_acct_id) # Belt and suspenders: these test emails are used by some tests outside # of this module as well, so can't be sure they are paired with the # above dummy account ids acct_repo.delete_account_by_email(TEST_EMAIL) acct_repo.delete_account_by_email(TEST_EMAIL_2) # Delete the kit and all samples that were attached to any sources # NB: This won't clean up any samples that were created but NOT # attached to any sources ... if len(all_sample_ids) == 0: all_sample_ids = None _remove_mock_kit(t, mock_sample_ids=all_sample_ids) t.commit()
def test_scrub(self): samp1 = 'd8592c74-85f0-2135-e040-8a80115d6401' # 000001766 with Transaction() as t: acct1, src1 = self._get_source_from_sample(t, samp1) sr = SampleRepo(t) # get original source associations src1_sample = sr.get_samples_by_source(acct1, src1)[0] res = sr.scrub(acct1, src1, src1_sample.id) self.assertTrue(res) obs_sample = sr.get_samples_by_source(acct1, src1)[0] self.assertNotEqual(src1_sample.notes, obs_sample.notes)
def dissociate_answered_survey_from_sample(self, account_id, source_id, sample_id, survey_id): sample_repo = SampleRepo(self._transaction) if not self._acct_source_owns_survey(account_id, source_id, survey_id): raise werkzeug.exceptions.NotFound("No survey ID: %s" % survey_id) s = sample_repo.get_sample(account_id, source_id, sample_id) if s is None: raise werkzeug.exceptions.NotFound("No sample ID: %s" % sample_id) with self._transaction.cursor() as cur: cur.execute( "DELETE FROM source_barcodes_surveys " "WHERE " "barcode = %s AND " "survey_id = %s", (s.barcode, survey_id)) # Also delete from vioscreen, myfoodrepo and polyphenol registries cur.execute( "UPDATE vioscreen_registry " "SET deleted=true " "WHERE " "account_id = %s AND " "source_id = %s AND " "sample_id = %s AND " "vio_id = %s", (account_id, source_id, sample_id, survey_id)) cur.execute( "UPDATE myfoodrepo_registry " "SET deleted=true " "WHERE " "account_id = %s AND " "source_id = %s AND " "myfoodrepo_id = %s", (account_id, source_id, survey_id)) try: uuid.UUID(survey_id) cur.execute( "UPDATE polyphenol_ffq_registry " "SET deleted = true " "WHERE " "account_id = %s AND " "source_id = %s AND " "polyphenol_ffq_id = %s", (account_id, source_id, survey_id)) except ValueError: # Note: we don't care about the error, just means it's not # the Polyphenol FFQ pass
def associate_answered_survey_with_sample(self, account_id, source_id, sample_id, survey_id): sample_repo = SampleRepo(self._transaction) if not self._acct_owns_survey(account_id, survey_id): raise werkzeug.exceptions.NotFound("No survey ID: %s" % survey_id) s = sample_repo.get_sample(account_id, source_id, sample_id) if s is None: raise werkzeug.exceptions.NotFound("No sample ID: %s" % sample_id) with self._transaction.cursor() as cur: cur.execute("INSERT INTO source_barcodes_surveys " "(barcode, survey_id) " "VALUES(%s, %s)", (s.barcode, survey_id))
def get_kit_all_samples_by_kit_id(self, kit_id): sample_repo = SampleRepo(self._transaction) with self._transaction.cursor() as cur: cur.execute("SELECT " "ag_kit.ag_kit_id, " "ag_kit_barcodes.ag_kit_barcode_id " "FROM ag_kit LEFT JOIN ag_kit_barcodes ON " "ag_kit.ag_kit_id = ag_kit_barcodes.ag_kit_id " "WHERE " "ag_kit.ag_kit_id = %s", (kit_id,)) rows = cur.fetchall() if len(rows) == 0: return None else: samples = [sample_repo._get_sample_by_id(r[1]) for r in rows] return Kit(rows[0][0], samples)
def dissociate_answered_survey_from_sample(self, account_id, source_id, sample_id, survey_id): sample_repo = SampleRepo(self._transaction) if not self._acct_source_owns_survey(account_id, source_id, survey_id): raise werkzeug.exceptions.NotFound("No survey ID: %s" % survey_id) s = sample_repo.get_sample(account_id, source_id, sample_id) if s is None: raise werkzeug.exceptions.NotFound("No sample ID: %s" % sample_id) with self._transaction.cursor() as cur: cur.execute( "DELETE FROM source_barcodes_surveys " "WHERE " "barcode = %s AND " "survey_id = %s", (s.barcode, survey_id))
def test_migrate_sample(self): samp1 = 'd8592c74-85f0-2135-e040-8a80115d6401' # 000001766 samp2 = 'ceaa6fd6-0861-4335-aa35-da1857bd5294' # 000067789 with Transaction() as t: acct1, src1 = self._get_source_from_sample(t, samp1) acct2, src2 = self._get_source_from_sample(t, samp2) sr = SampleRepo(t) sr.migrate_sample(samp1, src1, src2, True) # get new samples by source src1_samples = sr.get_samples_by_source(acct1, src1) src2_samples = sr.get_samples_by_source(acct2, src2) # verify samples are part of the new source self.assertFalse(self._sample_in_source(src1_samples, samp1)) self.assertTrue(self._sample_in_source(src2_samples, samp1))
def dissociate_sample(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: answers_repo = SurveyAnswersRepo(t) answered_survey_ids = answers_repo.list_answered_surveys_by_sample( account_id, source_id, sample_id) for curr_answered_survey_id in answered_survey_ids: answers_repo.dissociate_answered_survey_from_sample( account_id, source_id, sample_id, curr_answered_survey_id) sample_repo = SampleRepo(t) sample_repo.dissociate_sample(account_id, source_id, sample_id) t.commit() return '', 204
def test_dissociate_sample_from_source_success(self): dummy_acct_id, dummy_source_id = create_dummy_source( "Bo", Source.SOURCE_TYPE_HUMAN, DUMMY_HUMAN_SOURCE, create_dummy_1=True) create_dummy_kit(dummy_acct_id, dummy_source_id) dummy_answered_survey_id = create_dummy_answered_survey( dummy_acct_id, dummy_source_id, dummy_sample_id=MOCK_SAMPLE_ID) base_url = '/api/accounts/{0}/sources/{1}/samples'.format( dummy_acct_id, dummy_source_id) sample_url = "{0}/{1}".format(base_url, MOCK_SAMPLE_ID) delete_resp = self.client.delete( '%s?%s' % (sample_url, self.default_lang_querystring), headers=self.dummy_auth ) # check response code self.assertEqual(204, delete_resp.status_code) # load the samples associated to this source get_response = self.client.get( '%s?%s' % (base_url, self.default_lang_querystring), headers=self.dummy_auth) # check response code self.assertEqual(200, get_response.status_code) # ensure there are zero samples associated with this source get_resp_obj = json.loads(get_response.data) self.assertEqual(get_resp_obj, []) # load the sample info _, expected_sample = create_dummy_sample_objects(False) with Transaction() as t: # make sure the sample's collection info is wiped sample_repo = SampleRepo(t) obs_sample = sample_repo._get_sample_by_id(MOCK_SAMPLE_ID) self.assertEqual(expected_sample.__dict__, obs_sample.__dict__) # make sure answered survey no longer associated with any samples answered_survey_repo = SurveyAnswersRepo(t) answered_survey_ids = answered_survey_repo.\ _get_survey_sample_associations(dummy_answered_survey_id) self.assertEqual([], answered_survey_ids)
def get_kit_unused_samples(self, supplied_kit_id): sample_repo = SampleRepo(self._transaction) # Business Logic: We now define an unclaimed sample as a sample with # a null source_id in ag_kit_barcodes with self._transaction.cursor() as cur: cur.execute("SELECT " "ag_kit.ag_kit_id, " "ag_kit_barcodes.ag_kit_barcode_id " "FROM ag_kit LEFT JOIN ag_kit_barcodes ON " "ag_kit.ag_kit_id = ag_kit_barcodes.ag_kit_id " "WHERE " "ag_kit.supplied_kit_id = %s AND " "ag_kit_barcodes.source_id is null", (supplied_kit_id,)) rows = cur.fetchall() if len(rows) == 0: return None else: samples = [sample_repo._get_sample_by_id(r[1]) for r in rows] return Kit(rows[0][0], samples)
def list_answered_surveys_by_sample(self, account_id, source_id, sample_id): sample_repo = SampleRepo(self._transaction) # Note: Retrieving sample in this way validates permissions. sample = sample_repo.get_sample(account_id, source_id, sample_id) if sample is None: raise werkzeug.exceptions.NotFound("No sample ID: %s" % sample.id) with self._transaction.cursor() as cur: cur.execute( "SELECT " "survey_id " "FROM " "ag_kit_barcodes " "LEFT JOIN source_barcodes_surveys " "USING (barcode)" "WHERE " "ag_kit_barcode_id = %s", (sample_id, )) rows = cur.fetchall() answered_surveys = [r[0] for r in rows if r[0] is not None] return answered_surveys
def read_sample_association(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample = sample_repo.get_sample(account_id, source_id, sample_id) if sample is None: return jsonify(code=404, message="Sample not found"), 404 qiita_body = {'sample_ids': ["10317." + str(sample.barcode)]} try: qiita_data = qclient.post('/api/v1/study/10317/samples/status', json=qiita_body) accession_urls = [] for barcode_info in qiita_data: experiment_accession = barcode_info.get("ebi_experiment_accession") if experiment_accession is None: continue accession_urls.append("https://www.ebi.ac.uk/ena/browser/view/" + experiment_accession + "?show=reads") sample.set_accession_urls(accession_urls) except NotFoundError: # I guess qiita doesn't know about this barcode, # so probably no ebi accession info pass except BadRequestError: # How do I log these to gunicorn?? app.logger.warning("Couldn't communicate with qiita", exc_info=True) except ForbiddenError: # How do I log these to gunicorn?? app.logger.warning("Couldn't communicate with qiita", exc_info=True) except RuntimeError: # How do I log these to gunicorn?? app.logger.warning("Couldn't communicate with qiita", exc_info=True) raise return jsonify(sample.to_api()),
def test_update_sample_assocation_with_migration(self): samp1 = 'd8592c74-85f0-2135-e040-8a80115d6401' # 000001766 samp2 = 'ceaa6fd6-0861-4335-aa35-da1857bd5294' # 000067789 with Transaction() as t: acct1, src1 = self._get_source_from_sample(t, samp1) acct2, src2 = self._get_source_from_sample(t, samp2) sr = SampleRepo(t) # get original source associations src1_samples = sr.get_samples_by_source(acct1, src1) src2_samples = sr.get_samples_by_source(acct2, src2) # verify samples are part of the original source self.assertTrue(self._sample_in_source(src1_samples, samp1)) self.assertTrue(self._sample_in_source(src2_samples, samp2)) # swap associations sr._update_sample_association(samp1, src2, True) sr._update_sample_association(samp2, src1, True) # get new samples by source src1_samples = sr.get_samples_by_source(acct1, src1) src2_samples = sr.get_samples_by_source(acct2, src2) # verify samples are part of the new source self.assertTrue(self._sample_in_source(src1_samples, samp2)) self.assertTrue(self._sample_in_source(src2_samples, samp1)) # verify samples are not part of the original source self.assertFalse(self._sample_in_source(src1_samples, samp1)) self.assertFalse(self._sample_in_source(src2_samples, samp2)) # fix associations sr._update_sample_association(samp1, src1, True) sr._update_sample_association(samp2, src2, True) # ...and verify assocations are fixed src1_samples = sr.get_samples_by_source(acct1, src1) src2_samples = sr.get_samples_by_source(acct2, src2) self.assertTrue(self._sample_in_source(src1_samples, samp1)) self.assertTrue(self._sample_in_source(src2_samples, samp2))
def associate_answered_survey_with_sample(self, account_id, source_id, sample_id, survey_id): sample_repo = SampleRepo(self._transaction) if not self._acct_owns_survey(account_id, survey_id): raise werkzeug.exceptions.NotFound("No survey ID: %s" % survey_id) s = sample_repo.get_sample(account_id, source_id, sample_id) if s is None: raise werkzeug.exceptions.NotFound("No sample ID: %s" % sample_id) # Switching to insert if not exists semantics since vioscreen IDs will # be associated with samples prior to being filled out. with self._transaction.cursor() as cur: cur.execute( "SELECT * FROM source_barcodes_surveys " "WHERE barcode=%s AND survey_id=%s", (s.barcode, survey_id)) if cur.fetchone() is None: cur.execute( "INSERT INTO source_barcodes_surveys " "(barcode, survey_id) " "VALUES(%s, %s)", (s.barcode, survey_id))
def test_migrate_sample_exceptions(self): samp1 = 'd8592c74-85f0-2135-e040-8a80115d6401' # 000001766 samp2 = 'ceaa6fd6-0861-4335-aa35-da1857bd5294' # 000067789 bad = 'ffffffff-ffff-ffff-aaaa-aaaaaaaaaaaa' with Transaction() as t: _, src1 = self._get_source_from_sample(t, samp1) _, src2 = self._get_source_from_sample(t, samp2) sr = SampleRepo(t) with self.assertRaises(RepoException): # verify we dont do something unless we are intentional sr.migrate_sample(samp1, src1, src2, False) with self.assertRaises(RepoException): # the sample must be associated witht the source (src) # to move sr.migrate_sample(samp2, src1, src2, True) with self.assertRaises(RepoException): # the destination must exist sr.migrate_sample(samp1, src1, bad, True)
def create_dummy_kit(account_id=None, source_id=None): with Transaction() as t: _create_mock_kit(t, barcodes=[BARCODE], mock_sample_ids=[MOCK_SAMPLE_ID]) # if an account and source were provided, put some dummy # collection info into the sample and associate it to this source if account_id is not None and source_id is not None: sample_info, _ = create_dummy_sample_objects(True) sample_repo = SampleRepo(t) sample_repo.associate_sample(account_id, source_id, MOCK_SAMPLE_ID) sample_repo.update_info(account_id, source_id, sample_info) t.commit()
def query_email_stats(body, token_info): validate_admin_access(token_info) email_list = body.get("emails", []) project = body.get("project") results = [] with Transaction() as t: account_repo = AccountRepo(t) kit_repo = KitRepo(t) source_repo = SourceRepo(t) sample_repo = SampleRepo(t) for email in email_list: result = {'email': email, 'project': project} results.append(result) # can use internal lookup by email, because we have admin access account = account_repo._find_account_by_email(email) # noqa if account is None: result['summary'] = "No Account" continue else: result['account_id'] = account.id result['creation_time'] = account.creation_time result['kit_name'] = account.created_with_kit_id if account.created_with_kit_id is not None: unused = kit_repo.get_kit_unused_samples( account.created_with_kit_id ) if unused is None: result['unclaimed-samples-in-kit'] = 0 else: result['unclaimed-samples-in-kit'] = len(unused.samples) sample_statuses = defaultdict(int) sources = source_repo.get_sources_in_account(account.id) samples_in_project = 0 for source in sources: samples = sample_repo.get_samples_by_source(account.id, source.id) for sample in samples: if project is not None and \ project != "" and \ project not in sample.sample_projects: continue samples_in_project += 1 sample_status = sample_repo.get_sample_status( sample.barcode, sample._latest_scan_timestamp # noqa ) if sample_status is None: sample_status = "never-scanned" sample_statuses[sample_status] += 1 result.update(sample_statuses) if result.get('unclaimed-samples-in-kit', 0) > 0: result['summary'] = 'Possible Unreturned Samples' elif samples_in_project == 0: result['summary'] = "No Samples In Specified Project" elif result.get('sample-is-valid') == samples_in_project: result['summary'] = 'All Samples Valid' else: result['summary'] = 'May Require User Interaction' return jsonify(results), 200
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
def retrieve_diagnostics_by_barcode(self, sample_barcode, grab_kit=True): with self._transaction.dict_cursor() as cur: ids = self._get_ids_relevant_to_barcode(sample_barcode) if ids is None: sample_id = None source_id = None account_id = None kit_id = None else: sample_id = ids["sample_id"] source_id = ids["source_id"] account_id = ids["account_id"] kit_id = ids["kit_id"] account = None source = None sample = None kit = None if sample_id is not None: sample_repo = SampleRepo(self._transaction) sample = sample_repo._get_sample_by_id(sample_id) if source_id is not None and account_id is not None: account_repo = AccountRepo(self._transaction) source_repo = SourceRepo(self._transaction) account = account_repo.get_account(account_id) source = source_repo.get_source(account_id, source_id) if kit_id is not None and grab_kit: kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples_by_kit_id(kit_id) cur.execute("SELECT * from barcodes.barcode " "LEFT OUTER JOIN barcodes.project_barcode " "USING (barcode) " "LEFT OUTER JOIN barcodes.project " "USING (project_id) " "where barcode=%s", (sample_barcode,)) barcode_info = cur.fetchall() # How to unwrap a psycopg2 DictRow. I feel dirty. barcode_info = [{k: v for k, v in x.items()} for x in barcode_info] # Get Inceptioned!! # Collapse info from joined project_barcode and project tables # into array within barcode_info if barcode_info: first = barcode_info[0] first['projects'] = [ { 'project_id': r['project_id'], 'project': r['project'] } for r in barcode_info] del first['project_id'] del first['project'] barcode_info = first else: barcode_info = None if account is None and \ source is None and \ sample is None and \ barcode_info is None: return None diagnostic = { "barcode": sample_barcode, "account": account, "source": source, "sample": sample, "barcode_info": barcode_info } if grab_kit: diagnostic["kit"] = kit return diagnostic
def retrieve_diagnostics_by_barcode(self, sample_barcode, grab_kit=True): def _rows_to_dicts_list(rows): return [dict(x) for x in rows] with self._transaction.dict_cursor() as cur: ids = self._get_ids_relevant_to_barcode(sample_barcode) if ids is None: sample_id = None source_id = None account_id = None kit_id = None else: sample_id = ids["sample_id"] source_id = ids["source_id"] account_id = ids["account_id"] kit_id = ids["kit_id"] account = None source = None sample = None kit = None # get sample object for this barcode if sample_id is not None: sample_repo = SampleRepo(self._transaction) sample = sample_repo._get_sample_by_id(sample_id) # get account and source objects for this barcode if source_id is not None and account_id is not None: account_repo = AccountRepo(self._transaction) source_repo = SourceRepo(self._transaction) account = account_repo.get_account(account_id) source = source_repo.get_source(account_id, source_id) # get projects_info list for this barcode cur.execute("SELECT project, is_microsetta, " "bank_samples, plating_start_date " "FROM barcodes.project " "INNER JOIN barcodes.project_barcode " "USING (project_id) " "WHERE barcode=%s", (sample_barcode,)) # this can't be None; worst-case is an empty list projects_info = _rows_to_dicts_list(cur.fetchall()) # get scans_info list for this barcode # NB: ORDER MATTERS here. Do not change the order unless you # are positive you know what already depends on it. cur.execute("SELECT barcode_scan_id, barcode, " "scan_timestamp, sample_status, " "technician_notes " "FROM barcodes.barcode_scans " "WHERE barcode=%s " "ORDER BY scan_timestamp asc", (sample_barcode,)) # this can't be None; worst-case is an empty list scans_info = _rows_to_dicts_list(cur.fetchall()) latest_scan = None if len(scans_info) > 0: # NB: the correctness of this depends on the scans (queried # right above) being in ascending order by timestamp latest_scan = scans_info[len(scans_info)-1] # get details about this barcode itself; CAN be None if the # barcode doesn't exist in db barcode_info = None cur.execute("SELECT barcode, assigned_on, status, " "sample_postmark_date, biomass_remaining, " "sequencing_status, obsolete, " "create_date_time, kit_id " "FROM barcodes.barcode " "WHERE barcode = %s", (sample_barcode,)) barcode_row = cur.fetchone() if barcode_row is not None: barcode_info = dict(barcode_row) if account is None and source is None and sample is None and \ len(projects_info) == 0 and len(scans_info) == 0 \ and barcode_info is None: return None diagnostic = { "account": account, "source": source, "sample": sample, "latest_scan": latest_scan, "scans_info": scans_info, "barcode_info": barcode_info, "projects_info": projects_info } if grab_kit: # get kit object if kit_id is not None: kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples_by_kit_id(kit_id) diagnostic["kit"] = kit return diagnostic
def get_survey_metadata(self, sample_barcode, survey_template_id=None): ids = self._get_ids_relevant_to_barcode(sample_barcode) if ids is None: raise NotFound("No such barcode") account_id = ids.get('account_id') source_id = ids.get('source_id') sample_id = ids.get('sample_id') account = None source = None sample = None if sample_id is not None: sample_repo = SampleRepo(self._transaction) sample = sample_repo._get_sample_by_id(sample_id) if source_id is not None and account_id is not None: source_repo = SourceRepo(self._transaction) account_repo = AccountRepo(self._transaction) account = account_repo.get_account(account_id) source = source_repo.get_source(account_id, source_id) if source is None: raise RepoException("Barcode is not associated with a source") # TODO: This is my best understanding of how the data must be # transformed to get the host_subject_id, needs verification that it # generates the expected values for preexisting samples. prehash = account_id + source.name.lower() host_subject_id = sha512(prehash.encode()).hexdigest() survey_answers_repo = SurveyAnswersRepo(self._transaction) answer_ids = survey_answers_repo.list_answered_surveys_by_sample( account_id, source_id, sample_id) answer_to_template_map = {} for answer_id in answer_ids: template_id = survey_answers_repo.find_survey_template_id( answer_id) answer_to_template_map[answer_id] = template_id # if a survey template is specified, filter the returned surveys if survey_template_id is not None: # TODO: This schema is so awkward for this type of query... answers = [] for answer_id in answer_ids: if answer_to_template_map[answer_id] == survey_template_id: answers.append(answer_id) if len(answers) == 0: raise NotFound("This barcode is not associated with any " "surveys matching this template id") if len(answers) > 1: # I really hope this can't happen. (x . x) raise RepoException("This barcode is associated with more " "than one survey matching this template" " id") answer_ids = answers metadata_map = survey_answers_repo.build_metadata_map() all_survey_answers = [] for answer_id in answer_ids: answer_model = survey_answers_repo.get_answered_survey( account_id, source_id, answer_id, "en-US") survey_answers = {} for k in answer_model: new_k = metadata_map[int(k)] survey_answers[k] = [new_k, answer_model[k]] all_survey_answers.append({ "template": answer_to_template_map[answer_id], "response": survey_answers }) pulldown = { "sample_barcode": sample_barcode, "host_subject_id": host_subject_id, "account": account, "source": source, "sample": sample, "survey_answers": all_survey_answers } return pulldown
def retrieve_diagnostics_by_barcode(self, sample_barcode, grab_kit=True): def _rows_to_dicts_list(rows): return [dict(x) for x in rows] with self._transaction.dict_cursor() as cur: ids = self._get_ids_relevant_to_barcode(sample_barcode) if ids is None: ids = {} # default for not found is None sample_id = ids.get("sample_id") source_id = ids.get("source_id") account_id = ids.get("account_id") # NB: this is the true UUID kit id (the primary key of # ag.ag_kit), NOT the kit's participant-facing string "id" kit_id = ids.get("kit_id") account = None source = None sample = None kit = None # get sample object for this barcode, if any if sample_id is not None: sample_repo = SampleRepo(self._transaction) sample = sample_repo._get_sample_by_id(sample_id) # get account object for this barcode, if any if account_id is not None: account_repo = AccountRepo(self._transaction) account = account_repo.get_account(account_id) # and source object for this barcode, if any if source_id is not None: source_repo = SourceRepo(self._transaction) source = source_repo.get_source(account_id, source_id) # get (partial) projects_info list for this barcode query = f""" SELECT {p.DB_PROJ_NAME_KEY}, {p.IS_MICROSETTA_KEY}, {p.BANK_SAMPLES_KEY}, {p.PLATING_START_DATE_KEY} FROM barcodes.project INNER JOIN barcodes.project_barcode USING (project_id) WHERE barcode=%s;""" cur.execute(query, (sample_barcode, )) # this can't be None; worst-case is an empty list projects_info = _rows_to_dicts_list(cur.fetchall()) # get scans_info list for this barcode # NB: ORDER MATTERS here. Do not change the order unless you # are positive you know what already depends on it. cur.execute( "SELECT barcode_scan_id, barcode, " "scan_timestamp, sample_status, " "technician_notes " "FROM barcodes.barcode_scans " "WHERE barcode=%s " "ORDER BY scan_timestamp asc", (sample_barcode, )) # this can't be None; worst-case is an empty list scans_info = _rows_to_dicts_list(cur.fetchall()) latest_scan = None if len(scans_info) > 0: # NB: the correctness of this depends on the scans (queried # right above) being in ascending order by timestamp latest_scan = scans_info[len(scans_info) - 1] # get details about this barcode itself; CAN be None if the # barcode doesn't exist in db barcode_info = None cur.execute( "SELECT barcode, assigned_on, status, " "sample_postmark_date, biomass_remaining, " "sequencing_status, obsolete, " "create_date_time, kit_id " "FROM barcodes.barcode " "WHERE barcode = %s", (sample_barcode, )) barcode_row = cur.fetchone() if barcode_row is not None: barcode_info = dict(barcode_row) if account is None and source is None and sample is None and \ len(projects_info) == 0 and len(scans_info) == 0 \ and barcode_info is None: return None diagnostic = { "account": account, "source": source, "sample": sample, "latest_scan": latest_scan, "scans_info": scans_info, "barcode_info": barcode_info, "projects_info": projects_info } if grab_kit: # get kit object if kit_id is not None: kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples_by_kit_id(kit_id) diagnostic["kit"] = kit return diagnostic