|
4 | 4 |
|
5 | 5 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) |
6 | 6 | from classes.selection_builder_exception import SelectionBuilderException |
| 7 | +from classes.subject_selection_criteria_key import SubjectSelectionCriteriaKey |
7 | 8 |
|
8 | 9 |
|
9 | 10 | # Add helper class stubs below |
10 | | -class IntendedExtentType: |
| 11 | +class LatestEpisodeHasDataset: |
11 | 12 | """ |
12 | | - Resolves intended extent descriptions to valid value IDs or null-check constants. |
| 13 | + Maps dataset status descriptions to filter interpretations. |
13 | 14 | """ |
14 | 15 |
|
15 | | - NULL = "null" |
16 | | - NOT_NULL = "not null" |
| 16 | + NO = "no" |
| 17 | + YES_INCOMPLETE = "yes_incomplete" |
| 18 | + YES_COMPLETE = "yes_complete" |
| 19 | + PAST = "past" |
17 | 20 |
|
18 | 21 | _mapping = { |
19 | | - "null": NULL, |
20 | | - "not null": NOT_NULL, |
21 | | - "full": 9201, |
22 | | - "partial": 9202, |
23 | | - "none": 9203, |
| 22 | + "no": NO, |
| 23 | + "yes_incomplete": YES_INCOMPLETE, |
| 24 | + "yes_complete": YES_COMPLETE, |
| 25 | + "past": PAST, |
24 | 26 | } |
25 | 27 |
|
26 | 28 | @classmethod |
27 | | - def from_description(cls, description: str): |
| 29 | + def from_description(cls, description: str) -> str: |
28 | 30 | key = description.strip().lower() |
29 | 31 | if key not in cls._mapping: |
30 | | - raise ValueError(f"Unknown intended extent: '{description}'") |
| 32 | + raise ValueError(f"Unknown dataset filter type: '{description}'") |
31 | 33 | return cls._mapping[key] |
32 | 34 |
|
33 | | - @classmethod |
34 | | - def get_id(cls, description: str) -> int: |
35 | | - val = cls.from_description(description) |
36 | | - if isinstance(val, int): |
37 | | - return val |
38 | | - raise ValueError(f"No ID associated with extent: '{description}'") |
39 | | - |
40 | | - @classmethod |
41 | | - def get_description(cls, sentinel: str) -> str: |
42 | | - if sentinel == cls.NULL: |
43 | | - return "NULL" |
44 | | - if sentinel == cls.NOT_NULL: |
45 | | - return "NOT NULL" |
46 | | - raise ValueError(f"Unknown null sentinel: {sentinel}") |
47 | | - |
48 | 35 |
|
49 | 36 | class MockSelectionBuilder: |
50 | 37 | """ |
@@ -89,26 +76,69 @@ def _add_join_to_latest_episode(self) -> None: |
89 | 76 | """ |
90 | 77 | self.sql_from.append("-- JOIN to latest episode placeholder") |
91 | 78 |
|
| 79 | + def _dataset_source_for_criteria_key(self) -> dict: |
| 80 | + """ |
| 81 | + Maps criteria key to dataset table and alias. |
| 82 | + """ |
| 83 | + key = self.criteria_key |
| 84 | + if key == SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_CANCER_AUDIT_DATASET: |
| 85 | + return {"table": "ds_cancer_audit_t", "alias": "cads"} |
| 86 | + if ( |
| 87 | + key |
| 88 | + == SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_COLONOSCOPY_ASSESSMENT_DATASET |
| 89 | + ): |
| 90 | + return {"table": "ds_patient_assessment_t", "alias": "dspa"} |
| 91 | + if key == SubjectSelectionCriteriaKey.LATEST_EPISODE_HAS_MDT_DATASET: |
| 92 | + return {"table": "ds_mdt_t", "alias": "mdt"} |
| 93 | + raise SelectionBuilderException(self.criteria_key_name, self.criteria_value) |
| 94 | + |
92 | 95 | # === Example testable method below === |
93 | 96 | # Replace this with the one you want to test, |
94 | 97 | # then use utils/oracle/test_subject_criteria_dev.py to run your scenarios |
95 | 98 |
|
96 | | - def _add_criteria_diagnostic_test_intended_extent(self) -> None: |
| 99 | + def _add_criteria_latest_episode_has_dataset(self) -> None: |
97 | 100 | """ |
98 | | - Adds WHERE clause filtering diagnostic tests by intended_extent_id. |
99 | | - Supports null checks and value comparisons. |
| 101 | + Filters based on presence or completion status of a dataset in the latest episode. |
100 | 102 | """ |
101 | 103 | try: |
102 | | - idx = getattr(self, "criteria_index", 0) |
103 | | - xt = f"xt{idx}" |
104 | | - extent = IntendedExtentType.from_description(self.criteria_value) |
105 | | - |
106 | | - self.sql_where.append(f"AND {xt}.intended_extent_id ") |
107 | | - |
108 | | - if extent in (IntendedExtentType.NULL, IntendedExtentType.NOT_NULL): |
109 | | - self.sql_where.append(f"IS {IntendedExtentType.get_description(extent)}") |
| 104 | + self._add_join_to_latest_episode() |
| 105 | + |
| 106 | + dataset_info = self._dataset_source_for_criteria_key() |
| 107 | + dataset_table = dataset_info["table"] |
| 108 | + alias = dataset_info["alias"] |
| 109 | + |
| 110 | + clause = "AND EXISTS ( " |
| 111 | + value = self.criteria_value.strip().lower() |
| 112 | + status = LatestEpisodeHasDataset.from_description(value) |
| 113 | + filter_clause = "" |
| 114 | + |
| 115 | + if status == LatestEpisodeHasDataset.NO: |
| 116 | + clause = "AND NOT EXISTS ( " |
| 117 | + elif status == LatestEpisodeHasDataset.YES_INCOMPLETE: |
| 118 | + filter_clause = f"AND {alias}.dataset_completed_date IS NULL" |
| 119 | + elif status == LatestEpisodeHasDataset.YES_COMPLETE: |
| 120 | + filter_clause = f"AND {alias}.dataset_completed_date IS NOT NULL" |
| 121 | + elif status == LatestEpisodeHasDataset.PAST: |
| 122 | + filter_clause = ( |
| 123 | + f"AND TRUNC({alias}.dataset_completed_date) < TRUNC(SYSDATE)" |
| 124 | + ) |
110 | 125 | else: |
111 | | - self.sql_where.append(f"{self.criteria_comparator} {IntendedExtentType.get_id(self.criteria_value)}") |
| 126 | + raise SelectionBuilderException( |
| 127 | + self.criteria_key_name, self.criteria_value |
| 128 | + ) |
| 129 | + |
| 130 | + self.sql_where.append( |
| 131 | + "".join( |
| 132 | + [ |
| 133 | + clause, |
| 134 | + f"SELECT 1 FROM {dataset_table} {alias} ", |
| 135 | + f"WHERE {alias}.episode_id = ep.subject_epis_id ", |
| 136 | + f"AND {alias}.deleted_flag = 'N' ", |
| 137 | + filter_clause, |
| 138 | + ")", |
| 139 | + ] |
| 140 | + ) |
| 141 | + ) |
112 | 142 |
|
113 | 143 | except Exception: |
114 | 144 | raise SelectionBuilderException(self.criteria_key_name, self.criteria_value) |
0 commit comments