From 719a8f21309d21db83db8d7ae7a104ad2165e8c9 Mon Sep 17 00:00:00 2001 From: ayobi Date: Thu, 7 Mar 2024 23:11:48 -0500 Subject: [PATCH 01/33] init create barcodes for existing kits --- microsetta_private_api/admin/admin_impl.py | 25 +++++++ .../admin/tests/test_admin_repo.py | 63 ++++++++++++++++ .../api/microsetta_private_api.yaml | 59 +++++++++++++++ microsetta_private_api/repo/admin_repo.py | 74 +++++++++++++++++++ 4 files changed, 221 insertions(+) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index ff4d2d80c..4a1049fbe 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -268,6 +268,31 @@ def create_kits(body, token_info): return jsonify(kits), 201 +def generate_barcodes(body): + + number_of_kits = body['number_of_kits'] + number_of_samples = body['number_of_samples'] + + with Transaction() as t: + admin_repo = AdminRepo(t) + barcode = admin_repo._generate_novel_barcodes_admin( + number_of_kits, number_of_samples) + t.commit() + return barcode + + +def insert_barcodes(body): + + barcode = body['barcodes'] + project_id = [body['project_id']] + + with Transaction() as t: + admin_repo = AdminRepo(t) + admin_repo._insert_barcodes_to_existing_kit(barcode, project_id) + t.commit() + return '', 204 + + def get_account_events(account_id, token_info): validate_admin_access(token_info) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index d106e0cff..c60137bb9 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1384,3 +1384,66 @@ def test_update_perk_fulfillment_state(self): ) obs = cur.fetchone() self.assertFalse(obs[0]) + + def test_generate_novel_barcodes_admin_success(self): + number_of_kits = 1 + number_of_samples = 3 + + with Transaction() as t: + admin_repo = AdminRepo(t) + new_barcodes = admin_repo._generate_novel_barcodes_admin + (number_of_kits, number_of_samples) + self.assertEqual(len(new_barcodes), + number_of_kits * number_of_samples) + self.assertTrue(all(barcode.startswith('X') + for barcode in new_barcodes)) + + def test_generate_novel_barcodes_admin_failure(self): + number_of_kits = 0 + number_of_samples = 3 + + with Transaction() as t: + admin_repo = AdminRepo(t) + new_barcodes = admin_repo._generate_novel_barcodes_admin + (number_of_kits, number_of_samples) + self.assertTrue(new_barcodes == []) + + def test_insert_barcodes_admin_success(self): + kit_barcode = [['test', 'X00332312']] + project_id = '1' + + with Transaction() as t: + admin_repo = AdminRepo(t) + admin_repo._insert_barcodes_to_existing_kit + (kit_barcode, project_id) + with t.cursor() as cur: + cur.execute( + "SELECT barcode " + "FROM barcodes.barcode " + "WHERE kit_id = %s", + ('test',) + ) + obs = cur.fetchall() + self.assertEqual(obs[0][0], 'X00332312') + + def test_insert_barcodes_admin_fail_nonexisting_kit(self): + # test that inserting barcodes to a non-existent kit fails + kit_barcode = [['test1123', 'X00332312']] + project_id = '1' + + with Transaction() as t: + admin_repo = AdminRepo(t) + with self.assertRaises(psycopg2.errors.ForeignKeyViolation): + admin_repo._insert_barcodes_to_existing_kit + (kit_barcode, project_id) + + def test_insert_barcodes_admin_fail_dup_barcodes(self): + # test that inserting duplicate barcode fails + kit_barcode = [['test', '000000001']] + project_id = '1' + + with Transaction() as t: + admin_repo = AdminRepo(t) + with self.assertRaises(psycopg2.errors.UniqueViolation): + admin_repo._insert_barcodes_to_existing_kit + (kit_barcode, project_id) diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index c58e3115d..6e93e9eb2 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2764,6 +2764,65 @@ paths: '422': $ref: '#/components/responses/422UnprocessableEntity' + '/admin/create/barcodes': + post: + operationId: microsetta_private_api.admin.admin_impl.generate_barcodes + tags: + - Admin + summary: Create barcodes + description: Create barcodes for an exisiting kit + requestBody: + content: + application/json: + schema: + type: "object" + properties: + number_of_kits: + type: integer + number_of_samples: + type: integer + required: + - number_of_kits + - number_of_samples + responses: + '201': + description: Barcodes were successfully created + content: + application/json: + schema: + type: array + + '/admin/insert_barcodes': + post: + operationId: microsetta_private_api.admin.admin_impl.insert_barcodes + tags: + - Admin + summary: Insert barcodes into the database + description: Insert barcodes into the database + requestBody: + content: + application/json: + schema: + type: "object" + properties: + barcodes: + type: array + items: + type: array + kit_id: + type: string + project_id: + type: integer + responses: + '201': + description: Barcodes were successfully inserted + content: + application/json: + schema: + type: array + '500': + description: Duplicate barcodes found + '/admin/events/accounts/{account_id}': get: operationId: microsetta_private_api.admin.admin_impl.get_account_events diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index d3a56f0c9..f5aa0f634 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -851,6 +851,80 @@ def _generate_novel_barcodes(self, number_of_kits, number_of_samples, return kit_name_and_barcode_tuples_list, new_barcodes + def _generate_novel_barcodes_admin(self, + number_of_kits, + number_of_samples): + """Generate specified number of random barcodes for admin""" + + total_barcodes = number_of_kits * number_of_samples + + with self._transaction.cursor() as cur: + cur.execute( + "SELECT max(right(barcode,8)::integer) " + "FROM barcodes.barcode " + "WHERE barcode LIKE 'X%' OR barcode LIKE '0%'" + ) + start_bc = cur.fetchone()[0] + 1 + new_barcodes = ['X%0.8d' % (start_bc + i) + for i in range(total_barcodes)] + return new_barcodes + + def _insert_barcodes_to_existing_kit(self, + kit_name_and_barcode_tuples_list, + project_ids): + """Insert barcodes into the database for an exisiting kit + Parameters + ---------- + kit_name_and_barcode_tuples_list: list of tuple of str + Kit name and associated barcode (one tuple per barcode) + project_ids : list of int + Project ids that all barcodes are to be associated with + """ + # check for empty input + if kit_name_and_barcode_tuples_list \ + is None or len(kit_name_and_barcode_tuples_list) == 0: + raise ValueError("kit_name_and_barcode_tuples_list " + "cannot be empty") + + # integer project ids come in as strings ... + project_ids = [int(x) for x in project_ids] + + is_tmi = self._are_any_projects_tmi(project_ids) + + with self._transaction.cursor() as cur: + # add new barcodes to barcode table + barcode_insertions = [(n, b, 'unassigned') + for n, b in kit_name_and_barcode_tuples_list] + cur.executemany("INSERT INTO barcode (kit_id, barcode, status) " + "VALUES (%s, %s, %s)", + barcode_insertions) + + # create barcode project associations + barcode_projects = [] + for barcode in [b for _, b in kit_name_and_barcode_tuples_list]: + for prj_id in project_ids: + barcode_projects.append((barcode, prj_id)) + cur.executemany("INSERT INTO project_barcode " + "(barcode, project_id) " + "VALUES (%s, %s)", barcode_projects) + if is_tmi: + # for each new barcode, add a record to the ag_kit_barcodes + # table associating it to its ag kit, creating a new + # "sample_barcode" + kit_barcodes_insert = [(i, b) + for i, b + in kit_name_and_barcode_tuples_list] + + try: + cur.executemany("INSERT INTO ag_kit_barcodes " + "(ag_kit_id, barcode) " + "SELECT ag_kit_id, %s " + "FROM ag_kit " + "WHERE supplied_kit_id = %s", + [(b, i) for i, b in kit_barcodes_insert]) + except Exception as e: + print("Error executing query:", e) + def _create_kits(self, kit_names, new_barcodes, kit_name_and_barcode_tuples_list, number_of_samples, project_ids, kits_details=None): From 58463015158052f82b60adb662505ae3dbc9e9f5 Mon Sep 17 00:00:00 2001 From: ayobi Date: Thu, 7 Mar 2024 23:44:39 -0500 Subject: [PATCH 02/33] indent issues --- .../admin/tests/test_admin_repo.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index c60137bb9..d86165123 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1391,8 +1391,8 @@ def test_generate_novel_barcodes_admin_success(self): with Transaction() as t: admin_repo = AdminRepo(t) - new_barcodes = admin_repo._generate_novel_barcodes_admin - (number_of_kits, number_of_samples) + new_barcodes = admin_repo._generate_novel_barcodes_admin( + number_of_kits, number_of_samples) self.assertEqual(len(new_barcodes), number_of_kits * number_of_samples) self.assertTrue(all(barcode.startswith('X') @@ -1404,8 +1404,8 @@ def test_generate_novel_barcodes_admin_failure(self): with Transaction() as t: admin_repo = AdminRepo(t) - new_barcodes = admin_repo._generate_novel_barcodes_admin - (number_of_kits, number_of_samples) + new_barcodes = admin_repo._generate_novel_barcodes_admin( + number_of_kits, number_of_samples) self.assertTrue(new_barcodes == []) def test_insert_barcodes_admin_success(self): @@ -1414,8 +1414,8 @@ def test_insert_barcodes_admin_success(self): with Transaction() as t: admin_repo = AdminRepo(t) - admin_repo._insert_barcodes_to_existing_kit - (kit_barcode, project_id) + admin_repo._insert_barcodes_to_existing_kit( + kit_barcode, project_id) with t.cursor() as cur: cur.execute( "SELECT barcode " @@ -1434,8 +1434,8 @@ def test_insert_barcodes_admin_fail_nonexisting_kit(self): with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.ForeignKeyViolation): - admin_repo._insert_barcodes_to_existing_kit - (kit_barcode, project_id) + admin_repo._insert_barcodes_to_existing_kit( + kit_barcode, project_id) def test_insert_barcodes_admin_fail_dup_barcodes(self): # test that inserting duplicate barcode fails @@ -1445,5 +1445,5 @@ def test_insert_barcodes_admin_fail_dup_barcodes(self): with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.UniqueViolation): - admin_repo._insert_barcodes_to_existing_kit - (kit_barcode, project_id) + admin_repo._insert_barcodes_to_existing_kit( + kit_barcode, project_id) From 16f27214b290e5f23ec9c16cf5fbc5bead0d8f4c Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 18 Mar 2024 13:08:37 -0400 Subject: [PATCH 03/33] added validate admin access --- microsetta_private_api/admin/admin_impl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 4a1049fbe..0c093b8c1 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -268,7 +268,8 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def generate_barcodes(body): +def generate_barcodes(body, token_info): + validate_admin_access(token_info) number_of_kits = body['number_of_kits'] number_of_samples = body['number_of_samples'] @@ -281,7 +282,8 @@ def generate_barcodes(body): return barcode -def insert_barcodes(body): +def insert_barcodes(body, token_info): + validate_admin_access(token_info) barcode = body['barcodes'] project_id = [body['project_id']] From 846f9787f22ade5e1eb5b30de1bc6d2a42618c99 Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 18 Mar 2024 17:06:46 -0400 Subject: [PATCH 04/33] update insert barcode test --- .../admin/tests/test_admin_repo.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index d86165123..79c8c30b2 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1409,7 +1409,15 @@ def test_generate_novel_barcodes_admin_failure(self): self.assertTrue(new_barcodes == []) def test_insert_barcodes_admin_success(self): - kit_barcode = [['test', 'X00332312']] + number_of_kits = 1 + number_of_samples = 1 + + with Transaction() as t: + admin_repo = AdminRepo(t) + new_barcode = admin_repo._generate_novel_barcodes_admin( + number_of_kits, number_of_samples) + + kit_barcode = [['test', new_barcode[0]]] project_id = '1' with Transaction() as t: @@ -1424,7 +1432,7 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - self.assertEqual(obs[0][0], 'X00332312') + self.assertEqual(obs[0][0], new_barcode[0]) def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails From f9db8b445b132c0d06693a3f3d65eef9baaedc5a Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 18 Mar 2024 17:24:19 -0400 Subject: [PATCH 05/33] test --- microsetta_private_api/admin/admin_impl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 0c093b8c1..518d00ae2 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -282,8 +282,7 @@ def generate_barcodes(body, token_info): return barcode -def insert_barcodes(body, token_info): - validate_admin_access(token_info) +def insert_barcodes(body): barcode = body['barcodes'] project_id = [body['project_id']] From 5d458d10023a2c634ec0e04b00a7546583e070e5 Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 20:52:51 -0300 Subject: [PATCH 06/33] added user barcode field for create kits --- microsetta_private_api/admin/admin_impl.py | 8 ++- .../admin/tests/test_admin_repo.py | 62 ++++++++++++++++--- microsetta_private_api/repo/admin_repo.py | 16 +++-- .../repo/tests/test_sample.py | 1 + 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 518d00ae2..d8f18981d 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -253,13 +253,17 @@ def create_kits(body, token_info): number_of_samples = body['number_of_samples'] kit_prefix = body.get('kit_id_prefix', None) project_ids = body['project_ids'] + user_barcodes = body.get('user_barcodes', []) with Transaction() as t: admin_repo = AdminRepo(t) try: - kits = admin_repo.create_kits(number_of_kits, number_of_samples, - kit_prefix, project_ids) + kits = admin_repo.create_kits(number_of_kits, + number_of_samples, + kit_prefix, + user_barcodes, + project_ids) except KeyError: return jsonify(code=422, message="Unable to create kits"), 422 else: diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 79c8c30b2..3390f4b3a 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -448,7 +448,7 @@ def test_get_project_barcodes_by_id(self): output_id = admin_repo.create_project(input) # create some fake kits - created = admin_repo.create_kits(2, 3, 'foo', [output_id, ]) + created = admin_repo.create_kits(2, 3, 'foo', None, [output_id, ]) exp = [] for kit in created['created']: @@ -603,13 +603,14 @@ def test_create_kits_fail_nonexistent_project(self): admin_repo.create_kits(5, 3, '', + None, [10000, SurveyTemplateRepo.VIOSCREEN_ID]) def test_create_kits_success_not_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - non_tmi = admin_repo.create_kits(5, 3, '', + non_tmi = admin_repo.create_kits(5, 3, '', None, [33]) self.assertEqual(['created', ], list(non_tmi.keys())) self.assertEqual(len(non_tmi['created']), 5) @@ -637,7 +638,7 @@ def test_create_kits_success_not_microsetta(self): def test_create_kits_success_is_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - tmi = admin_repo.create_kits(4, 2, 'foo', + tmi = admin_repo.create_kits(4, 2, 'foo', None, [1]) self.assertEqual(['created', ], list(tmi.keys())) self.assertEqual(len(tmi['created']), 4) @@ -1418,12 +1419,12 @@ def test_insert_barcodes_admin_success(self): number_of_kits, number_of_samples) kit_barcode = [['test', new_barcode[0]]] - project_id = '1' + project_ids = '1' with Transaction() as t: admin_repo = AdminRepo(t) admin_repo._insert_barcodes_to_existing_kit( - kit_barcode, project_id) + kit_barcode, project_ids) with t.cursor() as cur: cur.execute( "SELECT barcode " @@ -1437,21 +1438,64 @@ def test_insert_barcodes_admin_success(self): def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails kit_barcode = [['test1123', 'X00332312']] - project_id = '1' + project_ids = '1' with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.ForeignKeyViolation): admin_repo._insert_barcodes_to_existing_kit( - kit_barcode, project_id) + kit_barcode, project_ids) def test_insert_barcodes_admin_fail_dup_barcodes(self): # test that inserting duplicate barcode fails kit_barcode = [['test', '000000001']] - project_id = '1' + project_ids = '1' with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.UniqueViolation): admin_repo._insert_barcodes_to_existing_kit( - kit_barcode, project_id) + kit_barcode, project_ids) + + def test_user_barcode_create_kit_success(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + user_barcode = 'X99887769' + admin_repo.create_kits(number_of_kits=1, + number_of_samples=1, + kit_prefix='', + user_barcodes=[user_barcode], + project_ids=[1]) + with t.cursor() as cur: + cur.execute( + "SELECT barcode " + "FROM barcodes.barcode " + "WHERE barcode = %s", + (user_barcode,) + ) + obs = cur.fetchall() + self.assertEqual(obs[0][0], user_barcode) + + def test_user_barcode_dup_create_kit_fail(self): + with Transaction() as t: + admin_repo = AdminRepo(t) + user_barcode = '000000001' + with self.assertRaises(psycopg2.errors.UniqueViolation): + admin_repo.create_kits(number_of_kits=1, + number_of_samples=1, + kit_prefix='', + user_barcodes=[user_barcode], + project_ids=[1]) + + def test_user_barcodes_num_samples_mismatch_create_kit_fail(self): + # When creating a kit, if a user selects just one sample, but + # provides two user barcodes, the transaction should fail + with Transaction() as t: + admin_repo = AdminRepo(t) + with self.assertRaises(psycopg2.errors.ForeignKeyViolation): + admin_repo.create_kits(number_of_kits=1, + number_of_samples=1, + kit_prefix='', + user_barcodes=['X92384885', + 'X92384886'], + project_ids=[1]) diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index f5aa0f634..51015edfa 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -747,7 +747,7 @@ def _generate_random_kit_name(self, name_length, prefix): return prefix + '_' + rand_name def create_kits(self, number_of_kits, number_of_samples, kit_prefix, - project_ids): + user_barcodes, project_ids): """Create multiple kits, each with the same number of samples Parameters @@ -763,9 +763,17 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, """ kit_names = self._generate_novel_kit_names(number_of_kits, kit_prefix) - kit_name_and_barcode_tuples_list, new_barcodes = \ - self._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names) + + if user_barcodes: + new_barcodes = user_barcodes + kit_name_and_barcode_tuples_list = [] + for i in range(number_of_samples * number_of_kits): + kit_name_and_barcode_tuples_list.append( + (kit_names[0], user_barcodes[i])) + else: + kit_name_and_barcode_tuples_list, new_barcodes = \ + self._generate_novel_barcodes( + number_of_kits, number_of_samples, kit_names) return self._create_kits(kit_names, new_barcodes, kit_name_and_barcode_tuples_list, diff --git a/microsetta_private_api/repo/tests/test_sample.py b/microsetta_private_api/repo/tests/test_sample.py index e917ed439..44b383d8a 100644 --- a/microsetta_private_api/repo/tests/test_sample.py +++ b/microsetta_private_api/repo/tests/test_sample.py @@ -152,6 +152,7 @@ def test_get_supplied_kit_id_by_sample(self): 1, 1, "UNITTEST", + None, [1] ) From ba0e51e904d556a80506919e3bd3dd14d0e0746d Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 21:07:01 -0300 Subject: [PATCH 07/33] remove admin validate for generate barcodes --- microsetta_private_api/admin/admin_impl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index d8f18981d..7968150a6 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -272,8 +272,7 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def generate_barcodes(body, token_info): - validate_admin_access(token_info) +def generate_barcodes(body): number_of_kits = body['number_of_kits'] number_of_samples = body['number_of_samples'] From c8fef4bcf95e4fdbb3e7ad5886a56d232309ad0a Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 21:21:25 -0300 Subject: [PATCH 08/33] added admin validate --- microsetta_private_api/admin/admin_impl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 7968150a6..4f62067e8 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -272,7 +272,8 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def generate_barcodes(body): +def generate_barcodes(body, token_info): + validate_admin_access(token_info) number_of_kits = body['number_of_kits'] number_of_samples = body['number_of_samples'] @@ -285,8 +286,9 @@ def generate_barcodes(body): return barcode -def insert_barcodes(body): - +def insert_barcodes(body, token_info): + validate_admin_access(token_info) + barcode = body['barcodes'] project_id = [body['project_id']] From 320788676f2dc4394d74c6da9d5b012be8f76fe2 Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 21:28:08 -0300 Subject: [PATCH 09/33] lint --- microsetta_private_api/admin/admin_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 4f62067e8..c966433e9 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -288,7 +288,7 @@ def generate_barcodes(body, token_info): def insert_barcodes(body, token_info): validate_admin_access(token_info) - + barcode = body['barcodes'] project_id = [body['project_id']] From 4fdc89018e4cf2952b9b8cb5fd6fb5b985d1414e Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 21:36:43 -0300 Subject: [PATCH 10/33] remove admin validate --- microsetta_private_api/admin/admin_impl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index c966433e9..d8f18981d 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -286,8 +286,7 @@ def generate_barcodes(body, token_info): return barcode -def insert_barcodes(body, token_info): - validate_admin_access(token_info) +def insert_barcodes(body): barcode = body['barcodes'] project_id = [body['project_id']] From 1ceb9b968ef65a65ffa0e262fbb2a9cd1c52672c Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 21:43:04 -0300 Subject: [PATCH 11/33] add admin val again --- microsetta_private_api/admin/admin_impl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index d8f18981d..c966433e9 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -286,7 +286,8 @@ def generate_barcodes(body, token_info): return barcode -def insert_barcodes(body): +def insert_barcodes(body, token_info): + validate_admin_access(token_info) barcode = body['barcodes'] project_id = [body['project_id']] From 43dc44f7a659e5783f25b4f449f028797c4456a1 Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 25 Mar 2024 21:48:59 -0300 Subject: [PATCH 12/33] test admin validate --- microsetta_private_api/admin/admin_impl.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index c966433e9..7968150a6 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -272,8 +272,7 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def generate_barcodes(body, token_info): - validate_admin_access(token_info) +def generate_barcodes(body): number_of_kits = body['number_of_kits'] number_of_samples = body['number_of_samples'] @@ -286,8 +285,7 @@ def generate_barcodes(body, token_info): return barcode -def insert_barcodes(body, token_info): - validate_admin_access(token_info) +def insert_barcodes(body): barcode = body['barcodes'] project_id = [body['project_id']] From 261d46627b836e47eaf4128bee222c7cce6309a3 Mon Sep 17 00:00:00 2001 From: ayobi Date: Thu, 2 May 2024 16:17:41 -0400 Subject: [PATCH 13/33] added admin validate --- microsetta_private_api/admin/admin_impl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 9126922bd..8dbf1c675 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -272,7 +272,8 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def generate_barcodes(body): +def generate_barcodes(body, token_info): + validate_admin_access(token_info) number_of_kits = body['number_of_kits'] number_of_samples = body['number_of_samples'] @@ -285,7 +286,8 @@ def generate_barcodes(body): return barcode -def insert_barcodes(body): +def insert_barcodes(body, token_info): + validate_admin_access(token_info) barcode = body['barcodes'] project_id = [body['project_id']] From b8466d3c17e0248682214c1365390bf7bfdc5bfc Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 19:31:47 -0400 Subject: [PATCH 14/33] improvements per suggestions --- microsetta_private_api/admin/admin_impl.py | 57 +++++++++++++++---- .../api/microsetta_private_api.yaml | 52 +++++------------ microsetta_private_api/repo/admin_repo.py | 31 +++++----- 3 files changed, 79 insertions(+), 61 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 8dbf1c675..bfe32f11b 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -272,29 +272,64 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def generate_barcodes(body, token_info): - validate_admin_access(token_info) +def handle_barcodes(body): + action = body.get('action') + + if action == 'create': + return generate_barcodes(body) + elif action == 'insert': + return insert_barcodes(body) + else: + raise ValueError("Invalid action specified") - number_of_kits = body['number_of_kits'] - number_of_samples = body['number_of_samples'] + +def generate_barcodes(body): + if 'generate_barcode_single' in body: + if body['generate_barcode_single'] == 'on': + number_of_kits = 1 + number_of_samples = 1 + else: + number_of_kits = len(body['kit_ids']) + number_of_samples = 1 with Transaction() as t: admin_repo = AdminRepo(t) - barcode = admin_repo._generate_novel_barcodes_admin( - number_of_kits, number_of_samples) + barcode = admin_repo._generate_novel_barcodes( + number_of_kits, number_of_samples, kit_names=None) t.commit() return barcode -def insert_barcodes(body, token_info): - validate_admin_access(token_info) +def insert_barcodes(body): + kit_ids = body['kit_ids'] + barcodes = body['barcodes'] + + # Ensure kit_ids is a list, even if it's a single value + if isinstance(kit_ids, str): + kit_ids = [kit_ids] + + # Check if the lengths match + if len(kit_ids) != len(barcodes): + raise ValueError("The number of kit IDs " + "must match the number of barcodes") - barcode = body['barcodes'] - project_id = [body['project_id']] + # Create list of tuples + kit_name_and_barcode_tuples_list = list(zip(kit_ids, barcodes)) + project_ids = [] with Transaction() as t: admin_repo = AdminRepo(t) - admin_repo._insert_barcodes_to_existing_kit(barcode, project_id) + for kit_id in kit_ids: + diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) + if diag['sample_diagnostic_info']: + sample_info = diag['sample_diagnostic_info'][0] + if sample_info['projects_info']: + project_id = sample_info['projects_info'][0]['project_id'] + project_ids.append(str(project_id)) + + admin_repo. \ + _insert_barcodes_to_existing_kit(kit_name_and_barcode_tuples_list, + project_ids) t.commit() return '', 204 diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index 949aa99cd..8c65c27ee 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2781,64 +2781,42 @@ paths: '422': $ref: '#/components/responses/422UnprocessableEntity' - '/admin/create/barcodes': + '/admin/barcodes': post: - operationId: microsetta_private_api.admin.admin_impl.generate_barcodes + operationId: microsetta_private_api.admin.admin_impl.handle_barcodes tags: - Admin - summary: Create barcodes - description: Create barcodes for an exisiting kit - requestBody: - content: - application/json: - schema: - type: "object" - properties: - number_of_kits: - type: integer - number_of_samples: - type: integer - required: - - number_of_kits - - number_of_samples - responses: - '201': - description: Barcodes were successfully created - content: - application/json: - schema: - type: array - - '/admin/insert_barcodes': - post: - operationId: microsetta_private_api.admin.admin_impl.insert_barcodes - tags: - - Admin - summary: Insert barcodes into the database - description: Insert barcodes into the database + summary: Create or insert barcodes + description: Create barcodes for an existing kit or insert barcodes into the database requestBody: content: application/json: schema: type: "object" properties: + action: + type: string + enum: [create, insert] + description: Specify 'create' to generate new barcodes or 'insert' to insert provided barcodes barcodes: type: array items: - type: array + type: string + description: Barcodes to insert (required if action is 'insert') kit_id: type: string - project_id: - type: integer + description: Kit ID to associate the barcodes with (required if action is 'insert') + required: + - action responses: '201': - description: Barcodes were successfully inserted + description: Barcodes were successfully created or inserted content: application/json: schema: type: array '500': - description: Duplicate barcodes found + description: Duplicate barcodes found (only relevant for insert action) '/admin/events/accounts/{account_id}': get: diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index 51015edfa..2726a6c80 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -375,8 +375,10 @@ def _rows_to_dicts_list(rows): # 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} + SELECT project_id, {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) @@ -849,15 +851,17 @@ def _generate_novel_barcodes(self, number_of_kits, number_of_samples, start_bc = cur.fetchone()[0] + 1 new_barcodes = ['X%0.8d' % (start_bc + i) for i in range(total_barcodes)] - - kit_name_and_barcode_tuples_list = [] - barcode_offset = range(0, total_barcodes, number_of_samples) - for offset, name in zip(barcode_offset, kit_names): - for i in range(number_of_samples): - kit_name_and_barcode_tuples_list.append( - (name, new_barcodes[offset + i])) - - return kit_name_and_barcode_tuples_list, new_barcodes + if kit_names: + kit_name_and_barcode_tuples_list = [] + barcode_offset = range(0, total_barcodes, number_of_samples) + for offset, name in zip(barcode_offset, kit_names): + for i in range(number_of_samples): + kit_name_and_barcode_tuples_list.append( + (name, new_barcodes[offset + i])) + + return kit_name_and_barcode_tuples_list, new_barcodes + else: + return new_barcodes def _generate_novel_barcodes_admin(self, number_of_kits, @@ -909,9 +913,10 @@ def _insert_barcodes_to_existing_kit(self, # create barcode project associations barcode_projects = [] - for barcode in [b for _, b in kit_name_and_barcode_tuples_list]: + for barcode in {b for _, b in kit_name_and_barcode_tuples_list}: for prj_id in project_ids: - barcode_projects.append((barcode, prj_id)) + if (barcode, prj_id) not in barcode_projects: + barcode_projects.append((barcode, prj_id)) cur.executemany("INSERT INTO project_barcode " "(barcode, project_id) " "VALUES (%s, %s)", barcode_projects) From 190b99f522fc759df47483bfa6871bed970fc50f Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 19:44:36 -0400 Subject: [PATCH 15/33] update tests --- .../admin/tests/test_admin_repo.py | 12 ++++++------ microsetta_private_api/repo/admin_repo.py | 18 ------------------ 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 3390f4b3a..3b1b1fc3c 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1392,8 +1392,8 @@ def test_generate_novel_barcodes_admin_success(self): with Transaction() as t: admin_repo = AdminRepo(t) - new_barcodes = admin_repo._generate_novel_barcodes_admin( - number_of_kits, number_of_samples) + new_barcodes = admin_repo._generate_novel_barcodes( + number_of_kits, number_of_samples, kit_names=None) self.assertEqual(len(new_barcodes), number_of_kits * number_of_samples) self.assertTrue(all(barcode.startswith('X') @@ -1405,8 +1405,8 @@ def test_generate_novel_barcodes_admin_failure(self): with Transaction() as t: admin_repo = AdminRepo(t) - new_barcodes = admin_repo._generate_novel_barcodes_admin( - number_of_kits, number_of_samples) + new_barcodes = admin_repo._generate_novel_barcodes( + number_of_kits, number_of_samples, kit_names=None) self.assertTrue(new_barcodes == []) def test_insert_barcodes_admin_success(self): @@ -1415,8 +1415,8 @@ def test_insert_barcodes_admin_success(self): with Transaction() as t: admin_repo = AdminRepo(t) - new_barcode = admin_repo._generate_novel_barcodes_admin( - number_of_kits, number_of_samples) + new_barcode = admin_repo._generate_novel_barcodes( + number_of_kits, number_of_samples, kit_names=None) kit_barcode = [['test', new_barcode[0]]] project_ids = '1' diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index 2726a6c80..ce9f0c149 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -863,24 +863,6 @@ def _generate_novel_barcodes(self, number_of_kits, number_of_samples, else: return new_barcodes - def _generate_novel_barcodes_admin(self, - number_of_kits, - number_of_samples): - """Generate specified number of random barcodes for admin""" - - total_barcodes = number_of_kits * number_of_samples - - with self._transaction.cursor() as cur: - cur.execute( - "SELECT max(right(barcode,8)::integer) " - "FROM barcodes.barcode " - "WHERE barcode LIKE 'X%' OR barcode LIKE '0%'" - ) - start_bc = cur.fetchone()[0] + 1 - new_barcodes = ['X%0.8d' % (start_bc + i) - for i in range(total_barcodes)] - return new_barcodes - def _insert_barcodes_to_existing_kit(self, kit_name_and_barcode_tuples_list, project_ids): From 461b98d96ed7b9b157d77e0b2893fafb98b4c2e4 Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 20:11:52 -0400 Subject: [PATCH 16/33] test --- microsetta_private_api/admin/tests/test_admin_repo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 3b1b1fc3c..83f6ce2f9 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1433,7 +1433,11 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() + print("OBS", obs) + print("Barcode gen", new_barcode) self.assertEqual(obs[0][0], new_barcode[0]) + self.assertEqual(obs[0][1], new_barcode[0]) + def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails From 0cef118a0c72e1242360201ccfdc778ae808578b Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 20:21:22 -0400 Subject: [PATCH 17/33] testv2 --- microsetta_private_api/admin/tests/test_admin_repo.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 83f6ce2f9..dba90e431 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1433,11 +1433,9 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - print("OBS", obs) - print("Barcode gen", new_barcode) + print("OBS", obs[0][0]) + print("Barcode gen", new_barcode[0]) self.assertEqual(obs[0][0], new_barcode[0]) - self.assertEqual(obs[0][1], new_barcode[0]) - def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails From 1f9ac091889a6aa52666935e03bf83ae69c9a375 Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 20:31:25 -0400 Subject: [PATCH 18/33] testv3 --- microsetta_private_api/admin/tests/test_admin_repo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index dba90e431..97eded59f 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1433,7 +1433,8 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - print("OBS", obs[0][0]) + print("OBS", obs, obs[0][0]) + print("OBS indices", obs[1][0], obs[2][0], obs[3][0]) print("Barcode gen", new_barcode[0]) self.assertEqual(obs[0][0], new_barcode[0]) From 686b3354395048facb60e39492b7872e35e55f37 Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 20:41:24 -0400 Subject: [PATCH 19/33] testv4 --- microsetta_private_api/admin/tests/test_admin_repo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 97eded59f..7211cc8ad 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1433,10 +1433,10 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - print("OBS", obs, obs[0][0]) - print("OBS indices", obs[1][0], obs[2][0], obs[3][0]) + print("OBS", obs) + print("OBS indices", obs[0][0], obs[1][0], obs[2][0], obs[3][0]) print("Barcode gen", new_barcode[0]) - self.assertEqual(obs[0][0], new_barcode[0]) + self.assertEqual(obs[4][0], new_barcode[0]) def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails From d1f52a50409fd1c3acfab6a658017ade8d4cbe3c Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 21 Jun 2024 20:47:37 -0400 Subject: [PATCH 20/33] fixed test, flake --- microsetta_private_api/admin/tests/test_admin_repo.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 7211cc8ad..37efab159 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1433,9 +1433,6 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - print("OBS", obs) - print("OBS indices", obs[0][0], obs[1][0], obs[2][0], obs[3][0]) - print("Barcode gen", new_barcode[0]) self.assertEqual(obs[4][0], new_barcode[0]) def test_insert_barcodes_admin_fail_nonexisting_kit(self): From f5cfdd22e35ee3d7da95fbe54b17a52b06067ee5 Mon Sep 17 00:00:00 2001 From: ayobi Date: Tue, 6 Aug 2024 00:49:06 -0400 Subject: [PATCH 21/33] changes per suggestions --- microsetta_private_api/admin/admin_impl.py | 48 +++++++++++++------ .../admin/tests/test_admin_repo.py | 40 ++++++++++++---- .../api/microsetta_private_api.yaml | 2 +- microsetta_private_api/repo/admin_repo.py | 27 ++++++----- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 7aecf686e..08f84a7db 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -282,35 +282,49 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def handle_barcodes(body): +def handle_barcodes(body, token_info): + validate_admin_access(token_info) + action = body.get('action') if action == 'create': - return generate_barcodes(body) + return generate_barcodes(body, token_info) elif action == 'insert': - return insert_barcodes(body) + return insert_barcodes(body, token_info) else: raise ValueError("Invalid action specified") -def generate_barcodes(body): +def generate_barcodes(body, token_info): + validate_admin_access(token_info) + if 'generate_barcode_single' in body: if body['generate_barcode_single'] == 'on': number_of_kits = 1 number_of_samples = 1 + elif 'generate_barcodes' in body: + number_of_kits = (body['num_kits']) + number_of_samples = (body['num_samples']) else: number_of_kits = len(body['kit_ids']) number_of_samples = 1 with Transaction() as t: admin_repo = AdminRepo(t) + + kit_names = admin_repo._generate_novel_kit_names( + number_of_kits, kit_prefix=None) + barcode = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names=None) + number_of_kits, number_of_samples, kit_names) + t.commit() - return barcode + return barcode[1] -def insert_barcodes(body): +def insert_barcodes(body, token_info): + validate_admin_access(token_info) + kit_ids = body['kit_ids'] barcodes = body['barcodes'] @@ -329,18 +343,24 @@ def insert_barcodes(body): project_ids = [] with Transaction() as t: admin_repo = AdminRepo(t) - for kit_id in kit_ids: - diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) - if diag['sample_diagnostic_info']: + try: + for kit_id in kit_ids: + diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) + if not diag or 'sample_diagnostic_info' not in diag: + raise ValueError(f"Invalid Kit ID: {kit_id}") sample_info = diag['sample_diagnostic_info'][0] if sample_info['projects_info']: project_id = sample_info['projects_info'][0]['project_id'] project_ids.append(str(project_id)) - admin_repo. \ - _insert_barcodes_to_existing_kit(kit_name_and_barcode_tuples_list, - project_ids) - t.commit() + admin_repo._insert_barcodes_to_existing_kit( + kit_name_and_barcode_tuples_list, project_ids) + t.commit() + except ValueError as e: + return {"error": str(e)}, 400 + except Exception as e: + return {"error": str(e)}, 500 + return '', 204 diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 9fef80271..507e93b3a 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1,3 +1,4 @@ +from collections import Counter from unittest import TestCase from datetime import date, datetime, timedelta, timezone import dateutil.parser @@ -1495,12 +1496,17 @@ def test_generate_novel_barcodes_admin_success(self): with Transaction() as t: admin_repo = AdminRepo(t) + + kit_names = admin_repo._generate_novel_kit_names( + number_of_kits, kit_prefix=None) + new_barcodes = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names=None) - self.assertEqual(len(new_barcodes), + number_of_kits, number_of_samples, kit_names) + + self.assertEqual(len(new_barcodes[1]), number_of_kits * number_of_samples) - self.assertTrue(all(barcode.startswith('X') - for barcode in new_barcodes)) + self.assertTrue(all(barcodes.startswith('X') + for barcodes in new_barcodes[1])) def test_generate_novel_barcodes_admin_failure(self): number_of_kits = 0 @@ -1508,9 +1514,14 @@ def test_generate_novel_barcodes_admin_failure(self): with Transaction() as t: admin_repo = AdminRepo(t) + + kit_names = admin_repo._generate_novel_kit_names( + number_of_kits, kit_prefix=None) + new_barcodes = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names=None) - self.assertTrue(new_barcodes == []) + number_of_kits, number_of_samples, kit_names) + + self.assertTrue(new_barcodes[1] == [], []) def test_insert_barcodes_admin_success(self): number_of_kits = 1 @@ -1518,16 +1529,23 @@ def test_insert_barcodes_admin_success(self): with Transaction() as t: admin_repo = AdminRepo(t) + + kit_names = admin_repo._generate_novel_kit_names( + number_of_kits, kit_prefix=None) + new_barcode = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names=None) + number_of_kits, number_of_samples, kit_names) + + new_barcode[1].insert(0, 'test') + kit_name_and_barcode_tuple = (new_barcode[1][0], new_barcode[1][1]) - kit_barcode = [['test', new_barcode[0]]] + kit_name_and_barcode_tuples_list = [kit_name_and_barcode_tuple] project_ids = '1' with Transaction() as t: admin_repo = AdminRepo(t) admin_repo._insert_barcodes_to_existing_kit( - kit_barcode, project_ids) + kit_name_and_barcode_tuples_list, project_ids) with t.cursor() as cur: cur.execute( "SELECT barcode " @@ -1536,7 +1554,9 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - self.assertEqual(obs[4][0], new_barcode[0]) + obs_first_element = obs[0][0] + new_barcode_second_element = new_barcode[0][0][1] + self.assertEqual(obs_first_element, new_barcode_second_element) def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index dd8853692..48c2de6e1 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2805,7 +2805,7 @@ paths: '422': $ref: '#/components/responses/422UnprocessableEntity' - '/admin/barcodes': + '/admin/add_barcodes': post: operationId: microsetta_private_api.admin.admin_impl.handle_barcodes tags: diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index 5a8207f81..2d2bbf2f1 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -870,7 +870,7 @@ def _generate_novel_kit_names(self, number_of_kits, kit_prefix): def _generate_novel_barcodes(self, number_of_kits, number_of_samples, kit_names): """Generate specified number of random barcodes for input kit names""" - + print("kit name", kit_names) total_barcodes = number_of_kits * number_of_samples with self._transaction.cursor() as cur: @@ -892,17 +892,15 @@ def _generate_novel_barcodes(self, number_of_kits, number_of_samples, start_bc = cur.fetchone()[0] + 1 new_barcodes = ['X%0.8d' % (start_bc + i) for i in range(total_barcodes)] - if kit_names: - kit_name_and_barcode_tuples_list = [] - barcode_offset = range(0, total_barcodes, number_of_samples) - for offset, name in zip(barcode_offset, kit_names): - for i in range(number_of_samples): - kit_name_and_barcode_tuples_list.append( - (name, new_barcodes[offset + i])) - - return kit_name_and_barcode_tuples_list, new_barcodes - else: - return new_barcodes + + kit_name_and_barcode_tuples_list = [] + barcode_offset = range(0, total_barcodes, number_of_samples) + for offset, name in zip(barcode_offset, kit_names): + for i in range(number_of_samples): + kit_name_and_barcode_tuples_list.append( + (name, new_barcodes[offset + i])) + + return kit_name_and_barcode_tuples_list, new_barcodes def _insert_barcodes_to_existing_kit(self, kit_name_and_barcode_tuples_list, @@ -915,6 +913,7 @@ def _insert_barcodes_to_existing_kit(self, project_ids : list of int Project ids that all barcodes are to be associated with """ + print("Insert", kit_name_and_barcode_tuples_list) # check for empty input if kit_name_and_barcode_tuples_list \ is None or len(kit_name_and_barcode_tuples_list) == 0: @@ -924,12 +923,16 @@ def _insert_barcodes_to_existing_kit(self, # integer project ids come in as strings ... project_ids = [int(x) for x in project_ids] + if len(set(project_ids)) > 1: + raise ValueError("All project_ids must be identical") + is_tmi = self._are_any_projects_tmi(project_ids) with self._transaction.cursor() as cur: # add new barcodes to barcode table barcode_insertions = [(n, b, 'unassigned') for n, b in kit_name_and_barcode_tuples_list] + print("Barcode insertions: ", barcode_insertions) cur.executemany("INSERT INTO barcode (kit_id, barcode, status) " "VALUES (%s, %s, %s)", barcode_insertions) From 783abecd2cc74c8c10f810b566f1f7b806105f59 Mon Sep 17 00:00:00 2001 From: ayobi Date: Tue, 6 Aug 2024 12:10:49 -0400 Subject: [PATCH 22/33] lint --- .../admin/tests/test_admin_repo.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 507e93b3a..41aefc682 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -1,4 +1,3 @@ -from collections import Counter from unittest import TestCase from datetime import date, datetime, timedelta, timezone import dateutil.parser @@ -1502,7 +1501,7 @@ def test_generate_novel_barcodes_admin_success(self): new_barcodes = admin_repo._generate_novel_barcodes( number_of_kits, number_of_samples, kit_names) - + self.assertEqual(len(new_barcodes[1]), number_of_kits * number_of_samples) self.assertTrue(all(barcodes.startswith('X') @@ -1520,7 +1519,7 @@ def test_generate_novel_barcodes_admin_failure(self): new_barcodes = admin_repo._generate_novel_barcodes( number_of_kits, number_of_samples, kit_names) - + self.assertTrue(new_barcodes[1] == [], []) def test_insert_barcodes_admin_success(self): @@ -1535,11 +1534,12 @@ def test_insert_barcodes_admin_success(self): new_barcode = admin_repo._generate_novel_barcodes( number_of_kits, number_of_samples, kit_names) - + new_barcode[1].insert(0, 'test') kit_name_and_barcode_tuple = (new_barcode[1][0], new_barcode[1][1]) - kit_name_and_barcode_tuples_list = [kit_name_and_barcode_tuple] + kit_name_and_barcode_tuples_list = [ + kit_name_and_barcode_tuple] project_ids = '1' with Transaction() as t: @@ -1554,7 +1554,7 @@ def test_insert_barcodes_admin_success(self): ('test',) ) obs = cur.fetchall() - obs_first_element = obs[0][0] + obs_first_element = obs[4][0] new_barcode_second_element = new_barcode[0][0][1] self.assertEqual(obs_first_element, new_barcode_second_element) From 224e5fe7b64c773fbfab8f55ab2b425f6d5aeb2c Mon Sep 17 00:00:00 2001 From: ayobi Date: Tue, 6 Aug 2024 12:19:17 -0400 Subject: [PATCH 23/33] removed debug statements --- microsetta_private_api/repo/admin_repo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index 2d2bbf2f1..887a52bb8 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -870,7 +870,7 @@ def _generate_novel_kit_names(self, number_of_kits, kit_prefix): def _generate_novel_barcodes(self, number_of_kits, number_of_samples, kit_names): """Generate specified number of random barcodes for input kit names""" - print("kit name", kit_names) + total_barcodes = number_of_kits * number_of_samples with self._transaction.cursor() as cur: @@ -913,7 +913,7 @@ def _insert_barcodes_to_existing_kit(self, project_ids : list of int Project ids that all barcodes are to be associated with """ - print("Insert", kit_name_and_barcode_tuples_list) + # check for empty input if kit_name_and_barcode_tuples_list \ is None or len(kit_name_and_barcode_tuples_list) == 0: @@ -932,7 +932,7 @@ def _insert_barcodes_to_existing_kit(self, # add new barcodes to barcode table barcode_insertions = [(n, b, 'unassigned') for n, b in kit_name_and_barcode_tuples_list] - print("Barcode insertions: ", barcode_insertions) + cur.executemany("INSERT INTO barcode (kit_id, barcode, status) " "VALUES (%s, %s, %s)", barcode_insertions) From eacb78c22cd72f3e8339aec0b63450d15199b832 Mon Sep 17 00:00:00 2001 From: ayobi Date: Wed, 7 Aug 2024 20:13:02 -0400 Subject: [PATCH 24/33] remove generate_barcodes --- microsetta_private_api/admin/admin_impl.py | 54 +++++++++++----------- microsetta_private_api/repo/admin_repo.py | 3 -- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 08f84a7db..cd39c4a22 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -285,41 +285,43 @@ def create_kits(body, token_info): def handle_barcodes(body, token_info): validate_admin_access(token_info) - action = body.get('action') + kit_ids = body['kit_ids'] - if action == 'create': - return generate_barcodes(body, token_info) - elif action == 'insert': - return insert_barcodes(body, token_info) - else: - raise ValueError("Invalid action specified") + if isinstance(kit_ids, str): + kit_ids = [kit_ids] + for kit_id in kit_ids: + response, code = search_kit_id(token_info, kit_id) + if code == 404: + raise ValueError("Invalid Kit ID") -def generate_barcodes(body, token_info): - validate_admin_access(token_info) + action = body.get('action') - if 'generate_barcode_single' in body: - if body['generate_barcode_single'] == 'on': - number_of_kits = 1 + if action == 'create': + if 'generate_barcode_single' in body: + if body['generate_barcode_single'] == 'on': + number_of_kits = 1 + number_of_samples = 1 + elif 'generate_barcodes' in body: + number_of_kits = (body['num_kits']) + number_of_samples = (body['num_samples']) + else: + number_of_kits = len(body['kit_ids']) number_of_samples = 1 - elif 'generate_barcodes' in body: - number_of_kits = (body['num_kits']) - number_of_samples = (body['num_samples']) - else: - number_of_kits = len(body['kit_ids']) - number_of_samples = 1 - with Transaction() as t: - admin_repo = AdminRepo(t) + with Transaction() as t: + admin_repo = AdminRepo(t) - kit_names = admin_repo._generate_novel_kit_names( - number_of_kits, kit_prefix=None) + barcode = admin_repo._generate_novel_barcodes( + number_of_kits, number_of_samples, kit_names=kit_ids) - barcode = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names) + return barcode[1] - t.commit() - return barcode[1] + elif action == 'insert': + return insert_barcodes(body, token_info) + + else: + raise ValueError("Invalid action specified") def insert_barcodes(body, token_info): diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index 887a52bb8..cd25c799f 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -923,9 +923,6 @@ def _insert_barcodes_to_existing_kit(self, # integer project ids come in as strings ... project_ids = [int(x) for x in project_ids] - if len(set(project_ids)) > 1: - raise ValueError("All project_ids must be identical") - is_tmi = self._are_any_projects_tmi(project_ids) with self._transaction.cursor() as cur: From 8dcf26879da22f861af92060b16a8761215801e4 Mon Sep 17 00:00:00 2001 From: ayobi Date: Thu, 15 Aug 2024 00:36:35 -0400 Subject: [PATCH 25/33] fixes and improvements --- microsetta_private_api/admin/admin_impl.py | 62 +++---- .../admin/tests/test_admin_repo.py | 96 +++++------ microsetta_private_api/repo/admin_repo.py | 153 ++++++++++++------ .../repo/tests/test_sample.py | 1 + 4 files changed, 188 insertions(+), 124 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index cd39c4a22..e75ebda7a 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -60,6 +60,17 @@ def search_kit_id(token_info, kit_id): return jsonify(diag), 200 +def kit_id_exists(token_info, kit_id): + validate_admin_access(token_info) + + with Transaction() as t: + admin_repo = AdminRepo(t) + diag = admin_repo.check_kit_id_exists(kit_id) + if diag is None: + return jsonify(code=404, message="Kit ID not found"), 404 + return jsonify(diag), 200 + + def search_email(token_info, email): validate_admin_access(token_info) @@ -264,6 +275,8 @@ def create_kits(body, token_info): kit_prefix = body.get('kit_id_prefix', None) project_ids = body['project_ids'] user_barcodes = body.get('user_barcodes', []) + remaining_barcodes_to_generate = body.get( + 'remaining_samples_to_generate', []) with Transaction() as t: admin_repo = AdminRepo(t) @@ -273,6 +286,7 @@ def create_kits(body, token_info): number_of_samples, kit_prefix, user_barcodes, + remaining_barcodes_to_generate, project_ids) except KeyError: return jsonify(code=422, message="Unable to create kits"), 422 @@ -291,9 +305,9 @@ def handle_barcodes(body, token_info): kit_ids = [kit_ids] for kit_id in kit_ids: - response, code = search_kit_id(token_info, kit_id) + response, code = kit_id_exists(token_info, kit_id) if code == 404: - raise ValueError("Invalid Kit ID") + return jsonify(code=404, message='Kit ID not found'), 404 action = body.get('action') @@ -315,13 +329,15 @@ def handle_barcodes(body, token_info): barcode = admin_repo._generate_novel_barcodes( number_of_kits, number_of_samples, kit_names=kit_ids) - return barcode[1] + t.commit() + + return jsonify(barcode[1]) elif action == 'insert': return insert_barcodes(body, token_info) else: - raise ValueError("Invalid action specified") + return jsonify(code=404, message='Invalid action'), 404 def insert_barcodes(body, token_info): @@ -336,32 +352,24 @@ def insert_barcodes(body, token_info): # Check if the lengths match if len(kit_ids) != len(barcodes): - raise ValueError("The number of kit IDs " - "must match the number of barcodes") - - # Create list of tuples - kit_name_and_barcode_tuples_list = list(zip(kit_ids, barcodes)) + raise ValueError("The number of kit IDs must " + "match the number of barcodes") - project_ids = [] with Transaction() as t: admin_repo = AdminRepo(t) - try: - for kit_id in kit_ids: - diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) - if not diag or 'sample_diagnostic_info' not in diag: - raise ValueError(f"Invalid Kit ID: {kit_id}") - sample_info = diag['sample_diagnostic_info'][0] - if sample_info['projects_info']: - project_id = sample_info['projects_info'][0]['project_id'] - project_ids.append(str(project_id)) - - admin_repo._insert_barcodes_to_existing_kit( - kit_name_and_barcode_tuples_list, project_ids) - t.commit() - except ValueError as e: - return {"error": str(e)}, 400 - except Exception as e: - return {"error": str(e)}, 500 + + insertion_data = [] + for kit_id, barcode in zip(kit_ids, barcodes): + diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) + sample_info = diag['sample_diagnostic_info'][0] + if sample_info['projects_info']: + for project in sample_info['projects_info']: + project_id = str(project['project_id']) + insertion_data.append((kit_id, barcode, project_id)) + + admin_repo._insert_barcodes_to_existing_kit(insertion_data) + + t.commit() return '', 204 diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 41aefc682..58808c080 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -352,7 +352,6 @@ def make_tz_datetime(y, m, d): self.assertGreater(len(diag['projects_info']), 0) self.assertEqual(len(diag['scans_info']), 2) # order matters in the returned vals, so test that - print(diag['scans_info'][0], first_scan) self.assertEqual(diag['scans_info'][0], first_scan) self.assertEqual(diag['scans_info'][1], second_scan) self.assertEqual(diag['latest_scan'], second_scan) @@ -454,7 +453,12 @@ def test_get_project_barcodes_by_id(self): output_id = admin_repo.create_project(input) # create some fake kits - created = admin_repo.create_kits(2, 3, 'foo', None, [output_id, ]) + created = admin_repo.create_kits(2, + 3, + 'foo', + None, + 0, + [output_id, ]) exp = [] for kit in created['created']: @@ -610,13 +614,14 @@ def test_create_kits_fail_nonexistent_project(self): 3, '', None, + 0, [10000, SurveyTemplateRepo.VIOSCREEN_ID]) def test_create_kits_success_not_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - non_tmi = admin_repo.create_kits(5, 3, '', None, + non_tmi = admin_repo.create_kits(5, 3, '', None, 0, [33]) self.assertEqual(['created', ], list(non_tmi.keys())) self.assertEqual(len(non_tmi['created']), 5) @@ -644,7 +649,7 @@ def test_create_kits_success_not_microsetta(self): def test_create_kits_success_is_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - tmi = admin_repo.create_kits(4, 2, 'foo', None, + tmi = admin_repo.create_kits(4, 2, 'foo', None, 0, [1]) self.assertEqual(['created', ], list(tmi.keys())) self.assertEqual(len(tmi['created']), 4) @@ -1523,72 +1528,58 @@ def test_generate_novel_barcodes_admin_failure(self): self.assertTrue(new_barcodes[1] == [], []) def test_insert_barcodes_admin_success(self): - number_of_kits = 1 - number_of_samples = 1 - - with Transaction() as t: - admin_repo = AdminRepo(t) - - kit_names = admin_repo._generate_novel_kit_names( - number_of_kits, kit_prefix=None) - - new_barcode = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names) - - new_barcode[1].insert(0, 'test') - kit_name_and_barcode_tuple = (new_barcode[1][0], new_barcode[1][1]) - - kit_name_and_barcode_tuples_list = [ - kit_name_and_barcode_tuple] - project_ids = '1' + kit_name = 'test' + barcode = 'X00332312' + project_id = '1' + kit_name_barcode_prj_id_tuple = [(kit_name, barcode, project_id)] with Transaction() as t: admin_repo = AdminRepo(t) admin_repo._insert_barcodes_to_existing_kit( - kit_name_and_barcode_tuples_list, project_ids) + kit_name_barcode_prj_id_tuple + ) + with t.cursor() as cur: cur.execute( "SELECT barcode " "FROM barcodes.barcode " - "WHERE kit_id = %s", - ('test',) + "WHERE kit_id = %s AND barcode = %s", + (kit_name, barcode) ) - obs = cur.fetchall() - obs_first_element = obs[4][0] - new_barcode_second_element = new_barcode[0][0][1] - self.assertEqual(obs_first_element, new_barcode_second_element) + obs = cur.fetchone() + self.assertIsNotNone(obs, + "Expected barcode not found in database") def test_insert_barcodes_admin_fail_nonexisting_kit(self): # test that inserting barcodes to a non-existent kit fails - kit_barcode = [['test1123', 'X00332312']] - project_ids = '1' + kit_name_barcode_prj_id_tuple = [['test1123', 'X00332312', '1']] with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.ForeignKeyViolation): admin_repo._insert_barcodes_to_existing_kit( - kit_barcode, project_ids) + kit_name_barcode_prj_id_tuple) def test_insert_barcodes_admin_fail_dup_barcodes(self): # test that inserting duplicate barcode fails - kit_barcode = [['test', '000000001']] - project_ids = '1' + kit_name_barcode_prj_id_tuple = [['test', '000000001', '1']] with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.UniqueViolation): admin_repo._insert_barcodes_to_existing_kit( - kit_barcode, project_ids) + kit_name_barcode_prj_id_tuple) def test_user_barcode_create_kit_success(self): with Transaction() as t: admin_repo = AdminRepo(t) user_barcode = 'X99887769' - admin_repo.create_kits(number_of_kits=1, - number_of_samples=1, - kit_prefix='', - user_barcodes=[user_barcode], - project_ids=[1]) + admin_repo.create_kits(1, + 1, + '', + [user_barcode], + 0, + [1]) with t.cursor() as cur: cur.execute( "SELECT barcode " @@ -1604,21 +1595,22 @@ def test_user_barcode_dup_create_kit_fail(self): admin_repo = AdminRepo(t) user_barcode = '000000001' with self.assertRaises(psycopg2.errors.UniqueViolation): - admin_repo.create_kits(number_of_kits=1, - number_of_samples=1, - kit_prefix='', - user_barcodes=[user_barcode], - project_ids=[1]) + admin_repo.create_kits(1, + 1, + '', + [user_barcode], + 0, + [1]) def test_user_barcodes_num_samples_mismatch_create_kit_fail(self): # When creating a kit, if a user selects just one sample, but # provides two user barcodes, the transaction should fail with Transaction() as t: admin_repo = AdminRepo(t) - with self.assertRaises(psycopg2.errors.ForeignKeyViolation): - admin_repo.create_kits(number_of_kits=1, - number_of_samples=1, - kit_prefix='', - user_barcodes=['X92384885', - 'X92384886'], - project_ids=[1]) + with self.assertRaises(ValueError): + admin_repo.create_kits(1, + 1, + '', + ['X92384885', 'X92384886'], + 0, + [1]) diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index cd25c799f..ad2de86f9 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -790,7 +790,8 @@ def _generate_random_kit_name(self, name_length, prefix): return prefix + '_' + rand_name def create_kits(self, number_of_kits, number_of_samples, kit_prefix, - user_barcodes, project_ids): + user_barcodes, remaining_barcodes_to_generate, + project_ids): """Create multiple kits, each with the same number of samples Parameters @@ -801,23 +802,71 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, Number of samples that each kit will contain kit_prefix : str or None A prefix to put on to the kit IDs, this is optional. + user_barcodes : list of str + User provided barcodes to use for the kits. If None, barcodes will + be generated. + remaining_barcodes_to_generate : int + Remaining number of barcodes to generate. project_ids : list of int Project ids the samples are to be associated with """ kit_names = self._generate_novel_kit_names(number_of_kits, kit_prefix) - if user_barcodes: - new_barcodes = user_barcodes - kit_name_and_barcode_tuples_list = [] - for i in range(number_of_samples * number_of_kits): - kit_name_and_barcode_tuples_list.append( - (kit_names[0], user_barcodes[i])) + total_required_barcodes = number_of_kits * number_of_samples + kit_name_and_barcode_tuples_list = [] + + if user_barcodes and not remaining_barcodes_to_generate: + if len(user_barcodes) == total_required_barcodes: + new_barcodes = user_barcodes + for i in range(number_of_kits): + for j in range(number_of_samples): + kit_name_and_barcode_tuples_list.append( + (kit_names[i], + new_barcodes[i * number_of_samples + j])) + else: + raise ValueError("Number of user provided barcodes does not " + "match the number of required barcodes") + + elif remaining_barcodes_to_generate > 0: + num_user_provided_kits = len(user_barcodes) // number_of_samples + + # Generate the remaining barcodes + kits, new_generated_barcodes = \ + self._generate_novel_barcodes( + 1, + int(remaining_barcodes_to_generate), + kit_names) + + if user_barcodes: + new_barcodes = user_barcodes + new_generated_barcodes + + barcode_index = 0 + + for i in range(number_of_kits): + # Loop through the number of samples for each kit + for j in range(number_of_samples): + # Append the current kit name + # and its corresponding barcode + kit_name_and_barcode_tuples_list.append( + (kit_names[i], new_barcodes[barcode_index])) + barcode_index += 1 + else: + for i in range(number_of_kits - num_user_provided_kits): + for j in range(number_of_samples): + barcode_index = i * number_of_samples + j + kit_name_and_barcode_tuples_list.append( + (kit_names[num_user_provided_kits + i], + new_generated_barcodes[barcode_index])) + + new_barcodes = user_barcodes + new_generated_barcodes else: + # If no user-provided barcodes or + # remaining to generate, generate all barcodes kit_name_and_barcode_tuples_list, new_barcodes = \ - self._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names) - + self._generate_novel_barcodes(number_of_kits, + number_of_samples, + kit_names) return self._create_kits(kit_names, new_barcodes, kit_name_and_barcode_tuples_list, number_of_samples, project_ids) @@ -902,62 +951,63 @@ def _generate_novel_barcodes(self, number_of_kits, number_of_samples, return kit_name_and_barcode_tuples_list, new_barcodes - def _insert_barcodes_to_existing_kit(self, - kit_name_and_barcode_tuples_list, - project_ids): - """Insert barcodes into the database for an exisiting kit + def _insert_barcodes_to_existing_kit(self, kit_barcode_project_tuples): + """Insert barcodes into the database for an existing kit + Parameters ---------- - kit_name_and_barcode_tuples_list: list of tuple of str - Kit name and associated barcode (one tuple per barcode) - project_ids : list of int - Project ids that all barcodes are to be associated with + kit_barcode_project_tuples: list of tuple + Each tuple contains (kit_id, barcode, project_id) """ # check for empty input - if kit_name_and_barcode_tuples_list \ - is None or len(kit_name_and_barcode_tuples_list) == 0: - raise ValueError("kit_name_and_barcode_tuples_list " - "cannot be empty") + if not kit_barcode_project_tuples: + raise ValueError("kit_barcode_project_tuples cannot be empty") - # integer project ids come in as strings ... - project_ids = [int(x) for x in project_ids] + # Extract project IDs + project_ids = [int(t[2]) for t in kit_barcode_project_tuples] is_tmi = self._are_any_projects_tmi(project_ids) - with self._transaction.cursor() as cur: - # add new barcodes to barcode table - barcode_insertions = [(n, b, 'unassigned') - for n, b in kit_name_and_barcode_tuples_list] + # Create a list to store unique (kit_id, barcode) pairs + unique_barcode_tuples = [] + seen_barcodes = set() - cur.executemany("INSERT INTO barcode (kit_id, barcode, status) " - "VALUES (%s, %s, %s)", - barcode_insertions) + # Populate the list with unique barcodes + for kit_id, barcode, _ in kit_barcode_project_tuples: + if barcode not in seen_barcodes: + unique_barcode_tuples.append((kit_id, barcode, 'unassigned')) + seen_barcodes.add(barcode) - # create barcode project associations - barcode_projects = [] - for barcode in {b for _, b in kit_name_and_barcode_tuples_list}: - for prj_id in project_ids: - if (barcode, prj_id) not in barcode_projects: - barcode_projects.append((barcode, prj_id)) + with self._transaction.cursor() as cur: + # Insert unique barcodes into the barcode table + if unique_barcode_tuples: + cur.executemany("INSERT INTO barcode (kit_id, barcode, " + "status) VALUES (%s, %s, %s)", + unique_barcode_tuples) + + # Create barcode project associations + barcode_projects = [(barcode, project_id) + for _, barcode, project_id + in kit_barcode_project_tuples] cur.executemany("INSERT INTO project_barcode " "(barcode, project_id) " "VALUES (%s, %s)", barcode_projects) - if is_tmi: - # for each new barcode, add a record to the ag_kit_barcodes - # table associating it to its ag kit, creating a new - # "sample_barcode" - kit_barcodes_insert = [(i, b) - for i, b - in kit_name_and_barcode_tuples_list] + if is_tmi: + # Insert into ag_kit_barcodes table for TMI projects + kit_barcodes_insert = [(kit_id, barcode) + for kit_id, barcode, _ + in unique_barcode_tuples] try: cur.executemany("INSERT INTO ag_kit_barcodes " "(ag_kit_id, barcode) " "SELECT ag_kit_id, %s " "FROM ag_kit " "WHERE supplied_kit_id = %s", - [(b, i) for i, b in kit_barcodes_insert]) + [(barcode, kit_id) + for kit_id, barcode + in kit_barcodes_insert]) except Exception as e: print("Error executing query:", e) @@ -1103,6 +1153,19 @@ def create_kit(self, kit_name, box_id, address_dict, kit_name_and_barcode_tuples_list, len(barcodes_list), project_ids, kit_details) + def check_kit_id_exists(self, supplied_kit_id): + with self._transaction.dict_cursor() as cur: + cur.execute( + "SELECT supplied_kit_id " + "FROM " + "ag.ag_kit " + "WHERE " + "supplied_kit_id = %s", + (supplied_kit_id,)) + row = cur.fetchone() + + return row + def retrieve_diagnostics_by_kit_id(self, supplied_kit_id): kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples(supplied_kit_id) diff --git a/microsetta_private_api/repo/tests/test_sample.py b/microsetta_private_api/repo/tests/test_sample.py index 44b383d8a..94cbbf08d 100644 --- a/microsetta_private_api/repo/tests/test_sample.py +++ b/microsetta_private_api/repo/tests/test_sample.py @@ -153,6 +153,7 @@ def test_get_supplied_kit_id_by_sample(self): 1, "UNITTEST", None, + 0, [1] ) From 4c256bfc52326a608bcabdae82fb7dc2fcb7038c Mon Sep 17 00:00:00 2001 From: ayobi Date: Mon, 19 Aug 2024 23:41:22 -0400 Subject: [PATCH 26/33] fixes --- microsetta_private_api/admin/admin_impl.py | 21 +++++++++++++++------ microsetta_private_api/repo/admin_repo.py | 13 +++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index e75ebda7a..6c19d3f09 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -56,7 +56,7 @@ def search_kit_id(token_info, kit_id): admin_repo = AdminRepo(t) diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) if diag is None: - return jsonify(code=404, message="Kit ID not found"), 404 + return jsonify(code=404, message=f'Kit ID {kit_id} not found'), 404 return jsonify(diag), 200 @@ -67,7 +67,7 @@ def kit_id_exists(token_info, kit_id): admin_repo = AdminRepo(t) diag = admin_repo.check_kit_id_exists(kit_id) if diag is None: - return jsonify(code=404, message="Kit ID not found"), 404 + return jsonify(code=404, message=f'Kit ID {kit_id} not found'), 404 return jsonify(diag), 200 @@ -304,10 +304,12 @@ def handle_barcodes(body, token_info): if isinstance(kit_ids, str): kit_ids = [kit_ids] - for kit_id in kit_ids: - response, code = kit_id_exists(token_info, kit_id) - if code == 404: - return jsonify(code=404, message='Kit ID not found'), 404 + with Transaction() as t: + admin_repo = AdminRepo(t) + for kit_id in kit_ids: + diag = admin_repo.check_kit_id_exists(kit_id) + if diag is None: + return jsonify(f'Kit ID {kit_id} not found'), 404 action = body.get('action') @@ -350,6 +352,13 @@ def insert_barcodes(body, token_info): if isinstance(kit_ids, str): kit_ids = [kit_ids] + with Transaction() as t: + admin_repo = AdminRepo(t) + for barcode in barcodes: + diag = admin_repo.check_barcode_exists(barcode) + if diag is not None: + return jsonify(f'Barcode {barcode} already exists'), 404 + # Check if the lengths match if len(kit_ids) != len(barcodes): raise ValueError("The number of kit IDs must " diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index ad2de86f9..ab9a4814c 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -1166,6 +1166,19 @@ def check_kit_id_exists(self, supplied_kit_id): return row + def check_barcode_exists(self, barcode): + with self._transaction.dict_cursor() as cur: + cur.execute( + "SELECT barcode " + "FROM " + "barcodes.barcode " + "WHERE " + "barcode = %s", + (barcode,)) + row = cur.fetchone() + + return row + def retrieve_diagnostics_by_kit_id(self, supplied_kit_id): kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples(supplied_kit_id) From 7dbe160089a472377dde654d7afdd2c05bf14aca Mon Sep 17 00:00:00 2001 From: ayobi Date: Thu, 22 Aug 2024 21:56:34 -0400 Subject: [PATCH 27/33] fix create kits for sample slots --- .../admin/tests/test_admin_repo.py | 20 +-- microsetta_private_api/repo/admin_repo.py | 153 +++++++++++------- .../repo/tests/test_sample.py | 1 - 3 files changed, 97 insertions(+), 77 deletions(-) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 58808c080..ed483c815 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -457,7 +457,6 @@ def test_get_project_barcodes_by_id(self): 3, 'foo', None, - 0, [output_id, ]) exp = [] @@ -614,15 +613,13 @@ def test_create_kits_fail_nonexistent_project(self): 3, '', None, - 0, [10000, SurveyTemplateRepo.VIOSCREEN_ID]) def test_create_kits_success_not_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - non_tmi = admin_repo.create_kits(5, 3, '', None, 0, - [33]) + non_tmi = admin_repo.create_kits(5, 3, '', None, [33]) self.assertEqual(['created', ], list(non_tmi.keys())) self.assertEqual(len(non_tmi['created']), 5) for obj in non_tmi['created']: @@ -649,8 +646,7 @@ def test_create_kits_success_not_microsetta(self): def test_create_kits_success_is_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - tmi = admin_repo.create_kits(4, 2, 'foo', None, 0, - [1]) + tmi = admin_repo.create_kits(4, 2, 'foo', None, [1]) self.assertEqual(['created', ], list(tmi.keys())) self.assertEqual(len(tmi['created']), 4) for obj in tmi['created']: @@ -1573,33 +1569,30 @@ def test_insert_barcodes_admin_fail_dup_barcodes(self): def test_user_barcode_create_kit_success(self): with Transaction() as t: admin_repo = AdminRepo(t) - user_barcode = 'X99887769' admin_repo.create_kits(1, 1, '', - [user_barcode], - 0, + [['X99887769']], [1]) with t.cursor() as cur: cur.execute( "SELECT barcode " "FROM barcodes.barcode " "WHERE barcode = %s", - (user_barcode,) + ('X99887769',) ) obs = cur.fetchall() - self.assertEqual(obs[0][0], user_barcode) + self.assertEqual(obs[0][0], 'X99887769') def test_user_barcode_dup_create_kit_fail(self): with Transaction() as t: admin_repo = AdminRepo(t) - user_barcode = '000000001' + user_barcode = ['000000001'] with self.assertRaises(psycopg2.errors.UniqueViolation): admin_repo.create_kits(1, 1, '', [user_barcode], - 0, [1]) def test_user_barcodes_num_samples_mismatch_create_kit_fail(self): @@ -1612,5 +1605,4 @@ def test_user_barcodes_num_samples_mismatch_create_kit_fail(self): 1, '', ['X92384885', 'X92384886'], - 0, [1]) diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index ab9a4814c..fbdaed915 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -790,8 +790,7 @@ def _generate_random_kit_name(self, name_length, prefix): return prefix + '_' + rand_name def create_kits(self, number_of_kits, number_of_samples, kit_prefix, - user_barcodes, remaining_barcodes_to_generate, - project_ids): + user_barcodes, project_ids): """Create multiple kits, each with the same number of samples Parameters @@ -805,71 +804,90 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, user_barcodes : list of str User provided barcodes to use for the kits. If None, barcodes will be generated. - remaining_barcodes_to_generate : int - Remaining number of barcodes to generate. project_ids : list of int Project ids the samples are to be associated with """ - kit_names = self._generate_novel_kit_names(number_of_kits, kit_prefix) + kit_names = self._generate_novel_kit_names(number_of_kits, + kit_prefix) + + all_barcodes_per_slot = [] + generated_barcodes = [] total_required_barcodes = number_of_kits * number_of_samples - kit_name_and_barcode_tuples_list = [] - if user_barcodes and not remaining_barcodes_to_generate: - if len(user_barcodes) == total_required_barcodes: - new_barcodes = user_barcodes - for i in range(number_of_kits): - for j in range(number_of_samples): - kit_name_and_barcode_tuples_list.append( - (kit_names[i], - new_barcodes[i * number_of_samples + j])) - else: - raise ValueError("Number of user provided barcodes does not " - "match the number of required barcodes") - - elif remaining_barcodes_to_generate > 0: - num_user_provided_kits = len(user_barcodes) // number_of_samples - - # Generate the remaining barcodes - kits, new_generated_barcodes = \ - self._generate_novel_barcodes( - 1, - int(remaining_barcodes_to_generate), - kit_names) - - if user_barcodes: - new_barcodes = user_barcodes + new_generated_barcodes - - barcode_index = 0 - - for i in range(number_of_kits): - # Loop through the number of samples for each kit - for j in range(number_of_samples): - # Append the current kit name - # and its corresponding barcode - kit_name_and_barcode_tuples_list.append( - (kit_names[i], new_barcodes[barcode_index])) - barcode_index += 1 - else: - for i in range(number_of_kits - num_user_provided_kits): - for j in range(number_of_samples): - barcode_index = i * number_of_samples + j - kit_name_and_barcode_tuples_list.append( - (kit_names[num_user_provided_kits + i], - new_generated_barcodes[barcode_index])) - - new_barcodes = user_barcodes + new_generated_barcodes + if user_barcodes: + if not isinstance(user_barcodes, list) \ + or not all(isinstance(slot, list) + for slot in user_barcodes): + raise ValueError("user_barcodes must be a list of lists, " + "where each sublist corresponds to a slot.") else: - # If no user-provided barcodes or - # remaining to generate, generate all barcodes - kit_name_and_barcode_tuples_list, new_barcodes = \ - self._generate_novel_barcodes(number_of_kits, - number_of_samples, - kit_names) - return self._create_kits(kit_names, new_barcodes, + user_barcodes = [] + + # Generate necessary barcodes + total_user_barcodes = sum(len(slot) for slot in user_barcodes) + total_barcodes_to_generate = \ + total_required_barcodes - total_user_barcodes + + if total_barcodes_to_generate < 0: + raise ValueError("More user barcodes provided than required.") + + if total_barcodes_to_generate > 0: + _, generated_barcodes = self._generate_novel_barcodes( + 1, + total_barcodes_to_generate, + kit_names + ) + + # Assign barcodes per slot + generated_barcodes_index = 0 + for slot_index in range(number_of_samples): + slot_barcodes = [] + + # Get user barcodes for current slot if available + if slot_index < len(user_barcodes): + slot_barcodes.extend(user_barcodes[slot_index]) + + # Calculate how many barcodes need to be generated for this slot + missing_barcodes_count = number_of_kits - len(slot_barcodes) + if missing_barcodes_count > 0: + # Assign generated barcodes to fill the slot + slot_barcodes.extend( + generated_barcodes[generated_barcodes_index: + generated_barcodes_index + + missing_barcodes_count] + ) + generated_barcodes_index += missing_barcodes_count + + # Validate slot barcode count + if len(slot_barcodes) != number_of_kits: + raise ValueError(f"Slot {slot_index + 1} " + "does not have enough barcodes assigned.") + + all_barcodes_per_slot.append(slot_barcodes) + + kit_name_and_barcode_tuples_list = [] + for kit_index, kit_name in enumerate(kit_names): + for slot_index in range(number_of_samples): + barcode = all_barcodes_per_slot[slot_index][kit_index] + kit_name_and_barcode_tuples_list.append( + (kit_name, barcode) + ) + + new_barcodes = [] + for kit_index in range(number_of_kits): + kit_barcodes = [ + all_barcodes_per_slot[slot_index][kit_index] + for slot_index in range(number_of_samples) + ] + new_barcodes.append(kit_barcodes) + + return self._create_kits(kit_names, + new_barcodes, kit_name_and_barcode_tuples_list, - number_of_samples, project_ids) + number_of_samples, + project_ids) def _are_any_projects_tmi(self, project_ids): """Return true if any input projects are part of microsetta""" @@ -1041,9 +1059,17 @@ def _create_kits(self, kit_names, new_barcodes, with self._transaction.cursor() as cur: # create barcode project associations barcode_projects = [] - for barcode in new_barcodes: - for prj_id in project_ids: - barcode_projects.append((barcode, prj_id)) + if isinstance(new_barcodes, list) and \ + all(isinstance(item, list) for item in new_barcodes): + for barcodes in new_barcodes: + for barcode in barcodes: + for prj_id in project_ids: + barcode_projects.append((barcode, prj_id)) + else: + for barcode in new_barcodes: + for prj_id in project_ids: + barcode_projects.append((barcode, prj_id)) + print("barcode projects", barcode_projects) # create kits in kit table new_kit_uuids = [str(uuid.uuid4()) for x in kit_names] @@ -1140,7 +1166,6 @@ def create_kit(self, kit_name, box_id, address_dict, Project ids that all barcodes in kit are to be associated with """ - kit_names = [kit_name] address = None if address_dict is None else json.dumps(address_dict) kit_details = [{KIT_BOX_ID_KEY: box_id, KIT_ADDRESS_KEY: address, @@ -1148,6 +1173,10 @@ def create_kit(self, kit_name, box_id, address_dict, KIT_INBOUND_KEY: inbound_fedex_code}] kit_name_and_barcode_tuples_list = \ [(kit_name, x) for x in barcodes_list] + kit_names = [kit_name] + + print("create kit barcode list", barcodes_list) + print("create kit tuple", kit_name_and_barcode_tuples_list) return self._create_kits(kit_names, barcodes_list, kit_name_and_barcode_tuples_list, diff --git a/microsetta_private_api/repo/tests/test_sample.py b/microsetta_private_api/repo/tests/test_sample.py index 94cbbf08d..44b383d8a 100644 --- a/microsetta_private_api/repo/tests/test_sample.py +++ b/microsetta_private_api/repo/tests/test_sample.py @@ -153,7 +153,6 @@ def test_get_supplied_kit_id_by_sample(self): 1, "UNITTEST", None, - 0, [1] ) From 373a24e851ef8144830bc457973e8bfaf844de1c Mon Sep 17 00:00:00 2001 From: ayobi Date: Thu, 22 Aug 2024 22:08:33 -0400 Subject: [PATCH 28/33] remove arg for create_kits --- microsetta_private_api/admin/admin_impl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 6c19d3f09..65af3fd2c 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -275,8 +275,6 @@ def create_kits(body, token_info): kit_prefix = body.get('kit_id_prefix', None) project_ids = body['project_ids'] user_barcodes = body.get('user_barcodes', []) - remaining_barcodes_to_generate = body.get( - 'remaining_samples_to_generate', []) with Transaction() as t: admin_repo = AdminRepo(t) @@ -286,7 +284,6 @@ def create_kits(body, token_info): number_of_samples, kit_prefix, user_barcodes, - remaining_barcodes_to_generate, project_ids) except KeyError: return jsonify(code=422, message="Unable to create kits"), 422 From 07fb798072f2de0d90b53940eb8e9f1df028ffb3 Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 30 Aug 2024 19:22:07 -0400 Subject: [PATCH 29/33] changes based on latest feedback --- microsetta_private_api/admin/admin_impl.py | 23 +-- .../admin/sample_summary.py | 1 + .../admin/tests/test_admin_repo.py | 13 -- .../api/microsetta_private_api.yaml | 4 + microsetta_private_api/repo/admin_repo.py | 155 ++++++------------ 5 files changed, 59 insertions(+), 137 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 65af3fd2c..bb5475261 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -60,17 +60,6 @@ def search_kit_id(token_info, kit_id): return jsonify(diag), 200 -def kit_id_exists(token_info, kit_id): - validate_admin_access(token_info) - - with Transaction() as t: - admin_repo = AdminRepo(t) - diag = admin_repo.check_kit_id_exists(kit_id) - if diag is None: - return jsonify(code=404, message=f'Kit ID {kit_id} not found'), 404 - return jsonify(diag), 200 - - def search_email(token_info, email): validate_admin_access(token_info) @@ -302,9 +291,9 @@ def handle_barcodes(body, token_info): kit_ids = [kit_ids] with Transaction() as t: - admin_repo = AdminRepo(t) + kit_repo = KitRepo(t) for kit_id in kit_ids: - diag = admin_repo.check_kit_id_exists(kit_id) + diag = kit_repo.get_kit_all_samples(kit_id) if diag is None: return jsonify(f'Kit ID {kit_id} not found'), 404 @@ -330,7 +319,7 @@ def handle_barcodes(body, token_info): t.commit() - return jsonify(barcode[1]) + return jsonify(barcode[1]), 201 elif action == 'insert': return insert_barcodes(body, token_info) @@ -352,14 +341,14 @@ def insert_barcodes(body, token_info): with Transaction() as t: admin_repo = AdminRepo(t) for barcode in barcodes: - diag = admin_repo.check_barcode_exists(barcode) + diag = admin_repo.retrieve_diagnostics_by_barcode(barcode) if diag is not None: return jsonify(f'Barcode {barcode} already exists'), 404 # Check if the lengths match if len(kit_ids) != len(barcodes): - raise ValueError("The number of kit IDs must " - "match the number of barcodes") + return jsonify("The number of kit IDs must " + "match the number of barcodes"), 400 with Transaction() as t: admin_repo = AdminRepo(t) diff --git a/microsetta_private_api/admin/sample_summary.py b/microsetta_private_api/admin/sample_summary.py index a64f00e2a..44b13194a 100644 --- a/microsetta_private_api/admin/sample_summary.py +++ b/microsetta_private_api/admin/sample_summary.py @@ -83,6 +83,7 @@ def per_sample(project, barcodes, strip_sampleid): ffq_complete, ffq_taken, _ = vs_repo.get_ffq_status_by_sample( sample.id ) + print("ffq complete", ffq_complete, "ffq taken", ffq_taken) summary = { "sampleid": None if strip_sampleid else barcode, diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index ed483c815..5c73a6117 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -880,7 +880,6 @@ def test_scan_with_multiple_observations(self): scans = [scan['observations'] for scan in diag['scans_info']] scans_observation_ids = [obs['observation_id'] for scan in scans for obs in scan] - self.assertEqual(scans_observation_ids, observation_ids) def test_scan_with_wrong_observation(self): @@ -1594,15 +1593,3 @@ def test_user_barcode_dup_create_kit_fail(self): '', [user_barcode], [1]) - - def test_user_barcodes_num_samples_mismatch_create_kit_fail(self): - # When creating a kit, if a user selects just one sample, but - # provides two user barcodes, the transaction should fail - with Transaction() as t: - admin_repo = AdminRepo(t) - with self.assertRaises(ValueError): - admin_repo.create_kits(1, - 1, - '', - ['X92384885', 'X92384886'], - [1]) diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index 48c2de6e1..9bc596870 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2839,6 +2839,10 @@ paths: application/json: schema: type: array + '204': + description: No data was returned + '404': + description: Kit ID not found or barcode already exists '500': description: Duplicate barcodes found (only relevant for insert action) diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index fbdaed915..c2e0385bd 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -808,86 +808,59 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, Project ids the samples are to be associated with """ - kit_names = self._generate_novel_kit_names(number_of_kits, - kit_prefix) + kit_names = self._generate_novel_kit_names(number_of_kits, kit_prefix) - all_barcodes_per_slot = [] - generated_barcodes = [] - - total_required_barcodes = number_of_kits * number_of_samples - - if user_barcodes: - if not isinstance(user_barcodes, list) \ - or not all(isinstance(slot, list) - for slot in user_barcodes): - raise ValueError("user_barcodes must be a list of lists, " - "where each sublist corresponds to a slot.") - else: + if user_barcodes is None: user_barcodes = [] - # Generate necessary barcodes + total_required_barcodes = number_of_kits * number_of_samples total_user_barcodes = sum(len(slot) for slot in user_barcodes) + total_barcodes_to_generate = \ total_required_barcodes - total_user_barcodes + generated_barcodes = self._generate_novel_barcodes( + 1, total_barcodes_to_generate, kit_names + )[1] if total_barcodes_to_generate > 0 else [] - if total_barcodes_to_generate < 0: - raise ValueError("More user barcodes provided than required.") - - if total_barcodes_to_generate > 0: - _, generated_barcodes = self._generate_novel_barcodes( - 1, - total_barcodes_to_generate, - kit_names - ) + all_barcodes_per_slot = [] - # Assign barcodes per slot - generated_barcodes_index = 0 - for slot_index in range(number_of_samples): + for i in range(number_of_samples): slot_barcodes = [] - # Get user barcodes for current slot if available - if slot_index < len(user_barcodes): - slot_barcodes.extend(user_barcodes[slot_index]) - - # Calculate how many barcodes need to be generated for this slot - missing_barcodes_count = number_of_kits - len(slot_barcodes) - if missing_barcodes_count > 0: - # Assign generated barcodes to fill the slot - slot_barcodes.extend( - generated_barcodes[generated_barcodes_index: - generated_barcodes_index + - missing_barcodes_count] - ) - generated_barcodes_index += missing_barcodes_count + # add user-provided barcodes if provided + if i < len(user_barcodes): + slot_barcodes.extend(user_barcodes[i]) - # Validate slot barcode count - if len(slot_barcodes) != number_of_kits: - raise ValueError(f"Slot {slot_index + 1} " - "does not have enough barcodes assigned.") + # add generated barcodes if there slots need to fill + remaining_barcodes_needed = \ + number_of_kits - len(slot_barcodes) + slot_barcodes.extend( + generated_barcodes[:remaining_barcodes_needed]) + generated_barcodes = \ + generated_barcodes[remaining_barcodes_needed:] all_barcodes_per_slot.append(slot_barcodes) - kit_name_and_barcode_tuples_list = [] - for kit_index, kit_name in enumerate(kit_names): - for slot_index in range(number_of_samples): - barcode = all_barcodes_per_slot[slot_index][kit_index] - kit_name_and_barcode_tuples_list.append( - (kit_name, barcode) - ) - - new_barcodes = [] - for kit_index in range(number_of_kits): - kit_barcodes = [ - all_barcodes_per_slot[slot_index][kit_index] - for slot_index in range(number_of_samples) - ] - new_barcodes.append(kit_barcodes) - - return self._create_kits(kit_names, - new_barcodes, - kit_name_and_barcode_tuples_list, - number_of_samples, - project_ids) + # make the final list of kit-barcode tuples + kit_name_and_barcode_tuples_list = [ + (kit_name, all_barcodes_per_slot[slot_index][kit_index]) + for kit_index, kit_name in enumerate(kit_names) + for slot_index in range(number_of_samples) + ] + + new_barcodes = [ + [all_barcodes_per_slot[slot_index][kit_index] + for slot_index in range(number_of_samples)] + for kit_index in range(number_of_kits) + ] + + return self._create_kits( + kit_names, + new_barcodes, + kit_name_and_barcode_tuples_list, + number_of_samples, + project_ids + ) def _are_any_projects_tmi(self, project_ids): """Return true if any input projects are part of microsetta""" @@ -1017,17 +990,15 @@ def _insert_barcodes_to_existing_kit(self, kit_barcode_project_tuples): kit_barcodes_insert = [(kit_id, barcode) for kit_id, barcode, _ in unique_barcode_tuples] - try: - cur.executemany("INSERT INTO ag_kit_barcodes " - "(ag_kit_id, barcode) " - "SELECT ag_kit_id, %s " - "FROM ag_kit " - "WHERE supplied_kit_id = %s", - [(barcode, kit_id) - for kit_id, barcode - in kit_barcodes_insert]) - except Exception as e: - print("Error executing query:", e) + + cur.executemany("INSERT INTO ag_kit_barcodes " + "(ag_kit_id, barcode) " + "SELECT ag_kit_id, %s " + "FROM ag_kit " + "WHERE supplied_kit_id = %s", + [(barcode, kit_id) + for kit_id, barcode + in kit_barcodes_insert]) def _create_kits(self, kit_names, new_barcodes, kit_name_and_barcode_tuples_list, @@ -1069,7 +1040,6 @@ def _create_kits(self, kit_names, new_barcodes, for barcode in new_barcodes: for prj_id in project_ids: barcode_projects.append((barcode, prj_id)) - print("barcode projects", barcode_projects) # create kits in kit table new_kit_uuids = [str(uuid.uuid4()) for x in kit_names] @@ -1175,39 +1145,10 @@ def create_kit(self, kit_name, box_id, address_dict, [(kit_name, x) for x in barcodes_list] kit_names = [kit_name] - print("create kit barcode list", barcodes_list) - print("create kit tuple", kit_name_and_barcode_tuples_list) - return self._create_kits(kit_names, barcodes_list, kit_name_and_barcode_tuples_list, len(barcodes_list), project_ids, kit_details) - def check_kit_id_exists(self, supplied_kit_id): - with self._transaction.dict_cursor() as cur: - cur.execute( - "SELECT supplied_kit_id " - "FROM " - "ag.ag_kit " - "WHERE " - "supplied_kit_id = %s", - (supplied_kit_id,)) - row = cur.fetchone() - - return row - - def check_barcode_exists(self, barcode): - with self._transaction.dict_cursor() as cur: - cur.execute( - "SELECT barcode " - "FROM " - "barcodes.barcode " - "WHERE " - "barcode = %s", - (barcode,)) - row = cur.fetchone() - - return row - def retrieve_diagnostics_by_kit_id(self, supplied_kit_id): kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples(supplied_kit_id) From 0d3764aacd3e8c4a64ffffcf05a6c6cb886eccf5 Mon Sep 17 00:00:00 2001 From: ayobi Date: Fri, 30 Aug 2024 19:33:09 -0400 Subject: [PATCH 30/33] missed 2 comments --- microsetta_private_api/admin/admin_impl.py | 2 +- microsetta_private_api/repo/admin_repo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index bb5475261..171bd2824 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -301,7 +301,7 @@ def handle_barcodes(body, token_info): if action == 'create': if 'generate_barcode_single' in body: - if body['generate_barcode_single'] == 'on': + if body['generate_barcode_single']: number_of_kits = 1 number_of_samples = 1 elif 'generate_barcodes' in body: diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index c2e0385bd..85b86569c 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -801,7 +801,7 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, Number of samples that each kit will contain kit_prefix : str or None A prefix to put on to the kit IDs, this is optional. - user_barcodes : list of str + user_barcodes : list of lists of str User provided barcodes to use for the kits. If None, barcodes will be generated. project_ids : list of int From 46346b303b1887883bbf436268079cffd5294b41 Mon Sep 17 00:00:00 2001 From: Cassidy Symons Date: Sun, 15 Dec 2024 20:32:33 -0800 Subject: [PATCH 31/33] Add barcodes to kits --- microsetta_private_api/admin/admin_impl.py | 142 ++++---- .../admin/tests/test_admin_repo.py | 314 +++++++++++++----- .../api/microsetta_private_api.yaml | 47 +-- microsetta_private_api/repo/admin_repo.py | 176 +++++----- .../repo/tests/test_sample.py | 2 +- 5 files changed, 399 insertions(+), 282 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 171bd2824..e74d5c318 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -263,17 +263,42 @@ def create_kits(body, token_info): number_of_samples = body['number_of_samples'] kit_prefix = body.get('kit_id_prefix', None) project_ids = body['project_ids'] - user_barcodes = body.get('user_barcodes', []) + barcodes = body['barcodes'] with Transaction() as t: admin_repo = AdminRepo(t) + # First, do some basic sanitation + errors = [] + for barcode_list in barcodes: + x = 1 + if len(barcode_list) != 0: + # If barcodes were provided for a given slot, ensure the + # quantity matches the number of kits + if len(barcode_list) != number_of_kits: + errors.append( + f'Incorrect number of barcodes given for Sample {x}' + ) + + # And verify that the barcodes don't already exist + for barcode in barcode_list: + diag = admin_repo.retrieve_diagnostics_by_barcode(barcode) + if diag is not None: + errors.append(f'Barcode {barcode} already exists') + x += 1 + + if len(errors) > 0: + error_str = "; ".join(errors) + return jsonify(code=422, message=error_str), 422 + try: - kits = admin_repo.create_kits(number_of_kits, - number_of_samples, - kit_prefix, - user_barcodes, - project_ids) + kits = admin_repo.create_kits( + number_of_kits, + number_of_samples, + kit_prefix, + barcodes, + project_ids + ) except KeyError: return jsonify(code=422, message="Unable to create kits"), 422 else: @@ -282,91 +307,50 @@ def create_kits(body, token_info): return jsonify(kits), 201 -def handle_barcodes(body, token_info): +def add_barcodes_to_kits(body, token_info): validate_admin_access(token_info) kit_ids = body['kit_ids'] + barcodes = body['barcodes'] + generate_barcodes = body['generate_barcodes'] - if isinstance(kit_ids, str): - kit_ids = [kit_ids] + errors = [] with Transaction() as t: kit_repo = KitRepo(t) + admin_repo = AdminRepo(t) + + # First, make sure all of the Kit IDs are valid for kit_id in kit_ids: diag = kit_repo.get_kit_all_samples(kit_id) if diag is None: - return jsonify(f'Kit ID {kit_id} not found'), 404 - - action = body.get('action') - - if action == 'create': - if 'generate_barcode_single' in body: - if body['generate_barcode_single']: - number_of_kits = 1 - number_of_samples = 1 - elif 'generate_barcodes' in body: - number_of_kits = (body['num_kits']) - number_of_samples = (body['num_samples']) - else: - number_of_kits = len(body['kit_ids']) - number_of_samples = 1 - - with Transaction() as t: - admin_repo = AdminRepo(t) - - barcode = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names=kit_ids) - - t.commit() - - return jsonify(barcode[1]), 201 - - elif action == 'insert': - return insert_barcodes(body, token_info) - - else: - return jsonify(code=404, message='Invalid action'), 404 - - -def insert_barcodes(body, token_info): - validate_admin_access(token_info) - - kit_ids = body['kit_ids'] - barcodes = body['barcodes'] - - # Ensure kit_ids is a list, even if it's a single value - if isinstance(kit_ids, str): - kit_ids = [kit_ids] - - with Transaction() as t: - admin_repo = AdminRepo(t) - for barcode in barcodes: - diag = admin_repo.retrieve_diagnostics_by_barcode(barcode) - if diag is not None: - return jsonify(f'Barcode {barcode} already exists'), 404 - - # Check if the lengths match - if len(kit_ids) != len(barcodes): - return jsonify("The number of kit IDs must " - "match the number of barcodes"), 400 - - with Transaction() as t: - admin_repo = AdminRepo(t) - - insertion_data = [] - for kit_id, barcode in zip(kit_ids, barcodes): - diag = admin_repo.retrieve_diagnostics_by_kit_id(kit_id) - sample_info = diag['sample_diagnostic_info'][0] - if sample_info['projects_info']: - for project in sample_info['projects_info']: - project_id = str(project['project_id']) - insertion_data.append((kit_id, barcode, project_id)) - - admin_repo._insert_barcodes_to_existing_kit(insertion_data) - + errors.append(f'Kit ID {kit_id} not found') + + if generate_barcodes is False: + # Next, check that the Barcodes are unique if we're not generating + # novel ones + for barcode in barcodes: + diag = admin_repo.retrieve_diagnostics_by_barcode(barcode) + if diag is not None: + errors.append(f'Barcode {barcode} already exists') + + # And verify that the number of barcodes and kit IDs are equal + if len(barcodes) != len(kit_ids): + errors.append("Unequal number of Kit IDs and Barcodes") + + # We found one or more issues with the supplied Kit IDs or Barcodes. + # Return the error message to microsetta-admin. + if len(errors) > 0: + error_str = "; ".join(errors) + return jsonify( + code=422, + message=error_str + ), 422 + + diag = admin_repo.add_barcodes_to_kits(kit_ids, barcodes) t.commit() - return '', 204 + return jsonify(diag), 201 def get_account_events(account_id, token_info): diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 5c73a6117..807109450 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -456,7 +456,7 @@ def test_get_project_barcodes_by_id(self): created = admin_repo.create_kits(2, 3, 'foo', - None, + [[],[],[]], [output_id, ]) exp = [] @@ -612,14 +612,14 @@ def test_create_kits_fail_nonexistent_project(self): admin_repo.create_kits(5, 3, '', - None, + [[],[],[]], [10000, SurveyTemplateRepo.VIOSCREEN_ID]) def test_create_kits_success_not_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - non_tmi = admin_repo.create_kits(5, 3, '', None, [33]) + non_tmi = admin_repo.create_kits(5, 3, '', [[],[],[]], [33]) self.assertEqual(['created', ], list(non_tmi.keys())) self.assertEqual(len(non_tmi['created']), 5) for obj in non_tmi['created']: @@ -646,7 +646,7 @@ def test_create_kits_success_not_microsetta(self): def test_create_kits_success_is_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - tmi = admin_repo.create_kits(4, 2, 'foo', None, [1]) + tmi = admin_repo.create_kits(4, 2, 'foo', [[],[]], [1]) self.assertEqual(['created', ], list(tmi.keys())) self.assertEqual(len(tmi['created']), 4) for obj in tmi['created']: @@ -671,6 +671,63 @@ def test_create_kits_success_is_microsetta(self): observed = cur.fetchall() self.assertEqual(len(observed), 4) + def test_create_kits_success_mix_provided_generated_barcodes(self): + # This test will confirm that AdminRepo.create_kits correctly places + # admin-provided barcodes in relation to intent. We'll do 1 kits with + # 3 samples. 2 samples will be admin-provided barcodes and 1 will be + # generated. + + # Sample Slot 1 + sample_1_barcodes = [ + "2806d1ef-8992-4693-b977-1ae73b398b4c" + ] + # Sample Slot 2 + sample_2_barcodes = [ + "af6ab062-163c-4abd-96f5-848c4729f8b0" + ] + # Sample Slot 3 - generated + sample_3_barcodes = [] + + with Transaction() as t: + admin_repo = AdminRepo(t) + tmi = admin_repo.create_kits( + 1, + 3, + 'foo', + [sample_1_barcodes, sample_2_barcodes, sample_3_barcodes], + [1] + ) + self.assertEqual(['created', ], list(tmi.keys())) + self.assertEqual(len(tmi['created']), 1) + for obj in tmi['created']: + self.assertEqual(len(obj['sample_barcodes']), 3) + self.assertEqual({'kit_id', 'kit_uuid', 'box_id', 'address', + 'outbound_fedex_tracking', + 'inbound_fedex_tracking', + 'sample_barcodes'}, set(obj)) + self.assertTrue(obj['kit_id'].startswith('foo_')) + self.assertEqual(obj['kit_uuid'], obj['box_id']) + self.assertEqual(None, obj['address']) + self.assertEqual(None, obj['outbound_fedex_tracking']) + self.assertEqual(None, obj['inbound_fedex_tracking']) + + # should be present in the ag tables + tmi_kits = [k['kit_id'] for k in tmi['created']] + with t.cursor() as cur: + cur.execute("SELECT supplied_kit_id " + "FROM ag.ag_kit " + "WHERE supplied_kit_id IN %s", + (tuple(tmi_kits),)) + observed = cur.fetchall() + self.assertEqual(len(observed), 1) + + kit = tmi['created'][0] + # Assert that the provided barcodes are present + self.assertEqual(kit['sample_barcodes'][0], sample_1_barcodes[0]) + self.assertEqual(kit['sample_barcodes'][1], sample_2_barcodes[0]) + # And the last barcode starts with X per the generation algorithm + self.assertEqual(kit['sample_barcodes'][2][0], "X") + def test_create_kit_success_is_microsetta(self): input_kit_name = "DM24-A3CF9" input_box_id = "DM89D-VW6Y" @@ -1489,107 +1546,190 @@ def test_update_perk_fulfillment_state(self): obs = cur.fetchone() self.assertFalse(obs[0]) - def test_generate_novel_barcodes_admin_success(self): - number_of_kits = 1 - number_of_samples = 3 + def test_add_barcodes_to_kits_generated_success(self): + # These two kit IDs are stable in the dev database and are TMI kits + kit_id_1 = "wiydx" + kit_id_2 = "TjrzA" + kit_ids = [kit_id_1, kit_id_2] + barcodes = [] with Transaction() as t: - admin_repo = AdminRepo(t) + with t.cursor() as cur: + # Establish counts for barcodes contained in our test kits + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_1,) + ) + kit_id_1_barcode_count = cur.fetchone()[0] + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_2,) + ) + kit_id_2_barcode_count = cur.fetchone()[0] - kit_names = admin_repo._generate_novel_kit_names( - number_of_kits, kit_prefix=None) + admin_repo = AdminRepo(t) + res = admin_repo.add_barcodes_to_kits(kit_ids, barcodes) + for kit_id, barcode in res: + # Verify that the barcode was created and is associated + # with the kit ID + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s AND barcode = %s", + (kit_id, barcode) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + # Verify that the barcode is associated with Project 1 + cur.execute( + "SELECT COUNT(*) FROM barcodes.project_barcode " + "WHERE barcode = %s AND project_id = 1", + (barcode, ) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + # Verify that the barcode was added to ag.ag_kit_barcodes + cur.execute( + "SELECT COUNT(*) FROM ag.ag_kit_barcodes " + "WHERE barcode = %s", + (barcode, ) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + # Verify that kit_id_1 has exactly one more barcode in the + # barcodes.barcode table + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_1,) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], kit_id_1_barcode_count+1) - new_barcodes = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names) + # Verify that kit_id_2 has exactly one more barcode in the + # barcodes.barcode table + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_2,) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], kit_id_2_barcode_count+1) - self.assertEqual(len(new_barcodes[1]), - number_of_kits * number_of_samples) - self.assertTrue(all(barcodes.startswith('X') - for barcodes in new_barcodes[1])) + def test_add_barcodes_to_kits_provided_success(self): + # These two kit IDs are stable in the dev database and are TMI kits + kit_id_1 = "wiydx" + kit_id_2 = "TjrzA" + kit_ids = [kit_id_1, kit_id_2] - def test_generate_novel_barcodes_admin_failure(self): - number_of_kits = 0 - number_of_samples = 3 + # Let's create some fake barcodes that won't exist in the database + barcode_1 = "cf2d5565-9c26-44f4-9aae-3e19d1cc78cf" + barcode_2 = "62596b88-bc63-4939-a8fd-7e53750808bc" + barcodes = [barcode_1, barcode_2] with Transaction() as t: - admin_repo = AdminRepo(t) - - kit_names = admin_repo._generate_novel_kit_names( - number_of_kits, kit_prefix=None) - - new_barcodes = admin_repo._generate_novel_barcodes( - number_of_kits, number_of_samples, kit_names) - - self.assertTrue(new_barcodes[1] == [], []) - - def test_insert_barcodes_admin_success(self): - kit_name = 'test' - barcode = 'X00332312' - project_id = '1' - kit_name_barcode_prj_id_tuple = [(kit_name, barcode, project_id)] + with t.cursor() as cur: + # Establish counts for barcodes contained in our test kits + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_1,) + ) + kit_id_1_barcode_count = cur.fetchone()[0] + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_2,) + ) + kit_id_2_barcode_count = cur.fetchone()[0] - with Transaction() as t: - admin_repo = AdminRepo(t) - admin_repo._insert_barcodes_to_existing_kit( - kit_name_barcode_prj_id_tuple - ) + admin_repo = AdminRepo(t) + res = admin_repo.add_barcodes_to_kits(kit_ids, barcodes) + for kit_id, barcode in res: + # Verify that the barcode was created and is associated + # with the kit ID + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s AND barcode = %s", + (kit_id, barcode) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + # Verify that the barcode is associated with Project 1 + cur.execute( + "SELECT COUNT(*) FROM barcodes.project_barcode " + "WHERE barcode = %s AND project_id = 1", + (barcode, ) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + # Verify that the barcode was added to ag.ag_kit_barcodes + cur.execute( + "SELECT COUNT(*) FROM ag.ag_kit_barcodes " + "WHERE barcode = %s", + (barcode, ) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + # Verify that kit_id_1 has exactly one more barcode in the + # barcodes.barcode table + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_1,) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], kit_id_1_barcode_count+1) - with t.cursor() as cur: + # Verify that barcode_1 is associated with kit_id_1 cur.execute( - "SELECT barcode " - "FROM barcodes.barcode " + "SELECT COUNT(*) FROM barcodes.barcode " "WHERE kit_id = %s AND barcode = %s", - (kit_name, barcode) + (kit_id_1, barcode_1) ) obs = cur.fetchone() - self.assertIsNotNone(obs, - "Expected barcode not found in database") + self.assertEqual(obs[0], 1) - def test_insert_barcodes_admin_fail_nonexisting_kit(self): - # test that inserting barcodes to a non-existent kit fails - kit_name_barcode_prj_id_tuple = [['test1123', 'X00332312', '1']] - - with Transaction() as t: - admin_repo = AdminRepo(t) - with self.assertRaises(psycopg2.errors.ForeignKeyViolation): - admin_repo._insert_barcodes_to_existing_kit( - kit_name_barcode_prj_id_tuple) - - def test_insert_barcodes_admin_fail_dup_barcodes(self): - # test that inserting duplicate barcode fails - kit_name_barcode_prj_id_tuple = [['test', '000000001', '1']] - - with Transaction() as t: - admin_repo = AdminRepo(t) - with self.assertRaises(psycopg2.errors.UniqueViolation): - admin_repo._insert_barcodes_to_existing_kit( - kit_name_barcode_prj_id_tuple) - - def test_user_barcode_create_kit_success(self): - with Transaction() as t: - admin_repo = AdminRepo(t) - admin_repo.create_kits(1, - 1, - '', - [['X99887769']], - [1]) - with t.cursor() as cur: + # Verify that kit_id_2 has exactly one more barcode in the + # barcodes.barcode table cur.execute( - "SELECT barcode " - "FROM barcodes.barcode " - "WHERE barcode = %s", - ('X99887769',) + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s", + (kit_id_2,) ) - obs = cur.fetchall() - self.assertEqual(obs[0][0], 'X99887769') + obs = cur.fetchone() + self.assertEqual(obs[0], kit_id_2_barcode_count+1) - def test_user_barcode_dup_create_kit_fail(self): + # Verify that barcode_2 is associated with kit_id_2 + cur.execute( + "SELECT COUNT(*) FROM barcodes.barcode " + "WHERE kit_id = %s AND barcode = %s", + (kit_id_2, barcode_2) + ) + obs = cur.fetchone() + self.assertEqual(obs[0], 1) + + def test_add_barcodes_to_kits_provided_failure(self): + # This test confirms that if the admin provides duplicate barcodes in + # the upload, it will fail to insert + self.assertFalse(False) + # These two kit IDs are stable in the dev database and are TMI kits + kit_id_1 = "wiydx" + kit_id_2 = "TjrzA" + kit_ids = [kit_id_1, kit_id_2] + + # This barcode doesn't exist in the database, but we'll try to use it + # twice + barcode_1 = "cf2d5565-9c26-44f4-9aae-3e19d1cc78cf" + barcodes = [barcode_1, barcode_1] with Transaction() as t: admin_repo = AdminRepo(t) - user_barcode = ['000000001'] with self.assertRaises(psycopg2.errors.UniqueViolation): - admin_repo.create_kits(1, - 1, - '', - [user_barcode], - [1]) + res = admin_repo.add_barcodes_to_kits(kit_ids, barcodes) diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index 9bc596870..24176e28a 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2789,10 +2789,17 @@ paths: type: array items: type: string + barcodes: + type: array + items: + type: array + items: + type: string required: - number_of_kits - number_of_samples - project_ids + - barcodes responses: '201': description: Kit identifiers and associated samples were successfully created @@ -2805,46 +2812,46 @@ paths: '422': $ref: '#/components/responses/422UnprocessableEntity' - '/admin/add_barcodes': + '/admin/add_barcodes_to_kits': post: - operationId: microsetta_private_api.admin.admin_impl.handle_barcodes + operationId: microsetta_private_api.admin.admin_impl.add_barcodes_to_kits tags: - Admin - summary: Create or insert barcodes - description: Create barcodes for an existing kit or insert barcodes into the database + summary: Add barcodes to an existing kit + description: Add barcodes to an existing kit requestBody: content: application/json: schema: type: "object" properties: - action: - type: string - enum: [create, insert] - description: Specify 'create' to generate new barcodes or 'insert' to insert provided barcodes + generate_barcodes: + type: boolean + description: Flag for whether barcodes are provided or must be generated barcodes: type: array items: type: string - description: Barcodes to insert (required if action is 'insert') - kit_id: - type: string - description: Kit ID to associate the barcodes with (required if action is 'insert') + description: Barcodes to insert, optional + kit_ids: + type: array + items: + type: string + description: Kit IDs to associate the barcodes with required: - - action + - generate_barcodes + - kit_ids responses: '201': - description: Barcodes were successfully created or inserted + description: Barcodes were successfully added content: application/json: schema: type: array - '204': - description: No data was returned - '404': - description: Kit ID not found or barcode already exists - '500': - description: Duplicate barcodes found (only relevant for insert action) + '401': + $ref: '#/components/responses/401Unauthorized' + '422': + $ref: '#/components/responses/422UnprocessableEntity' '/admin/events/accounts/{account_id}': get: diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index b85a18071..f09ee1291 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -787,7 +787,7 @@ def _generate_random_kit_name(self, name_length, prefix): return prefix + '_' + rand_name def create_kits(self, number_of_kits, number_of_samples, kit_prefix, - user_barcodes, project_ids): + barcodes, project_ids): """Create multiple kits, each with the same number of samples Parameters @@ -798,58 +798,38 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, Number of samples that each kit will contain kit_prefix : str or None A prefix to put on to the kit IDs, this is optional. - user_barcodes : list of lists of str - User provided barcodes to use for the kits. If None, barcodes will - be generated. + barcodes : list of lists of str + User provided barcodes to use for the kits. If empty, barcodes + will be generated. project_ids : list of int Project ids the samples are to be associated with """ kit_names = self._generate_novel_kit_names(number_of_kits, kit_prefix) - if user_barcodes is None: - user_barcodes = [] - - total_required_barcodes = number_of_kits * number_of_samples - total_user_barcodes = sum(len(slot) for slot in user_barcodes) - - total_barcodes_to_generate = \ - total_required_barcodes - total_user_barcodes - generated_barcodes = self._generate_novel_barcodes( - 1, total_barcodes_to_generate, kit_names - )[1] if total_barcodes_to_generate > 0 else [] - - all_barcodes_per_slot = [] - - for i in range(number_of_samples): - slot_barcodes = [] - - # add user-provided barcodes if provided - if i < len(user_barcodes): - slot_barcodes.extend(user_barcodes[i]) - - # add generated barcodes if there slots need to fill - remaining_barcodes_needed = \ - number_of_kits - len(slot_barcodes) - slot_barcodes.extend( - generated_barcodes[:remaining_barcodes_needed]) - generated_barcodes = \ - generated_barcodes[remaining_barcodes_needed:] - - all_barcodes_per_slot.append(slot_barcodes) - - # make the final list of kit-barcode tuples - kit_name_and_barcode_tuples_list = [ - (kit_name, all_barcodes_per_slot[slot_index][kit_index]) - for kit_index, kit_name in enumerate(kit_names) - for slot_index in range(number_of_samples) - ] - - new_barcodes = [ - [all_barcodes_per_slot[slot_index][kit_index] - for slot_index in range(number_of_samples)] - for kit_index in range(number_of_kits) - ] + new_barcodes = [] + kit_name_and_barcode_tuples_list = [] + + # Iterate through sample slots and use provided barcodes + for barcode_list in barcodes: + if len(barcode_list) > 0: + # Admin provided barcodes for this sample slot, use them + kit_barcode_tuples = list(zip(kit_names, barcode_list)) + kit_name_and_barcode_tuples_list += kit_barcode_tuples + new_barcodes += barcode_list + + # See if we need to generate barcodes + slots_to_generate = sum(1 for i in barcodes if len(i) == 0) + if slots_to_generate > 0: + # Generate remaining barcodes + kit_barcode_tuples, novel_barcodes = \ + self._generate_novel_barcodes( + number_of_kits, + slots_to_generate, + kit_names + ) + new_barcodes += novel_barcodes + kit_name_and_barcode_tuples_list += kit_barcode_tuples return self._create_kits( kit_names, @@ -939,63 +919,69 @@ def _generate_novel_barcodes(self, number_of_kits, number_of_samples, return kit_name_and_barcode_tuples_list, new_barcodes - def _insert_barcodes_to_existing_kit(self, kit_barcode_project_tuples): - """Insert barcodes into the database for an existing kit + def add_barcodes_to_kits(self, kit_ids, barcodes): + """Adds barcodes to supplied kits Parameters ---------- - kit_barcode_project_tuples: list of tuple - Each tuple contains (kit_id, barcode, project_id) - """ - - # check for empty input - if not kit_barcode_project_tuples: - raise ValueError("kit_barcode_project_tuples cannot be empty") + kit_ids : list of str + The kit IDs for which we're adding barcodes + barcodes : list of str + The list of admin-supplied barcodes; if empty generates novel + barcodes - # Extract project IDs - project_ids = [int(t[2]) for t in kit_barcode_project_tuples] - - is_tmi = self._are_any_projects_tmi(project_ids) - - # Create a list to store unique (kit_id, barcode) pairs - unique_barcode_tuples = [] - seen_barcodes = set() + Returns + ------- + list of tuples + The pairings of newly added kit IDs/barcodes + """ - # Populate the list with unique barcodes - for kit_id, barcode, _ in kit_barcode_project_tuples: - if barcode not in seen_barcodes: - unique_barcode_tuples.append((kit_id, barcode, 'unassigned')) - seen_barcodes.add(barcode) + if len(barcodes) == 0: + kits_barcodes_tuples, _ = self._generate_novel_barcodes( + len(kit_ids), + 1, + kit_ids + ) + else: + kits_barcodes_tuples = list(zip(kit_ids, barcodes)) with self._transaction.cursor() as cur: - # Insert unique barcodes into the barcode table - if unique_barcode_tuples: - cur.executemany("INSERT INTO barcode (kit_id, barcode, " - "status) VALUES (%s, %s, %s)", - unique_barcode_tuples) - - # Create barcode project associations - barcode_projects = [(barcode, project_id) - for _, barcode, project_id - in kit_barcode_project_tuples] - cur.executemany("INSERT INTO project_barcode " - "(barcode, project_id) " - "VALUES (%s, %s)", barcode_projects) + for kit_id, barcode in kits_barcodes_tuples: + kit_d = self.retrieve_diagnostics_by_kit_id(kit_id) + ag_kit_id = kit_d['kit_id'] + + projects = kit_d['sample_diagnostic_info'][0]['projects_info'] + project_ids = [x['project_id'] for x in projects] + is_tmi = self._are_any_projects_tmi(project_ids) + + barcode_projects = [] + for project_id in project_ids: + barcode_projects.append((barcode, project_id)) + + # Add barcodes to barcode table + cur.execute( + "INSERT INTO barcodes.barcode(kit_id, barcode, status) " + "VALUES (%s, %s, 'unassigned')", + (kit_id, barcode) + ) - if is_tmi: - # Insert into ag_kit_barcodes table for TMI projects - kit_barcodes_insert = [(kit_id, barcode) - for kit_id, barcode, _ - in unique_barcode_tuples] + # Create project/barcode association(s) + cur.executemany( + "INSERT INTO barcodes.project_barcode " + "(barcode, project_id) " + "VALUES (%s, %s)", + barcode_projects + ) - cur.executemany("INSERT INTO ag_kit_barcodes " - "(ag_kit_id, barcode) " - "SELECT ag_kit_id, %s " - "FROM ag_kit " - "WHERE supplied_kit_id = %s", - [(barcode, kit_id) - for kit_id, barcode - in kit_barcodes_insert]) + # If any projects are TMI-oriented, add to ag_kit_barcodes + if is_tmi: + cur.execute( + "INSERT INTO ag.ag_kit_barcodes (ag_kit_id, barcode) " + "VALUES (%s, %s)", + (ag_kit_id, barcode) + ) + + return kits_barcodes_tuples def _create_kits(self, kit_names, new_barcodes, kit_name_and_barcode_tuples_list, diff --git a/microsetta_private_api/repo/tests/test_sample.py b/microsetta_private_api/repo/tests/test_sample.py index 44b383d8a..01b13f5b6 100644 --- a/microsetta_private_api/repo/tests/test_sample.py +++ b/microsetta_private_api/repo/tests/test_sample.py @@ -152,7 +152,7 @@ def test_get_supplied_kit_id_by_sample(self): 1, 1, "UNITTEST", - None, + [[]], [1] ) From c30b63f8c65f135119d7ad07352f247a66cc55c2 Mon Sep 17 00:00:00 2001 From: Cassidy Symons Date: Sun, 15 Dec 2024 20:43:19 -0800 Subject: [PATCH 32/33] Lint --- microsetta_private_api/admin/admin_impl.py | 2 +- .../admin/tests/test_admin_repo.py | 15 +++++++-------- microsetta_private_api/repo/admin_repo.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 2ea004240..a128b94f1 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -350,7 +350,7 @@ def add_barcodes_to_kits(body, token_info): code=422, message=error_str ), 422 - + diag = admin_repo.add_barcodes_to_kits(kit_ids, barcodes) t.commit() diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 8e19d8237..1f8f7312b 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -456,7 +456,7 @@ def test_get_project_barcodes_by_id(self): created = admin_repo.create_kits(2, 3, 'foo', - [[],[],[]], + [[], [], []], [output_id, ]) exp = [] @@ -612,14 +612,14 @@ def test_create_kits_fail_nonexistent_project(self): admin_repo.create_kits(5, 3, '', - [[],[],[]], + [[], [], []], [10000, SurveyTemplateRepo.VIOSCREEN_ID]) def test_create_kits_success_not_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - non_tmi = admin_repo.create_kits(5, 3, '', [[],[],[]], [33]) + non_tmi = admin_repo.create_kits(5, 3, '', [[], [], []], [33]) self.assertEqual(['created', ], list(non_tmi.keys())) self.assertEqual(len(non_tmi['created']), 5) for obj in non_tmi['created']: @@ -646,7 +646,7 @@ def test_create_kits_success_not_microsetta(self): def test_create_kits_success_is_microsetta(self): with Transaction() as t: admin_repo = AdminRepo(t) - tmi = admin_repo.create_kits(4, 2, 'foo', [[],[]], [1]) + tmi = admin_repo.create_kits(4, 2, 'foo', [[], []], [1]) self.assertEqual(['created', ], list(tmi.keys())) self.assertEqual(len(tmi['created']), 4) for obj in tmi['created']: @@ -1599,7 +1599,7 @@ def test_add_barcodes_to_kits_generated_success(self): ) obs = cur.fetchone() self.assertEqual(obs[0], 1) - + # Verify that kit_id_1 has exactly one more barcode in the # barcodes.barcode table cur.execute( @@ -1677,7 +1677,7 @@ def test_add_barcodes_to_kits_provided_success(self): ) obs = cur.fetchone() self.assertEqual(obs[0], 1) - + # Verify that kit_id_1 has exactly one more barcode in the # barcodes.barcode table cur.execute( @@ -1732,7 +1732,7 @@ def test_add_barcodes_to_kits_provided_failure(self): with Transaction() as t: admin_repo = AdminRepo(t) with self.assertRaises(psycopg2.errors.UniqueViolation): - res = admin_repo.add_barcodes_to_kits(kit_ids, barcodes) + admin_repo.add_barcodes_to_kits(kit_ids, barcodes) def test_get_barcodes_filter_kit_ids_success(self): with Transaction() as t: @@ -1957,4 +1957,3 @@ def test_get_kit_by_barcode_failure(self): admin_repo = AdminRepo(t) kit_info = admin_repo.get_kit_by_barcode(['nonexistent_barcode']) self.assertIsNone(kit_info) - diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index fe9a4cc10..4054db7f3 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -935,7 +935,7 @@ def create_kits(self, number_of_kits, number_of_samples, kit_prefix, new_barcodes = [] kit_name_and_barcode_tuples_list = [] - + # Iterate through sample slots and use provided barcodes for barcode_list in barcodes: if len(barcode_list) > 0: From 43314794c0c7a687c0c24d4c1275ad4550caec94 Mon Sep 17 00:00:00 2001 From: Cassidy Symons Date: Fri, 3 Jan 2025 14:14:39 -0800 Subject: [PATCH 33/33] Adjustments based on feedback --- microsetta_private_api/admin/admin_impl.py | 32 +++++++++++---- .../admin/tests/test_admin_repo.py | 40 ++++++++++++++++++- microsetta_private_api/repo/admin_repo.py | 22 ++++++++++ 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index a128b94f1..9cee65ebc 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -267,11 +267,21 @@ def create_kits(body, token_info): number_of_samples = body['number_of_samples'] kit_prefix = body.get('kit_id_prefix', None) project_ids = body['project_ids'] + # NB: The barcodes variable is a list of lists, structured with the notion + # of representing barcodes' constituent lists each representing one sample + # slot in the set of kits being created. + # len(barcodes) == number_of_samples && len(barcodes[x]) == number_of_kits + # This allows the creation of a set of kits with a mixture of + # system-generated and admin-provided barcodes. barcodes = body['barcodes'] with Transaction() as t: admin_repo = AdminRepo(t) + # Lock the barcode table for the duration of checking the existience + # of barcodes, then (hopefully) creating/inserting new ones + t.lock_table("barcodes.barcode") + # First, do some basic sanitation errors = [] for barcode_list in barcodes: @@ -286,8 +296,8 @@ def create_kits(body, token_info): # And verify that the barcodes don't already exist for barcode in barcode_list: - diag = admin_repo.retrieve_diagnostics_by_barcode(barcode) - if diag is not None: + diag = admin_repo.check_exists_barcode(barcode) + if diag is True: errors.append(f'Barcode {barcode} already exists') x += 1 @@ -321,26 +331,34 @@ def add_barcodes_to_kits(body, token_info): errors = [] with Transaction() as t: - kit_repo = KitRepo(t) admin_repo = AdminRepo(t) + # Lock the barcode table for the duration of checking the existience + # of barcodes, then (hopefully) adding them to kits + t.lock_table("barcodes.barcode") + # First, make sure all of the Kit IDs are valid for kit_id in kit_ids: - diag = kit_repo.get_kit_all_samples(kit_id) - if diag is None: + diag = admin_repo.check_exists_kit(kit_id) + if diag is False: errors.append(f'Kit ID {kit_id} not found') if generate_barcodes is False: # Next, check that the Barcodes are unique if we're not generating # novel ones for barcode in barcodes: - diag = admin_repo.retrieve_diagnostics_by_barcode(barcode) - if diag is not None: + diag = admin_repo.check_exists_barcode(barcode) + if diag is True: errors.append(f'Barcode {barcode} already exists') # And verify that the number of barcodes and kit IDs are equal if len(barcodes) != len(kit_ids): errors.append("Unequal number of Kit IDs and Barcodes") + else: + if len(barcodes) > 0: + errors.append( + "Barcodes may not be both generated and provided" + ) # We found one or more issues with the supplied Kit IDs or Barcodes. # Return the error message to microsetta-admin. diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index 1f8f7312b..f1ee3d0a8 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -679,11 +679,11 @@ def test_create_kits_success_mix_provided_generated_barcodes(self): # Sample Slot 1 sample_1_barcodes = [ - "2806d1ef-8992-4693-b977-1ae73b398b4c" + "thisisabarcode" ] # Sample Slot 2 sample_2_barcodes = [ - "af6ab062-163c-4abd-96f5-848c4729f8b0" + "thisisabarcodetoo" ] # Sample Slot 3 - generated sample_3_barcodes = [] @@ -1957,3 +1957,39 @@ def test_get_kit_by_barcode_failure(self): admin_repo = AdminRepo(t) kit_info = admin_repo.get_kit_by_barcode(['nonexistent_barcode']) self.assertIsNone(kit_info) + + def test_check_exists_barcode_true(self): + # This is a stable barcode in the dev database + barcode = "000004801" + + with Transaction() as t: + admin_repo = AdminRepo(t) + bc_exists = admin_repo.check_exists_barcode(barcode) + self.assertTrue(bc_exists) + + def test_check_exists_barcode_false(self): + # This is not a barcode in the database + barcode = "absenzelysium" + + with Transaction() as t: + admin_repo = AdminRepo(t) + bc_exists = admin_repo.check_exists_barcode(barcode) + self.assertFalse(bc_exists) + + def test_check_exists_kit_true(self): + # This is a stable kit ID in the dev database + kit_id = "DXHsj" + + with Transaction() as t: + admin_repo = AdminRepo(t) + kit_exists = admin_repo.check_exists_kit(kit_id) + self.assertTrue(kit_exists) + + def test_check_exists_kit_false(self): + # This is not a kit ID in the database + kit_id = "absenzelysium" + + with Transaction() as t: + admin_repo = AdminRepo(t) + kit_exists = admin_repo.check_exists_kit(kit_id) + self.assertFalse(kit_exists) diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index 4054db7f3..4fccf630b 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -1718,3 +1718,25 @@ def update_perk_fulfillment_state(self, new_state): "SET perk_fulfillment_active = %s", (new_state, ) ) + + def check_exists_barcode(self, barcode): + with self._transaction.cursor() as cur: + cur.execute( + "SELECT COUNT(barcode) " + "FROM barcodes.barcode " + "WHERE barcode = %s", + (barcode, ) + ) + res = cur.fetchone() + return True if res[0] > 0 else False + + def check_exists_kit(self, kit_id): + with self._transaction.cursor() as cur: + cur.execute( + "SELECT COUNT(kit_id) " + "FROM barcodes.kit " + "WHERE kit_id = %s", + (kit_id, ) + ) + res = cur.fetchone() + return True if res[0] > 0 else False