diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index b52975852..46af6143c 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -29,7 +29,11 @@ post_daklapack_orders from microsetta_private_api import localization from microsetta_private_api.admin.sample_summary import per_sample -from microsetta_private_api.admin.sample_summary import get_barcodes_for +from microsetta_private_api.admin.sample_summary import \ + get_barcodes_by_project_id,\ + get_barcodes_by_kit_ids, get_barcodes_by_emails,\ + get_barcodes_by_outbound_tracking_numbers,\ + get_barcodes_by_inbound_tracking_numbers from microsetta_private_api.util.melissa import verify_address from microsetta_private_api.util.query_builder_to_sql import build_condition from werkzeug.exceptions import Unauthorized @@ -500,12 +504,26 @@ def query_project_barcode_stats(body, token_info, strip_sampleid): def query_barcode_stats(body, token_info, strip_sampleid): validate_admin_access(token_info) + + project_id = None + if 'sample_barcodes' in body: - project_id = None barcodes = body["sample_barcodes"] + elif 'kit_ids' in body: + barcodes = get_barcodes_by_kit_ids(body["kit_ids"]) + elif 'emails' in body: + barcodes = get_barcodes_by_emails(body["emails"]) + elif 'outbound_tracking_numbers' in body: + barcodes = get_barcodes_by_outbound_tracking_numbers( + body["outbound_tracking_numbers"] + ) + elif 'inbound_tracking_numbers' in body: + barcodes = get_barcodes_by_inbound_tracking_numbers( + body["inbound_tracking_numbers"] + ) elif 'project_id' in body: project_id = body["project_id"] - barcodes = get_barcodes_for(project_id) + barcodes = get_barcodes_by_project_id(project_id) unprocessed_barcodes = None diff --git a/microsetta_private_api/admin/sample_summary.py b/microsetta_private_api/admin/sample_summary.py index 5b3312571..4c22bcb57 100644 --- a/microsetta_private_api/admin/sample_summary.py +++ b/microsetta_private_api/admin/sample_summary.py @@ -7,14 +7,33 @@ from werkzeug.exceptions import NotFound -def get_barcodes_for(project_id): - if project_id is None: - raise ValueError("project_id must be defined.") - +def get_barcodes_by_project_id(project_id): with Transaction() as t: return AdminRepo(t).get_project_barcodes(project_id) +def get_barcodes_by_kit_ids(kit_ids): + with Transaction() as t: + return AdminRepo(t).get_barcodes_filter(kit_ids=kit_ids) + + +def get_barcodes_by_emails(emails): + with Transaction() as t: + return AdminRepo(t).get_barcodes_filter(emails=emails) + + +def get_barcodes_by_outbound_tracking_numbers(outbound_tracking_numbers): + with Transaction() as t: + return AdminRepo(t).get_barcodes_filter( + outbound_tracking_numbers=outbound_tracking_numbers) + + +def get_barcodes_by_inbound_tracking_numbers(inbound_tracking_numbers): + with Transaction() as t: + return AdminRepo(t).get_barcodes_filter( + inbound_tracking_numbers=inbound_tracking_numbers) + + def per_sample(project, barcodes, strip_sampleid): summaries = [] with Transaction() as t: @@ -38,6 +57,20 @@ def per_sample(project, barcodes, strip_sampleid): sample = diag['sample'] account = diag['account'] source = diag['source'] + first_scans_info = diag['scans_info'] + last_scans_info = diag['latest_scan'] + if first_scans_info: + first_scan_timestamp = first_scans_info[0]['scan_timestamp'] + first_scan_status = first_scans_info[0]['sample_status'] + else: + first_scan_timestamp = None + first_scan_status = None + if last_scans_info: + latest_scan_timestamp = last_scans_info['scan_timestamp'] + latest_scan_status = last_scans_info['sample_status'] + else: + latest_scan_timestamp = None + latest_scan_status = None account_email = None if account is None else account.email source_type = None if source is None else source.source_type @@ -94,6 +127,19 @@ def per_sample(project, barcodes, strip_sampleid): sample_date = None sample_time = None + kit_by_barcode = admin_repo.get_kit_by_barcode([barcode]) + + if kit_by_barcode and len(kit_by_barcode) > 0: + info = kit_by_barcode[0] + + kit_id_name = info['kit_id'] + outbound_fedex_tracking = info['outbound_tracking'] + inbound_fedex_tracking = info['inbound_tracking'] + else: + kit_id_name = None + outbound_fedex_tracking = None + inbound_fedex_tracking = None + summary = { "sampleid": None if strip_sampleid else barcode, "project": barcode_project, @@ -106,7 +152,14 @@ def per_sample(project, barcodes, strip_sampleid): "ffq-taken": ffq_taken, "ffq-complete": ffq_complete, "sample-status": sample_status, - "sample-received": sample_status is not None + "sample-received": sample_status is not None, + "first-scan-timestamp": first_scan_timestamp, + "first-scan-status": first_scan_status, + "latest-scan-timestamp": latest_scan_timestamp, + "latest-scan-status": latest_scan_status, + "kit-id": kit_id_name, + "outbound-tracking": outbound_fedex_tracking, + "inbound-tracking": inbound_fedex_tracking, } for status in ["sample-is-valid", diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 40da36a7a..89f5b7bb3 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1487,3 +1487,137 @@ def test_update_perk_fulfillment_state(self): ) obs = cur.fetchone() self.assertFalse(obs[0]) + + def test_get_barcodes_filter_kit_ids_success(self): + with Transaction() as t: + setup_sql = """ + INSERT INTO barcodes.kit (kit_id, box_id) + VALUES ('test1', '0001e15f-4170-4b28-b111-191cd567c347'); + + INSERT INTO barcodes.barcode (barcode, kit_id) + VALUES ('00001234', 'test1'); + """ + with t.cursor() as cur: + cur.execute(setup_sql) + + admin_repo = AdminRepo(t) + + barcodes = admin_repo.get_barcodes_filter(kit_ids=['test1']) + self.assertEqual(barcodes, ['00001234']) + + def test_get_barcodes_filter_kit_ids_failure(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + barcodes = admin_repo.get_barcodes_filter(kit_ids=['notarealkit']) + self.assertEqual(barcodes, []) + + def test_get_barcodes_filter_emails_success(self): + with Transaction() as t: + setup_sql = """ + INSERT INTO ag.source (id, account_id, + source_type, source_name) + VALUES ('0003ddfd-4949-4105-90a9-1b1530af5352', %s, + 'Human', 'Test Source'); + INSERT INTO barcodes.kit (kit_id, box_id) + VALUES ('test1', '0001e15f-4170-4b28-b111-191cd567c347'); + INSERT INTO barcodes.barcode (barcode, kit_id) + VALUES ('00001234', 'test1'); + INSERT INTO ag.ag_kit_barcodes (barcode, source_id) + VALUES ('00001234', '0003ddfd-4949-4105-90a9-1b1530af5352'); + """ + with t.cursor() as cur: + cur.execute(setup_sql, (STANDARD_ACCT_ID,)) + + admin_repo = AdminRepo(t) + + barcodes = admin_repo.get_barcodes_filter(emails=['foo@baz.com']) + self.assertEqual(barcodes, ['00001234']) + + def test_get_barcodes_filter_emails_failure(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + barcodes = admin_repo.get_barcodes_filter( + emails=['notarealemail@example.com']) + self.assertEqual(barcodes, []) + + def test_get_barcodes_filter_outbound_tracking_success(self): + with Transaction() as t: + setup_sql = """ + INSERT INTO barcodes.kit (kit_id, box_id, + outbound_fedex_tracking) + VALUES ('test1', '0001e15f-4170-4b28-b111-191cd567c347', + '12345'); + INSERT INTO barcodes.barcode (barcode, kit_id) + VALUES ('00001234', 'test1'); + """ + with t.cursor() as cur: + cur.execute(setup_sql) + + admin_repo = AdminRepo(t) + + barcodes = \ + admin_repo.get_barcodes_filter( + outbound_tracking_numbers=['12345']) + self.assertEqual(barcodes, ['00001234']) + + def test_get_barcodes_filter_outbound_tracking_failure(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + barcodes = admin_repo.get_barcodes_filter( + outbound_tracking_numbers=['99999']) + self.assertEqual(barcodes, []) + + def test_get_barcodes_filter_inbound_tracking_success(self): + with Transaction() as t: + setup_sql = """ + INSERT INTO barcodes.kit (kit_id, box_id, + inbound_fedex_tracking) + VALUES ('test1', '0001e15f-4170-4b28-b111-191cd567c347', + '67890'); + INSERT INTO barcodes.barcode (barcode, kit_id) + VALUES ('00001234', 'test1'); + """ + with t.cursor() as cur: + cur.execute(setup_sql) + + admin_repo = AdminRepo(t) + + barcodes = admin_repo.get_barcodes_filter( + inbound_tracking_numbers=['67890']) + self.assertEqual(barcodes, ['00001234']) + + def test_get_barcodes_filter_inbound_tracking_failure(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + barcodes = admin_repo.get_barcodes_filter( + inbound_tracking_numbers=['99999']) + self.assertEqual(barcodes, []) + + def test_get_kit_by_barcode_success(self): + with Transaction() as t: + setup_sql = """ + INSERT INTO barcodes.kit (kit_id, box_id) + VALUES ('test1', '0001e15f-4170-4b28-b111-191cd567c348'); + + INSERT INTO barcodes.barcode (barcode, kit_id) + VALUES ('00001234', 'test1'); + """ + with t.cursor() as cur: + cur.execute(setup_sql) + + admin_repo = AdminRepo(t) + + kit_info = admin_repo.get_kit_by_barcode(['00001234']) + expected = [{ + 'barcode': '00001234', + 'outbound_tracking': None, + 'inbound_tracking': None, + 'kit_id': 'test1' + }] + self.assertEqual(kit_info, expected) + + def test_get_kit_by_barcode_failure(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + kit_info = admin_repo.get_kit_by_barcode(['nonexistent_barcode']) + self.assertIsNone(kit_info) diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index 88c96e8dc..ac3b36ea2 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2998,6 +2998,22 @@ paths: # not using the defined schema for sample_barcode as it is # readOnly type: string + 'kit_ids': + type: array + items: + type: string + 'emails': + type: array + items: + type: string + 'outbound_tracking_numbers': + type: array + items: + type: string + 'inbound_tracking_numbers': + type: array + items: + type: string responses: '200': description: Return an object containing a list of dictionaries of sample status for requested accounts diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index ab032ea19..800815257 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -553,6 +553,115 @@ def get_project_barcodes(self, project_id): cur.execute(query, [project_id, ]) return list([v[0] for v in cur.fetchall()]) + def get_barcodes_filter(self, kit_ids=None, emails=None, + outbound_tracking_numbers=None, + inbound_tracking_numbers=None): + """Obtain the barcodes based on different filtering criteria. + + Parameters + ---------- + kit_ids : list, optional + List of kit IDs to obtain barcodes for. + emails : list, optional + List of emails to obtain barcodes for. + outbound_tracking_numbers : list, optional + List of outbound tracking numbers to obtain barcodes for. + inbound_tracking_numbers : list, optional + List of inbound tracking numbers to obtain barcodes for. + + Returns + ------- + list + The list of observed barcodes based on the provided criteria. + """ + query = """ + SELECT b.barcode + FROM barcodes.barcode AS b + JOIN barcodes.kit AS k ON b.kit_id = k.kit_id + """ + + conditions = [] + params = [] + + if kit_ids: + conditions.append("k.kit_id IN %s") + params.append(tuple(kit_ids)) + + if emails: + query += """ + JOIN ag.ag_kit_barcodes AS akb ON akb.barcode = b.barcode + JOIN ag.source AS s ON s.id = akb.source_id + JOIN ag.account AS a ON s.account_id = a.id + """ + conditions.append("a.email IN %s") + params.append(tuple(emails)) + + if outbound_tracking_numbers: + conditions.append("k.outbound_fedex_tracking IN %s") + params.append(tuple(outbound_tracking_numbers)) + + if inbound_tracking_numbers: + conditions.append("k.inbound_fedex_tracking IN %s") + params.append(tuple(inbound_tracking_numbers)) + + if conditions: + query += " WHERE " + " AND ".join(conditions) + + with self._transaction.cursor() as cur: + cur.execute(query, params) + barcodes = [row[0] for row in cur.fetchall()] + + return barcodes + + def get_kit_by_barcode(self, barcodes): + """Obtain the outbound tracking, inbound tracking numbers, + and kit ID associated with a list of barcodes. + + Parameters + ---------- + barcodes : list + The list of barcodes to obtain information for. + + Returns + ------- + list of dict + A list of dictionaries with outbound tracking, + inbound tracking, and kit ID for each barcode. + """ + query = """ + SELECT + b.barcode, + k.outbound_fedex_tracking, + k.inbound_fedex_tracking, + k.kit_id + FROM + barcodes.barcode b + JOIN + barcodes.kit k + ON + b.kit_id = k.kit_id + WHERE + b.barcode IN %s + """ + + with self._transaction.cursor() as cur: + cur.execute(query, [tuple(barcodes)]) + + rows = cur.fetchall() + + if len(rows) == 0: + return None + + return [ + { + "barcode": row[0], + "outbound_tracking": row[1], + "inbound_tracking": row[2], + "kit_id": row[3] + } + for row in rows + ] + def create_project(self, project): """Create a project entry in the database