Skip to content

Commit 1da5d8f

Browse files
Feature/bcss 21304 selenium0to playwright fobtregressiontests scenario 1 (#126)
<!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> Adding scenario 1 from FOBTRegressionTests.featuire along with any relevant classes/utils ## Context <!-- Why is this change required? What problem does it solve? --> Adding scenario 1 from FOBTRegressionTests.featuire along with any relevant classes/utils ## 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) - [ ] 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 - [x] I have added tests to cover my changes (where appropriate) - [x] I have updated the documentation accordingly - [x] 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. --------- Co-authored-by: AndyG <[email protected]>
1 parent a973edd commit 1da5d8f

39 files changed

+2137
-236
lines changed

classes/address.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def set_address_line(self, line_number: int, address_line: str) -> None:
2626
Raises:
2727
ValueError: If line_number is not between 1 and 5.
2828
"""
29-
logging.info(
29+
logging.debug(
3030
f"start: set_address_line(line_number={line_number}, address_line={address_line})"
3131
)
3232
if line_number == 1:
@@ -43,7 +43,7 @@ def set_address_line(self, line_number: int, address_line: str) -> None:
4343
raise ValueError(
4444
f"Invalid line number {line_number}, must be between 1 and 5"
4545
)
46-
logging.info(
46+
logging.debug(
4747
f"end: set_address_line(line_number={line_number}, address_line={address_line})"
4848
)
4949

classes/analyser.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
import pandas as pd
4+
5+
6+
@dataclass
7+
class Analyser:
8+
"""
9+
This data class is used to store information about a specific analyser device.
10+
The attributes correspond to the columns in the analyser query results.
11+
Attributes:
12+
analyser_id (Optional[int]): The ID of the analyser.
13+
analyser_code (Optional[str]): The code of the analyser.
14+
hub_id (Optional[int]): The ID of the hub the analyser is connected to.
15+
analyser_type_id (Optional[int]): The type ID of the analyser.
16+
spoil_result_code (Optional[int]): The result code for spoilage.
17+
tech_fail_result_code (Optional[int]): The result code for technical failure.
18+
below_range_result_code (Optional[int]): The result code for below range.
19+
above_range_result_code (Optional[int]): The result code for above range.
20+
"""
21+
22+
analyser_id: Optional[int] = None
23+
analyser_code: Optional[str] = None
24+
hub_id: Optional[int] = None
25+
analyser_type_id: Optional[int] = None
26+
spoil_result_code: Optional[int] = None
27+
tech_fail_result_code: Optional[int] = None
28+
below_range_result_code: Optional[int] = None
29+
above_range_result_code: Optional[int] = None
30+
31+
def __str__(self) -> str:
32+
return (
33+
f"Analyser [analyser_id={self.analyser_id}, analyser_code={self.analyser_code}, "
34+
f"hub_id={self.hub_id}, spoil_result_code={self.spoil_result_code}, "
35+
f"tech_fail_result_code={self.tech_fail_result_code}, "
36+
f"below_range_result_code={self.below_range_result_code}, "
37+
f"above_range_result_code={self.above_range_result_code}]"
38+
)
39+
40+
@staticmethod
41+
def from_dataframe_row(row: pd.Series) -> "Analyser":
42+
"""
43+
Creates an Analyser object from a pandas DataFrame row containing analyser query results.
44+
45+
Args:
46+
row (pd.Series): A row from a pandas DataFrame with columns:
47+
- tk_analyser_id
48+
- analyser_code
49+
- hub_id
50+
- tk_analyser_type_id
51+
The other columns are obtained from the SQL query as so are not present in this method.
52+
53+
Returns:
54+
Analyser: The constructed Analyser object.
55+
"""
56+
return Analyser(
57+
analyser_id=row.get("tk_analyser_id"),
58+
analyser_code=row.get("analyser_code"),
59+
hub_id=row.get("hub_id"),
60+
analyser_type_id=row.get("tk_analyser_type_id"),
61+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from enum import Enum
2+
from typing import Optional
3+
4+
5+
class AnalyserResultCodeType(Enum):
6+
"""
7+
Enum for analyser result code types, mapped to valid value IDs and descriptions.
8+
"""
9+
10+
ABOVE_DILUTION_RANGE = (309030, "Above dilution range")
11+
BELOW_MEASURING_RANGE = (309028, "Below measuring range")
12+
SPOILT = (309026, "Spoilt")
13+
TECHNICAL_FAIL = (309027, "Technical Fail")
14+
15+
def __init__(self, valid_value_id: int, description: str) -> None:
16+
self._valid_value_id = valid_value_id
17+
self._description = description
18+
19+
@property
20+
def valid_value_id(self) -> int:
21+
"""
22+
Returns the valid value ID for the analyser result code type.
23+
"""
24+
return self._valid_value_id
25+
26+
@property
27+
def description(self) -> str:
28+
"""
29+
Returns the description for the analyser result code type.
30+
"""
31+
return self._description
32+
33+
@classmethod
34+
def by_description(cls, description: str) -> Optional["AnalyserResultCodeType"]:
35+
"""
36+
Returns the enum member matching the given description (case-sensitive).
37+
"""
38+
for member in cls:
39+
if member.description == description:
40+
return member
41+
return None
42+
43+
@classmethod
44+
def by_description_case_insensitive(
45+
cls, description: str
46+
) -> Optional["AnalyserResultCodeType"]:
47+
"""
48+
Returns the enum member matching the given description (case-insensitive).
49+
"""
50+
desc_lower = description.lower()
51+
for member in cls:
52+
if member.description.lower() == desc_lower:
53+
return member
54+
return None
55+
56+
@classmethod
57+
def by_valid_value_id(
58+
cls, valid_value_id: int
59+
) -> Optional["AnalyserResultCodeType"]:
60+
"""
61+
Returns the enum member matching the given valid value ID.
62+
"""
63+
for member in cls:
64+
if member.valid_value_id == valid_value_id:
65+
return member
66+
return None

classes/data/data_creation.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def generate_random_subject(
107107
Returns:
108108
PISubject: A randomly generated PI Subject.
109109
"""
110-
logging.info(
110+
logging.debug(
111111
f"generateRandomSubject: {random_words_list}, {pi_reference}, {region}"
112112
)
113113
pi_subject = PISubject()
@@ -138,15 +138,15 @@ def generate_random_subject(
138138
pi_subject.gnc_code = self.generate_random_registration_code()
139139
gp_surgery = self.generate_random_gp_surgery(region)
140140
if gp_surgery is not None:
141-
pi_subject.gp_practice_code = gp_surgery.get_code()
141+
pi_subject.gp_practice_code = gp_surgery.code
142142
pi_subject.nhais_deduction_reason = None
143143
pi_subject.nhais_deduction_date = None
144144
pi_subject.exeter_system = "ATO"
145145
pi_subject.removed_to = None
146146
pi_subject.pi_reference = pi_reference
147147
pi_subject.superseded_by_nhs_number = None
148148
pi_subject.replaced_nhs_number = None
149-
logging.info("generateRandomSubject: end")
149+
logging.debug("generateRandomSubject: end")
150150
return pi_subject
151151

152152
def generate_random_registration_code(self) -> str:
@@ -155,9 +155,9 @@ def generate_random_registration_code(self) -> str:
155155
Returns:
156156
str: The generated registration code
157157
"""
158-
logging.info("generateRandomRegistrationCode: start")
158+
logging.debug("generateRandomRegistrationCode: start")
159159
code = random.choice(self.registration_code_list)
160-
logging.info("generateRandomRegistrationCode: end")
160+
logging.debug("generateRandomRegistrationCode: end")
161161
return code
162162

163163
def generate_random_gp_surgery(self, region: RegionType) -> Organisation | None:
@@ -168,7 +168,7 @@ def generate_random_gp_surgery(self, region: RegionType) -> Organisation | None:
168168
Returns:
169169
Organisation: The generated gp surgery or None if the region type cannot be found
170170
"""
171-
logging.info(f"generateRandomGPSurgery: {region}")
171+
logging.debug(f"generateRandomGPSurgery: {region}")
172172
if region == RegionType.ENGLAND:
173173
return random.choice(self.org_list_england)
174174
elif region == RegionType.ISLE_OF_MAN:
@@ -184,7 +184,7 @@ def generate_random_address(self, random_words_list: Dict[str, str]) -> Address:
184184
Returns:
185185
Address: The generated address
186186
"""
187-
logging.info(f"generateRandomAddress: {random_words_list}")
187+
logging.debug(f"generateRandomAddress: {random_words_list}")
188188
address = Address()
189189
house_number = self.rand.randint(0, 100)
190190
road_prefix = random_words_list.get("roadPrefix", "")
@@ -204,7 +204,7 @@ def generate_random_address(self, random_words_list: Dict[str, str]) -> Address:
204204
address.set_address_line(line_number, random_words_list.get("county", ""))
205205

206206
address.set_post_code(self.generate_random_postcode())
207-
logging.info("generateRandomAddress: end")
207+
logging.debug("generateRandomAddress: end")
208208
return address
209209

210210
def generate_random_person(
@@ -218,7 +218,7 @@ def generate_random_person(
218218
Returns:
219219
Person: The generated person
220220
"""
221-
logging.info("generateRandomPerson: start")
221+
logging.debug("generateRandomPerson: start")
222222
if gender not in [GenderType.MALE, GenderType.FEMALE]:
223223
gender = (
224224
GenderType.MALE if self.rand.randint(0, 100) < 50 else GenderType.FEMALE
@@ -235,7 +235,7 @@ def generate_random_person(
235235
if self.rand.randint(0, 100) < 5:
236236
person.set_previous_surname(random_words_list.get("surname2", ""))
237237

238-
logging.info("generateRandomPerson: end")
238+
logging.debug("generateRandomPerson: end")
239239
return person
240240

241241
def generate_random_postcode(self) -> str:
@@ -244,7 +244,7 @@ def generate_random_postcode(self) -> str:
244244
Returns:
245245
str: The generated postcode
246246
"""
247-
logging.info("generateRandomPostcode: start")
247+
logging.debug("generateRandomPostcode: start")
248248
unused_first_characters = ["V", "Q", "X"]
249249
inward_letters = [
250250
"A",
@@ -276,7 +276,7 @@ def generate_random_postcode(self) -> str:
276276
random_inward_letter1 = random.choice(inward_letters)
277277
random_inward_letter2 = random.choice(inward_letters)
278278
postcode = f"{random_outward_letters}{random_outward_digits} {random_inward_digit}{random_inward_letter1}{random_inward_letter2}"
279-
logging.info("generateRandomPostcode: end")
279+
logging.debug("generateRandomPostcode: end")
280280
return postcode
281281

282282
def generate_random_title(self, gender: GenderType) -> str:
@@ -287,7 +287,7 @@ def generate_random_title(self, gender: GenderType) -> str:
287287
Returns:
288288
str: The generated title
289289
"""
290-
logging.info("generateRandomTitle: start")
290+
logging.debug("generateRandomTitle: start")
291291
weighting = self.rand.randint(0, 9)
292292
if gender == GenderType.FEMALE:
293293
if weighting > 7:
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
from decimal import Decimal
4+
from datetime import datetime
5+
from classes.kit_service_management_record import KitServiceManagementRecord
6+
7+
8+
@dataclass
9+
class KitServiceManagementEntity:
10+
"""
11+
Data class representing a kit service management entity (KIT_QUEUE table).
12+
"""
13+
14+
device_id: Optional[str] = None
15+
test_kit_type: Optional[str] = None
16+
test_kit_name: Optional[str] = None
17+
test_kit_status: Optional[str] = None
18+
logged_by_hub: Optional[str] = None
19+
date_time_logged: Optional[datetime] = None
20+
test_result: Optional[Decimal] = None
21+
calculated_result: Optional[str] = None
22+
error_code: Optional[int] = None
23+
analyser_code: Optional[str] = None
24+
date_time_authorised: Optional[datetime] = None
25+
authoriser_user_code: Optional[str] = None
26+
datestamp: Optional[datetime] = None
27+
bcss_error_id: Optional[int] = None
28+
post_response: Optional[int] = None
29+
post_attempts: Optional[int] = None
30+
put_response: Optional[int] = None
31+
put_attempts: Optional[int] = None
32+
date_time_error_archived: Optional[datetime] = None
33+
error_archived_user_code: Optional[str] = None
34+
issue_date: Optional[datetime] = None
35+
issued_by_hub: Optional[str] = None
36+
nhs_number: Optional[str] = None
37+
analyser_error_description: Optional[str] = None
38+
error_type: Optional[str] = None
39+
screening_test_result: Optional[str] = None
40+
41+
@staticmethod
42+
def from_record(
43+
record: "KitServiceManagementRecord",
44+
) -> "KitServiceManagementEntity":
45+
"""
46+
Converts a KitServiceManagementRecord object into a KitServiceManagementEntity object.
47+
48+
Args:
49+
record (KitServiceManagementRecord): The record to convert.
50+
51+
Returns:
52+
KitServiceManagementEntity: The converted entity.
53+
"""
54+
return KitServiceManagementEntity(
55+
device_id=record.device_id,
56+
test_kit_type=(
57+
str(record.test_kit_type) if record.test_kit_type is not None else None
58+
), # test_kit_type is converted to a string as this is what is required in the database
59+
test_kit_name=record.test_kit_name,
60+
test_kit_status=record.test_kit_status,
61+
logged_by_hub=record.logged_by_hub,
62+
date_time_logged=record.date_time_logged,
63+
test_result=record.test_result,
64+
calculated_result=record.calculated_result,
65+
error_code=record.error_code,
66+
analyser_code=record.analyser_code,
67+
date_time_authorised=record.date_time_authorised,
68+
authoriser_user_code=record.authoriser_user_code,
69+
datestamp=record.datestamp,
70+
bcss_error_id=record.bcss_error_id,
71+
post_response=record.post_response,
72+
post_attempts=record.post_attempts,
73+
put_response=record.put_response,
74+
put_attempts=record.put_attempts,
75+
date_time_error_archived=record.date_time_error_archived,
76+
error_archived_user_code=record.error_archived_user_code,
77+
issue_date=record.date_time_issued,
78+
issued_by_hub=record.issued_by_hub,
79+
nhs_number=record.nhs_number,
80+
analyser_error_description=record.analyser_error_description,
81+
error_type=record.error_type,
82+
screening_test_result=record.screening_test_result,
83+
)

0 commit comments

Comments
 (0)