Skip to content

Commit 6345048

Browse files
committed
Added filters for Lynch incident episode and Lynch due date change reason using symbolic logic and subject-aware comparison
1 parent 0e15ade commit 6345048

File tree

5 files changed

+152
-32
lines changed

5 files changed

+152
-32
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class LynchDueDateReasonType:
2+
"""
3+
Maps Lynch surveillance due date reason descriptions to IDs and symbolic types.
4+
"""
5+
6+
NULL = "null"
7+
NOT_NULL = "not_null"
8+
UNCHANGED = "unchanged"
9+
10+
_label_to_id = {
11+
"holiday": 9801,
12+
"clinical request": 9802,
13+
"external delay": 9803,
14+
# Extend as needed
15+
}
16+
17+
@classmethod
18+
def from_description(cls, description: str):
19+
key = description.strip().lower()
20+
if key in {cls.NULL, cls.NOT_NULL, cls.UNCHANGED}:
21+
return key
22+
if key in cls._label_to_id:
23+
return cls._label_to_id[key]
24+
raise ValueError(f"Unknown Lynch due date change reason: '{description}'")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class LynchIncidentEpisodeType:
2+
"""
3+
Maps symbolic values used to filter Lynch incident episode linkage.
4+
"""
5+
6+
NULL = "null"
7+
NOT_NULL = "not_null"
8+
LATEST_EPISODE = "latest_episode"
9+
EARLIER_EPISODE = "earlier_episode"
10+
11+
_symbolics = {NULL, NOT_NULL, LATEST_EPISODE, EARLIER_EPISODE}
12+
13+
@classmethod
14+
def from_description(cls, description: str) -> str:
15+
key = description.strip().lower()
16+
if key not in cls._symbolics:
17+
raise ValueError(
18+
f"Unknown Lynch incident episode criteria: '{description}'"
19+
)
20+
return key

utils/oracle/mock_selection_builder.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,39 @@
11
# mock_selection_builder.py — Development-only testing harness for criteria logic
22
import sys
33
import os
4+
from typing import Optional
45

56
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
67
from classes.selection_builder_exception import SelectionBuilderException
78
from classes.subject_selection_criteria_key import SubjectSelectionCriteriaKey
89

910

1011
# Add helper class stubs below
11-
class ScreeningReferralType:
12+
class Subject:
13+
def __init__(self, lynch_due_date_change_reason_id):
14+
self.lynch_due_date_change_reason_id = lynch_due_date_change_reason_id
15+
16+
17+
class LynchIncidentEpisodeType:
1218
"""
13-
Maps screening referral descriptions to valid value IDs.
19+
Maps symbolic criteria values for Lynch incident episode linkage.
1420
"""
1521

16-
_label_to_id = {
17-
"gp": 9701,
18-
"self referral": 9702,
19-
"hospital": 9703,
20-
# Extend as needed
21-
}
22+
NULL = "null"
23+
NOT_NULL = "not_null"
24+
LATEST_EPISODE = "latest_episode"
25+
EARLIER_EPISODE = "earlier_episode"
26+
27+
_symbolics = {NULL, NOT_NULL, LATEST_EPISODE, EARLIER_EPISODE}
2228

2329
@classmethod
24-
def get_id(cls, description: str) -> int:
30+
def from_description(cls, description: str) -> str:
2531
key = description.strip().lower()
26-
if key not in cls._label_to_id:
27-
raise ValueError(f"Unknown screening referral type: '{description}'")
28-
return cls._label_to_id[key]
32+
if key not in cls._symbolics:
33+
raise ValueError(
34+
f"Unknown Lynch incident episode criteria: '{description}'"
35+
)
36+
return key
2937

3038

3139
class MockSelectionBuilder:
@@ -71,6 +79,10 @@ def _add_join_to_latest_episode(self) -> None:
7179
"""
7280
self.sql_from.append("-- JOIN to latest episode placeholder")
7381

82+
def _force_not_modifier_is_invalid(self):
83+
# Placeholder for rule enforcement. No-op in mock builder.
84+
pass
85+
7486
def _dataset_source_for_criteria_key(self) -> dict:
7587
"""
7688
Maps criteria key to dataset table and alias.
@@ -94,19 +106,26 @@ def _add_join_to_surveillance_review(self):
94106
# Replace this with the one you want to test,
95107
# then use utils/oracle/test_subject_criteria_dev.py to run your scenarios
96108

97-
def _add_criteria_screening_referral_type(self) -> None:
109+
def _add_criteria_lynch_incident_episode(self) -> None:
98110
"""
99-
Filters based on screening referral type ID or null presence.
111+
Filters based on linkage to a Lynch incident episode.
100112
"""
101113
try:
102-
column = "xt.screening_referral_type_id"
103-
value = self.criteria_value.strip().lower()
114+
self._add_join_to_latest_episode()
115+
column = "ss.lynch_incident_subject_epis_id"
116+
value = LynchIncidentEpisodeType.from_description(self.criteria_value)
104117

