Skip to content

Commit 1194b33

Browse files
Feature/bcss 21859 sspi utils fix (#147)
<!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> Updating the methods used to populate a subject class object from an NHS number by altering the subject selection query builder to be able to add columns and joins. Then updating the mapping of the result which is stored in a pandas dataframe to the subject class object. Updated documentation accordingly. ## Context <!-- Why is this change required? What problem does it solve? --> This was required as when doing SSPI updates some information such as the subject's GP practice would be overwritten and set to None / Null. This change has resolved that issue. ## Type of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. --> - [x] Refactoring (non-breaking change) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would change existing functionality) - [x] Bug fix (non-breaking change which fixes an issue) ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I am familiar with the [contributing guidelines](https://github.com/nhs-england-tools/playwright-python-blueprint/blob/main/CONTRIBUTING.md) - [x] I have followed the code style of the project - [x] I have added tests to cover my changes (where appropriate) - [x] I have updated the documentation accordingly - [x] This PR is a result of pair or mob programming --- ## Sensitive Information Declaration To ensure the utmost confidentiality and protect your and others privacy, we kindly ask you to NOT including [PII (Personal Identifiable Information) / PID (Personal Identifiable Data)](https://digital.nhs.uk/data-and-information/keeping-data-safe-and-benefitting-the-public) or any other sensitive data in this PR (Pull Request) and the codebase changes. We will remove any PR that do contain any sensitive information. We really appreciate your cooperation in this matter. - [x] I confirm that neither PII/PID nor sensitive data are included in this PR and the codebase changes.
1 parent c52ae27 commit 1194b33

File tree

6 files changed

+52
-9
lines changed

6 files changed

+52
-9
lines changed

classes/subject/gender_type.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,18 @@ def by_redefined_value(cls, redefined_value: int) -> Optional["GenderType"]:
8888
return next(
8989
(item for item in cls if item.redefined_value == redefined_value), None
9090
)
91+
92+
@classmethod
93+
def by_allowed_value(cls, allowed_value: str) -> Optional["GenderType"]:
94+
"""
95+
Returns the GenderType enum member matching the given allowed value.
96+
97+
Args:
98+
allowed_value (str): The allowed value to search for.
99+
100+
Returns:
101+
Optional[GenderType]: The matching enum member, or None if not found.
102+
"""
103+
return next(
104+
(item for item in cls if item.allowed_value == allowed_value), None
105+
)

classes/subject/subject.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,16 @@ def from_dataframe_row(row: pd.Series) -> "Subject":
13021302
),
13031303
"date_of_birth": DateTimeUtils.parse_date(row.get("date_of_birth")),
13041304
"date_of_death": DateTimeUtils.parse_date(row.get("date_of_death")),
1305+
"title": row.get("person_title"),
1306+
"other_names": row.get("person_other_given_names"),
1307+
"address_line1": row.get("address_line_1"),
1308+
"address_line2": row.get("address_line_2"),
1309+
"address_line3": row.get("address_line_3"),
1310+
"address_line4": row.get("address_line_4"),
1311+
"address_line5": row.get("address_line_5"),
1312+
"postcode": row.get("postcode"),
1313+
"gp_practice_code": row.get("gp_practice_code"),
1314+
"gender": GenderType.by_allowed_value(str(row.get("person_gender"))),
13051315
}
13061316

13071317
return Subject(**field_map)
@@ -1318,13 +1328,17 @@ def populate_subject_object_from_nhs_no(self, nhs_no: str) -> "Subject":
13181328
SubjectSelectionQueryBuilder,
13191329
)
13201330

1321-
nhs_no_criteria = {"nhs number": nhs_no}
1331+
criteria = {
1332+
"nhs number": nhs_no,
1333+
"add column to select statement": " c.person_title, c.person_other_given_names, CASE c.person_gender WHEN 130 THEN 'M' WHEN 131 THEN 'F' WHEN 132 THEN 'I' WHEN 160 THEN 'U' END, adds.address_line_1, adds.address_line_2, adds.address_line_3, adds.address_line_4, adds.address_line_5, adds.postcode, (SELECT hub.org_code FROM org hub WHERE org_id = c.gp_practice_id) AS gp_practice_code ",
1334+
"add join to from statement": " INNER JOIN sd_address_t adds ON adds.contact_id = c.contact_id ",
1335+
}
13221336
subject = Subject()
13231337
user = User()
13241338
builder = SubjectSelectionQueryBuilder()
13251339

