Skip to content

Commit b11ecfa

Browse files
BCSS - 20470 - playwright - refactor fit-kit utilities (#58)
…ital.nhs.uk/browse/BCSS-20470 - BCSS - playwright - refactor fit-kit utilities <!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> BCSS - playwright - refactor fit-kit utilities Merged 2 Util Files into 1 New File fit_kit.py (New File Created) ## Context <!-- Why is this change required? What problem does it solve? --> Fit Kit Utilities spread across 2 different files now it is into a Single New File (fit_kit.py) with 2 New Classes created & merged relevant functions under those classes. ## 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) - [X] 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 - [ ] 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 68c68d1 commit b11ecfa

File tree

5 files changed

+165
-171
lines changed

5 files changed

+165
-171
lines changed

tests/smokescreen/test_compartment_2.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pages.logout.log_out_page import LogoutPage
88
from pages.fit_test_kits.log_devices_page import LogDevicesPage
99
from utils.batch_processing import batch_processing
10-
from utils.fit_kit_generation import create_fit_id_df
10+
from utils.fit_kit import FitKitGeneration
1111
from utils.screening_subject_page_searcher import verify_subject_event_status_by_nhs_no
1212
from utils.user_tools import UserTools
1313

@@ -33,8 +33,7 @@ def test_compartment_2(page: Page, smokescreen_properties: dict) -> None:
3333
tk_type_id = smokescreen_properties["c2_fit_kit_tk_type_id"]
3434
hub_id = smokescreen_properties["c2_fit_kit_logging_test_org_id"]
3535
no_of_kits_to_retrieve = smokescreen_properties["c2_total_fit_kits_to_retieve"]
36-
subjectdf = create_fit_id_df(tk_type_id, hub_id, no_of_kits_to_retrieve)
37-
36+
subjectdf = FitKitGeneration().create_fit_id_df(tk_type_id, hub_id, no_of_kits_to_retrieve)
3837
for subject in range(int(smokescreen_properties["c2_normal_kits_to_log"])):
3938
fit_device_id = subjectdf["fit_device_id"].iloc[subject]
4039
logging.info(f"Logging FIT Device ID: {fit_device_id}")

tests/smokescreen/test_compartment_3.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from playwright.sync_api import Page
44
from pages.logout.log_out_page import LogoutPage
55
from utils.batch_processing import batch_processing
6-
from utils.fit_kit_logged import process_kit_data
6+
from utils.fit_kit import FitKitLogged
77
from utils.screening_subject_page_searcher import verify_subject_event_status_by_nhs_no
88
from utils.oracle.oracle_specific_functions import (
99
update_kit_service_management_entity,
@@ -26,9 +26,10 @@ def test_compartment_3(page: Page, smokescreen_properties: dict) -> None:
2626
"""
2727
UserTools.user_login(page, "Hub Manager State Registered at BCS01")
2828

29+
2930
# Find data , separate it into normal and abnormal, Add results to the test records in the KIT_QUEUE table (i.e. mimic receiving results from the middleware)
3031
# and get device IDs and their flags
31-
device_ids = process_kit_data(smokescreen_properties)
32+
device_ids =FitKitLogged().process_kit_data(smokescreen_properties)
3233
# Retrieve NHS numbers for each device_id and determine normal/abnormal status
3334
nhs_numbers = []
3435
normal_flags = []

utils/fit_kit.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
from oracle.oracle_specific_functions import get_kit_id_logged_from_db
2+
from utils.oracle.oracle_specific_functions import get_kit_id_from_db
3+
from pages.base_page import BasePage
4+
from datetime import datetime
5+
import logging
6+
import pandas as pd
7+
import pytest
8+
9+
10+
class FitKitGeneration:
11+
"""This class is responsible for generating FIT Device IDs from test kit data."""
12+
13+
def create_fit_id_df(
14+
self,
15+
tk_type_id: int,
16+
hub_id: int,
17+
no_of_kits_to_retrieve: int,
18+
) -> pd.DataFrame:
19+
"""
20+
This function retrieves test kit data from the database for the specified compartment (using the 'get_kit_id_from_db' function from 'oracle_specific_functions.py').
21+
It then calculates a check digit for each retrieved kit ID and appends it to the kit ID.
22+
Finally, it generates a FIT Device ID by appending an expiry date and a fixed suffix to the kit ID.
23+
24+
For example:
25+
Given the following inputs:
26+
tk_type_id = 1, hub_id = 101, no_of_kits_to_retrieve = 2
27+
The function retrieves two kit IDs from the database, e.g., ["ABC123", "DEF456"].
28+
It calculates the check digit for each kit ID, resulting in ["ABC123-K", "DEF456-M"].
29+
Then, it generates the FIT Device IDs, e.g., ["ABC123-K122512345/KD00001", "DEF456-M122512345/KD00001"].
30+
31+
Args:
32+
tk_type_id (int): The type ID of the test kit.
33+
hub_id (int): The ID of the hub from which to retrieve the kits.
34+
no_of_kits_to_retrieve (int): The number of kits to retrieve from the database.
35+
36+
Returns:
37+
pd.DataFrame: A DataFrame containing the processed kit IDs, including the calculated check digit
38+
and the final formatted FIT Device ID.
39+
"""
40+
df = get_kit_id_from_db(tk_type_id, hub_id, no_of_kits_to_retrieve)
41+
df["fit_device_id"] = df["kitid"].apply(self.calculate_check_digit)
42+
df["fit_device_id"] = df["fit_device_id"].apply(
43+
self.convert_kit_id_to_fit_device_id
44+
)
45+
return df
46+
47+
def calculate_check_digit(self, kit_id: str) -> str:
48+
"""
49+
Calculates the check digit for a given kit ID.
50+
51+
The check digit is determined by summing the positions of each character in the kit ID
52+
within a predefined character set. The remainder of the sum divided by 43 is used to
53+
find the corresponding character in the character set, which becomes the check digit.
54+
55+
For example:
56+
Given the kit ID "ABC123", the positions of the characters in the predefined
57+
character set are summed. If the total is 123, the remainder when divided by 43
58+
is 37. The character at position 37 in the character set is "K". The resulting
59+
kit ID with the check digit appended would be "ABC123-K".
60+
61+
Args:
62+
kit_id (str): The kit ID to calculate the check digit for.
63+
64+
Returns:
65+
str: The kit ID with the calculated check digit appended.
66+
"""
67+
logging.info(f"Calculating check digit for kit id: {kit_id}")
68+
total = 0
69+
char_string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%"
70+
for i in range(len(kit_id)):
71+
total += char_string.index(kit_id[i - 1])
72+
check_digit = char_string[total % 43]
73+
return f"{kit_id}-{check_digit}"
74+
75+
def convert_kit_id_to_fit_device_id(self, kit_id: str) -> str:
76+
"""
77+
Converts a Kit ID into a FIT Device ID by appending an expiry date and a fixed suffix.
78+
79+
The expiry date is calculated by setting the month to December and the year to one year
80+
in the future based on the current date. For example, if the current date is June 2024,
81+
the expiry date will be set to December 2025.
82+
83+
Args:
84+
kit_id (str): The Kit ID to be converted.
85+
86+
Returns:
87+
str: The generated FIT Device ID in the format "{kit_id}12{next_year}12345/KD00001".
88+
"""
89+
logging.info(f"Generating FIT Device ID from: {kit_id}")
90+
today = datetime.now()
91+
year = today.strftime("%y") # Get the year from todays date in YY format
92+
return f"{kit_id}12{int(year) + 1}12345/KD00001"
93+
94+
95+
class FitKitLogged:
96+
"""This class is responsible for processing FIT Device IDs and logging them as normal or abnormal."""
97+
98+
def process_kit_data(self, smokescreen_properties: dict) -> list:
99+
"""
100+
This method retrieved the test data needed for compartment 3 and then splits it into two data frames:
101+
- 1 normal
102+
- 1 abnormal
103+
Once the dataframe is split in two it then creates two lists, one for normal and one for abnormal
104+
Each list will either have true or false appended depending on if it is normal or abnormal
105+
"""
106+
# Get test data for compartment 3
107+
kit_id_df = get_kit_id_logged_from_db(smokescreen_properties)
108+
109+
# Split dataframe into two different dataframes, normal and abnormal
110+
normal_fit_kit_df, abnormal_fit_kit_df = self.split_fit_kits(
111+
kit_id_df, smokescreen_properties
112+
)
113+
114+
# Prepare a list to store device IDs and their respective flags
115+
device_ids = []
116+
117+
# Process normal kits (only 1)
118+
if not normal_fit_kit_df.empty:
119+
device_id = normal_fit_kit_df["device_id"].iloc[0]
120+
logging.info(
121+
f"Processing normal kit with Device ID: {device_id}"
122+
) # Logging normal device_id
123+
device_ids.append((device_id, True)) # Add to the list with normal flag
124+
else:
125+
pytest.fail("No normal kits found for processing.")
126+
127+
# Process abnormal kits (multiple, loop through)
128+
if not abnormal_fit_kit_df.empty:
129+
for index, row in abnormal_fit_kit_df.iterrows():
130+
device_id = row["device_id"]
131+
logging.info(
132+
f"Processing abnormal kit with Device ID: {device_id}"
133+
) # Logging abnormal device_id
134+
device_ids.append(
135+
(device_id, False)
136+
) # Add to the list with abnormal flag
137+
else:
138+
pytest.fail("No abnormal kits found for processing.")
139+
140+
return device_ids
141+
142+
def split_fit_kits(
143+
self, kit_id_df: pd.DataFrame, smokescreen_properties: dict
144+
) -> pd.DataFrame:
145+
"""
146+
This method splits the dataframe into two, 1 normal and 1 abnormal
147+
"""
148+
number_of_normal = int(
149+
smokescreen_properties["c3_eng_number_of_normal_fit_kits"]
150+
)
151+
number_of_abnormal = int(
152+
smokescreen_properties["c3_eng_number_of_abnormal_fit_kits"]
153+
)
154+
155+
# Split dataframe into two dataframes
156+
normal_fit_kit_df = kit_id_df.iloc[:number_of_normal]
157+
abnormal_fit_kit_df = kit_id_df.iloc[
158+
number_of_normal : number_of_normal + number_of_abnormal
159+
]
160+
return normal_fit_kit_df, abnormal_fit_kit_df

utils/fit_kit_generation.py

Lines changed: 0 additions & 83 deletions
This file was deleted.

utils/fit_kit_logged.py

Lines changed: 0 additions & 83 deletions
This file was deleted.

0 commit comments

Comments
 (0)