Skip to content

Commit b974a28

Browse files
committed
Added kit, event, and referral date filters to query builder
Includes logic for significant kit results, test kit class, referral date conditions, and event-based subqueries.
1 parent 096e2e6 commit b974a28

File tree

3 files changed

+238
-31
lines changed

3 files changed

+238
-31
lines changed

utils/oracle/mock_selection_builder.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,33 +35,26 @@ def dump_sql(self):
3535
# Replace this with the one you want to test,
3636
# then use utils/oracle/test_subject_criteria_dev.py to run your scenarios
3737

38-
def _add_criteria_latest_episode_recall_surveillance_type(self) -> None:
38+
def _add_criteria_has_referral_date(self) -> None:
3939
"""
40-
Adds a filter for recall_polyp_surv_type_id based on the type of surveillance used during recall.
41-
Supports mapped descriptions and null values.
40+
Adds a filter for the presence or timing of referral_date in the latest episode.
41+
Accepts values: yes, no, past, more_than_28_days_ago, within_the_last_28_days.
4242
"""
4343
try:
44-
value = self.criteria_value.lower()
45-
46-
recall_surv_type_map = {
47-
"routine": 500,
48-
"enhanced": 501,
49-
"annual": 502,
50-
"null": None,
44+
value = self.criteria_value.strip().lower()
45+
46+
clause_map = {
47+
"yes": "ep.referral_date IS NOT NULL",
48+
"no": "ep.referral_date IS NULL",
49+
"past": "ep.referral_date < trunc(sysdate)",
50+
"more_than_28_days_ago": "(ep.referral_date + 28) < trunc(sysdate)",
51+
"within_the_last_28_days": "(ep.referral_date + 28) > trunc(sysdate)",
5152
}
5253

53-
if value not in recall_surv_type_map:
54-
raise ValueError(f"Unknown recall surveillance type: {value}")
55-
56-
surv_id = recall_surv_type_map[value]
54+
if value not in clause_map:
55+
raise ValueError(f"Unknown referral date condition: {value}")
5756

58-
if surv_id is None:
59-
self.sql_where.append("AND ep.recall_polyp_surv_type_id IS NULL")
60-
else:
61-
comparator = self.criteria_comparator
62-
self.sql_where.append(
63-
f"AND ep.recall_polyp_surv_type_id {comparator} {surv_id}"
64-
)
57+
self.sql_where.append(f"AND {clause_map[value]}")
6558

6659
except Exception:
6760
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)

utils/oracle/subject_selection_query_builder.py

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,8 @@ def _add_variable_selection_criteria(
325325
SubjectSelectionCriteriaKey.LATEST_EPISODE_RECALL_SURVEILLANCE_TYPE
326326
):
327327
self._add_criteria_latest_episode_recall_surveillance_type()
328-
# TODO: Continue working on the case statements below, copying the Java code
329328
# ------------------------------------------------------------------------
330-
# 🔄 Event & Workflow State Criteria
329+
# 🎯 Event Code & Status Inclusion Criteria
331330
# ------------------------------------------------------------------------
332331
case SubjectSelectionCriteriaKey.LATEST_EVENT_STATUS:
333332
self._add_criteria_event_status("ep.latest_event_status_id")
@@ -922,6 +921,195 @@ def _add_criteria_latest_episode_recall_surveillance_type(self) -> None:
922921
except Exception:
923922
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
924923

