Skip to content

Commit f3203a4

Browse files
authored
Merge pull request #52 from NHSDigital/AMB-1771-abstract-validation
AMB-1771 abstract validation
2 parents 349fcf2 + 54db17b commit f3203a4

14 files changed

+1311
-425
lines changed

lambda_code/src/csv_to_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../")
99

1010

11-
from models.immunization import CsvImmunizationModel
11+
from models.csv_immunization import CsvImmunizationModel
1212
from models.failures import CsvImmunizationErrorModel
1313

1414

lambda_code/src/mesh.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from dataclasses import dataclass
22
from csv_to_model import read_csv_to_model
3-
from models.immunization import CsvImmunizationModel
3+
from models.csv_immunization import CsvImmunizationModel
44
from models.failures import CsvImmunizationErrorModel
55
import boto3
66

lambda_code/src/models/FHIRUKImmunization.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1+
import datetime
2+
import json
13
from fhir.resources.immunization import Immunization as FHIRImmunization
24
from fhir.resources.patient import Patient as FHIRPatient
35
from fhir.resources.organization import Organization as FHIROrganization
46
from fhir.resources.codeableconcept import CodeableConcept
57
from fhir.resources.practitioner import Practitioner as FHIRPractitioner
6-
import datetime
7-
import json
88

99

