1+ from tkinter import N
12import oracledb
23import os
34from dotenv import load_dotenv
@@ -177,7 +178,7 @@ def execute_query(
177178 return df
178179
179180 def execute_stored_procedure (
180- self , procedure : str
181+ self , procedure : str , params : list = [ None ]
181182 ) -> None : # To use when "exec xxxx" (stored procedures)
182183 """
183184 This is to be used whenever we need to execute a stored procedure.
@@ -190,7 +191,10 @@ def execute_stored_procedure(
190191 try :
191192 logging .info (f"Attempting to execute stored procedure: { procedure } " )
192193 cursor = conn .cursor ()
193- cursor .callproc (procedure )
194+ if params is not None :
195+ cursor .callproc (procedure , params )
196+ else :
197+ cursor .callproc (procedure )
194198 conn .commit ()
195199 logging .info ("stored procedure execution successful!" )
196200 except Exception as executionError :
@@ -226,3 +230,125 @@ def update_or_insert_data_to_table(
226230 finally :
227231 if conn is not None :
228232 self .disconnect_from_db (conn )
233+
234+
235+ class OracleSubjectTools (OracleDB ):
236+ def __init__ (self ):
237+ super ().__init__ ()
238+
239+ def create_subjects_via_sspi (
240+ self ,
241+ count : int ,
242+ screening_centre : str ,
243+ base_age : int ,
244+ start_offset : int = - 2 ,
245+ end_offset : int = 4 ,
246+ nhs_start : int = 9200000000 ,
247+ ) -> None :
248+ """
249+ Creates a batch of test screening subjects using the SSPI stored procedure.
250+
251+ This method invokes `PKG_SSPI.p_process_pi_subject` in the database, which generates
252+ synthetic subjects with varying dates of birth and NHS numbers starting from a given base.
253+
254+ Args:
255+ count (int): Number of subjects to create.
256+ screening_centre (str): Code for the target screening centre (e.g., 'BCS01').
257+ base_age (int): Age around which the subjects are distributed.
258+ start_offset (int, optional): Days before today for earliest DOB (default -2).
259+ end_offset (int, optional): Days after today for latest DOB (default 4).
260+ nhs_start (int, optional): Starting NHS number for generated subjects (default 9200000000).
261+
262+ Logs:
263+ Error message if subject generation fails.
264+
265+ Side Effects:
266+ Commits to the database; subjects are available for further test flows.
267+ """
268+ conn = self .connect_to_db ()
269+ try :
270+ cursor = conn .cursor ()
271+ cursor .callproc (
272+ "PKG_SSPI.p_process_pi_subject" ,
273+ [
274+ count ,
275+ screening_centre ,
276+ base_age ,
277+ start_offset ,
278+ end_offset ,
279+ nhs_start ,
280+ ],
281+ )
282+ conn .commit ()
283+ except Exception as e :
284+ logging .error (f"Failed to generate subjects: { e } " )
285+ finally :
286+ self .disconnect_from_db (conn )
287+
288+ def create_self_referral_ready_subject (
289+ self , screening_centre : str = "BCS002" , base_age : int = 75
290+ ) -> str :
291+ """
292+ Creates a subject in the database with no screening history who is eligible to self refer.
293+ Uses PKG_SSPI.p_process_pi_subject to insert the subject,
294+ then retrieves the NHS number based on age and applies bcss_timed_events.
295+
296+ Args:
297+ screening_centre (str): The screening centre code to associate the subject with.
298+ base_age (int): The minimum age threshold for subject date of birth.
299+
300+ Returns:
301+ str: The NHS number of the created subject.
302+ """
303+ # Step 1: Generate subject via stored procedure
304+ self .create_subjects_via_sspi (
305+ count = 1 ,
306+ screening_centre = screening_centre ,
307+ base_age = base_age ,
308+ start_offset = - 2 ,
309+ end_offset = 4 ,
310+ nhs_start = 9200000000 ,
311+ )
312+
313+ # Step 1a: Retrieve NHS number by joining SCREENING_SUBJECT_T and SD_CONTACT_T
314+ # - Ensures subject exists in both tables
315+ # - Filters by minimum age using DATE_OF_BIRTH
316+ # - Sorted by NHS number descending to get most recent insert
317+
318+ query = """
319+ SELECT s.SUBJECT_NHS_NUMBER
320+ FROM SCREENING_SUBJECT_T s
321+ JOIN SD_CONTACT_T c ON s.SUBJECT_NHS_NUMBER = c.NHS_NUMBER
322+ WHERE c.DATE_OF_BIRTH <= ADD_MONTHS(TRUNC(SYSDATE), -12 * :min_age)
323+ ORDER BY s.SUBJECT_NHS_NUMBER DESC
324+ FETCH FIRST 1 ROWS ONLY
325+ """
326+ df = self .execute_query (query , {"min_age" : base_age })
327+
328+ if df .empty :
329+ raise RuntimeError (f"No subjects found aged { base_age } + in both tables." )
330+
331+ nhs_number = df .iloc [0 ]["subject_nhs_number" ]
332+ logging .info (f"[SUBJECT CREATED WITH AGE { base_age } +] NHS number: { nhs_number } " )
333+
334+ # Step 2: Progress timeline
335+ nhs_df = pd .DataFrame ({"subject_nhs_number" : [nhs_number ]})
336+ self .exec_bcss_timed_events (nhs_df )
337+
338+ logging .info (f"[SUBJECT READY FOR SELF REFERRAL] NHS number: { nhs_number } " )
339+ return nhs_number
340+
341+ def open_subject_by_nhs (self , nhs_number : str ) -> "OracleSubjectTools" :
342+ """
343+ Placeholder for accessing a subject by NHS number.
344+ While no front-end page exists for subject viewing, this method preserves test readability
345+ and signals that the subject record is now active within the test context.
346+
347+ Args:
348+ nhs_number (str): The NHS number of the subject you want to access.
349+
350+ Returns:
351+ OracleSubjectTools: Returns self for method chaining.
352+ """
353+ logging .info (f"[SUBJECT ACCESS] NHS number: { nhs_number } loaded into test flow" )
354+ return self
0 commit comments