Skip to content

Commit

Permalink
Merge pull request #324 from wasade/persampe-tweaks
Browse files Browse the repository at this point in the history
Per sample barcode summaries including email support
  • Loading branch information
wasade authored May 21, 2021
2 parents 7896392 + 0a4818f commit e2acc14
Show file tree
Hide file tree
Showing 14 changed files with 375 additions and 122 deletions.
91 changes: 14 additions & 77 deletions microsetta_private_api/admin/admin_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from microsetta_private_api.config_manager import SERVER_CONFIG
from microsetta_private_api.model.log_event import LogEvent
from microsetta_private_api.model.source import Source
from microsetta_private_api.model.project import Project
from microsetta_private_api.model.daklapack_order import DaklapackOrder, \
ORDER_ID_KEY, SUBMITTER_ACCT_KEY
Expand All @@ -18,15 +17,15 @@
from microsetta_private_api.repo.source_repo import SourceRepo
from microsetta_private_api.repo.transaction import Transaction
from microsetta_private_api.repo.admin_repo import AdminRepo
from microsetta_private_api.repo.survey_template_repo import SurveyTemplateRepo
from microsetta_private_api.repo.metadata_repo import (retrieve_metadata,
drop_private_columns)
from microsetta_private_api.repo.vioscreen_repo import VioscreenSessionRepo
from microsetta_private_api.tasks import send_email as celery_send_email
from microsetta_private_api.tasks import send_email as celery_send_email,\
per_sample_summary as celery_per_sample_summary
from microsetta_private_api.admin.email_templates import EmailMessage
from microsetta_private_api.util.redirects import build_login_redirect
from microsetta_private_api.admin.daklapack_communication import \
post_daklapack_order, send_daklapack_hold_email
from microsetta_private_api.admin.sample_summary import per_sample
from werkzeug.exceptions import Unauthorized


Expand Down Expand Up @@ -397,83 +396,21 @@ def query_email_stats(body, token_info):
return jsonify(results), 200


def query_barcode_stats(body, token_info):
def query_project_barcode_stats(body, token_info, strip_sampleid):
validate_admin_access(token_info)

barcodes = body.get("sample_barcodes")
email = body.get("email")
project = body["project"]
celery_per_sample_summary.delay(email, project, strip_sampleid)
return None, 200

summaries = []
with Transaction() as t:
admin_repo = AdminRepo(t)
sample_repo = SampleRepo(t)
template_repo = SurveyTemplateRepo(t)
vs_repo = VioscreenSessionRepo(t)

project_barcodes = admin_repo.get_project_barcodes(project)

if barcodes is None:
barcodes = project_barcodes
else:
barcodes = [b for b in barcodes if b in set(project_barcodes)]
not_found = [b for b in barcodes if b not in set(project_barcodes)]
if len(not_found) > 0:
nf = ", ".join(not_found)
message = f"The following barcodes were not found: '[{nf}]'"
return jsonify(code=404, message=message), 404

