diff --git a/microsetta_private_api/admin/admin_impl.py b/microsetta_private_api/admin/admin_impl.py index 44e25c08b..ff62c9ada 100644 --- a/microsetta_private_api/admin/admin_impl.py +++ b/microsetta_private_api/admin/admin_impl.py @@ -917,3 +917,32 @@ def get_vioscreen_sample_to_user(token_info): st_repo = SurveyTemplateRepo(t) data = st_repo.get_vioscreen_sample_to_user() return jsonify(data), 200 + + +def map_to_rack(token_info, sample_barcode, body): + validate_admin_access(token_info) + + with Transaction() as t: + admin_repo = AdminRepo(t) + scan_id = admin_repo.map_to_rack(sample_barcode, body) + t.commit() + + response = jsonify({"scan_id": scan_id}) + response.status_code = 201 + return response + + +def get_samples_from_rack(token_info, rack_id, bulk_scan_id): + validate_admin_access(token_info) + + with Transaction() as t: + admin_repo = AdminRepo(t) + row = admin_repo.get_rack_samples(rack_id, bulk_scan_id) + if row is None: + response = jsonify({"message": "sample does not exist!"}) + response.status_code = 404 + return response + else: + response = jsonify({"result": row}) + response.status_code = 201 + return response diff --git a/microsetta_private_api/admin/tests/test_admin_api.py b/microsetta_private_api/admin/tests/test_admin_api.py index 8c9349399..11955bc29 100644 --- a/microsetta_private_api/admin/tests/test_admin_api.py +++ b/microsetta_private_api/admin/tests/test_admin_api.py @@ -1,6 +1,7 @@ import pytest from unittest import TestCase from unittest.mock import patch +import uuid from flask import Response import json import microsetta_private_api.server @@ -1270,6 +1271,24 @@ def mock_func(*args, **kwargs): headers=MOCK_HEADERS) self.assertEqual(204, response.status_code) + def test_sample_map_to_rack(self): + barcode = "000001024" + scan_info = { + 'rack_id': '005', + 'location_row': 'B', + 'location_col': '01', + 'bulk_scan_id': str(uuid.uuid4()) + } + + response = self.client.post( + '/api/admin/rack/{0}/add'.format(barcode), + content_type="application/json", + data=json.dumps(scan_info), + headers=MOCK_HEADERS + ) + + self.assertEquals(201, response.status_code) + def test_generate_ffq_codes(self): input_json = json.dumps({"code_quantity": 2}) diff --git a/microsetta_private_api/admin/tests/test_admin_repo.py b/microsetta_private_api/admin/tests/test_admin_repo.py index f6a6e645f..435bd2350 100644 --- a/microsetta_private_api/admin/tests/test_admin_repo.py +++ b/microsetta_private_api/admin/tests/test_admin_repo.py @@ -4,6 +4,7 @@ import psycopg2 import psycopg2.extras from dateutil.relativedelta import relativedelta +import uuid import microsetta_private_api.model.project as p @@ -1324,6 +1325,43 @@ def test_set_kit_uuids_for_dak_order(self): for expected_record in expected_records: self.assertIn(expected_record, curr_records) + def test_map_to_rack_fail(self): + scan_info = { + 'rack_id': '001', + 'location_row': 'A', + 'location_col': '02' + } + + with Transaction() as t: + barcode = '00000000' + + admin_repo = AdminRepo(t) + with self.assertRaises(NotFound): + admin_repo.map_to_rack(barcode, scan_info) + + def test_map_to_rack_success(self): + bulk_scan_id = str(uuid.uuid4()) + scan_info = { + 'rack_id': '001', + 'location_row': 'A', + 'location_col': '02', + 'bulk_scan_id': bulk_scan_id + } + + id = "" + samples = None + with Transaction() as t: + barcode = '000001024' + + admin_repo = AdminRepo(t) + id = admin_repo.map_to_rack(barcode, scan_info) + + rack_id = '001' + samples = admin_repo.get_rack_samples(rack_id, bulk_scan_id) + + self.assertTrue(len(id) > 0) + self.assertEquals(len(samples), 1) + def test_create_ffq_code(self): with Transaction() as t: admin_repo = AdminRepo(t) diff --git a/microsetta_private_api/api/microsetta_private_api.yaml b/microsetta_private_api/api/microsetta_private_api.yaml index d773cf37b..7874735f4 100644 --- a/microsetta_private_api/api/microsetta_private_api.yaml +++ b/microsetta_private_api/api/microsetta_private_api.yaml @@ -2394,6 +2394,63 @@ paths: schema: type: object + '/admin/rack/{sample_barcode}/add': + post: + # Note: We might want to be able to differentiate system administrator operations + # from technician operations in the future by user accounts and the routes they post to + operationId: microsetta_private_api.admin.admin_impl.map_to_rack + tags: + - Admin + summary: Add sample to rack for a barcode + description: Add sample to rack for a barcode + parameters: + - $ref: '#/components/parameters/sample_barcode' + requestBody: + content: + application/json: + schema: + type: object + properties: + rack_id: + type: string + location_col: + type: string + location_row: + type: string + bulk_scan_id: + type: string + responses: + '201': + description: Successfully mapped new barcode scan to rack + content: + application/json: + schema: + type: object + + '/admin/rack/{bulk_scan_id}/sample/{rack_id}': + get: + operationId: microsetta_private_api.admin.admin_impl.get_samples_from_rack + tags: + - Admin + summary: Get sample details from rack by rack id + description: Get sample details from rack by rack id + parameters: + - $ref: '#/components/parameters/rack_id' + - $ref: '#/components/parameters/bulk_scan_id' + responses: + '201': + description: Successfully fetched sample details from rack by rack id + content: + application/json: + schema: + type: object + '404': + description: Sample does not exist + content: + application/json: + schema: + type: object + '/admin/metadata/samples/{sample_barcode}/surveys/{survey_template_id}': get: operationId: microsetta_private_api.admin.admin_impl.sample_pulldown_single_survey @@ -2977,6 +3034,18 @@ components: description: Type of consent schema: $ref: '#/components/schemas/consent_type' + rack_id: + name: rack_id + in: path + description: Rack id for a particular sample placed in rack + schema: + $ref: '#/components/schemas/rack_id' + bulk_scan_id: + name: bulk_scan_id + in: path + description: Unique identifier for each bulk scan cycle + schema: + $ref: '#/components/schemas/bulk_scan_id' # query parameters activation_code: @@ -3292,6 +3361,21 @@ components: items: type: string example: "https://wherever.com" + rack_id: + type: string + example: "0000" + location_col: + type: string + example: "1" + location_row: + type: string + example: "A" + rack_id: + type: string + example: "aaaaaaaa-bbbb-cccc-dddd-eeeeffffffff" + bulk_scan_id: + type: string + example: "aaaaaaaa-bbbb-cccc-dddd-eeeeffffffff" sample: type: object @@ -3319,6 +3403,18 @@ components: accession_urls: $ref: '#/components/schemas/accession_urls' + scanned_sample: + type: object + properties: + rack_id: + $ref: '#/components/schemas/rack_id' + location_col: + $ref: '#/components/schemas/location_col' + location_row: + $ref: '#/components/schemas/location_row' + sample_datetime: + $ref: '#/components/schemas/sample_datetime' + preparation: type: object properties: diff --git a/microsetta_private_api/db/patches/0116.sql b/microsetta_private_api/db/patches/0116.sql new file mode 100644 index 000000000..65d9467c3 --- /dev/null +++ b/microsetta_private_api/db/patches/0116.sql @@ -0,0 +1,12 @@ +CREATE TABLE barcodes.rack_samples ( + location_id uuid NOT NULL DEFAULT uuid_generate_v4(), + rack_id varchar NOT NULL, + sample_id varchar NOT NULL, + location_row varchar NOT NULL, + location_col varchar NOT NULL, + date_time TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY (location_id) +); + +ALTER TABLE barcodes.rack_samples +ADD COLUMN scan_id varchar NOT NULL; \ No newline at end of file diff --git a/microsetta_private_api/repo/admin_repo.py b/microsetta_private_api/repo/admin_repo.py index e5cb13835..572c5c7c1 100644 --- a/microsetta_private_api/repo/admin_repo.py +++ b/microsetta_private_api/repo/admin_repo.py @@ -1136,6 +1136,70 @@ def search_barcode(self, sql_cond, cond_params): ) return [r[0] for r in cur.fetchall()] + def map_to_rack(self, sample_barcode, scan_info): + with self._transaction.cursor() as cur: + + # not actually using the result, just checking there IS one + # to ensure this is a valid barcode + cur.execute( + "SELECT barcode FROM barcodes.barcode WHERE barcode=%s", + (sample_barcode,) + ) + + if cur.rowcount == 0: + raise NotFound("No such barcode: %s" % sample_barcode) + elif cur.rowcount > 1: + # Note: This "can't" happen. + raise RepoException("ERROR: Multiple barcode entries would be " + "affected by scan; failing out") + + # put a new row in the barcodes.barcode_scans table + new_uuid = str(uuid.uuid4()) + scan_args = ( + new_uuid, + scan_info['rack_id'], + sample_barcode, + scan_info['location_row'], + scan_info['location_col'], + datetime.datetime.now(), + scan_info['bulk_scan_id'] + ) + + cur.execute( + "INSERT INTO barcodes.rack_samples " + "(location_id, rack_id, sample_id, " + "location_row, location_col, date_time, scan_id) " + "VALUES (%s, %s, %s, %s, %s, %s, %s)", + scan_args + ) + + return new_uuid + + def get_rack_samples(self, rack_id, bulk_scan_id): + with self._transaction.dict_cursor() as cur: + + cur.execute( + "SELECT DISTINCT ON (sample_id) sample_id, location_row, " + "location_col FROM barcodes.rack_samples WHERE rack_id=%s " + "AND scan_id=%s " + "ORDER BY sample_id, date_time DESC", + (rack_id, bulk_scan_id, ) + ) + + sample_rows = cur.fetchall() + + if len(sample_rows) == 0: + raise NotFound("No barcode with rack id: %s" % rack_id) + + result = [] + for row in sample_rows: + data_to_return = {} + data_to_return["location_row"] = row["location_row"] + data_to_return["location_col"] = row["location_col"] + data_to_return["barcode"] = row["sample_id"] + result.append(data_to_return) + return result + def get_survey_metadata(self, sample_barcode, survey_template_id=None): ''' Return all surveys associated with a given barcode.