Skip to content

Commit 33f911c

Browse files
committed
Added diagnostic test join logic and WhichDiagnosticTest mapping class to selection builder
1 parent f6d7894 commit 33f911c

File tree

4 files changed

+257
-34
lines changed

4 files changed

+257
-34
lines changed

classes/which_diagnostic_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class WhichDiagnosticTest:
2+
"""
3+
Maps descriptive diagnostic test selection types to internal constants.
4+
Used to determine join and filter behavior in the query builder.
5+
"""
6+
7+
ANY_TEST_IN_ANY_EPISODE = "any_test_in_any_episode"
8+
ANY_TEST_IN_LATEST_EPISODE = "any_test_in_latest_episode"
9+
ONLY_TEST_IN_LATEST_EPISODE = "only_test_in_latest_episode"
10+
ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE = "only_not_void_test_in_latest_episode"
11+
LATEST_TEST_IN_LATEST_EPISODE = "latest_test_in_latest_episode"
12+
LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE = "latest_not_void_test_in_latest_episode"
13+
EARLIEST_NOT_VOID_TEST_IN_LATEST_EPISODE = (
14+
"earliest_not_void_test_in_latest_episode"
15+
)
16+
EARLIER_TEST_IN_LATEST_EPISODE = "earlier_test_in_latest_episode"
17+
LATER_TEST_IN_LATEST_EPISODE = "later_test_in_latest_episode"
18+
19+
_valid_values = {
20+
ANY_TEST_IN_ANY_EPISODE,
21+
ANY_TEST_IN_LATEST_EPISODE,
22+
ONLY_TEST_IN_LATEST_EPISODE,
23+
ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE,
24+
LATEST_TEST_IN_LATEST_EPISODE,
25+
LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE,
26+
EARLIEST_NOT_VOID_TEST_IN_LATEST_EPISODE,
27+
EARLIER_TEST_IN_LATEST_EPISODE,
28+
LATER_TEST_IN_LATEST_EPISODE,
29+
}
30+
31+
@classmethod
32+
def from_description(cls, description: str) -> str:
33+
key = description.strip().lower()
34+
if key not in cls._valid_values:
35+
raise ValueError(f"Unknown diagnostic test selection: '{description}'")
36+
return key

utils/oracle/mock_selection_builder.py

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,41 @@
77

88

99
# Add helper class stubs below
10-
class AppointmentStatusType:
10+
class WhichDiagnosticTest:
1111
"""
12-
Mocked appointment status mapping for test purposes.
13-
Replace IDs with real values from production if needed.
12+
Test stub that maps criteria values to normalized diagnostic test selection keys.
13+
Used by _add_join_to_diagnostic_tests for test harness evaluation.
1414
"""
1515

16+
ANY_TEST_IN_ANY_EPISODE = "any_test_in_any_episode"
17+
ANY_TEST_IN_LATEST_EPISODE = "any_test_in_latest_episode"
18+
ONLY_TEST_IN_LATEST_EPISODE = "only_test_in_latest_episode"
19+
ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE = "only_not_void_test_in_latest_episode"
20+
LATEST_TEST_IN_LATEST_EPISODE = "latest_test_in_latest_episode"
21+
LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE = "latest_not_void_test_in_latest_episode"
22+
EARLIEST_NOT_VOID_TEST_IN_LATEST_EPISODE = (
23+
"earliest_not_void_test_in_latest_episode"
24+
)
25+
EARLIER_TEST_IN_LATEST_EPISODE = "earlier_test_in_latest_episode"
26+
LATER_TEST_IN_LATEST_EPISODE = "later_test_in_latest_episode"
27+
1628
_mapping = {
17-
"booked": 2001,
18-
"attended": 2002,
19-
"cancelled": 2003,
20-
"dna": 2004, # Did Not Attend
29+
"any_test_in_any_episode": ANY_TEST_IN_ANY_EPISODE,
30+
"any_test_in_latest_episode": ANY_TEST_IN_LATEST_EPISODE,
31+
"only_test_in_latest_episode": ONLY_TEST_IN_LATEST_EPISODE,
32+
"only_not_void_test_in_latest_episode": ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE,
33+
"latest_test_in_latest_episode": LATEST_TEST_IN_LATEST_EPISODE,
34+
"latest_not_void_test_in_latest_episode": LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE,
35+
"earliest_not_void_test_in_latest_episode": EARLIEST_NOT_VOID_TEST_IN_LATEST_EPISODE,
36+
"earlier_test_in_latest_episode": EARLIER_TEST_IN_LATEST_EPISODE,
37+
"later_test_in_latest_episode": LATER_TEST_IN_LATEST_EPISODE,
2138
}
2239

