Skip to content

Commit 78beac0

Browse files
committed
Added SubjectSelectionQueryBuilder utility guide
1 parent 9ace2c2 commit 78beac0

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# SubjectSelectionQueryBuilder Utility Guide
2+
3+
## Overview
4+
5+
The `SubjectSelectionQueryBuilder` is a flexible utility for constructing SQL queries to retrieve screening subjects from the NHS BCSS Oracle database. It supports a comprehensive set of filters (subject selection criteria) based on:
6+
7+
- Demographics and GP details
8+
- Screening status and due dates
9+
- Events and episodes
10+
- Kit usage and diagnostic activity
11+
- Appointments, tests, and CADS datasets
12+
- Lynch pathway logic and Notify message status
13+
14+
For example:
15+
- NHS number
16+
- Subject age
17+
- Hub code
18+
- Screening centre code
19+
- GP practice linkage
20+
- Screening status
21+
- and many more, (including date-based and status-based filters).
22+
23+
It also handles special cases such as `unchanged` values and supports modifiers like `NOT:` for negation.
24+
25+
Queries are constructed dynamically based on `criteria`, `user`, and `subject` inputs.
26+
27+
---
28+
29+
## How to Use
30+
31+
### Import and Instantiate the Builder
32+
33+
```python
34+
from utils.oracle.subject_selection_query_builder import SubjectSelectionQueryBuilder
35+
36+
builder = SubjectSelectionQueryBuilder()
37+
```
38+
39+
### Build a subject selection query
40+
41+
Using `build_subject_selection_query`:
42+
43+
```python
44+
query, bind_vars = builder.build_subject_selection_query(
45+
criteria=criteria_dict, # Dict[str, str] of selection criteria
46+
user=test_user, # User object
47+
subject=test_subject, # Subject object (can be None)
48+
subjects_to_retrieve=100 # Optional limit
49+
)
50+
```
51+
When you call `build_subject_selection_query(...)`, it returns a tuple containing:
52+
53+
`query` — a complete SQL string with placeholders like :nhs_number, ready to be run against the database.
54+
55+
`bind_vars` — a dictionary mapping those placeholders to their actual values, like {"nhs_number": "1234567890"}.
56+
57+
This approach ensures injection-safe execution (defending against SQL injection attacks) and allows database engines to optimize and cache query plans for repeated execution.
58+
59+
## Example Usage
60+
61+
### Input
62+
63+
```python
64+
criteria = {
65+
"NHS_NUMBER": "1234567890",
66+
"SCREENING_STATUS": "invited"
67+
}
68+
69+
user = User(user_id=42, organisation=None) # Simulated user
70+
subject = Subject() # Optional; used for 'unchanged' logic
71+
72+
builder = SubjectSelectionQueryBuilder()
73+
query, bind_vars = builder.build_subject_selection_query(criteria, user, subject)
74+
```
75+
76+
### Output
77+
78+
#### Query
79+
80+
```SQL
81+
SELECT ss.screening_subject_id, ...
82+
FROM screening_subject_t ss
83+
INNER JOIN sd_contact_t c ON c.nhs_number = ss.subject_nhs_number
84+
WHERE 1=1
85+
AND c.nhs_number = :nhs_number
86+
AND ss.screening_status_id = 1001
87+
FETCH FIRST 1 ROWS ONLY
88+
```
89+
(Note: 1001 would be the resolved ID for "invited" in ScreeningStatusType.)
90+
91+
#### bind_vars
92+
93+
```python
94+
{
95+
"nhs_number": "1234567890"
96+
}
97+
```
98+
99+
### What happens next?
100+
101+
You can pass both values directly into your DB layer or test stub:
102+
103+
```python
104+
cursor.execute(query, bind_vars)
105+
```
106+
107+
## Supported Inputs
108+
109+
### 1. criteria (Dict[str, str])
110+
111+
This is the main filter configuration, where each entry represents one selection condition.
112+
113+
The `key` is a string matching one of the `SubjectSelectionCriteriaKey` `values` (e.g. "SUBJECT_AGE", "SCREENING_STATUS", etc.)
114+
115+
The `value` is the actual filter (e.g. "55", "> 60", "yes", "not:null", etc.)
116+
117+
Example:
118+
119+
```python
120+
{
121+
"SUBJECT_HAS_EVENT_STATUS": "ES01",
122+
"SUBJECT_AGE": "> 60",
123+
"DATE_OF_DEATH": "null"
124+
}
125+
```
126+
Each of those triggers a different clause in the generated SQL.
127+
128+
### 2. user (User)
129+
130+
This gives the builder context about who’s requesting the query, including their organisation and permissions.
131+
132+
Some criteria (like "USER_HUB" or "USER_ORGANISATION") don’t refer to a fixed hub code, but instead dynamically map to the hub or screening centre of the user running the search. That’s where this comes into play.
133+
134+
Example:
135+
136+
```python
137+
"SUBJECT_HUB_CODE": "USER_HUB"
138+
```
139+
This means “filter by the hub assigned to this user’s organisation,” not a fixed hub like ABC.
140+
141+
### 3. subject (Subject)
142+
This is used when a filter wants to compare the current value in the database to an existing value on file—often represented by the "UNCHANGED" keyword.
143+
144+
Example:
145+
146+
```python
147+
"SCREENING_STATUS": "unchanged"
148+
```
149+
That’s saying: “Only return subjects whose screening status has not changed compared to what’s currently recorded on the subject object.”
150+
151+
Without a subject, "unchanged" logic isn’t possible and will raise a validation error.
152+
153+
Together, these three inputs give the builder all it needs to translate human-friendly selection criteria into valid, safe, dynamic SQL.
154+
155+
## Key Behavior Details
156+
157+
Values like `yes`, `no`, `null`, `not null`, `unchanged` are normalized and interpreted internally.
158+
159+
`NOT:` prefix in values flips logic where allowed (e.g. "NOT:ES01").
160+
161+
Most enums (like `YesNoType`, `ScreeningStatusType`, etc.) are resolved by description using .by_description() or .by_description_case_insensitive() calls.
162+
163+
Joins to related datasets are added dynamically only when required (e.g. latest episode, diagnostic test joins).
164+
165+
All dates are handled via Oracle `TRUNC(SYSDATE)` and `TO_DATE()` expressions to ensure consistent date logic.
166+
167+
168+
## Reference
169+
170+
For a full list of supported `SubjectSelectionCriteriaKey` values and expected inputs, refer to the enumeration in:
171+
172+
`classes/subject_selection_criteria_key.py`
173+
174+
Or explore the full `SubjectSelectionQueryBuilder._dispatch_criteria_key()` method to review how each key is implemented.

0 commit comments

Comments
 (0)