From 91a2f3df20f3c5b6a3bc2592b80ec305d8b208de Mon Sep 17 00:00:00 2001 From: Megha Prasannan Date: Mon, 5 Jan 2026 10:07:22 +0000 Subject: [PATCH 1/4] scenario 1 --- classes/repositories/general_repository.py | 16 ++ pytest.ini | 2 + .../test_lynch_scenario_1.py | 235 ++++++++++++++++++ utils/lynch_utils.py | 40 ++- 4 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py diff --git a/classes/repositories/general_repository.py b/classes/repositories/general_repository.py index fd2cda53..fa18a11e 100644 --- a/classes/repositories/general_repository.py +++ b/classes/repositories/general_repository.py @@ -67,3 +67,19 @@ def process_new_lynch_patients(self): ) logging.debug("[END] process_new_lynch_patients") + + def run_lynch_invitations(self): + logging.debug("[START] run_lynch_invitations") + try: + self.oracle_db.execute_stored_procedure( + "pkg_lynch.p_invite_lynch_subjects", + in_params=None, + out_params=[oracledb.CURSOR], + ) + except Exception as e: + logging.error("Error executing pkg_lynch.p_invite_lynch_subjects", exc_info=True) + raise oracledb.DatabaseError( + f"Error executing pkg_lynch.p_invite_lynch_subjects: {e}" + ) + + logging.debug("[END] run_lynch_invitations") diff --git a/pytest.ini b/pytest.ini index 78667463..249a2222 100644 --- a/pytest.ini +++ b/pytest.ini @@ -57,3 +57,5 @@ markers = fobt_regression_tests: tests that are part of the fobt regression test suite lynch_self_referral_tests: tests that are part of the lynch self referral test suite surveillance_regression_tests: tests that are part of the surveillance regression test suite + lynch_regression_tests: tests that are part of the lynch regression test suite + diff --git a/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py b/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py new file mode 100644 index 00000000..04e1dccd --- /dev/null +++ b/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py @@ -0,0 +1,235 @@ +from turtle import up +from typing import Self +import pytest +from playwright.sync_api import Page +from classes.repositories.general_repository import GeneralRepository +from classes.screening.subject_screening_centre_code import SubjectScreeningCentreCode +from classes.subject import subject +from classes.subject.subject import Subject +from classes.user.user_role_type import UserRoleType +from pages.base_page import BasePage +from pages.base_page import BasePage +from pages.screening_practitioner_appointments import book_appointment_page +from pages.screening_practitioner_appointments.appointment_detail_page import AppointmentDetailPage +from pages.screening_subject_search import subject_screening_search_page +from pages.screening_subject_search.episode_events_and_notes_page import EpisodeEventsAndNotesPage +from pages.screening_subject_search.subject_screening_summary_page import SubjectScreeningSummaryPage +from utils import screening_subject_page_searcher +from utils.appointments import book_appointments +from utils.lynch_utils import LynchUtils +from utils.oracle.oracle import OracleDB +from utils.user_tools import UserTools +from utils.oracle.subject_creation_util import CreateSubjectSteps +from utils.subject_assertion import subject_assertion +from utils.call_and_recall_utils import CallAndRecallUtils +import logging +from utils.batch_processing import batch_processing +from utils.fit_kit import FitKitGeneration, FitKitLogged +from pages.logout.log_out_page import LogoutPage +from datetime import datetime + + +@pytest.mark.wip +@pytest.mark.usefixtures("setup_org_and_appointments") +@pytest.mark.vpn_required +@pytest.mark.regression +@pytest.mark.lynch_regression_tests +def test_scenario_1(page: Page) -> None: + """ + Scenario: 1 - Patient refuses colonoscopy assessment appointment + + G1-G2-G3-A183-A25-J10-J1-J34-J35-J36-J27-J37-J38-J14-J22-J8-J9-C203 in age range [SSCL4b(J9)] + + This scenario tests where a subject attends a colonoscopy assessment appointment, but is then referred for a subsequent assessment appointment which does not take place because the subject first DNAs the appointment then cancels to consider. As the subject then does not book a new appointment within the time limit so is deemed to have refused the assessment and the episode is closed. + + Scenario summary: + + > Process Lynch diagnosis for a new in-age subject suitable for immediate invitation + > Run Lynch invitations > G1 (5.1) + > Process G1 letter batch > G2 (5.1) + > Run timed events > G3 (5.1) + > Book appointment > A183 (1.11) + > Process A183 letter batch > A25 (1.11) + > Attend appointment > J10 (1.11) + > Advance for "Subsequent Assessment Appointment Required" > J1 (1.11) + > Book appointment > J34 (1.11) + > Process J34 letter batch > J35 (1.11) + > Subject DNA > J36 (1.11) + > Process J36 letter batch > J27 (1.11) + > Book appointment > J37 (1.11) + > Process J37 letter batch > J38 (1.11) + > Patient cancels to consider > J14 (1.11) + > Process J14 letter batch > J22 (1.11) + > Run timed events > J22 (1.11) + > Process J22 letter batch > J8 (1.11) + > Process J8 letter batch > J9 (1.11) > C203 (1.13) + > Check recall [SSCL4b(J9)] + """ + # Given I log in to BCSS "England" as user role "Hub Manager" + user_role = UserTools.user_login( + page, "Hub Manager State Registered at BCS01", return_role_type=True + ) + if user_role is None: + raise ValueError("User cannot be assigned to a UserRoleType") + + # # When I receive Lynch diagnosis "EPCAM" for a new subject in my hub aged "25" with diagnosis date "1 year ago" and no last colonoscopy date + + # nhs_no = LynchUtils(page).insert_validated_lynch_patient_from_new_subject_with_age( + # age="25", + # gene="EPCAM", + # when_diagnosis_took_place="1 year ago", + # when_last_colonoscopy_took_place="unknown", + # user_role=user_role, + # ) + # # Now use nhs_number in your test assertions or further steps + # assert nhs_no is not None + # logging.info(f"Created Lynch subject with NHS number: {nhs_no}") + # # Then my subject has been updated as follows: + + # subject_assertion( + # nhs_no, + # { + # "Calculated FOBT due date": "Null", + # "Calculated lynch due date": "Null", + # "Calculated surveillance due date": "Null", + # "Lynch due date": "Null", + # "Lynch due date date of change": "Null", + # "Lynch due date reason": "Null", + # "Previous screening status": "Null", + # "Screening due date": "Null", + # "Screening due date date of change": "Null", + # "Screening due date reason": "Null", + # "Subject has lynch diagnosis": "Yes", + # "Subject lower FOBT age": "Default", + # "Subject lower lynch age": "25", + # "Screening status": "Lynch Surveillance", + # "Screening status date of change": "Today", + # "Screening status reason": "Eligible for Lynch Surveillance", + # "Subject age": "25", + # "Surveillance due date": "Null", + # "Surveillance due date date of change": "Null", + # "Surveillance due date reason": "Null", + # }, + # user_role, + # ) + + # # When I set the Lynch invitation rate for all screening centres to 50 + # LynchUtils(page).set_lynch_invitation_rate(rate=50) + # # And I run the Lynch invitations process + # GeneralRepository().run_lynch_invitations() + # # And my subject has been updated as follows: + + # criteria = { + # "Calculated FOBT due date": "Null", + # "Calculated lynch due date": "Unchanged", + # "Calculated surveillance due date": "Null", + # "Lynch due date": "25th birthday", + # "Lynch due date date of change": "Today", + # "Lynch due date reason": "Selected for Lynch Surveillance", + # "Previous screening status": "Null", + # "Screening due date": "Null", + # "Screening due date date of change": "Null", + # "Screening due date reason": "Null", + # "Subject has an open episode": "Yes", + # "Subject has lynch diagnosis": "Yes", + # "Subject lower FOBT age": "Default", + # "Subject lower lynch age": "25", + # "Screening status": "Lynch Surveillance", + # "Screening status date of change": "Today", + # "Screening status reason": "Eligible for Lynch Surveillance", + # "Subject age": "25", + # "Surveillance due date": "Null", + # "Surveillance due date date of change": "Null", + # "Surveillance due date reason": "Null", + # } + # subject_assertion( + # nhs_no, + # criteria, + # ) + + # # And there is a "G1" letter batch for my subject with the exact title "Lynch Surveillance Pre-invitation" + # # When I process the open "G1" letter batch for my subject + # batch_processing( + # page, + # "G1", + # "Lynch Surveillance Pre-invitation" + # ) + # # Then my subject has been updated as follows: + # subject_assertion(nhs_no, {"latest event status": "G2 Lynch Pre-invitation Sent"}) + + nhs_no = "9426510028" + # When I run Timed Events for my subject + OracleDB().exec_bcss_timed_events(nhs_number=nhs_no) + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "G3 Lynch Surveillance Colonoscopy Assessment Appointment Required" + }, + ) + # When I view the subject + screening_subject_page_searcher.navigate_to_subject_summary_page(page, nhs_no) + pytest.skip("Skipping remainder of test until Lynch utils are complete") + # And I view the practitioner appointment booking screen + subject_screening_search_page.go_to_practitioner_appointment_booking_page(page) + # And I select "BCS001" as the screening centre where the practitioner appointment will be held + book_appointment_page.select_screening_centre( + page, "BCS001 - Wolverhampton Bowel Cancer Screening Centre" + ) + # And I set the practitioner appointment date to "today" + # And I book the "earliest" available practitioner appointment on this date + book_appointments( + page, + "BCS001 - Wolverhampton Bowel Cancer Screening Centre", + "The Royal Hospital (Wolverhampton)", + ) + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "A183 1st Colonoscopy Assessment Appointment Requested" + }, + ) + # And there is a "A183" letter batch for my subject with the exact title "Practitioner Clinic 1st Appointment (Lynch)" + batch_processing.assert_letter_batch_exists( + nhs_no, + "A183", + exact_title="Practitioner Clinic 1st Appointment (Lynch)", + ) + # When I process the open "A183" letter batch for my subject + batch_processing.process_open_letter_batch( + nhs_no, + "A183", + ) + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + {"latest event status": "A25 1st Colonoscopy Assessment Appointment Letter Sent"}, + ) + # When I switch users to BCSS "England" as user role "Screening Centre Manager" + + LogoutPage(page).log_out(close_page=False) + BasePage(page).go_to_log_in_page() + UserTools.user_login(page, "Screening Centre Manager at BCS001") + # And I view the event history for the subject's latest episode + SubjectScreeningSummaryPage(page).expand_episodes_list() + # SubjectScreeningSummaryPage(page).click_first_fobt_episode_link()-check this is FOBT link + # And I view the latest practitioner appointment in the subject's episode + EpisodeEventsAndNotesPage(page).click_most_recent_view_appointment_link() + + # And I attend the subject's practitioner appointment "today" + AppointmentDetailPage(page).mark_appointment_as_attended(datetime.today()) + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + {"latest event status": "J10 Attended Colonoscopy Assessment Appointment"}, + ) + + # When I view the subject + screening_subject_page_searcher.navigate_to_subject_summary_page(page, nhs_no) + # And I view the advance episode options + # SubjectScreeningSummaryPage(page).click_advance_fobt_screening_episode_button()--lynch equivalent + # And I select Subsequent Assessment Appointment Required reason "SC interpreter DNA" + # LynchUtils(page).advance_episode_for_subsequent_assessment_appointment_required( + # reason="SC interpreter DNA" + # ) diff --git a/utils/lynch_utils.py b/utils/lynch_utils.py index 1edeb5eb..9820b7ce 100644 --- a/utils/lynch_utils.py +++ b/utils/lynch_utils.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta, date from dateutil.relativedelta import relativedelta from typing import Optional +from playwright.sync_api import Page from classes.date.date_description_utils import DateDescriptionUtils from classes.repositories.general_repository import GeneralRepository @@ -27,8 +28,11 @@ class LynchUtils: - @staticmethod - def insert_validated_lynch_patient_from_new_subject_with_age( + + def __init__(self, page: Page): + self.page = page + + def insert_validated_lynch_patient_from_new_subject_with_age(self, age: str, gene: str, when_diagnosis_took_place: str, @@ -64,14 +68,12 @@ def insert_validated_lynch_patient_from_new_subject_with_age( days = int(parts[2]) else: years = int(age) - days = random.randint(0, 363) + days = 0 plus_or_minus = PLUS except Exception: raise SelectionBuilderException("Invalid age format", age) - date_of_birth = datetime.today().date().replace( - day=15, month=10 - ) - relativedelta(years=years) + date_of_birth = datetime.today().date() - relativedelta(years=years) if plus_or_minus in [MINUS, LESS]: date_of_birth += timedelta(days=days) else: @@ -264,3 +266,29 @@ def process_new_lynch_patients() -> None: """ general_repository = GeneralRepository() general_repository.process_new_lynch_patients() + + def set_lynch_invitation_rate(self, rate): + """ + Sets the Lynch invitation rate for all screening centres. + """ + db = OracleDB() + connection = db.connect_to_db() + try: + sql = ( + "UPDATE lynch_invitation_rate lir " + "SET lir.audit_reason = 'AUTO TEST', " + "lir.datestamp = systimestamp, " + "lir.rate = :rate" + ) + with connection.cursor() as cursor: + cursor.execute(sql, {"rate": rate}) + connection.commit() + logging.info(f"Rows affected: {cursor.rowcount}") + finally: + db.disconnect_from_db(connection) + + def run_lynch_invitations(self): + logging.info("Starting Lynch invitations") + general_repository = GeneralRepository() + general_repository.run_lynch_invitations() + logging.info("Finished Lynch invitations") From ba1efb78d261172641f6b35c43146948a414db09 Mon Sep 17 00:00:00 2001 From: Adriano Aru Date: Mon, 5 Jan 2026 11:05:34 +0000 Subject: [PATCH 2/4] Migrating Scenario 2 --- .../subject_screening_summary_page.py | 7 + pytest.ini | 1 - .../test_lynch_scenario_2.py | 278 ++++++++++++++++++ 3 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_2.py diff --git a/pages/screening_subject_search/subject_screening_summary_page.py b/pages/screening_subject_search/subject_screening_summary_page.py index 07095386..375b38a1 100644 --- a/pages/screening_subject_search/subject_screening_summary_page.py +++ b/pages/screening_subject_search/subject_screening_summary_page.py @@ -62,6 +62,9 @@ def __init__(self, page: Page): self.first_surveillance_episode_link = self.page.get_by_role( "link", name="Surveillance" ).first + self.first_lynch_surveillance_episode_link = self.page.get_by_role( + "link", name="Lynch Surveillance" + ).first self.datasets_link = self.page.get_by_role("link", name="Datasets") self.advance_fobt_screening_episode_button = self.page.get_by_role( "button", name="Advance FOBT Screening Episode" @@ -251,6 +254,10 @@ def click_first_surveillance_episode_link(self) -> None: """Click on the first Surveillance episode link.""" self.click(self.first_surveillance_episode_link) + def click_first_lynch_surveillance_episode_link(self) -> None: + """Click on the first Lynch Surveillance episode link.""" + self.click(self.first_lynch_surveillance_episode_link) + def click_datasets_link(self) -> None: """Click on the 'Datasets' link.""" self.click(self.datasets_link) diff --git a/pytest.ini b/pytest.ini index 249a2222..5ccc82a8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -58,4 +58,3 @@ markers = lynch_self_referral_tests: tests that are part of the lynch self referral test suite surveillance_regression_tests: tests that are part of the surveillance regression test suite lynch_regression_tests: tests that are part of the lynch regression test suite - diff --git a/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_2.py b/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_2.py new file mode 100644 index 00000000..cad2f8b3 --- /dev/null +++ b/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_2.py @@ -0,0 +1,278 @@ +import logging +import pytest +from playwright.sync_api import Page +from classes.repositories.general_repository import GeneralRepository +from pages.base_page import BasePage +from pages.logout.log_out_page import LogoutPage +from pages.screening_practitioner_appointments.appointment_detail_page import ( + AppointmentDetailPage, +) +from pages.screening_subject_search.episode_events_and_notes_page import ( + EpisodeEventsAndNotesPage, +) +from pages.screening_subject_search.subject_screening_summary_page import ( + SubjectScreeningSummaryPage, +) +from utils import screening_subject_page_searcher +from utils.appointments import book_appointments +from utils.batch_processing import batch_processing +from utils.lynch_utils import LynchUtils +from utils.oracle.oracle import OracleDB +from utils.subject_assertion import subject_assertion +from utils.user_tools import UserTools + + +@pytest.mark.usefixtures("setup_org_and_appointments") +@pytest.mark.vpn_required +@pytest.mark.regression +@pytest.mark.lynch_regression_tests +def test_scenario_2(page: Page) -> None: + """ + Scenario: 2 - Screening Centre discharges patient prior to colonoscopy assessment + + G1-G2-G3-A183-A25-J24-J25-J26-C203 in age range [SSCL4b(J26)] + + This scenario tests where the screening centre discharges the patient before the colonoscopy assessment appointment takes place. + + Scenario summary: + + > Process Lynch diagnosis for a new in-age subject suitable for immediate invitation + > Run Lynch invitations > G1 (5.1) + > Process G1 letter batch > G2 (5.1) + > Run timed events > G3 (5.1) + > Book appointment > A183 (1.11) + > Process A183 letter batch > A25 (1.11) + > SC discharges patient > J24 (1.11) + > Process J24 letter batch > J25 (1.11) + > Process J25 letter batch > J26 (1.11) + > Check recall [SSCL4b(J26)] + """ + # Given I log in to BCSS "England" as user role "Hub Manager" + user_role = UserTools.user_login( + page, "Hub Manager State Registered at BCS01", return_role_type=True + ) + if user_role is None: + raise ValueError("User cannot be assigned to a UserRoleType") + + # When I receive Lynch diagnosis "EPCAM" for a new subject in my hub aged "25" with diagnosis date "1 year ago" and no last colonoscopy date + nhs_no = LynchUtils(page).insert_validated_lynch_patient_from_new_subject_with_age( + age="25", + gene="EPCAM", + when_diagnosis_took_place="1 year ago", + when_last_colonoscopy_took_place="unknown", + user_role=user_role, + ) + + # Then Comment: NHS number + assert nhs_no is not None + logging.info(f"[SUBJECT CREATION] Created Lynch subject with NHS number: {nhs_no}") + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "Calculated FOBT due date": "Null", + "Calculated lynch due date": "Null", + "Calculated surveillance due date": "Null", + "Lynch due date": "Null", + "Lynch due date date of change": "Null", + "Lynch due date reason": "Null", + "Previous screening status": "Null", + "Screening due date": "Null", + "Screening due date date of change": "Null", + "Screening due date reason": "Null", + "Subject has lynch diagnosis": "Yes", + "Subject lower FOBT age": "Default", + "Subject lower lynch age": "25", + "Screening status": "Lynch Surveillance", + "Screening status date of change": "Today", + "Screening status reason": "Eligible for Lynch Surveillance", + "Subject age": "25", + "Surveillance due date": "Null", + "Surveillance due date date of change": "Null", + "Surveillance due date reason": "Null", + }, + user_role, + ) + + # When I set the Lynch invitation rate for all screening centres to 50 + LynchUtils(page).set_lynch_invitation_rate(rate=50) + + # And I run the Lynch invitations process + GeneralRepository().run_lynch_invitations() + + # And my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "Calculated FOBT due date": "Null", + "Calculated lynch due date": "Unchanged", + "Calculated surveillance due date": "Null", + "Lynch due date": "25th birthday", + "Lynch due date date of change": "Today", + "Lynch due date reason": "Selected for Lynch Surveillance", + "Previous screening status": "Null", + "Screening due date": "Null", + "Screening due date date of change": "Null", + "Screening due date reason": "Null", + "Subject has an open episode": "Yes", + "Subject has lynch diagnosis": "Yes", + "Subject lower FOBT age": "Default", + "Subject lower lynch age": "25", + "Screening status": "Lynch Surveillance", + "Screening status date of change": "Today", + "Screening status reason": "Eligible for Lynch Surveillance", + "Subject age": "25", + "Surveillance due date": "Null", + "Surveillance due date date of change": "Null", + "Surveillance due date reason": "Null", + }, + ) + + # And there is a "G1" letter batch for my subject with the exact title "Lynch Surveillance Pre-invitation" + # When I process the open "G1" letter batch for my subject + batch_processing(page, "G1", "Lynch Surveillance Pre-invitation") + + # Then my subject has been updated as follows: + subject_assertion(nhs_no, {"latest event status": "G2 Lynch Pre-invitation Sent"}) + + # When I run Timed Events for my subject + OracleDB().exec_bcss_timed_events(nhs_number=nhs_no) + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "G3 Lynch Surveillance Colonoscopy Assessment Appointment Required" + }, + ) + + logging.info("Progress the episode through the required pathway") + + # When I view the subject + screening_subject_page_searcher.navigate_to_subject_summary_page(page, nhs_no) + + # And I view the practitioner appointment booking screen + SubjectScreeningSummaryPage(page).click_book_practitioner_clinic_button() + + # And I select "BCS001" as the screening centre where the practitioner appointment will be held + # And I set the practitioner appointment date to "tomorrow" + # And I book the "earliest" available practitioner appointment on this date + book_appointments( + page, + "BCS001 - Wolverhampton Bowel Cancer Screening Centre", + "The Royal Hospital (Wolverhampton)", + ) + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "A183 1st Colonoscopy Assessment Appointment Requested", + }, + ) + + # And there is a "A183" letter batch for my subject with the exact title "Practitioner Clinic 1st Appointment (Lynch)" + # When I process the open "A183" letter batch for my subject + batch_processing(page, "A183", "Practitioner Clinic 1st Appointment (Lynch)") + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "A25 1st Colonoscopy Assessment Appointment Booked, letter sent", + }, + ) + + # When I switch users to BCSS "England" as user role "Screening Centre Manager" + LogoutPage(page).log_out(close_page=False) + BasePage(page).go_to_log_in_page() + UserTools.user_login(page, "Screening Centre Manager at BCS001") + + # And I view the subject + screening_subject_page_searcher.navigate_to_subject_summary_page(page, nhs_no) + + # And I view the event history for the subject's latest episode + SubjectScreeningSummaryPage(page).expand_episodes_list() + SubjectScreeningSummaryPage(page).click_first_lynch_surveillance_episode_link() + + # And I view the latest practitioner appointment in the subject's episode + EpisodeEventsAndNotesPage(page).click_most_recent_view_appointment_link() + + # And The Screening Centre cancels the practitioner appointment with reason "Patient Unsuitable - Recently Screened" + AppointmentDetailPage(page).check_cancel_radio() + AppointmentDetailPage(page).select_reason_for_cancellation_option( + "Patient Unsuitable - Recently Screened" + ) + + # And I press OK on my confirmation prompt + AppointmentDetailPage(page).click_save_button(accept_dialog=True) + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "J24 Screening Centre Discharge Patient", + }, + ) + + # And there is a "J24" letter batch for my subject with the exact title "Subject Discharge (Screening Centre) (Lynch)" + # When I process the open "J24" letter batch for my subject + batch_processing(page, "J24", "Subject Discharge (Screening Centre) (Lynch)") + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "latest event status": "J25 Patient discharge sent (Screening Centre discharge patient)", + }, + ) + + # When I switch users to BCSS "England" as user role "Hub Manager" + LogoutPage(page).log_out(close_page=False) + BasePage(page).go_to_log_in_page() + UserTools.user_login(page, "Hub Manager State Registered at BCS01") + + # And there is a "J25" letter batch for my subject with the exact title "GP Discharge (Discharged By Screening Centre) (Lynch)" + # When I process the open "J25" letter batch for my subject + batch_processing( + page, "J25", "GP Discharge (Discharged By Screening Centre) (Lynch)" + ) + + logging.info("Check subject details against closure scenario SSCL4b(J26)") + + # Then my subject has been updated as follows: + subject_assertion( + nhs_no, + { + "calculated fobt due date": "Null", + "calculated lynch due date": "2 years from latest J25 event", + "calculated surveillance due date": "Null", + "ceased confirmation date": "Null", + "ceased confirmation details": "Null", + "ceased confirmation user id": "Null", + "clinical reason for cease": "Null", + "latest episode accumulated result": "Lynch non-participation", + "latest episode recall calculation method": "Date of last patient letter", + "latest episode recall episode type": "Lynch Surveillance", + "latest episode recall surveillance type": "Null", + "latest episode status": "Closed", + "latest episode status reason": "Discharged", + "latest event status": "J26 GP Discharge letter sent (Discharge by Screening centre)", + "lynch due date": "Calculated Lynch due date", + "lynch due date date of change": "Today", + "lynch due date reason": "Lynch Surveillance", + "lynch incident episode": "Null", + "screening due date": "Null", + "screening due date date of change": "Unchanged", + "screening due date reason": "Unchanged", + "screening status": "Lynch Surveillance", + "screening status date of change": "Unchanged", + "screening status reason": "Lynch Surveillance", + "surveillance due date": "Null", + "surveillance due date date of change": "Unchanged", + "surveillance due date reason": "Unchanged", + }, + ) + + LogoutPage(page).log_out() From 77249aee24bd9cf9faf28484d5ff6cdd742b0bc7 Mon Sep 17 00:00:00 2001 From: adrianoaru-nhs <194267545+adrianoaru-nhs@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:07:56 +0000 Subject: [PATCH 3/4] Delete tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py Removing as this should not be part of this PR Signed-off-by: adrianoaru-nhs <194267545+adrianoaru-nhs@users.noreply.github.com> --- .../test_lynch_scenario_1.py | 235 ------------------ 1 file changed, 235 deletions(-) delete mode 100644 tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py diff --git a/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py b/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py deleted file mode 100644 index 04e1dccd..00000000 --- a/tests/regression/regression_tests/lynch_regression_tests/test_lynch_scenario_1.py +++ /dev/null @@ -1,235 +0,0 @@ -from turtle import up -from typing import Self -import pytest -from playwright.sync_api import Page -from classes.repositories.general_repository import GeneralRepository -from classes.screening.subject_screening_centre_code import SubjectScreeningCentreCode -from classes.subject import subject -from classes.subject.subject import Subject -from classes.user.user_role_type import UserRoleType -from pages.base_page import BasePage -from pages.base_page import BasePage -from pages.screening_practitioner_appointments import book_appointment_page -from pages.screening_practitioner_appointments.appointment_detail_page import AppointmentDetailPage -from pages.screening_subject_search import subject_screening_search_page -from pages.screening_subject_search.episode_events_and_notes_page import EpisodeEventsAndNotesPage -from pages.screening_subject_search.subject_screening_summary_page import SubjectScreeningSummaryPage -from utils import screening_subject_page_searcher -from utils.appointments import book_appointments -from utils.lynch_utils import LynchUtils -from utils.oracle.oracle import OracleDB -from utils.user_tools import UserTools -from utils.oracle.subject_creation_util import CreateSubjectSteps -from utils.subject_assertion import subject_assertion -from utils.call_and_recall_utils import CallAndRecallUtils -import logging -from utils.batch_processing import batch_processing -from utils.fit_kit import FitKitGeneration, FitKitLogged -from pages.logout.log_out_page import LogoutPage -from datetime import datetime - - -@pytest.mark.wip -@pytest.mark.usefixtures("setup_org_and_appointments") -@pytest.mark.vpn_required -@pytest.mark.regression -@pytest.mark.lynch_regression_tests -def test_scenario_1(page: Page) -> None: - """ - Scenario: 1 - Patient refuses colonoscopy assessment appointment - - G1-G2-G3-A183-A25-J10-J1-J34-J35-J36-J27-J37-J38-J14-J22-J8-J9-C203 in age range [SSCL4b(J9)] - - This scenario tests where a subject attends a colonoscopy assessment appointment, but is then referred for a subsequent assessment appointment which does not take place because the subject first DNAs the appointment then cancels to consider. As the subject then does not book a new appointment within the time limit so is deemed to have refused the assessment and the episode is closed. - - Scenario summary: - - > Process Lynch diagnosis for a new in-age subject suitable for immediate invitation - > Run Lynch invitations > G1 (5.1) - > Process G1 letter batch > G2 (5.1) - > Run timed events > G3 (5.1) - > Book appointment > A183 (1.11) - > Process A183 letter batch > A25 (1.11) - > Attend appointment > J10 (1.11) - > Advance for "Subsequent Assessment Appointment Required" > J1 (1.11) - > Book appointment > J34 (1.11) - > Process J34 letter batch > J35 (1.11) - > Subject DNA > J36 (1.11) - > Process J36 letter batch > J27 (1.11) - > Book appointment > J37 (1.11) - > Process J37 letter batch > J38 (1.11) - > Patient cancels to consider > J14 (1.11) - > Process J14 letter batch > J22 (1.11) - > Run timed events > J22 (1.11) - > Process J22 letter batch > J8 (1.11) - > Process J8 letter batch > J9 (1.11) > C203 (1.13) - > Check recall [SSCL4b(J9)] - """ - # Given I log in to BCSS "England" as user role "Hub Manager" - user_role = UserTools.user_login( - page, "Hub Manager State Registered at BCS01", return_role_type=True - ) - if user_role is None: - raise ValueError("User cannot be assigned to a UserRoleType") - - # # When I receive Lynch diagnosis "EPCAM" for a new subject in my hub aged "25" with diagnosis date "1 year ago" and no last colonoscopy date - - # nhs_no = LynchUtils(page).insert_validated_lynch_patient_from_new_subject_with_age( - # age="25", - # gene="EPCAM", - # when_diagnosis_took_place="1 year ago", - # when_last_colonoscopy_took_place="unknown", - # user_role=user_role, - # ) - # # Now use nhs_number in your test assertions or further steps - # assert nhs_no is not None - # logging.info(f"Created Lynch subject with NHS number: {nhs_no}") - # # Then my subject has been updated as follows: - - # subject_assertion( - # nhs_no, - # { - # "Calculated FOBT due date": "Null", - # "Calculated lynch due date": "Null", - # "Calculated surveillance due date": "Null", - # "Lynch due date": "Null", - # "Lynch due date date of change": "Null", - # "Lynch due date reason": "Null", - # "Previous screening status": "Null", - # "Screening due date": "Null", - # "Screening due date date of change": "Null", - # "Screening due date reason": "Null", - # "Subject has lynch diagnosis": "Yes", - # "Subject lower FOBT age": "Default", - # "Subject lower lynch age": "25", - # "Screening status": "Lynch Surveillance", - # "Screening status date of change": "Today", - # "Screening status reason": "Eligible for Lynch Surveillance", - # "Subject age": "25", - # "Surveillance due date": "Null", - # "Surveillance due date date of change": "Null", - # "Surveillance due date reason": "Null", - # }, - # user_role, - # ) - - # # When I set the Lynch invitation rate for all screening centres to 50 - # LynchUtils(page).set_lynch_invitation_rate(rate=50) - # # And I run the Lynch invitations process - # GeneralRepository().run_lynch_invitations() - # # And my subject has been updated as follows: - - # criteria = { - # "Calculated FOBT due date": "Null", - # "Calculated lynch due date": "Unchanged", - # "Calculated surveillance due date": "Null", - # "Lynch due date": "25th birthday", - # "Lynch due date date of change": "Today", - # "Lynch due date reason": "Selected for Lynch Surveillance", - # "Previous screening status": "Null", - # "Screening due date": "Null", - # "Screening due date date of change": "Null", - # "Screening due date reason": "Null", - # "Subject has an open episode": "Yes", - # "Subject has lynch diagnosis": "Yes", - # "Subject lower FOBT age": "Default", - # "Subject lower lynch age": "25", - # "Screening status": "Lynch Surveillance", - # "Screening status date of change": "Today", - # "Screening status reason": "Eligible for Lynch Surveillance", - # "Subject age": "25", - # "Surveillance due date": "Null", - # "Surveillance due date date of change": "Null", - # "Surveillance due date reason": "Null", - # } - # subject_assertion( - # nhs_no, - # criteria, - # ) - - # # And there is a "G1" letter batch for my subject with the exact title "Lynch Surveillance Pre-invitation" - # # When I process the open "G1" letter batch for my subject - # batch_processing( - # page, - # "G1", - # "Lynch Surveillance Pre-invitation" - # ) - # # Then my subject has been updated as follows: - # subject_assertion(nhs_no, {"latest event status": "G2 Lynch Pre-invitation Sent"}) - - nhs_no = "9426510028" - # When I run Timed Events for my subject - OracleDB().exec_bcss_timed_events(nhs_number=nhs_no) - # Then my subject has been updated as follows: - subject_assertion( - nhs_no, - { - "latest event status": "G3 Lynch Surveillance Colonoscopy Assessment Appointment Required" - }, - ) - # When I view the subject - screening_subject_page_searcher.navigate_to_subject_summary_page(page, nhs_no) - pytest.skip("Skipping remainder of test until Lynch utils are complete") - # And I view the practitioner appointment booking screen - subject_screening_search_page.go_to_practitioner_appointment_booking_page(page) - # And I select "BCS001" as the screening centre where the practitioner appointment will be held - book_appointment_page.select_screening_centre( - page, "BCS001 - Wolverhampton Bowel Cancer Screening Centre" - ) - # And I set the practitioner appointment date to "today" - # And I book the "earliest" available practitioner appointment on this date - book_appointments( - page, - "BCS001 - Wolverhampton Bowel Cancer Screening Centre", - "The Royal Hospital (Wolverhampton)", - ) - # Then my subject has been updated as follows: - subject_assertion( - nhs_no, - { - "latest event status": "A183 1st Colonoscopy Assessment Appointment Requested" - }, - ) - # And there is a "A183" letter batch for my subject with the exact title "Practitioner Clinic 1st Appointment (Lynch)" - batch_processing.assert_letter_batch_exists( - nhs_no, - "A183", - exact_title="Practitioner Clinic 1st Appointment (Lynch)", - ) - # When I process the open "A183" letter batch for my subject - batch_processing.process_open_letter_batch( - nhs_no, - "A183", - ) - # Then my subject has been updated as follows: - subject_assertion( - nhs_no, - {"latest event status": "A25 1st Colonoscopy Assessment Appointment Letter Sent"}, - ) - # When I switch users to BCSS "England" as user role "Screening Centre Manager" - - LogoutPage(page).log_out(close_page=False) - BasePage(page).go_to_log_in_page() - UserTools.user_login(page, "Screening Centre Manager at BCS001") - # And I view the event history for the subject's latest episode - SubjectScreeningSummaryPage(page).expand_episodes_list() - # SubjectScreeningSummaryPage(page).click_first_fobt_episode_link()-check this is FOBT link - # And I view the latest practitioner appointment in the subject's episode - EpisodeEventsAndNotesPage(page).click_most_recent_view_appointment_link() - - # And I attend the subject's practitioner appointment "today" - AppointmentDetailPage(page).mark_appointment_as_attended(datetime.today()) - # Then my subject has been updated as follows: - subject_assertion( - nhs_no, - {"latest event status": "J10 Attended Colonoscopy Assessment Appointment"}, - ) - - # When I view the subject - screening_subject_page_searcher.navigate_to_subject_summary_page(page, nhs_no) - # And I view the advance episode options - # SubjectScreeningSummaryPage(page).click_advance_fobt_screening_episode_button()--lynch equivalent - # And I select Subsequent Assessment Appointment Required reason "SC interpreter DNA" - # LynchUtils(page).advance_episode_for_subsequent_assessment_appointment_required( - # reason="SC interpreter DNA" - # ) From dca82122ec509b45f971353cdcbf9c4f53fc7da9 Mon Sep 17 00:00:00 2001 From: Adriano Aru Date: Mon, 5 Jan 2026 11:12:27 +0000 Subject: [PATCH 4/4] Adding docstrings and typing to methods --- classes/repositories/general_repository.py | 9 ++++++++- utils/lynch_utils.py | 14 ++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/classes/repositories/general_repository.py b/classes/repositories/general_repository.py index fa18a11e..035bcdaf 100644 --- a/classes/repositories/general_repository.py +++ b/classes/repositories/general_repository.py @@ -69,6 +69,11 @@ def process_new_lynch_patients(self): logging.debug("[END] process_new_lynch_patients") def run_lynch_invitations(self): + """ + Executes the pkg_lynch.p_invite_lynch_subjects stored procedure to send invitations to Lynch subjects. + Raises: + oracledb.DatabaseError: If there is an error executing the stored procedure. + """ logging.debug("[START] run_lynch_invitations") try: self.oracle_db.execute_stored_procedure( @@ -77,7 +82,9 @@ def run_lynch_invitations(self): out_params=[oracledb.CURSOR], ) except Exception as e: - logging.error("Error executing pkg_lynch.p_invite_lynch_subjects", exc_info=True) + logging.error( + "Error executing pkg_lynch.p_invite_lynch_subjects", exc_info=True + ) raise oracledb.DatabaseError( f"Error executing pkg_lynch.p_invite_lynch_subjects: {e}" ) diff --git a/utils/lynch_utils.py b/utils/lynch_utils.py index 9820b7ce..31107106 100644 --- a/utils/lynch_utils.py +++ b/utils/lynch_utils.py @@ -32,7 +32,8 @@ class LynchUtils: def __init__(self, page: Page): self.page = page - def insert_validated_lynch_patient_from_new_subject_with_age(self, + def insert_validated_lynch_patient_from_new_subject_with_age( + self, age: str, gene: str, when_diagnosis_took_place: str, @@ -267,9 +268,11 @@ def process_new_lynch_patients() -> None: general_repository = GeneralRepository() general_repository.process_new_lynch_patients() - def set_lynch_invitation_rate(self, rate): + def set_lynch_invitation_rate(self, rate: float) -> None: """ Sets the Lynch invitation rate for all screening centres. + Args: + rate (float): The new invitation rate to set. """ db = OracleDB() connection = db.connect_to_db() @@ -288,7 +291,10 @@ def set_lynch_invitation_rate(self, rate): db.disconnect_from_db(connection) def run_lynch_invitations(self): - logging.info("Starting Lynch invitations") + """ + Executes the pkg_lynch.p_invite_lynch_subjects stored procedure to send invitations to Lynch subjects. + """ + logging.debug("Starting Lynch invitations") general_repository = GeneralRepository() general_repository.run_lynch_invitations() - logging.info("Finished Lynch invitations") + logging.debug("Finished Lynch invitations")