From 0577d559dbbe103234a0bd905d09b7c50d88f835 Mon Sep 17 00:00:00 2001 From: mgcam Date: Tue, 19 Mar 2024 11:40:11 +0000 Subject: [PATCH] Introduced 'On hold external' QC state. This state will be assigned to wells when the outcome of the QC assessment depends on liasing with a third party. The existing 'On hold' state will be used for flagging internal investigations. As fas as QC statuses and workflow is concerned, the new state is handled in the same way as the existing 'On hold' state. --- .../versions/2.1.0_extend_qc_state_dict.py | 22 +++++++++++++++++++ lang_qc/db/helper/wells.py | 4 ++-- lang_qc/endpoints/config.py | 4 +++- tests/endpoints/test_config.py | 1 + tests/endpoints/test_dump_qc_states.py | 2 +- tests/endpoints/test_filtered_wells.py | 5 ++++- tests/endpoints/test_wells4run.py | 2 +- tests/fixtures/well_data.py | 3 ++- tests/test_pb_wells_factory.py | 11 +++++++--- tests/test_qc_state_retrieval.py | 9 ++++---- 10 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 alembic/versions/2.1.0_extend_qc_state_dict.py diff --git a/alembic/versions/2.1.0_extend_qc_state_dict.py b/alembic/versions/2.1.0_extend_qc_state_dict.py new file mode 100644 index 00000000..8844d8dd --- /dev/null +++ b/alembic/versions/2.1.0_extend_qc_state_dict.py @@ -0,0 +1,22 @@ +"""extend_qc_state_dict + +Revision ID: 2.1.0 +Revises: 2.0.0 +Create Date: 2024-03-19 12:31:26.359652 + +""" +from alembic import op + +# revision identifiers, used by Alembic. +revision = "2.1.0" +down_revision = "2.0.0" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute("INSERT INTO qc_state_dict VALUES ('On hold external', NULL)") + + +def downgrade() -> None: + op.execute("DELETE FROM qc_state_dict WHERE state='On hold external'") diff --git a/lang_qc/db/helper/wells.py b/lang_qc/db/helper/wells.py index 8e56a0a8..976dd632 100644 --- a/lang_qc/db/helper/wells.py +++ b/lang_qc/db/helper/wells.py @@ -169,10 +169,10 @@ class PacBioPagedWellsFactory(WellWh, PagedResponse): # For MySQL it's OK to use case-sensitive comparison operators since # its string comparisons for the collation we use are case-insensitive. FILTERS: ClassVar = { - QcFlowStatusEnum.ON_HOLD.name: (QcStateDict.state == "On hold"), + QcFlowStatusEnum.ON_HOLD.name: (QcStateDict.state.ilike("On hold%")), QcFlowStatusEnum.QC_COMPLETE.name: (QcState.is_preliminary == 0), QcFlowStatusEnum.IN_PROGRESS.name: and_( - QcState.is_preliminary == 1, QcStateDict.state != "On hold" + QcState.is_preliminary == 1, QcStateDict.state.notilike("On hold%") ), QcFlowStatusEnum.ABORTED.name: or_( PacBioRunWellMetrics.well_status.like("Abort%"), diff --git a/lang_qc/endpoints/config.py b/lang_qc/endpoints/config.py index 2436ab16..0f8dbb19 100644 --- a/lang_qc/endpoints/config.py +++ b/lang_qc/endpoints/config.py @@ -65,6 +65,8 @@ def _states_for_update(session) -> List: states = [] for (name, row) in qc_state_dict(session).items(): if name not in ["Aborted", "Claimed"]: - states.append({"description": name, "only_prelim": row.state == "On hold"}) + states.append( + {"description": name, "only_prelim": "on hold" in row.state.lower()} + ) return states diff --git a/tests/endpoints/test_config.py b/tests/endpoints/test_config.py index 85f63557..97ab9e76 100644 --- a/tests/endpoints/test_config.py +++ b/tests/endpoints/test_config.py @@ -23,6 +23,7 @@ def test_get_config(test_client: TestClient, load_dicts_and_users): {"description": "Failed, Instrument", "only_prelim": False}, {"description": "Failed, SMRT cell", "only_prelim": False}, {"description": "On hold", "only_prelim": True}, + {"description": "On hold external", "only_prelim": True}, {"description": "Undecided", "only_prelim": False}, ], } diff --git a/tests/endpoints/test_dump_qc_states.py b/tests/endpoints/test_dump_qc_states.py index 057fe3d5..49247ee0 100644 --- a/tests/endpoints/test_dump_qc_states.py +++ b/tests/endpoints/test_dump_qc_states.py @@ -41,7 +41,7 @@ def test_get_qc_by_product_id(test_client: TestClient, load_data4well_retrieval) assert SECOND_GOOD_CHECKSUM in response_data list_1 = response_data[FIRST_GOOD_CHECKSUM] list_2 = response_data[SECOND_GOOD_CHECKSUM] - qc_states = ["On hold", "Failed, Instrument"] + qc_states = ["On hold external", "Failed, Instrument"] for index, l in enumerate([list_1, list_2]): assert len(l) == 2 # The list of QC state objects contains QC states diff --git a/tests/endpoints/test_filtered_wells.py b/tests/endpoints/test_filtered_wells.py index c65bd71f..4ea86edb 100644 --- a/tests/endpoints/test_filtered_wells.py +++ b/tests/endpoints/test_filtered_wells.py @@ -81,7 +81,10 @@ def test_on_hold_filter(test_client: TestClient, load_data4well_retrieval): """Test passing `on_hold` filter.""" status = "on_hold" - expected_data = [{"TRACTION_RUN_1:D1": "On hold"}, {"TRACTION_RUN_1:B1": "On hold"}] + expected_data = [ + {"TRACTION_RUN_1:D1": "On hold external"}, + {"TRACTION_RUN_1:B1": "On hold"}, + ] num_total = len(expected_data) response = test_client.get( diff --git a/tests/endpoints/test_wells4run.py b/tests/endpoints/test_wells4run.py index 518e1c20..1347fce7 100644 --- a/tests/endpoints/test_wells4run.py +++ b/tests/endpoints/test_wells4run.py @@ -26,7 +26,7 @@ def test_existing_run(test_client: TestClient, load_data4well_retrieval): label_list = [well["label"] for well in resp["wells"]] assert label_list == ["A1", "B1", "C1", "D1"] qc_states = [well["qc_state"]["qc_state"] for well in resp["wells"]] - assert qc_states == ["Claimed", "On hold", "Claimed", "On hold"] + assert qc_states == ["Claimed", "On hold", "Claimed", "On hold external"] assert ( resp["wells"][0]["instrument_name"] == "64016" diff --git a/tests/fixtures/well_data.py b/tests/fixtures/well_data.py index abb99a36..3754c927 100644 --- a/tests/fixtures/well_data.py +++ b/tests/fixtures/well_data.py @@ -30,6 +30,7 @@ {"state": "Passed", "outcome": 1}, {"state": "Claimed", "outcome": None}, {"state": "On hold", "outcome": None}, + {"state": "On hold external", "outcome": None}, ] PLATFORMS = [ @@ -66,7 +67,7 @@ ["TRACTION_RUN_1", "A1", "Claimed", True, "2022-12-07 07:15:19", None], ["TRACTION_RUN_1", "B1", "On hold", True, "2022-12-08 07:15:19", None], ["TRACTION_RUN_1", "C1", "Claimed", True, "2022-12-08 08:15:19", None], - ["TRACTION_RUN_1", "D1", "On hold", True, "2022-12-08 09:15:19", None], + ["TRACTION_RUN_1", "D1", "On hold external", True, "2022-12-08 09:15:19", None], ["TRACTION_RUN_1", "E1", "Claimed", True, "2022-12-07 09:15:19", None], ["TRACTION_RUN_2", "A1", "Failed, Instrument", True, "2022-12-07 15:13:56", 1], ["TRACTION_RUN_2", "B1", "Failed, Instrument", False, "2022-12-08 15:18:56", 1], diff --git a/tests/test_pb_wells_factory.py b/tests/test_pb_wells_factory.py index c9bbc92d..dc02f612 100644 --- a/tests/test_pb_wells_factory.py +++ b/tests/test_pb_wells_factory.py @@ -31,7 +31,7 @@ def test_query_for_status( assert isinstance(state, QcState) assert state.is_preliminary == 1 assert state.qc_type.qc_type == "sequencing" - assert state.qc_state_dict.state == "On hold" + assert state.qc_state_dict.state in ("On hold", "On hold external") compare_dates(state.date_updated, update_dates[index]) factory = PacBioPagedWellsFactory( @@ -400,8 +400,13 @@ def test_known_run_names_input( label_list = [well.label for well in wells] assert label_list == ["A1", "B1", "C1", "D1"] - qc_states = [well.qc_state.qc_state for well in wells] - expected_qc_states = ["Claimed", "On hold", "Claimed", "On hold"] + qc_states = sorted([well.qc_state.qc_state for well in wells]) + expected_qc_states = [ + "Claimed", + "Claimed", + "On hold", + "On hold external", + ] assert qc_states == expected_qc_states factory = PacBioPagedWellsFactory( diff --git a/tests/test_qc_state_retrieval.py b/tests/test_qc_state_retrieval.py index 7babe28d..6d5813e4 100644 --- a/tests/test_qc_state_retrieval.py +++ b/tests/test_qc_state_retrieval.py @@ -30,7 +30,7 @@ def test_bulk_retrieval(qcdb_test_session, load_data4well_retrieval): # product IDs is performed. assert get_qc_states_by_id_product_list(qcdb_test_session, ["dodo"]) == {} - qc_state_descriptions = ["On hold", "Failed, Instrument"] + qc_state_descriptions = ["On hold external", "Failed, Instrument"] qc_states = get_qc_states_by_id_product_list(qcdb_test_session, two_good_ids_list) assert len(qc_states) == 2 @@ -140,7 +140,7 @@ def test_product_qc_state_retrieval(qcdb_test_session, load_data4well_retrieval) assert qc_state is not None assert qc_state.seq_product.id_product == FIRST_GOOD_CHECKSUM assert qc_state.qc_type.qc_type == "sequencing" - assert qc_state.qc_state_dict.state == "On hold" + assert qc_state.qc_state_dict.state == "On hold external" qc_state = get_qc_state_for_product( session=qcdb_test_session, id_product=FIRST_GOOD_CHECKSUM, qc_type="sequencing" @@ -148,7 +148,7 @@ def test_product_qc_state_retrieval(qcdb_test_session, load_data4well_retrieval) assert qc_state is not None assert qc_state.seq_product.id_product == FIRST_GOOD_CHECKSUM assert qc_state.qc_type.qc_type == "sequencing" - assert qc_state.qc_state_dict.state == "On hold" + assert qc_state.qc_state_dict.state == "On hold external" qc_state = get_qc_state_for_product( session=qcdb_test_session, id_product=FIRST_GOOD_CHECKSUM, qc_type="library" @@ -156,7 +156,7 @@ def test_product_qc_state_retrieval(qcdb_test_session, load_data4well_retrieval) assert qc_state is not None assert qc_state.seq_product.id_product == FIRST_GOOD_CHECKSUM assert qc_state.qc_type.qc_type == "library" - assert qc_state.qc_state_dict.state == "On hold" + assert qc_state.qc_state_dict.state == "On hold external" qc_state = get_qc_state_for_product(qcdb_test_session, SECOND_GOOD_CHECKSUM) assert qc_state is not None @@ -183,6 +183,7 @@ def test_dict_helper(qcdb_test_session, load_dicts_and_users): "Failed, SMRT cell", "Claimed", "On hold", + "On hold external", "Undecided", ] assert list(qc_state_dict(qcdb_test_session).keys()) == expected_sorted_states