924+
def _add_criteria_event_status(self, column_name: str) -> None:
925+
"""
926+
Filters based on the event status code found in the specified column.
927+
928+
Extracts a code from the criteria value (e.g. "ES01 - Invitation Received"),
929+
and injects its corresponding event_status_id into SQL.
930+
"""
931+
try:
932+
# Extract the code (e.g. "ES01") from the first word
933+
code = self.criteria_value.strip().split()[0].upper()
934+
comparator = self.criteria_comparator
935+
936+
# Simulated EventStatusType registry
937+
event_status_code_map = {
938+
"ES01": 600,
939+
"ES02": 601,
940+
"ES03": 602,
941+
"ES99": 699,
942+
# ...update with real mappings
943+
}
944+
945+
if code not in event_status_code_map:
946+
raise ValueError(f"Unknown event status code: {code}")
947+
948+
event_status_id = event_status_code_map[code]
949+
self.sql_where.append(f"AND {column_name} {comparator} {event_status_id}")
950+
951+
except Exception:
952+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
953+
954+
def _add_criteria_event_code_in_episode(self, event_is_included: bool) -> None:
955+
"""
956+
Adds a filter checking whether the given event code appears in the latest episode's event list.
957+
Uses EXISTS or NOT EXISTS depending on the flag.
958+
"""
959+
try:
960+
code = self.criteria_value.strip().split()[0].upper()
961+
962+
# Simulated EventCodeType registry
963+
event_code_map = {
964+
"EV101": 701,
965+
"EV102": 702,
966+
"EV900": 799,
967+
# ...extend with real mappings
968+
}
969+
970+
if code not in event_code_map:
971+
raise ValueError(f"Unknown event code: {code}")
972+
973+
event_code_id = event_code_map[code]
974+
975+
exists_clause = "EXISTS" if event_is_included else "NOT EXISTS"
976+
977+
self.sql_where.append(
978+
f"""AND {exists_clause} (
979+
SELECT 'evc'
980+
FROM ep_events_t evc
981+
WHERE evc.event_code_id = {event_code_id}
982+
AND evc.subject_epis_id = ep.subject_epis_id
983+
)"""
984+
)
985+
986+
except Exception:
987+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
988+
989+
def _add_criteria_event_status_in_episode(self, event_is_included: bool) -> None:
990+
"""
991+
Adds a filter that checks whether the specified event status is present
992+
in the latest episode. Uses EXISTS or NOT EXISTS depending on the flag.
993+
"""
994+
try:
995+
code = self.criteria_value.strip().split()[0].upper()
996+
997+
# Simulated EventStatusType code-to-ID map
998+
event_status_code_map = {
999+
"ES01": 600,
1000+
"ES02": 601,
1001+
"ES03": 602,
1002+
"ES99": 699,
1003+
# Extend with actual mappings
1004+
}
1005+
1006+
if code not in event_status_code_map:
1007+
raise ValueError(f"Unknown event status code: {code}")
1008+
1009+
status_id = event_status_code_map[code]
1010+
exists_clause = "EXISTS" if event_is_included else "NOT EXISTS"
1011+
1012+
self.sql_where.append(
1013+
f"""AND {exists_clause} (
1014+
SELECT 'ev'
1015+
FROM ep_events_t ev
1016+
WHERE ev.event_status_id = {status_id}
1017+
AND ev.subject_epis_id = ep.subject_epis_id
1018+
)"""
1019+
)
1020+
1021+
except Exception:
1022+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
1023+
1024+
def _add_criteria_latest_episode_kit_class(self) -> None:
1025+
"""
1026+
Filters based on the test kit class of the latest episode using a nested IN clause.
1027+
Resolves from symbolic class name (e.g. 'FIT') to test class ID.
1028+
"""
1029+
try:
1030+
value = self.criteria_value.upper()
1031+
comparator = self.criteria_comparator
1032+
1033+
# Simulated TestKitClass enum
1034+
test_kit_class_map = {
1035+
"GFOBT": 800,
1036+
"FIT": 801,
1037+
# Extend as needed
1038+
}
1039+
1040+
if value not in test_kit_class_map:
1041+
raise ValueError(f"Unknown test kit class: {value}")
1042+
1043+
kit_class_id = test_kit_class_map[value]
1044+
1045+
self.sql_where.append(
1046+
f"""AND ep.tk_type_id IN (
1047+
SELECT tkt.tk_type_id
1048+
FROM tk_type_t tkt
1049+
WHERE tkt.tk_test_class_id {comparator} {kit_class_id}
1050+
)"""
1051+
)
1052+
1053+
except Exception:
1054+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
1055+
1056+
def _add_criteria_has_significant_kit_result(self) -> None:
1057+
"""
1058+
Adds a filter to check if the latest episode has a significant kit result.
1059+
Significant values: NORMAL, ABNORMAL, WEAK_POSITIVE.
1060+
Accepts criteriaValue: "yes" or "no".
1061+
"""
1062+
try:
1063+
value = self.criteria_value.strip().lower()
1064+
1065+
if value == "yes":
1066+
exists_clause = "EXISTS"
1067+
elif value == "no":
1068+
exists_clause = "NOT EXISTS"
1069+
else:
1070+
raise ValueError(
1071+
f"Unknown response for significant kit result: {value}"
1072+
)
1073+
1074+
self.sql_where.append(
1075+
f"""AND {exists_clause} (
1076+
SELECT 'tks'
1077+
FROM tk_items_t tks
1078+
WHERE tks.screening_subject_id = ss.screening_subject_id
1079+
AND tks.logged_subject_epis_id = ep.subject_epis_id
1080+
AND tks.test_results IN ('NORMAL', 'ABNORMAL', 'WEAK_POSITIVE')
1081+
)"""
1082+
)
1083+
1084+
except Exception:
1085+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
1086+
1087+
def _add_criteria_has_referral_date(self) -> None:
1088+
"""
1089+
Adds a filter for the presence or timing of referral_date in the latest episode.
1090+
Accepts values: yes, no, past, more_than_28_days_ago, within_the_last_28_days.
1091+
"""
1092+
try:
1093+
value = self.criteria_value.strip().lower()
1094+
1095+
clause_map = {
1096+
"yes": "ep.referral_date IS NOT NULL",
1097+
"no": "ep.referral_date IS NULL",
1098+
"past": "ep.referral_date < trunc(sysdate)",
1099+
"more_than_28_days_ago": "(ep.referral_date + 28) < trunc(sysdate)",
1100+
"within_the_last_28_days": "(ep.referral_date + 28) > trunc(sysdate)",
1101+
}
1102+
1103+
if value not in clause_map:
1104+
raise ValueError(f"Unknown referral date condition: {value}")
1105+
1106+
self.sql_where.append(f"AND {clause_map[value]}")
1107+
1108+
except Exception:
1109+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
1110+
1111+
#TODO: Add methods below for other criteria keys as needed
1112+
9251113
def _add_criteria_subject_hub_code(self, user: "User") -> None:
9261114
hub_code = None
9271115
try:

