Skip to content

Commit 11a3cba

Browse files
committed
Added Notify message filter for queued event existence and status using parsed criteria format; included parser utility for criteria string decomposition
1 parent 5aeb1fb commit 11a3cba

File tree

5 files changed

+134
-26
lines changed

5 files changed

+134
-26
lines changed

classes/notify_event_status.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class NotifyEventStatus:
2+
_label_to_id = {
3+
"S1": 9901,
4+
"S2": 9902,
5+
"M1": 9903,
6+
# Extend as needed
7+
}
8+
9+
@classmethod
10+
def get_id(cls, description: str) -> int:
11+
key = description.strip().upper()
12+
if key not in cls._label_to_id:
13+
raise ValueError(f"Unknown Notify event type: '{description}'")
14+
return cls._label_to_id[key]

utils/notify_criteria_parser.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import re
2+
3+
def parse_notify_criteria(criteria: str) -> dict:
4+
"""
5+
Parses criteria strings like 'S1 - new' or 'S1 (S1w) - sending' into parts.
6+
"""
7+
criteria = criteria.strip()
8+
if criteria.lower() == "none":
9+
return {"status": "none"}
10+
11+
pattern = r"^(?P<type>[^\s(]+)(?:\s+\((?P<code>[^)]+)\))?\s*-\s*(?P<status>\w+)$"
12+
match = re.match(pattern, criteria, re.IGNORECASE)
13+
if not match:
14+
raise ValueError(f"Invalid Notify criteria format: '{criteria}'")
15+
16+
return {
17+
"type": match.group("type"),
18+
"code": match.group("code"),
19+
"status": match.group("status").lower(),
20+
}

utils/oracle/mock_selection_builder.py

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

67
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
@@ -14,23 +15,40 @@ def __init__(self, lynch_due_date_change_reason_id):
1415
self.lynch_due_date_change_reason_id = lynch_due_date_change_reason_id
1516

1617

17-
class PrevalentIncidentStatusType:
18-
"""
19-
Maps symbolic values for FOBT prevalent/incident episode classification.
20-
"""
18+
class NotifyEventStatus:
19+
_label_to_id = {
20+
"S1": 9901,
21+
"S2": 9902,
22+
"M1": 9903,
23+
# Extend as needed
24+
}
2125

22-
PREVALENT = "prevalent"
23-
INCIDENT = "incident"
26+
@classmethod
27+
def get_id(cls, description: str) -> int:
28+
key = description.strip().upper()
29+
if key not in cls._label_to_id:
30+
raise ValueError(f"Unknown Notify event type: '{description}'")
31+
return cls._label_to_id[key]
2432

25-
_valid_values = {PREVALENT, INCIDENT}
2633

27-
@classmethod
28-
def from_description(cls, description: str) -> str:
29-
key = description.strip().lower()
30-
if key not in cls._valid_values:
31-
raise ValueError(f"Unknown FOBT episode status: '{description}'")
32-
return key
34+
def parse_notify_criteria(criteria: str) -> dict:
35+
"""
36+
Parses criteria strings like 'S1 - new' or 'S1 (S1w) - sending' into parts.
37+
"""
38+
criteria = criteria.strip()
39+
if criteria.lower() == "none":
40+
return {"status": "none"}
41+
42+
pattern = r"^(?P<type>[^\s(]+)(?:\s+\((?P<code>[^)]+)\))?\s*-\s*(?P<status>\w+)$"
43+
match = re.match(pattern, criteria, re.IGNORECASE)
44+
if not match:
45+
raise ValueError(f"Invalid Notify criteria format: '{criteria}'")
3346

47+
return {
48+
"type": match.group("type"),
49+
"code": match.group("code"),
50+
"status": match.group("status").lower(),
51+
}
3452

