Skip to content

Commit ddaed84

Browse files
committed
working on scenario 1 surveillance
1 parent ddbf909 commit ddaed84

File tree

9 files changed

+263
-5
lines changed

9 files changed

+263
-5
lines changed

classes/event/event_status_type.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,7 @@ class EventStatusType(Enum):
10321032
U81 = (11283, "U81", "Kit Returned and Logged (Technical Fail; Weak Positive")
10331033
U97 = (11284, "U97", "Weak Positive, Waiting for Screening Centre Assistance")
10341034
U98 = (11285, "U98", "Weak Positive, Waiting for Programme Hub Assistance")
1035+
X500=(20100, "X500", "Selected For Surveillance")
10351036

10361037
def __init__(self, valid_value_id: int, allowed_value: str, description: str):
10371038
"""

classes/repositories/subject_repository.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from ast import Str
12
import logging
23
from typing import Optional
34
from classes.subject.pi_subject import PISubject
@@ -280,3 +281,42 @@ def there_is_letter_batch_for_subject(
280281
logging.info(
281282
f"[DB ASSERTION Passed] Subject {nhs_no} does not have a {letter_batch_code} - {letter_batch_title} batch"
282283
)
284+
285+
286+
def get_early_subject_to_be_invited_for_surveillance(
287+
self, screening_centre_id: int, s_due_count_date: str
288+
) -> Optional[str]:
289+
"""
290+
Finds a subject in the DB who is due to be invited for surveillance early.
291+
Args:
292+
screening_centre_id (int): The screening centre ID.
293+
s_due_count_date (str): The due count date in 'DD/MM/YYYY' format.
294+
Returns:
295+
str: The NHS number of a subject that matches the provided criteria, or None if not found.
296+
"""
297+
sql_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+
bind_vars = {
316+
"screeningCentreId": screening_centre_id,
317+
"sDueCountDate": s_due_count_date,
318+
}
319+
df = self.oracle_db.execute_query(sql_query, bind_vars)
320+
if df.empty:
321+
return None
322+
return df["subject_nhs_number"].iloc[0]

pages/base_page.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(self, page: Page):
5454
"#ntshPageTitle"
5555
)
5656
self.main_menu_header = self.page.locator("#ntshPageTitle")
57+
self.surveillance = self.page.get_by_text("Surveillance", exact=True)
5758

5859
def click_main_menu_link(self) -> None:
5960
"""Click the Base Page 'Main Menu' link if it is visible."""
@@ -121,6 +122,10 @@ def click_help_link(self) -> None:
121122
"""Click the Base Page 'Help' link."""
122123
self.click(self.help_link)
123124

125+
def click_surveillance_link(self) -> None:
126+
"""Click the Surveillance Page link."""
127+
self.click(self.surveillance)
128+
124129
def bowel_cancer_screening_system_header_is_displayed(self) -> None:
125130
"""Asserts that the Bowel Cancer Screening System header is displayed."""
126131
expect(self.bowel_cancer_screening_system_header).to_contain_text(
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from playwright.sync_api import Page
2+
from pages.base_page import BasePage
3+
4+
5+
class ProduceHealthCheckPage(BasePage):
6+
"""Page object for navigating to and interacting with the Produce Healthcheck Forms section."""
7+
8+
def __init__(self, page: Page):
9+
super().__init__(page)
10+
11+
# Locators
12+
self.surveillance_due_count_volume = self.page.locator(
13+
'input[name="btnChangeVolume"]'
14+
)
15+
self.surveillance_due_count_volume_textbox = self.page.locator('[name="txtVolume"]')
16+
self.recalculate_button = self.page.locator('[name="btnRecalc"]')
17+
self.surveillance_due_count_date = self.page.locator('[name="txtDueDateTo"]')
18+
self.health_check_forms=self.page.get_by_role('button', name='Generate HealthCheck Forms')
19+
20+
def click_on_surveillance_due_count_volume_button(self):
21+
"""Clicks on the Surveillance Due Count Volume Change button."""
22+
self.click(self.surveillance_due_count_volume)
23+
24+
def fill_volume_in_surveillance_due_count_volume_textbox(self, volume: int):
25+
"""Enters a volume in the Surveillance Due Count Volume textbox.
26+
27+
Args:
28+
volume (str): The volume to enter in the textbox.
29+
"""
30+
self.surveillance_due_count_volume_textbox.fill(str(volume))
31+
def click_on_recalculate_button(self):
32+
"""Clicks on the Recalculate button."""
33+
self.click(self.recalculate_button)
34+
35+
def get_surveillance_due_count_date(self):
36+
"""Gets the value from the Surveillance Due Count Date textbox.
37+
"""
38+
return self.surveillance_due_count_date.input_value()
39+
40+
def click_on_generate_healthcheck_forms_button(self):
41+
"""Clicks on the Generate HealthCheck Forms button."""
42+
self.safe_accept_dialog(self.health_check_forms)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from playwright.sync_api import Page
2+
from pages.base_page import BasePage
3+
4+
5+
class SurveillancePage(BasePage):
6+
"""Page object for navigating to and interacting with the Surveillance Pagesection."""
7+
8+
def __init__(self, page: Page):
9+
super().__init__(page)
10+
11+
# Locators
12+
self.produce_healthcheck_forms = self.page.get_by_role('link', name='Produce Healthcheck Forms')
13+
14+
15+
def navigate_to_produce_healthcheck_forms(self):
16+
"""Navigates to the Produce Healthcheck Forms section."""
17+
self.click(self.produce_healthcheck_forms)
18+

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ markers =
5555
spine_retrieval_search_tests: tests that are part of subject spine retrieval demographics
5656
hub_user_tests: tests that are part of the hub user test suite
5757
fobt_regression_tests: tests that are part of the fobt regression test suite
58+
survelliance_regression_tests: tests that are part of the survelliance regression test suite
5859
lynch_self_referral_tests: tests that are part of the lynch self referral test suite
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import pytest
2+
from playwright.sync_api import Page
3+
from classes.repositories.subject_repository import SubjectRepository
4+
from conftest import general_properties
5+
from pages.base_page import BasePage
6+
from pages.surveillance import produce_healthcheck_forms
7+
from pages.surveillance.produce_healthcheck_forms import ProduceHealthCheckPage
8+
from pages.surveillance.surveillance_page import SurveillancePage
9+
from utils import screening_subject_page_searcher
10+
from utils.generate_healthcheck_forms import generate_healthcheck_forms
11+
from utils.sspi_change_steps import SSPIChangeSteps
12+
from utils.user_tools import UserTools
13+
from utils.oracle.subject_creation_util import CreateSubjectSteps
14+
from utils.subject_assertion import subject_assertion
15+
from utils.call_and_recall_utils import CallAndRecallUtils
16+
import logging
17+
from utils.batch_processing import batch_processing
18+
from utils.fit_kit import FitKitGeneration, FitKitLogged
19+
from pages.logout.log_out_page import LogoutPage
20+
from datetime import datetime
21+
from pages.surveillance.produce_healthcheck_forms import ProduceHealthCheckPage
22+
from classes.repositories.subject_repository import SubjectRepository
23+
from utils.oracle.oracle_specific_functions.organisation_parameters import (
24+
set_org_parameter_value,
25+
check_parameter,
26+
)
27+
28+
@pytest.mark.wip
29+
@pytest.mark.vpn_required
30+
@pytest.mark.regression
31+
@pytest.mark.survelliance_regression_tests
32+
def test_scenario_1(page: Page, general_properties: dict) -> None:
33+
"""
34+
Scenario: 1: Discharge for clinical decision (GP letter required)
35+
36+
X500-X505-X600-X610-X615-X641-X600-X610-X615-X650-X390-X379-C203 [SSCL28b] X900-X600-X610-X2-X610-X615-X650-X382-X79-C203 [SSCL25a]
37+
38+
This scenario takes both an in-age and an over-age surveillance subject from invitation through to episode closure on X379 - discharge for clinical reason, GP letter required. The scenario includes both DNA and reschedule of the SSP appointment. It also includes a reopen, and checks that the episode could be postponed at most points during this pathway.
39+
40+
Because we cannot know if we will be inviting a subject who has had previous FOBT episodes, or only a bowel scope episode, it is impossible to check if they will be set to Call or Recall; we can only check that they are not longer in Surveillance status.
41+
42+
Note: parameter 82 controls whether or not a GP letter is required when a patient is discharged from Surveillance as a result of a clinical decision. It actually defaults to Y, but it's set at SC level in the scenario to be sure it holds the correct value. As a parameter can't be set with immediate effect through the screens, the scenario uses a direct database update to do this.
43+
44+
45+
Scenario summary:
46+
>Run surveillance invitations for 1 subject > X500 (3.1)
47+
> SSPI update changes subject to in-age
48+
> Process X500 letter batch > X505 (3.1)
49+
> Record patient contact – contacted, SSP appointment > X600 (3.1)
50+
> Book SSP appointment from report > X610 (3.3)
51+
> Process X610 letter batch > X615 (3.3)
52+
> Patient DNA SSP appointment > X641 (3.3)
53+
> Choose to book SSP appointment > X600 (3.3)
54+
> Book SSP appointment > X610 (3.3)
55+
> Process X610 letter batch > X615 (3.3)
56+
> Attend SSP appointment > X650 (3.3)
57+
> Record discharge, clinical decision, GP letter required > X390 (3.4)
58+
> Process X390 letter batch > X379 > C203 (3.4)
59+
> Check recall [SSCL28b]
60+
> Reopen episode for correction > X900 (3.1)
61+
> Record patient contact – contacted, SSP appointment > X600 (3.1)
62+
> Book SSP appointment from subject summary > X610 (3.3)
63+
> Reschedule SSP appointment > X2 > X610 (3.3)
64+
> Process X610 letter batch > X615 (3.3)
65+
> Attend SSP appointment > X650 (3.3)
66+
> SSPI update changes subject to over-age
67+
> Record discharge, clinical decision, GP letter required > X382 (3.4)
68+
> Process X382 letter batch > X79 > C203 (3.4)
69+
> Check recall [SSCL25a]
70+
71+
"""
72+
# Given I log in to BCSS "England" as user role "Screening Centre Manager at BCS001"
73+
user_role = UserTools.user_login(
74+
page, "Screening Centre Manager at BCS001", return_role_type=True
75+
)
76+
if user_role is None:
77+
raise ValueError("User cannot be assigned to a UserRoleType")
78+
79+
# When I run surveillance invitations for 1 subject
80+
81+
nhs_no = generate_healthcheck_forms(page, general_properties)
82+
# Then my subject has been updated as follows:
83+
84+
criteria = {
85+
"latest episode status": "Open",
86+
"latest episode type": "Surveillance",
87+
"latest event status": "X500 Selected For Surveillance",
88+
"responsible screening centre code": "User's screening centre",
89+
"subject has unprocessed sspi updates": "No",
90+
"subject has user dob updates": "No",
91+
}
92+
93+
subject_assertion(nhs_no, criteria,user_role)
94+
# And there is a "X500" letter batch for my subject with the exact title "Surveillance Selection"
95+
SubjectRepository().there_is_letter_batch_for_subject(
96+
nhs_no, "X500", "Surveillance Selection"
97+
)
98+
# Then Comment: NHS number logging.info(f"Surveillance Scenario NHS Number: {nhs_number}")
99+
# When I set the value of parameter 82 to "Y" for my organisation with immediate effect
100+
org_id = general_properties["eng_screening_centre_id"]
101+
set_org_parameter_value(82, "Y", org_id)
102+
# When I receive an SSPI update to change their date of birth to "72" years old
103+
SSPIChangeSteps().sspi_update_to_change_dob_received(nhs_no, 72)
104+
# Then my subject has been updated as follows:
105+
subject_assertion(nhs_no, {"subject age": "72"})
106+
# When I process the open "X500" letter batch for my subject
107+
batch_processing(
108+
page,
109+
batch_type="X500",
110+
batch_description="Surveillance Selection",
111+
latest_event_status="X505 HealthCheck Form Printed",
112+
)
113+
# Then my subject has been updated as follows:
114+
subject_assertion(
115+
nhs_no,
116+
{
117+
"latest event status": "X505 HealthCheck Form Printed",
118+
},
119+
)
120+
# When I view the subject
121+
screening_subject_page_searcher.navigate_to_subject_summary_page(
122+
page=page, nhs_no=nhs_no
123+
)
124+
# And I select the advance episode option for "Record Contact with Patient"
125+
# SurveillancePage(page).select_advance_episode_option("Record Contact with Patient")
126+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import logging
2+
from classes.repositories.subject_repository import SubjectRepository
3+
from pages.base_page import BasePage
4+
from pages.surveillance.surveillance_page import SurveillancePage
5+
from pages.surveillance.produce_healthcheck_forms import ProduceHealthCheckPage
6+
from logging import Logger
7+
8+
9+
def generate_healthcheck_forms(page, general_properties):
10+
BasePage(page).click_surveillance_link()
11+
SurveillancePage(page).navigate_to_produce_healthcheck_forms()
12+
ProduceHealthCheckPage(page).click_on_surveillance_due_count_volume_button()
13+
ProduceHealthCheckPage(page).fill_volume_in_surveillance_due_count_volume_textbox(1)
14+
ProduceHealthCheckPage(page).click_on_recalculate_button()
15+
subject_repo = SubjectRepository()
16+
screening_centre_id = general_properties["eng_screening_centre_id"]
17+
s_due_count_date = ProduceHealthCheckPage(page).get_surveillance_due_count_date()
18+
nhs_number = subject_repo.get_early_subject_to_be_invited_for_surveillance(
19+
screening_centre_id, s_due_count_date
20+
)
21+
assert nhs_number is not None
22+
logging.info(f"Early subject NHS number: {nhs_number}")
23+
ProduceHealthCheckPage(page).click_on_generate_healthcheck_forms_button()
24+
return nhs_number

utils/oracle/subject_selection_query_builder.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ def _dispatch_criteria_key(self, user: "User", subject: "Subject") -> None:
468468
case SubjectSelectionCriteriaKey.LATEST_EPISODE_SUB_TYPE:
469469
self._add_criteria_latest_episode_sub_type()
470470
case SubjectSelectionCriteriaKey.LATEST_EPISODE_STATUS:
471-
self._add_criteria_latest_episode_status()
471+
self._add_criteria_latest_episode_status()
472472

473473
case SubjectSelectionCriteriaKey.LATEST_EPISODE_STATUS_REASON:
474474
self._add_criteria_latest_episode_status_reason()
@@ -718,6 +718,7 @@ def _dispatch_criteria_key(self, user: "User", subject: "Subject") -> None:
718718
self._add_extra_column_to_select_statement()
719719
case SubjectSelectionCriteriaKey.ADD_JOIN_TO_FROM_STATEMENT:
720720
self._add_extra_join_to_from_statement()
721+
721722
# ------------------------------------------------------------------------
722723
# 🛑 Fallback: Unmatched Criteria Key
723724
# ------------------------------------------------------------------------
@@ -2966,7 +2967,7 @@ def _add_criteria_subject_screening_centre_code(self, user: "User"):
29662967
sc_code = None
29672968

29682969
try:
2969-
option = SubjectScreeningCentreCode.by_description(
2970+
option = SubjectScreeningCentreCode.by_description_case_insensitive(
29702971
self.criteria_value.lower()
29712972
)
29722973
match option:
@@ -2979,9 +2980,9 @@ def _add_criteria_subject_screening_centre_code(self, user: "User"):
29792980
| SubjectScreeningCentreCode.USER_SC
29802981
| SubjectScreeningCentreCode.USER_ORGANISATION
29812982
):
2982-
if user.organisation is None or user.organisation.id is None:
2983-
raise ValueError("User organisation or organisation_id is None")
2984-
sc_code = user.organisation.id
2983+
if user.organisation is None or user.organisation.code is None:
2984+
raise ValueError("User organisation or organisation_code is None")
2985+
sc_code = user.organisation.code
29852986
case _:
29862987
raise SelectionBuilderException(
29872988
self.criteria_key_name, self.criteria_value

0 commit comments

Comments
 (0)