105-
if value == "null":
118+
if value == LynchIncidentEpisodeType.NULL:
106119
self.sql_where.append(f"AND {column} IS NULL")
107-
else:
108-
type_id = ScreeningReferralType.get_id(self.criteria_value)
109-
self.sql_where.append(f"AND {column} {self.criteria_comparator} {type_id}")
120+
121+
elif value == LynchIncidentEpisodeType.NOT_NULL:
122+
self.sql_where.append(f"AND {column} IS NOT NULL")
123+
124+
elif value == LynchIncidentEpisodeType.LATEST_EPISODE:
125+
self.sql_where.append(f"AND {column} = ep.subject_epis_id")
126+
127+
elif value == LynchIncidentEpisodeType.EARLIER_EPISODE:
128+
self.sql_where.append(f"AND {column} < ep.subject_epis_id")
110129

111130
except Exception:
112131
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)

utils/oracle/subject_selection_query_builder.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
from classes.episode_result_type import EpisodeResultType
5050
from classes.symptomatic_procedure_result_type import SymptomaticProcedureResultType
5151
from classes.screening_referral_type import ScreeningReferralType
52+
from classes.lynch_due_date_reason_type import LynchDueDateReasonType
53+
from classes.lynch_incident_episode_type import (
54+
LynchIncidentEpisodeType,
55+
)
5256

5357

5458
class SubjectSelectionQueryBuilder:
@@ -2165,6 +2169,67 @@ def _add_criteria_screening_referral_type(self) -> None:
21652169
except Exception:
21662170
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
21672171

2172+
def _add_criteria_lynch_due_date_reason(
2173+
self, subject: Optional[Subject] = None
2174+
) -> None:
2175+
"""
2176+
Filters based on Lynch due date change reason. Supports symbolic types and subject comparison.
2177+
"""
2178+
try:
2179+
column = "ss.lynch_sdd_reason_for_change_id"
2180+
reason = LynchDueDateReasonType.from_description(self.criteria_value)
2181+
2182+
if reason == LynchDueDateReasonType.NULL:
2183+
self.sql_where.append(f"AND {column} IS NULL")
2184+
2185+
elif reason == LynchDueDateReasonType.NOT_NULL:
2186+
self.sql_where.append(f"AND {column} IS NOT NULL")
2187+
2188+
elif reason == LynchDueDateReasonType.UNCHANGED:
2189+
if subject is None:
2190+
raise SelectionBuilderException(
2191+
self.criteria_key_name,
2192+
"No subject provided for 'unchanged' logic",
2193+
)
2194+
elif getattr(subject, "lynch_due_date_change_reason_id", None) is None:
2195+
self.sql_where.append(f"AND {column} IS NULL")
2196+
else:
2197+
self.sql_where.append(
2198+
f"AND {column} = {subject.lynch_due_date_change_reason_id}"
2199+
)
2200+
2201+
else:
2202+
self.sql_where.append(
2203+
f"AND {column} {self.criteria_comparator} {reason}"
2204+
)
2205+
2206+
except Exception:
2207+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
2208+
2209+
def _add_criteria_lynch_incident_episode(self) -> None:
2210+
"""
2211+
Filters based on linkage to a Lynch incident episode.
2212+
"""
2213+
try:
2214+
self._add_join_to_latest_episode()
2215+
column = "ss.lynch_incident_subject_epis_id"
2216+
value = LynchIncidentEpisodeType.from_description(self.criteria_value)
2217+
2218+
if value == LynchIncidentEpisodeType.NULL:
2219+
self.sql_where.append(f"AND {column} IS NULL")
2220+
2221+
elif value == LynchIncidentEpisodeType.NOT_NULL:
2222+
self.sql_where.append(f"AND {column} IS NOT NULL")
2223+
2224+
elif value == LynchIncidentEpisodeType.LATEST_EPISODE:
2225+
self.sql_where.append(f"AND {column} = ep.subject_epis_id")
2226+
2227+
elif value == LynchIncidentEpisodeType.EARLIER_EPISODE:
2228+
self.sql_where.append(f"AND {column} < ep.subject_epis_id")
2229+
2230+
except Exception:
2231+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
2232+
21682233
# ------------------------------------------------------------------------
21692234
# 🧬 CADS Clinical Dataset Filters
21702235
# ------------------------------------------------------------------------

utils/oracle/test_subject_criteria_dev.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,8 @@ def make_builder(key, value, index=0, comparator="="):
4141
return b
4242

4343

44-
# === Test: SCREENING_REFERRAL_TYPE (self referral) ===
45-
b = make_builder(
46-
SubjectSelectionCriteriaKey.SCREENING_REFERRAL_TYPE, "self referral", comparator="="
47-
)
48-
b._add_criteria_screening_referral_type()
49-
print("=== SCREENING_REFERRAL_TYPE (self referral) ===")
50-
print(b.dump_sql(), end="\n\n")
51-
52-
# === Test: SCREENING_REFERRAL_TYPE (null) ===
53-
b = make_builder(SubjectSelectionCriteriaKey.SCREENING_REFERRAL_TYPE, "null")
54-
b._add_criteria_screening_referral_type()
55-
print("=== SCREENING_REFERRAL_TYPE (null) ===")
44+
# === Test: LYNCH_INCIDENT_EPISODE (earlier_episode) ===
45+
b = make_builder(SubjectSelectionCriteriaKey.LYNCH_INCIDENT_EPISODE, "earlier_episode")
46+
b._add_criteria_lynch_incident_episode()
47+
print("=== LYNCH_INCIDENT_EPISODE (earlier_episode) ===")
5648
print(b.dump_sql(), end="\n\n")

0 commit comments

Comments
 (0)