Skip to content

Commit 82aeb1b

Browse files
As per PR# 107 comments, code change is implemented.
1 parent 5b10de6 commit 82aeb1b

File tree

1 file changed

+128
-2
lines changed

1 file changed

+128
-2
lines changed

utils/oracle/oracle.py

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from tkinter import N
12
import oracledb
23
import os
34
from 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

Comments
 (0)