3553
class MockSelectionBuilder:
3654
"""
@@ -98,22 +116,42 @@ def _dataset_source_for_criteria_key(self) -> dict:
98116
def _add_join_to_surveillance_review(self):
99117
self.sql_from.append("-- JOIN to surveillance review placeholder")
100118

119+
101120
# === Example testable method below ===
102121
# Replace this with the one you want to test,
103122
# then use utils/oracle/test_subject_criteria_dev.py to run your scenarios
104123

105-
def _add_criteria_fobt_prevalent_incident_status(self) -> None:
124+
def _add_criteria_notify_queued_message_status(self) -> None:
106125
"""
107-
Filters subjects by whether their FOBT episode is prevalent or incident.
126+
Filters subjects based on Notify queued message status, e.g. 'S1 (S1w) - new'.
108127
"""
109128
try:
110-
value = PrevalentIncidentStatusType.from_description(self.criteria_value)
111-
column = "ss.fobt_incident_subject_epis_id"
129+
parts = parse_notify_criteria(self.criteria_value)
130+
status = parts["status"]
131+
132+
if status == "none":
133+
clause = "NOT EXISTS"
134+
else:
135+
clause = "EXISTS"
136+
137+
self.sql_where.append(f"AND {clause} (")
138+
self.sql_where.append(
139+
"SELECT 1 FROM notify_message_queue nmq "
140+
"INNER JOIN notify_message_definition nmd ON nmd.message_definition_id = nmq.message_definition_id "
141+
"WHERE nmq.nhs_number = c.nhs_number "
142+
)
143+
144+
# Simulate getNotifyMessageEventStatusIdFromCriteria()
145+
event_status_id = NotifyEventStatus.get_id(parts["type"])
146+
self.sql_where.append(f"AND nmd.event_status_id = {event_status_id} ")
147+
148+
if status != "none":
149+
self.sql_where.append(f"AND nmq.message_status = '{status}' ")
150+
151+
if "code" in parts and parts["code"]:
152+
self.sql_where.append(f"AND nmd.message_code = '{parts['code']}' ")
112153

113-
if value == PrevalentIncidentStatusType.PREVALENT:
114-
self.sql_where.append(f"AND {column} IS NULL")
115-
elif value == PrevalentIncidentStatusType.INCIDENT:
116-
self.sql_where.append(f"AND {column} IS NOT NULL")
154+
self.sql_where.append(")")
117155

118156
except Exception:
119157
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)

utils/oracle/subject_selection_query_builder.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Dict, Union, Tuple, Optional
22
import logging
33
from datetime import datetime, date
4+
from utils.notify_criteria_parser import parse_notify_criteria
45
from classes.bowel_scope_dd_reason_for_change_type import (
56
BowelScopeDDReasonForChangeType,
67
)
@@ -54,6 +55,7 @@
5455
LynchIncidentEpisodeType,
5556
)
5657
from classes.prevalent_incident_status_type import PrevalentIncidentStatusType
58+
from classes.notify_event_status import NotifyEventStatus
5759

5860

5961
class SubjectSelectionQueryBuilder:
@@ -2247,6 +2249,41 @@ def _add_criteria_fobt_prevalent_incident_status(self) -> None:
22472249
except Exception:
22482250
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
22492251

2252+
def _add_criteria_notify_queued_message_status(self) -> None:
2253+
"""
2254+
Filters subjects based on Notify queued message status, e.g. 'S1 (S1w) - new'.
2255+
"""
2256+
try:
2257+
parts = parse_notify_criteria(self.criteria_value)
2258+
status = parts["status"]
2259+
2260+
if status == "none":
2261+
clause = "NOT EXISTS"
2262+
else:
2263+
clause = "EXISTS"
2264+
2265+
self.sql_where.append(f"AND {clause} (")
2266+
self.sql_where.append(
2267+
"SELECT 1 FROM notify_message_queue nmq "
2268+
"INNER JOIN notify_message_definition nmd ON nmd.message_definition_id = nmq.message_definition_id "
2269+
"WHERE nmq.nhs_number = c.nhs_number "
2270+
)
2271+
2272+
# Simulate getNotifyMessageEventStatusIdFromCriteria()
2273+
event_status_id = NotifyEventStatus.get_id(parts["type"])
2274+
self.sql_where.append(f"AND nmd.event_status_id = {event_status_id} ")
2275+
2276+
if status != "none":
2277+
self.sql_where.append(f"AND nmq.message_status = '{status}' ")
2278+
2279+
if "code" in parts and parts["code"]:
2280+
self.sql_where.append(f"AND nmd.message_code = '{parts['code']}' ")
2281+
2282+
self.sql_where.append(")")
2283+
2284+
except Exception:
2285+
raise SelectionBuilderException(self.criteria_key_name, self.criteria_value)
2286+
22502287
# ------------------------------------------------------------------------
22512288
# 🧬 CADS Clinical Dataset Filters
22522289
# ------------------------------------------------------------------------

utils/oracle/test_subject_criteria_dev.py

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

4343

44-
# === Test: FOBT_PREVALENT_INCIDENT_STATUS (incident) ===
45-
b = make_builder(SubjectSelectionCriteriaKey.FOBT_PREVALENT_INCIDENT_STATUS, "incident")
46-
b._add_criteria_fobt_prevalent_incident_status()
47-
print("=== FOBT_PREVALENT_INCIDENT_STATUS (incident) ===")
48-
print(b.dump_sql(), end="\n\n")
44+
# === Test: Notify message (S1 - new) ===
45+
b = make_builder(SubjectSelectionCriteriaKey.NOTIFY_QUEUED_MESSAGE_STATUS, "S1 - new")
46+
b._add_criteria_notify_queued_message_status()
47+
print(b.dump_sql())

0 commit comments

Comments
 (0)