1010
class UKFHIRImmunization:
1111
def __init__(self, *args, **kwargs):
12-
1312
self.immunization: FHIRImmunization = None
1413
self.patient: FHIRPatient = None
1514
self.recorded: datetime.date = None
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import re
2+
from datetime import datetime
3+
4+
5+
class Constants:
6+
person_gender_codes = {"0", "1", "2", "9"}
7+
action_flags = {"completed", "entered-in-error"}
8+
vaccination_not_given_flag: str = "not-done"
9+
vaccination_given_flag: str = "empty"
10+
11+
@staticmethod
12+
def convert_iso8601_to_datetime(iso_datetime_str):
13+
try:
14+
time_str = "T00:00:00+00:00"
15+
# Check if time information is present
16+
if "T" in iso_datetime_str:
17+
# Check if timezone information is present
18+
if "+" in iso_datetime_str:
19+
# Add the colon (:00) in the timezone offset
20+
timestamp_str_with_colon = iso_datetime_str + ":00"
21+
dt_obj = datetime.strptime(
22+
timestamp_str_with_colon, "%Y-%m-%dT%H:%M:%S%z"
23+
)
24+
else:
25+
dt_obj = datetime.strptime(iso_datetime_str, "%Y-%m-%dT%H:%M:%S")
26+
else:
27+
# Add the the timezone offset
28+
timestamp_str_with_colon = iso_datetime_str + time_str
29+
dt_obj = datetime.strptime(
30+
timestamp_str_with_colon, "%Y-%m-%dT%H:%M:%S%z"
31+
)
32+
33+
return dt_obj
34+
except ValueError:
35+
raise ValueError("Invalid datetime format. Use YYYY-MM-DDThh:mm:ss+zz.")
36+
37+
@staticmethod
38+
def convert_to_date(value):
39+
try:
40+
return datetime.strptime(value, "%Y-%m-%d").date()
41+
except ValueError:
42+
raise ValueError("Invalid date format. Use YYYY-MM-DD.")
43+
44+
@staticmethod
45+
def is_urn_resource(s):
46+
# Check if the lowercase version of the string starts with "urn"
47+
return s.lower().startswith("urn")
48+
49+
@staticmethod
50+
def if_vaccine_not_give(not_given_flag):
51+
if not not_given_flag or not_given_flag == Constants.vaccination_given_flag:
52+
return False
53+
else:
54+
if not_given_flag == Constants.vaccination_not_given_flag:
55+
return True
56+
57+
@staticmethod
58+
def has_max_decimal_places(input_string, max_places=4):
59+
# Define a regular expression pattern for matching up to four decimal places
60+
pattern = r"^\d+(\.\d{1,4})?$"
61+
62+
# Use re.match to check if the input matches the pattern
63+
return bool(re.match(pattern, input_string))
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
"""CSV Immunization Model"""
2+
3+
from datetime import date
4+
from datetime import datetime
5+
from typing import Optional, Any
6+
from pydantic import BaseModel, validator
7+
from models.nhs_validators import NHSValidators
8+
9+
10+
class CsvImmunizationModel(BaseModel):
11+
"""Pydantic CSV Immunization BaseModel"""
12+
13+
NHS_NUMBER: str = None
14+
PERSON_FORENAME: str = None
15+
PERSON_SURNAME: str = None
16+
PERSON_DOB: Optional[date]
17+
PERSON_GENDER_CODE: str = None
18+
PERSON_POSTCODE: str = None
19+
DATE_AND_TIME: datetime # YYYY-MM-DDThh:mm:ss+zz (2020-12-14T10:08:15+00)
20+
SITE_CODE: str = None
21+
SITE_NAME: str = None
22+
UNIQUE_ID: str = None
23+
UNIQUE_ID_URI: str = None
24+
ACTION_FLAG: str = None
25+
PERFORMING_PROFESSIONAL_SURNAME: str = None
26+
PERFORMING_PROFESSIONAL_FORENAME: str = None
27+
PERFORMING_PROFESSIONAL_BODY_REG_URI: str = None
28+
PERFORMING_PROFESSIONAL_BODY_REG_CODE: str = None
29+
SDS_JOB_ROLE_NAME: str = None
30+
RECORDED_DATE: Optional[date]
31+
PRIMARY_SOURCE: Any = None # Use Any to allow any type, including bool or None
32+
REPORT_ORIGIN: str = None
33+
NOT_GIVEN: str = None
34+
VACCINATION_PROCEDURE_CODE: str = None
35+
VACCINATION_PROCEDURE_TERM: str = None
36+
VACCINATION_SITUATION_CODE: str = None
37+
VACCINATION_SITUATION_TERM: str = None
38+
REASON_NOT_GIVEN_CODE: str = None
39+
REASON_NOT_GIVEN_TERM: str = None
40+
DOSE_SEQUENCE: int = None
41+
VACCINE_PRODUCT_CODE: str = None
42+
VACCINE_PRODUCT_TERM: str = None
43+
VACCINE_MANUFACTURER: str = None
44+
BATCH_NUMBER: str = None
45+
EXPIRY_DATE: Optional[date]
46+
SITE_OF_VACCINATION_CODE: str = None
47+
SITE_OF_VACCINATION_TERM: str = None
48+
ROUTE_OF_VACCINATION_CODE: str = None
49+
ROUTE_OF_VACCINATION_TERM: str = None
50+
DOSE_AMOUNT: str = None
51+
DOSE_UNIT_CODE: str = None
52+
DOSE_UNIT_TERM: str = None
53+
INDICATION_CODE: str = None
54+
INDICATION_TERM: str = None
55+
NHS_NUMBER_STATUS_INDICATOR_CODE: str = None
56+
NHS_NUMBER_STATUS_INDICATOR_DESCRIPTION: str = None
57+
SITE_CODE_TYPE_URI: str = None
58+
LOCAL_PATIENT_ID: str = None
59+
LOCAL_PATIENT_URI: str = None
60+
CONSENT_FOR_TREATMENT_CODE: str = None
61+
CONSENT_FOR_TREATMENT_DESCRIPTION: str = None
62+
CARE_SETTING_TYPE_CODE: str = None
63+
CARE_SETTING_TYPE_DESCRIPTION: str = None
64+
IP_ADDRESS: str = None
65+
USER_ID: str = None
66+
USER_NAME: str = None
67+
USER_EMAIL: str = None
68+
SUBMITTED_TIMESTAMP: Optional[datetime] # YYYY-MM-DDThh:mm:ss+zz
69+
LOCATION_CODE: str = None
70+
LOCATION_CODE_TYPE_URI: str = None
71+
VACCINE_CUSTOM_LIST: str = None
72+
REDUCE_VALIDATION_REASON: str = None
73+
REDUCE_VALIDATION_CODE: Any = (
74+
None # Use Any to allow any type, including bool or None
75+
)
76+
77+
@validator("NHS_NUMBER")
78+
def validate_nhs_number(cls, value):
79+
"""Validate NHS Number"""
80+
return NHSValidators.validate_nhs_number(value)
81+
82+
@validator("PERSON_DOB", pre=True, always=True)
83+
def validate_person_dob(cls, value):
84+
"""Validate Person DOB"""
85+
return NHSValidators.validate_person_dob(value)
86+
87+
@validator("PERSON_GENDER_CODE", pre=True, always=True)
88+
def validate_person_gender_code(cls, value):
89+
"""Validate person gender code"""
90+
return NHSValidators.validate_person_gender_code(value)
91+
92+
@validator("PERSON_POSTCODE", pre=True, always=True)
93+
def validate_person_postcode(cls, value):
94+
"""Validate person postcode"""
95+
return NHSValidators.validate_person_postcode(value)
96+
97+
@validator("DATE_AND_TIME", pre=True, always=True)
98+
def validate_date_and_time(cls, value):
99+
"""Validate date and time"""
100+
return NHSValidators.validate_date_and_time(value)
101+
102+
@validator("SITE_CODE", pre=True, always=True)
103+
def validate_site_code(cls, value):
104+
"""Validate site code"""
105+
return NHSValidators.validate_site_code(value)
106+
107+
@validator("ACTION_FLAG", pre=True, always=True)
108+
def validate_action_flag(cls, value):
109+
"""Validate action flag"""
110+
return NHSValidators.validate_action_flag(value)
111+
112+
@validator("PERFORMING_PROFESSIONAL_FORENAME", pre=True, always=True)
113+
def validate_professional_forename(cls, v, values):
114+
"""Validate performing professional forename"""
115+
return NHSValidators.validate_performing_professional_forename(
116+
v, values.get("PERFORMING_PROFESSIONAL_SURNAME", None)
117+
)
118+
119+
@validator("PERFORMING_PROFESSIONAL_BODY_REG_CODE", pre=True, always=True)
120+
def validate_professional_reg_code(cls, v, values):
121+
"""Validate performing professional body reg code"""
122+
return NHSValidators.validate_performing_professional_body_reg_code(
123+
v, values.get("PERFORMING_PROFESSIONAL_BODY_REG_URI", None)
124+
)
125+
126+
@validator("RECORDED_DATE", pre=True, always=True)
127+
def validate_recorded_date(cls, value):
128+
"""Validate recorded date"""
129+
return NHSValidators.validate_recorded_date(value)
130+
131+
@validator("REPORT_ORIGIN", pre=True, always=True)
132+
def validate_report_origin(cls, v, values):
133+
"""Validate report origin"""
134+
primary_source = values["PRIMARY_SOURCE"]
135+
return NHSValidators.validate_report_origin(v, primary_source)
136+
137+
@validator("NOT_GIVEN", pre=True, always=True)
138+
def validate_not_given_flag(cls, value):
139+
"""Validate not given flag"""
140+
return NHSValidators.validate_not_given(value)
141+
142+
@validator("VACCINATION_PROCEDURE_CODE", pre=True, always=True)
143+
def validate_vaccination_procedure_code(cls, v, values):
144+
"""Validate vaccination procedure code"""
145+
return NHSValidators.validate_vaccination_procedure_code(
146+
v, values.get("NOT_GIVEN", None)
147+
)
148+
149+
@validator("VACCINATION_SITUATION_CODE", pre=True, always=True)
150+
def validate_vaccination_situatuion_code(cls, v, values):
151+
"""Validate vaccination situation code"""
152+
return NHSValidators.validate_vaccination_situation_code(
153+
v, values.get("NOT_GIVEN", None)
154+
)
155+
156+
@validator("REASON_NOT_GIVEN_CODE", pre=True, always=True)
157+
def validate_reason_not_given_code(cls, v, values):
158+
"""Validate reason not given code"""
159+
return NHSValidators.validate_reason_not_given_code(
160+
v, values.get("NOT_GIVEN", None)
161+
)
162+
163+
@validator("DOSE_SEQUENCE", pre=True, always=True)
164+
def validate_dose_sequence(cls, v, values):
165+
"""Validate dose sequence"""
166+
return NHSValidators.validate_dose_sequence(v, values.get("NOT_GIVEN", None))
167+
168+
@validator("VACCINE_PRODUCT_CODE", pre=True, always=True)
169+
def validate_vaccine_product_code(cls, v, values):
170+
"""Validate vaccine product code"""
171+
return NHSValidators.validate_vaccine_product_code(
172+
v, values.get("NOT_GIVEN", None)
173+
)
174+
175+
@validator("VACCINE_MANUFACTURER", pre=True, always=True)
176+
def validate_vaccine_manufacturer(cls, v, values):
177+
"""Validate vaccine manufacturer"""
178+
return NHSValidators.validate_vaccine_manufacturer(
179+
v, values.get("NOT_GIVEN", None)
180+
)
181+
182+
@validator("BATCH_NUMBER", pre=True, always=True)
183+
def validate_batch_number(cls, v, values):
184+
"""Validate batch number"""
185+
return NHSValidators.validate_batch_number(v, values.get("NOT_GIVEN", None))
186+
187+
@validator("EXPIRY_DATE", pre=True, always=True)
188+
def validate_expiry_date(cls, v, values):
189+
"""Validate expiry date"""
190+
return NHSValidators.validate_expiry_date(v, values.get("NOT_GIVEN", None))
191+
192+
@validator("ROUTE_OF_VACCINATION_CODE", pre=True, always=True)
193+
def validate_route_of_vaccination_code(cls, v, values):
194+
"""Validate route of vaccination code"""
195+
return NHSValidators.validate_route_of_vaccination_code(
196+
v, values.get("NOT_GIVEN", None)
197+
)
198+
199+
@validator("DOSE_AMOUNT", pre=True, always=True)
200+
def validate_dose_amount(cls, v, values):
201+
"""Validate dose amount"""
202+
return NHSValidators.validate_dose_amount(v, values.get("NOT_GIVEN", None))
203+
204+
@validator("DOSE_UNIT_CODE", pre=True, always=True)
205+
def validate_dose_unit_code(cls, v, values):
206+
"""Validate dose unit code"""
207+
return NHSValidators.validate_dose_unit_code(v, values.get("NOT_GIVEN", None))
208+
209+
@validator("INDICATION_CODE", pre=True, always=True)
210+
def validate_indication_code(cls, v, values):
211+
"""Validate indication code"""
212+
return NHSValidators.validate_indication_code(v, values.get("NOT_GIVEN", None))
213+
214+
@validator("CONSENT_FOR_TREATMENT_CODE", pre=True, always=True)
215+
def validate_consent_for_treatment_code(cls, v, values):
216+
"""Validate consent for treatment code"""
217+
return NHSValidators.validate_consent_for_treatment_code(
218+
v, values.get("NOT_GIVEN", None)
219+
)
220+
221+
@validator("SUBMITTED_TIMESTAMP", pre=True, always=True)
222+
def validate_submitted_timestamp(cls, value):
223+
"""Validate submitted timestamp"""
224+
return NHSValidators.validate_submitted_timestamp(value)
225+
226+
@validator("LOCATION_CODE", pre=True, always=True)
227+
def validate_location_code(cls, value):
228+
"""Validate location code"""
229+
return NHSValidators.validate_location_code(value)
230+
231+
@validator("REDUCE_VALIDATION_CODE", pre=True, always=True)
232+
def validate_reduce_validation_code(cls, v, values):
233+
"""Validate reduce validation code"""
234+
return NHSValidators.validate_reduce_validation_code(
235+
v, values.get("REDUCE_VALIDATION_REASON", None)
236+
)

0 commit comments

Comments
 (0)