Skip to content

Commit 7289f67

Browse files
Feature/bcss 22014 surveillanceregressiontests scenario 2 (#155)
<!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> Adding scenario 2 from SurevillanceRegressionTests and refactoring POMs / Utils to work for the new surveillance pages. ## Context <!-- Why is this change required? What problem does it solve? --> Adding scenario 2 from SurevillanceRegressionTests and refactoring POMs / Utils to work for the new surveillance pages. ## 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) - [ ] 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 - [ ] 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 ddbf909 commit 7289f67

40 files changed

+3309
-2006
lines changed

classes/event/event_status_type.py

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from enum import Enum
22
from typing import Optional
33

4+
discharged_from_surveillance_string = "Discharged from Surveillance - GP Letter Printed"
5+
handover_into_symptomatic_care_string = "Handover into Symptomatic Care"
6+
47

58
class EventStatusType(Enum):
69
"""
@@ -145,7 +148,7 @@ class EventStatusType(Enum):
145148
A343 = (160158, "A343", "High-risk Adenoma")
146149
A344 = (160159, "A344", "Abnormal")
147150
A345 = (160153, "A345", "Cancer Result, Refer MDT")
148-
A346 = (160163, "A346", "Handover into Symptomatic Care")
151+
A346 = (160163, "A346", handover_into_symptomatic_care_string)
149152
A347 = (20074, "A347", "Refer to Symptomatic")
150153
A348 = (20075, "A348", "MDT Referral Required")
151154
A350 = (
@@ -193,7 +196,7 @@ class EventStatusType(Enum):
193196
A382 = (11553, "A382", "Handover into Symptomatic Care - GP Letter Printed")
194197
A383 = (20421, "A383", "Handover into Symptomatic Care - Patient Letter Printed")
195198
A384 = (20420, "A384", "Discharged from Screening - GP letter not required")
196-
A385 = (20419, "A385", "Handover into Symptomatic Care")
199+
A385 = (20419, "A385", handover_into_symptomatic_care_string)
197200
A389 = (
198201
305783,
199202
"A389",
@@ -1032,6 +1035,130 @@ class EventStatusType(Enum):
10321035
U81 = (11283, "U81", "Kit Returned and Logged (Technical Fail; Weak Positive")
10331036
U97 = (11284, "U97", "Weak Positive, Waiting for Screening Centre Assistance")
10341037
U98 = (11285, "U98", "Weak Positive, Waiting for Programme Hub Assistance")
1038+
X2 = (20346, "X2", "Surveillance appointment rescheduled")
1039+
X372 = (11554, "X372", "Handover into Symptomatic Care - GP Letter Printed")
1040+
X374 = (20085, "X374", "Handover into Symptomatic Care - Patient Letter Printed")
1041+
X376 = (20086, "X376", discharged_from_surveillance_string)
1042+
X377 = (20087, "X377", discharged_from_surveillance_string)
1043+
X379 = (20088, "X379", discharged_from_surveillance_string)
1044+
X380 = (20089, "X380", "Discharge from Screening and Surveillance - Patient Choice")
1045+
X381 = (
1046+
20090,
1047+
"X381",
1048+
"Discharge from Screening and Surveillance - No Patient Contact",
1049+
)
1050+
X382 = (
1051+
20091,
1052+
"X382",
1053+
"Discharge from Screening and Surveillance - Clinical Decision",
1054+
)
1055+
X384 = (
1056+
20183,
1057+
"X384",
1058+
"Discharged from Screening & Surveillance - GP Letter Not Required",
1059+
)
1060+
X386 = (20092, "X386", "Discharged from Surveillance - Patient Letter Printed")
1061+
X387 = (20093, "X387", "Discharged from Surveillance - Patient Letter Printed")
1062+
X389 = (20184, "X389", "Discharge from Surveillance - Clinical Decision")
1063+
X390 = (20094, "X390", "Discharge from Surveillance - Clinical Decision")
1064+
X391 = (20095, "X391", handover_into_symptomatic_care_string)
1065+
X392 = (20096, "X392", "Discharge from Surveillance - Patient Choice")
1066+
X394 = (20097, "X394", "Handover into Symptomatic Care - Patient Age")
1067+
X395 = (
1068+
20098,
1069+
"X395",
1070+
"Discharged from Surveillance - National Guidelines Return FOBT",
1071+
)
1072+
X398 = (20099, "X398", "Discharge from Surveillance - No Patient Contact")
1073+
X399 = (
1074+
200263,
1075+
"X399",
1076+
"Discharged from Surveillance - National Guidelines Cease Screening",
1077+
)
1078+
X500 = (20100, "X500", "Selected For Surveillance")
1079+
X501 = (20101, "X501", "No Response to HealthCheck Form")
1080+
X505 = (20102, "X505", "HealthCheck Form Printed")
1081+
X510 = (20103, "X510", "Surveillance Reminder Printed")
1082+
X512 = (20104, "X512", "Patient Contact Resulted in Discharge from Surveillance")
1083+
X513 = (20196, "X513", "No Patient Contact - Discharge from Surveillance")
1084+
X600 = (20107, "X600", "Surveillance Appointment Required")
1085+
X610 = (20114, "X610", "Surveillance Appointment Made")
1086+
X615 = (20116, "X615", "Surveillance Appointment Invitation Letter Printed")
1087+
X617 = (20117, "X617", "Surveillance Appointment Cancelled by SC")
1088+
X620 = (20118, "X620", "Surveillance Appointment Cancelled by Patient")
1089+
X622 = (20122, "X622", "Surveillance Appointment Cancellation Letter Printed")
1090+
X625 = (20119, "X625", "Practitioner did not attend Surveillance Appointment")
1091+
X641 = (20120, "X641", "Patient did not attend Surveillance Appointment")
1092+
X650 = (20108, "X650", "Patient Attended Surveillance Appointment")
1093+
X76 = (20109, "X76", "Discharged from Surveillance & Screening - GP Letter Printed")
1094+
X77 = (20110, "X77", "Discharged from Surveillance & Screening - GP Letter Printed")
1095+
X79 = (20111, "X79", "Discharge from Surveillance & Screening - GP Letter Printed")
1096+
X86 = (
1097+
20112,
1098+
"X86",
1099+
"Discharged from Surveillance & Screening - Patient Letter Printed",
1100+
)
1101+
X87 = (
1102+
20113,
1103+
"X87",
1104+
"Discharged from Surveillance & Screening - Patient Letter Printed",
1105+
)
1106+
X89 = (
1107+
20185,
1108+
"X89",
1109+
"Discharge from Screening and Surveillance - Clinical Decision",
1110+
)
1111+
X9 = (20115, "X9", "Surveillance Appointment Cancelled Letters not Prepared")
1112+
X900 = (20237, "X900", "Surveillance Episode reopened")
1113+
X92 = (20188, "X92", "Close Surveillance Episode via interrupt")
1114+
Z1 = (
1115+
11289,
1116+
"Z1",
1117+
"Appointment Cancellation Requested by SC prior to Letter Preparation",
1118+
)
1119+
Z10 = (20344, "Z10", "Colonoscopy assessment appointment rescheduled")
1120+
Z11 = (20345, "Z11", "Post-Investigation appointment rescheduled")
1121+
Z12 = (
1122+
205221,
1123+
"Z12",
1124+
"Redirected Colonoscopy Assessment Appointment Cancellation Requested by SC prior to Preparation of Letters",
1125+
)
1126+
Z2 = (
1127+
11290,
1128+
"Z2",
1129+
"Appointment Cancellation Requested by SC (follows a DNA) prior to Letter Preparation",
1130+
)
1131+
Z3 = (
1132+
15006,
1133+
"Z3",
1134+
"Appointment Cancellation Requested by SC prior to Letter Preparation",
1135+
)
1136+
Z4 = (
1137+
15007,
1138+
"Z4",
1139+
"Appointment Cancellation Requested by SC (follows a DNA) prior to Letter Preparation",
1140+
)
1141+
Z5 = (
1142+
15008,
1143+
"Z5",
1144+
"Appointment Cancellation Requested prior to Letter Preparation (Patient to Reschedule)",
1145+
)
1146+
Z6 = (
1147+
15009,
1148+
"Z6",
1149+
"Appointment Cancellation Requested (follows a DNA) prior to Letter Preparation (Patient to Reschedule)",
1150+
)
1151+
Z7 = (
1152+
15010,
1153+
"Z7",
1154+
"Appointment Cancellation Requested prior to Letter Preparation (SC Non-attendance)",
1155+
)
1156+
Z8 = (
1157+
15011,
1158+
"Z8",
1159+
"Appointment Cancellation Requested (follows a DNA) prior to Letter Preparation (SC Non-attendance)",
1160+
)
1161+
Z9 = (160174, "Z9", "Post-investigation Appointment Cancelled Letters not Prepared")
10351162

10361163
def __init__(self, valid_value_id: int, allowed_value: str, description: str):
10371164
"""

classes/repositories/subject_repository.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,43 @@ def there_is_letter_batch_for_subject(
280280
logging.info(
281281
f"[DB ASSERTION Passed] Subject {nhs_no} does not have a {letter_batch_code} - {letter_batch_title} batch"
282282
)
283+
284+
def get_early_subject_to_be_invited_for_surveillance(
285+
self, screening_centre_id: str, surveillance_due_count_date: str
286+
) -> str:
287+
"""
288+
Finds an early invite surveillance subject based on the surveillance due count date.
289+
Args:
290+
screening_centre_id (str): The screening centre ID.
291+
surveillance_due_count_date (str): The surveillance due count date.
292+
Returns:
293+
str: The NHS number of the early invite surveillance subject.
294+
Raises:
295+
ValueError: If no early invite surveillance subjects are found.
296+
"""
297+
query = """ SELECT ss.subject_nhs_number
298+
FROM screening_subject_t ss
299+
INNER JOIN sd_contact_t c ON ss.subject_nhs_number = c.nhs_number
300+
WHERE c.responsible_sc_id = :screeningCentreId
301+
AND c.deduction_reason IS NULL
302+
AND c.date_of_death IS NULL
303+
AND TRUNC (ss.surveillance_screen_due_date) <= TO_DATE(:sDueCountDate, 'DD/MM/YYYY')
304+
AND ss.screening_status_id = 4006
305+
AND c.date_of_death IS NULL
306+
AND NOT EXISTS (
307+
SELECT 1
308+
FROM ep_subject_episode_t ep
309+
WHERE ep.screening_subject_id = ss.screening_subject_id
310+
AND ep.episode_status_id IN (11352, 11354)
311+
)
312+
ORDER BY TRUNC(ss.surveillance_screen_due_date)
313+
FETCH FIRST 1 ROW ONLY
314+
"""
315+
parameters = {
316+
"screeningCentreId": int(screening_centre_id),
317+
"sDueCountDate": surveillance_due_count_date,
318+
}
319+
df = self.oracle_db.execute_query(query, parameters)
320+
if df.empty:
321+
raise ValueError("No early invite surveillance subjects found")
322+
return df["subject_nhs_number"].iloc[0]
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Utility Guide: Generate Health Check Forms
2+
3+
The **Generate Health Check Forms utility** provides helper methods for generating health check forms and inviting surveillance subjects early in BCSS.<br>
4+
This utility interacts with the UI and database to automate the process of producing health check forms for eligible subjects.
5+
6+
## Table of Contents
7+
8+
- [Utility Guide: Generate Health Check Forms](#utility-guide-generate-health-check-forms)
9+
- [Table of Contents](#table-of-contents)
10+
- [Summary of Utility Methods](#summary-of-utility-methods)
11+
- [Main Methods](#main-methods)
12+
- [`invite_surveillance_subjects_early`](#invite_surveillance_subjects_early)
13+
- [`find_early_invite_subjects`](#find_early_invite_subjects)
14+
- [Prerequisites](#prerequisites)
15+
- [Supporting Classes](#supporting-classes)
16+
- [Example Usage](#example-usage)
17+
18+
---
19+
20+
## Summary of Utility Methods
21+
22+
| Method | Purpose | Key Arguments | Expected Behaviour |
23+
|--------------------------------------|-------------------------------------------------------------------------|------------------------------|--------------------|
24+
| `invite_surveillance_subjects_early` | Generates health check forms and invites a surveillance subject early. | `screening_centre_id` (`str`)| Navigates UI, recalculates due count, finds subject, generates forms, returns NHS number. |
25+
| `find_early_invite_subjects` | Finds an eligible subject for early surveillance invitation. | `screening_centre_id` (`str`), `surveillance_due_count_date` (`str`) | Returns NHS number of eligible subject. |
26+
27+
---
28+
29+
## Main Methods
30+
31+
### `invite_surveillance_subjects_early`
32+
33+
Generates health check forms and invites a surveillance subject early by automating the relevant UI steps.
34+
35+
**Arguments:**
36+
37+
- `screening_centre_id` (`str`): The screening centre ID for which to generate forms and invite a subject.
38+
39+
**How it works:**
40+
41+
1. Navigates to the Surveillance page and then to the Produce Health Check Forms page.
42+
2. Sets the "Surveillance Due Count Volume" to 1.
43+
3. Clicks the "Recalculate" button to update the due count.
44+
4. Retrieves the current "Surveillance Due Count Date" from the UI.
45+
5. Finds an eligible subject for early invitation using the due count date and centre ID.
46+
6. Clicks the "Generate Health Check Forms" button.
47+
7. Returns the NHS number of the invited subject.
48+
49+
**Returns:**
50+
51+
- `str`: The NHS number of the early-invite subject.
52+
53+
---
54+
55+
### `find_early_invite_subjects`
56+
57+
Finds an eligible subject for early surveillance invitation based on the due count date and screening centre.
58+
59+
**Arguments:**
60+
61+
- `screening_centre_id` (`str`): The screening centre ID.
62+
- `surveillance_due_count_date` (`str`): The due count date as a string.
63+
64+
**How it works:**
65+
66+
1. Uses the `SubjectRepository` to query the database for a subject eligible for early surveillance invitation.
67+
2. Returns the NHS number of the found subject.
68+
69+
**Returns:**
70+
71+
- `str`: The NHS number of the eligible subject.
72+
73+
---
74+
75+
## Prerequisites
76+
77+
Before using the Generate Health Check Forms utility, ensure that the following prerequisites are met:
78+
79+
1. **UI Access**: The utility requires access to the BCSS UI and a valid Playwright `Page` object.
80+
2. **Database Access**: The utility uses the `SubjectRepository` to query the database for eligible subjects.
81+
3. **Screening Centre ID**: You must provide a valid screening centre ID for which to generate forms and invite subjects.
82+
83+
---
84+
85+
## Supporting Classes
86+
87+
These classes are required by the utility:
88+
89+
- `BasePage` — Provides navigation and common UI actions.
90+
- `SurveillancePage` — Handles navigation to the surveillance section.
91+
- `ProduceHealthCheckFormsPage` — Interacts with the health check forms UI.
92+
- `SubjectRepository` — Queries the database for eligible subjects.
93+
94+
---
95+
96+
## Example Usage
97+
98+
```python
99+
from utils.generate_health_check_forms_util import GenerateHealthCheckFormsUtil
100+
101+
# Assume you have a Playwright page object and a valid screening centre ID
102+
page = ... # Playwright Page object
103+
screening_centre_id = "12345"
104+
105+
# Create the utility
106+
health_check_util = GenerateHealthCheckFormsUtil(page)
107+
108+
# Invite a surveillance subject early and generate health check forms
109+
nhs_no = health_check_util.invite_surveillance_subjects_early(screening_centre_id)
110+
111+
print(f"Invited subject NHS number: {nhs_no}")
112+
```

pages/base_page.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def __init__(self, page: Page):
3737
self.lynch_surveillance_page = self.page.get_by_role(
3838
"link", name="Lynch Surveillance"
3939
)
40+
self.surveillance_page = self.page.get_by_role(
41+
"link", name="Surveillance", exact=True
42+
)
4043
self.organisations_page = self.page.get_by_role("link", name="Organisations")
4144
self.reports_page = self.page.get_by_role("link", name="Reports")
4245
self.screening_practitioner_appointments_page = self.page.get_by_role(
@@ -182,6 +185,10 @@ def go_to_lynch_surveillance_page(self) -> None:
182185
"""Click the Base Page 'Lynch Surveillance' link."""
183186
self.click(self.lynch_surveillance_page)
184187

188+
def go_to_surveillance_page(self) -> None:
189+
"""Click the Base Page 'Surveillance' link."""
190+
self.click(self.surveillance_page)
191+
185192
def go_to_organisations_page(self) -> None:
186193
"""Click the Base Page 'Organisations' link."""
187194
self.click(self.organisations_page)

pages/screening_practitioner_appointments/appointment_detail_page.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,12 @@ def select_reason_for_cancellation_option(self, option: str) -> None:
8484
Selects the reason for cancellation from the dropdown.
8585
Args:
8686
option: The reason for cancellation to select.
87-
The options are in the ReasonForCancellationOptions class
87+
The options are in the ReasonForCancellationOptions class or can be the string value.
8888
"""
89-
self.reason_for_cancellation_dropdown.select_option(value=option)
89+
if option in ReasonForCancellationOptions._value2member_map_:
90+
self.reason_for_cancellation_dropdown.select_option(value=option)
91+
else:
92+
self.reason_for_cancellation_dropdown.select_option(label=option)
9093

9194
def mark_appointment_as_attended(self, date: datetime) -> None:
9295
"""

0 commit comments

Comments
 (0)