2340
@classmethod
24-
def get_id(cls, description: str) -> int:
41+
def from_description(cls, description: str) -> str:
2542
key = description.strip().lower()
2643
if key not in cls._mapping:
27-
raise ValueError(f"Unknown appointment status: {description}")
44+
raise ValueError(f"Unknown diagnostic test type: {description}")
2845
return cls._mapping[key]
2946

3047

@@ -47,6 +64,7 @@ def __init__(self, criteria_key, criteria_value, criteria_comparator=">="):
4764
self.criteria_key_name = criteria_key.description
4865
self.criteria_value = criteria_value
4966
self.criteria_comparator = criteria_comparator
67+
self.criteria_index: int = 0
5068
self.sql_where = []
5169
self.sql_from = []
5270

@@ -74,21 +92,79 @@ def _add_join_to_latest_episode(self) -> None:
7492
# Replace this with the one you want to test,
7593
# then use utils/oracle/test_subject_criteria_dev.py to run your scenarios
7694

77-
def _add_criteria_appointment_status(self) -> None:
78-
"""
79-
Filters appointments by status (e.g. booked, attended).
80-
Requires prior join to appointment_t as alias 'ap'.
81-
82-
Uses comparator and resolves status label to ID via AppointmentStatusType.
83-
"""
95+
def _add_join_to_diagnostic_tests(self) -> None:
8496
try:
85-
comparator = self.criteria_comparator
86-
value = self.criteria_value.strip()
87-
status_id = AppointmentStatusType.get_id(value)
97+
which = WhichDiagnosticTest.from_description(self.criteria_value)
98+
idx = getattr(self, "criteria_index", 0)
99+
xt = f"xt{idx}"
100+
xtp = f"xt{idx - 1}"
88101