13261340
query, bind_vars = builder.build_subject_selection_query(
1327-
criteria=nhs_no_criteria,
1341+
criteria=criteria,
13281342
user=user,
13291343
subject=subject,
13301344
subjects_to_retrieve=1,

classes/subject_selection_query_builder/subject_selection_criteria_key.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class SubjectSelectionCriteriaKey(Enum):
2424
"""
2525

2626
ADD_COLUMN_TO_SELECT_STATEMENT = ("add column to select statement", False, False)
27+
ADD_JOIN_TO_FROM_STATEMENT = ("add join to from statement", False, False)
2728
APPOINTMENT_DATE = ("appointment date", False, True)
2829
APPOINTMENT_STATUS = ("appointment status", True, True)
2930
APPOINTMENT_TYPE = ("appointment type", False, True)

docs/utility-guides/SubjectSelectionQueryBuilder.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ This approach ensures injection-safe execution (defending against SQL injection
6464

6565
```python
6666
criteria = {
67-
"nhs_number": "1234567890",
68-
"screening_status": "invited"
67+
"nhs number": "1234567890",
68+
"screening status": "invited"
6969
}
7070

7171
user = User(user_id=42, organisation=None) # Optional; used for 'unchanged' logic
@@ -122,9 +122,9 @@ Example:
122122

123123
```python
124124
{
125-
"subject_has_event_status": "ES01",
126-
"subject_age": "> 60",
127-
"date_of_death": "null"
125+
"subject has event status": "ES01",
126+
"subject age": "> 60",
127+
"date of death": "null"
128128
}
129129
```
130130

@@ -134,12 +134,12 @@ Each of those triggers a different clause in the generated SQL.
134134

135135
This gives the builder context about who’s requesting the query, including their organisation and permissions.
136136

137-
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.
137+
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.
138138

139139
Example:
140140

141141
```python
142-
"SUBJECT_HUB_CODE": "USER_HUB"
142+
"SUBJECT HUB CODE": "USER HUB"
143143
```
144144

145145
This means “filter by the hub assigned to this user’s organisation,” not a fixed hub like ABC.

subject_criteria_builder/criteria.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4891,5 +4891,10 @@
48914891
"key": "ADD_COLUMN_TO_SELECT_STATEMENT",
48924892
"value_source": "",
48934893
"notes": "Enter the name of the column to add to the select statement, also specify the prefix. E.g. ss.screening_subject_id."
4894+
},
4895+
{
4896+
"key": "ADD_JOIN_TO_FROM_STATEMENT",
4897+
"value_source": "",
4898+
"notes": "Enter the join you wish to add to the query. E.g. 'INNER JOIN sd_address_t adds ON adds.contact_id = c.contact_id'"
48944899
}
48954900
]

utils/oracle/subject_selection_query_builder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,8 @@ def _dispatch_criteria_key(self, user: "User", subject: "Subject") -> None:
716716
# ------------------------------------------------------------------------
717717
case SubjectSelectionCriteriaKey.ADD_COLUMN_TO_SELECT_STATEMENT:
718718
self._add_extra_column_to_select_statement()
719+
case SubjectSelectionCriteriaKey.ADD_JOIN_TO_FROM_STATEMENT:
720+
self._add_extra_join_to_from_statement()
719721
# ------------------------------------------------------------------------
720722
# 🛑 Fallback: Unmatched Criteria Key
721723
# ------------------------------------------------------------------------
@@ -792,6 +794,12 @@ def _add_extra_column_to_select_statement(self) -> None:
792794
"""
793795
self.sql_select.append(f", {self.criteria_value}")
794796

797+
def _add_extra_join_to_from_statement(self) -> None:
798+
"""
799+
Adds extra joins to the FROM statement.
800+
"""
801+
self.sql_from.append(f" {self.criteria_value} ")
802+
795803
def _add_criteria_nhs_number(self) -> None:
796804
"""
797805
Adds a check for the subject's NHS number

0 commit comments

Comments
 (0)