Skip to content

Commit 70c6231

Browse files
committed
Added filters for prior cancer history and temporary address using symbolic criteria
- Cancer filter maps 'yes'/'no' to pkg_letters.f_subj_prev_diagnosed_cancer = 'Y' or 'N' - Temporary address filter joins sd_address_t with ADDRESS_TYPE = 13043 - For 'yes': filters on EFFECTIVE_FROM IS NOT NULL - For 'no': filters on EFFECTIVE_FROM IS NULL - Both use shared YesNoType mapping for symbolic input validation
1 parent a91dc9b commit 70c6231

File tree

4 files changed

+87
-29
lines changed

4 files changed

+87
-29
lines changed

classes/yes_no_type.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class YesNoType:
2+
YES = "yes"
3+
NO = "no"
4+
5+
_valid = {YES, NO}
6+
7+
@classmethod
8+
def from_description(cls, description: str) -> str:
9+
key = description.strip().lower()
10+
if key not in cls._valid:
11+
raise ValueError(f"Expected 'yes' or 'no', got: '{description}'")
12+
return key

utils/oracle/mock_selection_builder.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ def get_id(cls, description: str) -> int:
3131
return cls._label_to_id[key]
3232

3333

34+
class YesNoType:
35+
YES = "yes"
36+
NO = "no"
37+
38+
_valid = {YES, NO}
39+
40+
@classmethod
41+
def from_description(cls, description: str) -> str:
42+
key = description.strip().lower()
43+
if key not in cls._valid:
44+
raise ValueError(f"Expected 'yes' or 'no', got: '{description}'")
45+
return key
46+
47+
3448
def parse_notify_criteria(criteria: str) -> dict:
3549
"""
3650
Parses criteria strings like 'S1 - new' or 'S1 (S1w) - sending' into parts.
@@ -121,34 +135,25 @@ def _add_join_to_surveillance_review(self):
121135
# Replace this with the one you want to test,
122136
# then use utils/oracle/test_subject_criteria_dev.py to run your scenarios
123137

124-
def _add_criteria_notify_archived_message_status(self) -> None:
138+
def _add_criteria_has_temporary_address(self) -> None:
125139
"""
126-
Filters subjects based on archived Notify message criteria, e.g. 'S1 (S1w) - sending'.
140+
Filters subjects based on whether they have a temporary address on record.
127141
"""
128142
try:
129-
parts = parse_notify_criteria(self.criteria_value)
130-
status = parts["status"]
143+
answer = YesNoType.from_description(self.criteria_value)
131144

132-
clause = "NOT EXISTS" if status == "none" else "EXISTS"
133-
134-
self.sql_where.append(f"AND {clause} (")
135-
self.sql_where.append(
136-
"SELECT 1 FROM notify_message_record nmr "
137-
"INNER JOIN notify_message_batch nmb ON nmb.batch_id = nmr.batch_id "
138-
"INNER JOIN notify_message_definition nmd ON nmd.message_definition_id = nmb.message_definition_id "
139-
"WHERE nmr.subject_id = ss.screening_subject_id "
145+
# INNER JOIN on sd_address_t with address type 13043 (temporary)
146+
self.sql_from.append(
147+
"INNER JOIN sd_address_t adds ON adds.contact_id = c.contact_id "
148+
"AND adds.ADDRESS_TYPE = 13043"
140149
)
141150

142-
event_status_id = NotifyEventStatus.get_id(parts["type"])
143-
self.sql_where.append(f"AND nmd.event_status_id = {event_status_id} ")
144-
145-
if "code" in parts and parts["code"]:
146-
self.sql_where.append(f"AND nmd.message_code = '{parts['code']}' ")
147-
148-
if status != "none":
149-
self.sql_where.append(f"AND nmr.message_status = '{status}' ")
150-
151-
self.sql_where.append(")")
152-
151+
# Apply logic for EFFECTIVE_FROM based on yes/no
152+
if answer == YesNoType.YES:
153+
self.sql_from.append("AND adds.EFFECTIVE_FROM IS NOT NULL")
154+
elif answer == YesNoType.NO:
155+
self.sql_where.append("AND adds.EFFECTIVE_FROM IS NULL")
156+
else:
157+
raise ValueError()
153158
except Exception:
154159
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)

utils/oracle/subject_selection_query_builder.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
)
5757
from classes.prevalent_incident_status_type import PrevalentIncidentStatusType
5858
from classes.notify_event_status import NotifyEventStatus
59+
from classes.yes_no_type import YesNoType
5960

6061

6162
class SubjectSelectionQueryBuilder:
@@ -2316,6 +2317,43 @@ def _add_criteria_notify_archived_message_status(self) -> None:
23162317
except Exception:
23172318
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
23182319

2320+
def _add_criteria_has_previously_had_cancer(self) -> None:
2321+
"""
2322+
Filters based on whether the subject previously had cancer.
2323+
"""
2324+
try:
2325+
answer = YesNoType.from_description(self.criteria_value)
2326+
condition = "'Y'" if answer == YesNoType.YES else "'N'"
2327+
2328+
self.sql_where.append(
2329+
f"AND pkg_letters.f_subj_prev_diagnosed_cancer(pi_subject_id => ss.screening_subject_id) = {condition}"
2330+
)
2331+
except Exception:
2332+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
2333+
2334+
def _add_criteria_has_temporary_address(self) -> None:
2335+
"""
2336+
Filters subjects based on whether they have a temporary address on record.
2337+
"""
2338+
try:
2339+
answer = YesNoType.from_description(self.criteria_value)
2340+
2341+
# INNER JOIN on sd_address_t with address type 13043 (temporary)
2342+
self.sql_from.append(
2343+
"INNER JOIN sd_address_t adds ON adds.contact_id = c.contact_id "
2344+
"AND adds.ADDRESS_TYPE = 13043"
2345+
)
2346+
2347+
# Apply logic for EFFECTIVE_FROM based on yes/no
2348+
if answer == YesNoType.YES:
2349+
self.sql_from.append("AND adds.EFFECTIVE_FROM IS NOT NULL")
2350+
elif answer == YesNoType.NO:
2351+
self.sql_where.append("AND adds.EFFECTIVE_FROM IS NULL")
2352+
else:
2353+
raise ValueError()
2354+
except Exception:
2355+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
2356+
23192357
# ------------------------------------------------------------------------
23202358
# 🧬 CADS Clinical Dataset Filters
23212359
# ------------------------------------------------------------------------

utils/oracle/test_subject_criteria_dev.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ def make_builder(key, value, index=0, comparator="="):
4141
return b
4242

4343

44-
# === Test: NOTIFY_ARCHIVED_MESSAGE_STATUS (S1 (S1w) - sending) ===
45-
b = make_builder(
46-
SubjectSelectionCriteriaKey.NOTIFY_ARCHIVED_MESSAGE_STATUS, "S1 (S1w) - sending"
47-
)
48-
b._add_criteria_notify_archived_message_status()
49-
print("=== NOTIFY_ARCHIVED_MESSAGE_STATUS (S1 (S1w) - sending) ===")
44+
# === Test: DEMOGRAPHICS_TEMPORARY_ADDRESS (yes) ===
45+
b = make_builder(SubjectSelectionCriteriaKey.DEMOGRAPHICS_TEMPORARY_ADDRESS, "yes")
46+
b._add_criteria_has_temporary_address()
47+
print(b.dump_sql())
48+
49+
# === Test: DEMOGRAPHICS_TEMPORARY_ADDRESS (no) ===
50+
b = make_builder(SubjectSelectionCriteriaKey.DEMOGRAPHICS_TEMPORARY_ADDRESS, "no")
51+
b._add_criteria_has_temporary_address()
52+
print("=== DEMOGRAPHICS_TEMPORARY_ADDRESS (no) ===")
5053
print(b.dump_sql(), end="\n\n")

0 commit comments

Comments
 (0)