89-
self.sql_where.append(
90-
f"AND ap.appointment_status_id {comparator} {status_id}"
102+
self.sql_from.append(
103+
f"INNER JOIN external_tests_t {xt} ON {xt}.screening_subject_id = ss.screening_subject_id"
91104
)
92105

106+
if which == WhichDiagnosticTest.ANY_TEST_IN_ANY_EPISODE:
107+
return
108+
109+
self._add_join_to_latest_episode()
110+
111+
handlers = {
112+
WhichDiagnosticTest.ANY_TEST_IN_LATEST_EPISODE: self._handle_any_test_in_latest_episode,
113+
WhichDiagnosticTest.ONLY_TEST_IN_LATEST_EPISODE: self._handle_only_test_in_latest_episode,
114+
WhichDiagnosticTest.ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE: self._handle_only_test_in_latest_episode,
115+
WhichDiagnosticTest.LATEST_TEST_IN_LATEST_EPISODE: self._handle_latest_test_in_latest_episode,
116+
WhichDiagnosticTest.LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE: self._handle_latest_test_in_latest_episode,
117+
WhichDiagnosticTest.EARLIEST_NOT_VOID_TEST_IN_LATEST_EPISODE: self._handle_earliest_test_in_latest_episode,
118+
WhichDiagnosticTest.EARLIER_TEST_IN_LATEST_EPISODE: self._handle_earlier_or_later_test,
119+
WhichDiagnosticTest.LATER_TEST_IN_LATEST_EPISODE: self._handle_earlier_or_later_test,
120+
}
121+
122+
if which in handlers:
123+
handlers[which](which, xt, xtp)
124+
else:
125+
raise ValueError(f"Unsupported diagnostic test type: {which}")
126+
93127
except Exception:
94128
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
129+
130+
def _handle_any_test_in_latest_episode(self, which, xt, _):
131+
self.sql_from.append(f"AND {xt}.subject_epis_id = ep.subject_epis_id")
132+
133+
def _handle_only_test_in_latest_episode(self, which, xt, _):
134+
self.sql_from.append(f"AND {xt}.subject_epis_id = ep.subject_epis_id")
135+
if which == WhichDiagnosticTest.ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE:
136+
self.sql_from.append(f"AND {xt}.void = 'N'")
137+
self.sql_from.append(
138+
f"""AND NOT EXISTS (
139+
SELECT 'xto' FROM external_tests_t xto
140+
WHERE xto.screening_subject_id = ss.screening_subject_id
141+
{'AND xto.void = \'N\'' if which == WhichDiagnosticTest.ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE else ''}
142+
AND xto.subject_epis_id = ep.subject_epis_id
143+
AND xto.ext_test_id != {xt}.ext_test_id )"""
144+
)
145+
146+
def _handle_latest_test_in_latest_episode(self, which, xt, _):
147+
self.sql_from.append(
148+
f"""AND {xt}.ext_test_id = (
149+
SELECT MAX(xtx.ext_test_id) FROM external_tests_t xtx
150+
WHERE xtx.screening_subject_id = ss.screening_subject_id
151+
{'AND xtx.void = \'N\'' if which == WhichDiagnosticTest.LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE else ''}
152+
AND xtx.subject_epis_id = ep.subject_epis_id )"""
153+
)
154+
155+
def _handle_earliest_test_in_latest_episode(self, which, xt, _):
156+
self.sql_from.append(
157+
f"""AND {xt}.ext_test_id = (
158+
SELECT MIN(xtn.ext_test_id) FROM external_tests_t xtn
159+
WHERE xtn.screening_subject_id = ss.screening_subject_id
160+
AND xtn.void = 'N'
161+
AND xtn.subject_epis_id = ep.subject_epis_id )"""
162+
)
163+
164+
def _handle_earlier_or_later_test(self, which, xt, xtp):
165+
if getattr(self, "criteria_index", 0) == 0:
166+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
167+
comparator = (
168+
"<" if which == WhichDiagnosticTest.EARLIER_TEST_IN_LATEST_EPISODE else ">"
169+
)
170+
self.sql_from.append(f"AND {xt}.ext_test_id {comparator} {xtp}.ext_test_id")

utils/oracle/subject_selection_query_builder.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from classes.selection_builder_exception import SelectionBuilderException
2828
from classes.appointments_slot_type import AppointmentSlotType
2929
from classes.appointment_status_type import AppointmentStatusType
30+
from classes.which_diagnostic_test import WhichDiagnosticTest
3031

3132

3233
class SubjectSelectionQueryBuilder:
@@ -1602,6 +1603,93 @@ def _add_criteria_appointment_status(self) -> None:
16021603
except Exception:
16031604
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
16041605

1606+
# ------------------------------------------------------------------------
1607+
# 🧪 Diagnostic Test Selection & Join Logic
1608+
# ------------------------------------------------------------------------
1609+
1610+
def _add_join_to_diagnostic_tests(self) -> None:
1611+
try:
1612+
which = WhichDiagnosticTest.from_description(self.criteria_value)
1613+
idx = getattr(self, "criteria_index", 0)
1614+
xt = f"xt{idx}"
1615+
xtp = f"xt{idx - 1}"
1616+
1617+
self.sql_from.append(
1618+
f"INNER JOIN external_tests_t {xt} ON {xt}.screening_subject_id = ss.screening_subject_id"
1619+
)
1620+
1621+
if which == WhichDiagnosticTest.ANY_TEST_IN_ANY_EPISODE:
1622+
return
1623+
1624+
self._add_join_to_latest_episode()
1625+
1626+
handlers = {
1627+
WhichDiagnosticTest.ANY_TEST_IN_LATEST_EPISODE: self._handle_any_test_in_latest_episode,
1628+
WhichDiagnosticTest.ONLY_TEST_IN_LATEST_EPISODE: self._handle_only_test_in_latest_episode,
1629+
WhichDiagnosticTest.ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE: self._handle_only_test_in_latest_episode,
1630+
WhichDiagnosticTest.LATEST_TEST_IN_LATEST_EPISODE: self._handle_latest_test_in_latest_episode,
1631+
WhichDiagnosticTest.LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE: self._handle_latest_test_in_latest_episode,
1632+
WhichDiagnosticTest.EARLIEST_NOT_VOID_TEST_IN_LATEST_EPISODE: self._handle_earliest_test_in_latest_episode,
1633+
WhichDiagnosticTest.EARLIER_TEST_IN_LATEST_EPISODE: self._handle_earlier_or_later_test,
1634+
WhichDiagnosticTest.LATER_TEST_IN_LATEST_EPISODE: self._handle_earlier_or_later_test,
1635+
}
1636+
1637+
if which in handlers:
1638+
handlers[which](which, xt, xtp)
1639+
else:
1640+
raise ValueError(f"Unsupported diagnostic test type: {which}")
1641+
1642+
except Exception:
1643+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
1644+
1645+
def _handle_any_test_in_latest_episode(self, which, xt, _):
1646+
"""Helper method for diagnostic test filtering"""
1647+
1648+
self.sql_from.append(f"AND {xt}.subject_epis_id = ep.subject_epis_id")
1649+
1650+
def _handle_only_test_in_latest_episode(self, which, xt, _):
1651+
"""Helper method for diagnostic test filtering"""
1652+
self.sql_from.append(f"AND {xt}.subject_epis_id = ep.subject_epis_id")
1653+
if which == WhichDiagnosticTest.ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE:
1654+
self.sql_from.append(f"AND {xt}.void = 'N'")
1655+
self.sql_from.append(
1656+
f"""AND NOT EXISTS (
1657+
SELECT 'xto' FROM external_tests_t xto
1658+
WHERE xto.screening_subject_id = ss.screening_subject_id
1659+
{'AND xto.void = \'N\'' if which == WhichDiagnosticTest.ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE else ''}
1660+
AND xto.subject_epis_id = ep.subject_epis_id
1661+
AND xto.ext_test_id != {xt}.ext_test_id )"""
1662+
)
1663+
1664+
def _handle_latest_test_in_latest_episode(self, which, xt, _):
1665+
"""Helper method for diagnostic test filtering"""
1666+
self.sql_from.append(
1667+
f"""AND {xt}.ext_test_id = (
1668+
SELECT MAX(xtx.ext_test_id) FROM external_tests_t xtx
1669+
WHERE xtx.screening_subject_id = ss.screening_subject_id
1670+
{'AND xtx.void = \'N\'' if which == WhichDiagnosticTest.LATEST_NOT_VOID_TEST_IN_LATEST_EPISODE else ''}
1671+
AND xtx.subject_epis_id = ep.subject_epis_id )"""
1672+
)
1673+
1674+
def _handle_earliest_test_in_latest_episode(self, which, xt, _):
1675+
"""Helper method for diagnostic test filtering"""
1676+
self.sql_from.append(
1677+
f"""AND {xt}.ext_test_id = (
1678+
SELECT MIN(xtn.ext_test_id) FROM external_tests_t xtn
1679+
WHERE xtn.screening_subject_id = ss.screening_subject_id
1680+
AND xtn.void = 'N'
1681+
AND xtn.subject_epis_id = ep.subject_epis_id )"""
1682+
)
1683+
1684+
def _handle_earlier_or_later_test(self, which, xt, xtp):
1685+
"""Helper method for diagnostic test filtering"""
1686+
if getattr(self, "criteria_index", 0) == 0:
1687+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
1688+
comparator = (
1689+
"<" if which == WhichDiagnosticTest.EARLIER_TEST_IN_LATEST_EPISODE else ">"
1690+
)
1691+
self.sql_from.append(f"AND {xt}.ext_test_id {comparator} {xtp}.ext_test_id")
1692+
16051693
# ------------------------------------------------------------------------
16061694
# 🧬 CADS Clinical Dataset Filters
16071695
# ------------------------------------------------------------------------

utils/oracle/test_subject_criteria_dev.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,39 @@
2929
# === Example usage ===
3030
# Replace the examples below with your tests for the method you want to test
3131

32-
# === Test: APPOINTMENT_STATUS — booked ===
33-
builder = MockSelectionBuilder(SubjectSelectionCriteriaKey.APPOINTMENT_STATUS, "booked")
34-
builder.criteria_comparator = "="
35-
builder._add_criteria_appointment_status()
36-
print("=== APPOINTMENT_STATUS — booked ===")
37-
print(builder.dump_sql(), end="\n\n")
38-
39-
# === Test: APPOINTMENT_STATUS — dna !== ===
40-
builder = MockSelectionBuilder(SubjectSelectionCriteriaKey.APPOINTMENT_STATUS, "dna")
41-
builder.criteria_comparator = "!="
42-
builder._add_criteria_appointment_status()
43-
print("=== APPOINTMENT_STATUS — dna !== ===")
44-
print(builder.dump_sql(), end="\n\n")
32+
33+
# Helper for mock sequencing
34+
def make_builder(key, value, index=0):
35+
b = MockSelectionBuilder(key, value)
36+
b.criteria_index = index
37+
return b
38+
39+
40+
# === Test: ANY_TEST_IN_ANY_EPISODE ===
41+
b = make_builder(
42+
SubjectSelectionCriteriaKey.WHICH_DIAGNOSTIC_TEST, "any_test_in_any_episode"
43+
)
44+
b._add_join_to_diagnostic_tests()
45+
print("=== ANY_TEST_IN_ANY_EPISODE ===")
46+
print(b.dump_sql(), end="\n\n")
47+
48+
# === Test: EARLIER_TEST_IN_LATEST_EPISODE ===
49+
b = make_builder(
50+
SubjectSelectionCriteriaKey.WHICH_DIAGNOSTIC_TEST,
51+
"earlier_test_in_latest_episode",
52+
index=1,
53+
)
54+
b._add_join_to_diagnostic_tests()
55+
print("=== EARLIER_TEST_IN_LATEST_EPISODE ===")
56+
print(b.dump_sql(), end="\n\n")
57+
58+
# === Test: ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE ===
59+
b = make_builder(
60+
SubjectSelectionCriteriaKey.WHICH_DIAGNOSTIC_TEST,
61+
"only_not_void_test_in_latest_episode",
62+
)
63+
b._add_join_to_diagnostic_tests()
64+
print("=== ONLY_NOT_VOID_TEST_IN_LATEST_EPISODE ===")
65+
print(b.dump_sql(), end="\n\n")
66+
67+
# Add more as needed for full coverage

0 commit comments

Comments
 (0)