Skip to content

Commit

Permalink
Merge pull request #377 from sanger/DPL-018-dont-sequence-ZZA-controls
Browse files Browse the repository at this point in the history
DPL-018 Don't sequence control sample with root sample ID prefix “ZZA” [Heron]
  • Loading branch information
KatyTaylor authored Jul 7, 2021
2 parents 2b07fce + 11e7368 commit 5be9fbd
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .release-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.18.0
1.19.0
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ A sample is filtered positive if:
More information on this version can be found on [this](https://ssg-confluence.internal.sanger.ac.uk/display/PSDPUB/UAT+6th+October+2020)
Confluence page.

#### Version 2 `v2` - **Current Version**
#### Version 2 `v2`

A sample is filtered positive if:

Expand All @@ -215,6 +215,14 @@ A sample is filtered positive if:
More information on this version can be found on [this](https://ssg-confluence.internal.sanger.ac.uk/display/PSDPUB/Fit+to+pick+-+v2)
Confluence page.

#### Version 3 `v3` - **Current Version**

A sample is filtered positive if:

- it has a 'Positive' RESULT
- it is not a control (ROOT_SAMPLE_ID does not start with 'CBIQA_', 'QC0', or 'ZZA')
- all of CH1_CQ, CH2_CQ and CH3_CQ are `None`, or one of these is less than or equal to 30

#### Propagating Filtered Positive version changes to MongoDB, MLWH and (optionally) DART

On changing the positive filtering version/definition, all unpicked samples stored in MongoDB, MLWH and DART need
Expand Down
13 changes: 12 additions & 1 deletion crawler/filtered_positive_identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
FILTERED_POSITIVE_VERSION_0 = "v0" # pre-filtered_positive definitions
FILTERED_POSITIVE_VERSION_1 = "v1" # initial implementation, as per GPL-669
FILTERED_POSITIVE_VERSION_2 = "v2" # updated as per GPL-699 and GPL-740
FILTERED_POSITIVE_VERSION_3 = "v3" # updated as per DPL-018


class FilteredPositiveIdentifier(ABC):
Expand Down Expand Up @@ -81,7 +82,7 @@ def current_filtered_positive_identifier() -> FilteredPositiveIdentifier:
Returns:
{FilteredPositiveIdentifier} -- the current filtered positive identifier
"""
return FilteredPositiveIdentifierV2()
return FilteredPositiveIdentifierV3()


def filtered_positive_identifier_by_version(version: str) -> FilteredPositiveIdentifier:
Expand All @@ -99,6 +100,8 @@ def filtered_positive_identifier_by_version(version: str) -> FilteredPositiveIde
return FilteredPositiveIdentifierV1()
elif version == FILTERED_POSITIVE_VERSION_2:
return FilteredPositiveIdentifierV2()
elif version == FILTERED_POSITIVE_VERSION_3:
return FilteredPositiveIdentifierV3()
else:
raise ValueError(f"'{version}' is not a known filtered positive version")

Expand All @@ -123,3 +126,11 @@ def __init__(self):
self.version = FILTERED_POSITIVE_VERSION_2
self.root_sample_id_control_regex = re.compile("^(?:CBIQA_|QC0|ZZA000)")
self.evaluate_ct_values = True


class FilteredPositiveIdentifierV3(FilteredPositiveIdentifier):
def __init__(self):
super(FilteredPositiveIdentifierV3, self).__init__()
self.version = FILTERED_POSITIVE_VERSION_3
self.root_sample_id_control_regex = re.compile("^(?:CBIQA_|QC0|ZZA)")
self.evaluate_ct_values = True
116 changes: 115 additions & 1 deletion tests/test_filtered_positive_identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
FILTERED_POSITIVE_VERSION_0,
FILTERED_POSITIVE_VERSION_1,
FILTERED_POSITIVE_VERSION_2,
FILTERED_POSITIVE_VERSION_3,
FilteredPositiveIdentifierV0,
FilteredPositiveIdentifierV1,
FilteredPositiveIdentifierV2,
FilteredPositiveIdentifierV3,
current_filtered_positive_identifier,
filtered_positive_identifier_by_version,
)
Expand All @@ -39,7 +41,7 @@ def positive_sample():

def test_current_filtered_positive_identifier():
identifier = current_filtered_positive_identifier()
assert identifier.version == FILTERED_POSITIVE_VERSION_2
assert identifier.version == FILTERED_POSITIVE_VERSION_3


# ----- tests for filtered_positive_identifier_by_version() -----
Expand All @@ -60,6 +62,11 @@ def test_filtered_positive_identifier_by_version_returns_V2_identifier():
assert identifier.version == FILTERED_POSITIVE_VERSION_2


def test_filtered_positive_identifier_by_version_returns_V3_identifier():
identifier = filtered_positive_identifier_by_version(FILTERED_POSITIVE_VERSION_3)
assert identifier.version == FILTERED_POSITIVE_VERSION_3


def test_filtered_positive_identifier_by_version_raises_unknown_version():
with pytest.raises(ValueError):
filtered_positive_identifier_by_version("unknown version")
Expand Down Expand Up @@ -301,3 +308,110 @@ def test_v2_is_positive_returns_false_all_ct_values_greater_than_30():
sample[FIELD_CH2_CQ] = Decimal128("41.12345678")
sample[FIELD_CH3_CQ] = Decimal128("42.12345678")
assert identifier.is_positive(sample) is False


# ----- tests for FilteredPositiveIdentifierV3 -----


def test_v3_version():
identifier = FilteredPositiveIdentifierV3()
assert identifier.version == FILTERED_POSITIVE_VERSION_3


def test_v3_is_positive_returns_true_matching_criteria():
identifier = FilteredPositiveIdentifierV3()

# expected positive match
sample = positive_sample()
assert identifier.is_positive(sample) is True

# case invariant positive match
sample = positive_sample()
sample[FIELD_RESULT] = "POSITIVE"
assert identifier.is_positive(sample) is True

# 3x mix of ct values
sample = positive_sample()
sample[FIELD_CH2_CQ] = Decimal128("41.12345678")
sample[FIELD_CH3_CQ] = None
assert identifier.is_positive(sample) is True

sample = positive_sample()
sample[FIELD_CH1_CQ] = None
sample[FIELD_CH3_CQ] = Decimal128("42.12345678")
assert identifier.is_positive(sample) is True

sample = positive_sample()
sample[FIELD_CH1_CQ] = Decimal128("40.12345678")
sample[FIELD_CH2_CQ] = None
assert identifier.is_positive(sample) is True

# all ct values None
sample = positive_sample()
sample[FIELD_CH1_CQ] = None
sample[FIELD_CH2_CQ] = None
sample[FIELD_CH3_CQ] = None
assert identifier.is_positive(sample) is True

# no FIELD_CHX_CQ fields
sample = {FIELD_RESULT: RESULT_VALUE_POSITIVE, FIELD_ROOT_SAMPLE_ID: "MCM001"}
assert identifier.is_positive(sample) is True


def test_v3_is_positive_returns_false_result_not_positive():
identifier = FilteredPositiveIdentifierV3()

# does not conform to regex
sample = positive_sample()
sample[FIELD_RESULT] = " positive"
assert identifier.is_positive(sample) is False

# negative result
sample = positive_sample()
sample[FIELD_RESULT] = "Negative"
assert identifier.is_positive(sample) is False

# void result
sample = positive_sample()
sample[FIELD_RESULT] = "Void"
assert identifier.is_positive(sample) is False

# 'limit of detection' result
sample = positive_sample()
sample[FIELD_RESULT] = RESULT_VALUE_LIMIT_OF_DETECTION
assert identifier.is_positive(sample) is False

# case invariant 'limit of detection' result
sample = positive_sample()
sample[FIELD_RESULT] = RESULT_VALUE_LIMIT_OF_DETECTION.upper()
assert identifier.is_positive(sample) is False


def test_v3_is_positive_returns_false_control_sample():
identifier = FilteredPositiveIdentifierV3()

sample = positive_sample()
sample[FIELD_ROOT_SAMPLE_ID] = "CBIQA_MCM001"
assert identifier.is_positive(sample) is False

sample = positive_sample()
sample[FIELD_ROOT_SAMPLE_ID] = "QC0_MCM001"
assert identifier.is_positive(sample) is False

sample = positive_sample()
sample[FIELD_ROOT_SAMPLE_ID] = "ZZA000_MCM001"
assert identifier.is_positive(sample) is False

sample = positive_sample()
sample[FIELD_ROOT_SAMPLE_ID] = "ZZA3c4j_MCM001"
assert identifier.is_positive(sample) is False


def test_v3_is_positive_returns_false_all_ct_values_greater_than_30():
identifier = FilteredPositiveIdentifierV3()

sample = positive_sample()
sample[FIELD_CH1_CQ] = Decimal128("40.12345678")
sample[FIELD_CH2_CQ] = Decimal128("41.12345678")
sample[FIELD_CH3_CQ] = Decimal128("42.12345678")
assert identifier.is_positive(sample) is False

0 comments on commit 5be9fbd

Please sign in to comment.