for barcode in barcodes:
diag = admin_repo.retrieve_diagnostics_by_barcode(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 is Source.SOURCE_TYPE_HUMAN:
source_email = source.email

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

sample_status = sample_repo.get_sample_status(
sample.barcode,
sample._latest_scan_timestamp
)

ffq_complete, ffq_taken, _ = vs_repo.get_ffq_status_by_sample(
sample.id
)

summary = {
"sampleid": 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 jsonify(summaries), 200
def query_barcode_stats(body, token_info, strip_sampleid):
validate_admin_access(token_info)
barcodes = body["sample_barcodes"]
if len(barcodes) > 1000:
return jsonify({"message": "Too manny barcodes requested"}), 400
summary = per_sample(None, barcodes, strip_sampleid)
return jsonify(summary), 200


def create_daklapack_order(body, token_info):
Expand Down
88 changes: 88 additions & 0 deletions microsetta_private_api/admin/sample_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from microsetta_private_api.model.source import Source
from microsetta_private_api.repo.sample_repo import SampleRepo
from microsetta_private_api.repo.transaction import Transaction
from microsetta_private_api.repo.admin_repo import AdminRepo
from microsetta_private_api.repo.survey_template_repo import SurveyTemplateRepo
from microsetta_private_api.repo.vioscreen_repo import VioscreenSessionRepo
from werkzeug.exceptions import NotFound


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
84 changes: 52 additions & 32 deletions microsetta_private_api/admin/tests/test_admin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,54 +976,72 @@ def test_post_daklapack_orders_failure_dak_api(self):
mock_email.side_effect = [True]
self._test_post_daklapack_orders(order_info, 401)

def test_query_barcode_stats_no_project_no_barcodes(self):
input_json = json.dumps({'project': 0, 'sample_barcodes': None})
def test_query_project_barcode_stats_project_without_strip(self):
input_json = json.dumps({'project': 7, 'email': 'foobar'})

response = self.client.post(
"api/admin/account_barcode_summary",
content_type='application/json',
data=input_json,
headers=MOCK_HEADERS
)
with patch("microsetta_private_api.tasks."
"per_sample_summary.delay") as mock_delay:
mock_delay.return_value = None
response = self.client.post(
"api/admin/account_project_barcode_summary?"
"strip_sampleid=false",
content_type='application/json',
data=input_json,
headers=MOCK_HEADERS
)

# an empty string project should be unkown
self.assertEqual(404, response.status_code)
# ...we assume the system is processing to send an email
# so nothing specific to verify on the response data
self.assertEqual(200, response.status_code)

def test_query_barcode_stats_project_no_barcodes(self):
input_json = json.dumps({'project': 7, 'sample_barcodes': None})
def test_query_project_barcode_stats_project_with_strip(self):
input_json = json.dumps({'project': 7, 'email': 'foobar'})

with patch("microsetta_private_api.tasks."
"per_sample_summary.delay") as mock_delay:
mock_delay.return_value = None
response = self.client.post(
"api/admin/account_project_barcode_summary?"
"strip_sampleid=True",
content_type='application/json',
data=input_json,
headers=MOCK_HEADERS
)

# ...we assume the system is processing to send an email
# so nothing specific to verify on the response data
self.assertEqual(200, response.status_code)

def test_query_barcode_stats_project_barcodes_without_strip(self):
barcodes = ['000010307', '000023344', '000036855']
input_json = json.dumps({'sample_barcodes': barcodes})

response = self.client.post(
"api/admin/account_barcode_summary",
"api/admin/account_barcode_summary?strip_sampleid=False",
content_type='application/json',
data=input_json,
headers=MOCK_HEADERS
)

# an empty string project should be unkown
self.assertEqual(200, response.status_code)

response_obj = json.loads(response.data)
self.assertEqual(len(response_obj), 3)

# there are 24 samples with project 7 in the test db
self.assertEqual(len(response_obj), 24)

# ...10 have been received
self.assertEqual(sum([v['sample-received']
for v in response_obj]), 10)

# ...9 are valid
self.assertEqual(sum([v['sample-is-valid']
for v in response_obj]), 9)

# ...1 is missing a source
self.assertEqual(sum([v['no-associated-source']
for v in response_obj]), 1)
self.assertEqual([v['sampleid'] for v in response_obj],
barcodes)
exp_status = [None, 'no-associated-source', 'sample-is-valid']
self.assertEqual([v['sample-status'] for v in response_obj],
exp_status)
n_src = sum([v['source-email'] is not None for v in response_obj])
self.assertEqual(n_src, 1)

def test_query_barcode_stats_project_barcodes(self):
def test_query_barcode_stats_project_barcodes_with_strip(self):
barcodes = ['000010307', '000023344', '000036855']
input_json = json.dumps({'project': 7, 'sample_barcodes': barcodes})
input_json = json.dumps({'sample_barcodes': barcodes})

response = self.client.post(
"api/admin/account_barcode_summary",
"api/admin/account_barcode_summary?strip_sampleid=True",
content_type='application/json',
data=input_json,
headers=MOCK_HEADERS
Expand All @@ -1035,7 +1053,9 @@ def test_query_barcode_stats_project_barcodes(self):
self.assertEqual(len(response_obj), 3)

self.assertEqual([v['sampleid'] for v in response_obj],
barcodes)
[None] * len(barcodes))
exp_status = [None, 'no-associated-source', 'sample-is-valid']
self.assertEqual([v['sample-status'] for v in response_obj],
exp_status)
n_src = sum([v['source-email'] is not None for v in response_obj])
self.assertEqual(n_src, 1)
13 changes: 13 additions & 0 deletions microsetta_private_api/admin/tests/test_admin_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,19 @@ def test_retrieve_diagnostics_by_barcode_not_agp(self):
self.assertGreater(len(diag['scans_info']), 0)
self.assertGreater(len(diag['projects_info']), 0)

def test_get_project_name_fail(self):
with Transaction() as t:
admin_repo = AdminRepo(t)

with self.assertRaisesRegex(NotFound, "not found"):
admin_repo.get_project_name(9999999)

def test_get_project_name(self):
with Transaction() as t:
admin_repo = AdminRepo(t)
obs = admin_repo.get_project_name(1)
self.assertEqual(obs, 'American Gut Project')

def test_get_project_barcodes_fail(self):
with Transaction() as t:
admin_repo = AdminRepo(t)
Expand Down
Loading

0 comments on commit e2acc14

Please sign in to comment.