utils/oracle/test_subject_criteria_dev.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,44 @@
3030
# === Example usage ===
3131
# Replace the examples below with the method you want to test
3232

33-
# === Test: LATEST_EPISODE_RECALL_SURVEILLANCE_TYPEEnhanced ===
33+
# === Test: LATEST_EPISODE_HAS_REFERRAL_DATEyes ===
3434
builder = MockSelectionBuilder(
35-
SubjectSelectionCriteriaKey.LATEST_EPISODE_RECALL_SURVEILLANCE_TYPE, "enhanced"
35+
SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_REFERRAL_DATE, "yes"
3636
)
37-
builder._add_criteria_latest_episode_recall_surveillance_type()
38-
print("=== LATEST_EPISODE_RECALL_SURVEILLANCE_TYPEenhanced ===")
37+
builder._add_criteria_has_referral_date()
38+
print("=== LATEST_EPISODE_HAS_REFERRAL_DATEyes ===")
3939
print(builder.dump_sql(), end="\n\n")
4040

41-
# === Test: LATEST_EPISODE_RECALL_SURVEILLANCE_TYPENull ===
41+
# === Test: LATEST_EPISODE_HAS_REFERRAL_DATEno ===
4242
builder = MockSelectionBuilder(
43-
SubjectSelectionCriteriaKey.LATEST_EPISODE_RECALL_SURVEILLANCE_TYPE, "null"
43+
SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_REFERRAL_DATE, "no"
4444
)
45-
builder._add_criteria_latest_episode_recall_surveillance_type()
46-
print("=== LATEST_EPISODE_RECALL_SURVEILLANCE_TYPE — null ===")
45+
builder._add_criteria_has_referral_date()
46+
print("=== LATEST_EPISODE_HAS_REFERRAL_DATE — no ===")
47+
print(builder.dump_sql(), end="\n\n")
48+
49+
# === Test: LATEST_EPISODE_HAS_REFERRAL_DATE — past ===
50+
builder = MockSelectionBuilder(
51+
SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_REFERRAL_DATE, "past"
52+
)
53+
builder._add_criteria_has_referral_date()
54+
print("=== LATEST_EPISODE_HAS_REFERRAL_DATE — past ===")
55+
print(builder.dump_sql(), end="\n\n")
56+
57+
# === Test: LATEST_EPISODE_HAS_REFERRAL_DATE — more_than_28_days_ago ===
58+
builder = MockSelectionBuilder(
59+
SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_REFERRAL_DATE,
60+
"more_than_28_days_ago",
61+
)
62+
builder._add_criteria_has_referral_date()
63+
print("=== LATEST_EPISODE_HAS_REFERRAL_DATE — more_than_28_days_ago ===")
64+
print(builder.dump_sql(), end="\n\n")
65+
66+
# === Test: LATEST_EPISODE_HAS_REFERRAL_DATE — within_the_last_28_days ===
67+
builder = MockSelectionBuilder(
68+
SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_REFERRAL_DATE,
69+
"within_the_last_28_days",
70+
)
71+
builder._add_criteria_has_referral_date()
72+
print("=== LATEST_EPISODE_HAS_REFERRAL_DATE — within_the_last_28_days ===")
4773
print(builder.dump_sql(), end="\n\n")

0 commit comments

Comments
 (0)