|
8 | 8 |
|
9 | 9 | from clients import redis_client, logger |
10 | 10 | from models.errors import ParameterException |
| 11 | +from models.utils.generic_utils import nhs_number_mod11_check |
11 | 12 | from models.constants import Constants |
12 | 13 |
|
13 | 14 | ParamValue = list[str] |
@@ -35,6 +36,110 @@ class SearchParams: |
35 | 36 | def __repr__(self): |
36 | 37 | return str(self.__dict__) |
37 | 38 |
|
| 39 | +def process_patient_identifier(identifier_params: ParamContainer) -> str: |
| 40 | + """Validate and parse patient identifier parameter. |
| 41 | +
|
| 42 | + :raises ParameterException: |
| 43 | + """ |
| 44 | + patient_identifiers = identifier_params.get(patient_identifier_key, []) |
| 45 | + patient_identifier = patient_identifiers[0] if len(patient_identifiers) == 1 else None |
| 46 | + |
| 47 | + if patient_identifier is None: |
| 48 | + raise ParameterException(f"Search parameter {patient_identifier_key} must have one value.") |
| 49 | + |
| 50 | + patient_identifier_parts = patient_identifier.split("|") |
| 51 | + identifier_system = patient_identifier_parts[0] |
| 52 | + if len(patient_identifier_parts) != 2 or identifier_system != patient_identifier_system: |
| 53 | + raise ParameterException("patient.identifier must be in the format of " |
| 54 | + f"\"{patient_identifier_system}|{{NHS number}}\" " |
| 55 | + f"e.g. \"{patient_identifier_system}|9000000009\"") |
| 56 | + |
| 57 | + nhs_number = patient_identifier_parts[1] |
| 58 | + if not nhs_number_mod11_check(nhs_number): |
| 59 | + raise ParameterException("Search parameter patient.identifier must be a valid NHS number.") |
| 60 | + |
| 61 | + return nhs_number |
| 62 | + |
| 63 | + |
| 64 | +def process_immunization_target(imms_params: ParamContainer) -> list[str]: |
| 65 | + """Validate and parse immunization target parameter. |
| 66 | +
|
| 67 | + :raises ParameterException: |
| 68 | + """ |
| 69 | + vaccine_types = [vaccine_type for vaccine_type in set(imms_params.get(immunization_target_key, [])) if |
| 70 | + vaccine_type is not None] |
| 71 | + if len(vaccine_types) < 1: |
| 72 | + raise ParameterException(f"Search parameter {immunization_target_key} must have one or more values.") |
| 73 | + |
| 74 | + valid_vaccine_types = redis_client.hkeys(Constants.VACCINE_TYPE_TO_DISEASES_HASH_KEY) |
| 75 | + if any(x not in valid_vaccine_types for x in vaccine_types): |
| 76 | + raise ParameterException( |
| 77 | + f"immunization-target must be one or more of the following: {', '.join(valid_vaccine_types)}") |
| 78 | + |
| 79 | + return vaccine_types |
| 80 | + |
| 81 | + |
| 82 | +def process_mandatory_params(params: ParamContainer) -> tuple[str, list[str]]: |
| 83 | + """Validate mandatory params and return (patient_identifier, vaccine_types). |
| 84 | + Raises ParameterException for any validation error. |
| 85 | + """ |
| 86 | + # patient.identifier |
| 87 | + patient_identifier = process_patient_identifier(params) |
| 88 | + |
| 89 | + # immunization.target |
| 90 | + vaccine_types = process_immunization_target(params) |
| 91 | + |
| 92 | + return patient_identifier, vaccine_types |
| 93 | + |
| 94 | + |
| 95 | +def process_optional_params(params: ParamContainer) -> tuple[datetime.date, datetime.date, Optional[str], list[str]]: |
| 96 | + """Parse optional params (date.from, date.to, _include). |
| 97 | + Returns (date_from, date_to, include, errors). |
| 98 | + """ |
| 99 | + errors: list[str] = [] |
| 100 | + date_from = None |
| 101 | + date_to = None |
| 102 | + |
| 103 | + date_froms = params.get(date_from_key, []) |
| 104 | + if len(date_froms) > 1: |
| 105 | + errors.append(f"Search parameter {date_from_key} may have one value at most.") |
| 106 | + |
| 107 | + try: |
| 108 | + date_from = datetime.datetime.strptime(date_froms[0], "%Y-%m-%d").date() if len(date_froms) == 1 else date_from_default |
| 109 | + except ValueError: |
| 110 | + errors.append(f"Search parameter {date_from_key} must be in format: YYYY-MM-DD") |
| 111 | + |
| 112 | + date_tos = params.get(date_to_key, []) |
| 113 | + if len(date_tos) > 1: |
| 114 | + errors.append(f"Search parameter {date_to_key} may have one value at most.") |
| 115 | + |
| 116 | + try: |
| 117 | + date_to = datetime.datetime.strptime(date_tos[0], "%Y-%m-%d").date() if len(date_tos) == 1 else date_to_default |
| 118 | + except ValueError: |
| 119 | + errors.append(f"Search parameter {date_to_key} must be in format: YYYY-MM-DD") |
| 120 | + |
| 121 | + includes = params.get(include_key, []) |
| 122 | + if includes and includes[0].lower() != "immunization:patient": |
| 123 | + errors.append(f"Search parameter {include_key} may only be 'Immunization:patient' if provided.") |
| 124 | + include = includes[0] if len(includes) > 0 else None |
| 125 | + |
| 126 | + return date_from, date_to, include, errors |
| 127 | + |
| 128 | +def process_search_params(params: ParamContainer) -> SearchParams: |
| 129 | + """Validate and parse search parameters. |
| 130 | + :raises ParameterException: |
| 131 | + """ |
| 132 | + patient_identifier, vaccine_types = process_mandatory_params(params) |
| 133 | + date_from, date_to, include, errors = process_optional_params(params) |
| 134 | + |
| 135 | + if date_from and date_to and date_from > date_to: |
| 136 | + errors.append(f"Search parameter {date_from_key} must be before {date_to_key}") |
| 137 | + |
| 138 | + if errors: |
| 139 | + raise ParameterException("; ".join(errors)) |
| 140 | + |
| 141 | + return SearchParams(patient_identifier, vaccine_types, date_from, date_to, include) |
| 142 | + |
38 | 143 |
|
39 | 144 | def process_params(aws_event: APIGatewayProxyEventV1) -> ParamContainer: |
40 | 145 | """Combines query string and content parameters. Duplicates not allowed. Splits on a comma.""" |
@@ -80,74 +185,6 @@ def parse_body_params(aws_event: APIGatewayProxyEventV1) -> ParamContainer: |
80 | 185 | return parsed_params |
81 | 186 |
|
82 | 187 |
|
83 | | -def process_search_params(params: ParamContainer) -> SearchParams: |
84 | | - """Validate and parse search parameters. |
85 | | -
|
86 | | - :raises ParameterException: |
87 | | - """ |
88 | | - |
89 | | - # patient.identifier |
90 | | - patient_identifiers = params.get(patient_identifier_key, []) |
91 | | - patient_identifier = patient_identifiers[0] if len(patient_identifiers) == 1 else None |
92 | | - |
93 | | - if patient_identifier is None: |
94 | | - raise ParameterException(f"Search parameter {patient_identifier_key} must have one value.") |
95 | | - |
96 | | - patient_identifier_parts = patient_identifier.split("|") |
97 | | - if len(patient_identifier_parts) != 2 or not patient_identifier_parts[ |
98 | | - 0] == patient_identifier_system: |
99 | | - raise ParameterException("patient.identifier must be in the format of " |
100 | | - f"\"{patient_identifier_system}|{{NHS number}}\" " |
101 | | - f"e.g. \"{patient_identifier_system}|9000000009\"") |
102 | | - |
103 | | - patient_identifier = patient_identifier.split("|")[1] |
104 | | - |
105 | | - # immunization.target |
106 | | - params[immunization_target_key] = list(set(params.get(immunization_target_key, []))) |
107 | | - vaccine_types = [vaccine_type for vaccine_type in params[immunization_target_key] if |
108 | | - vaccine_type is not None] |
109 | | - if len(vaccine_types) < 1: |
110 | | - raise ParameterException(f"Search parameter {immunization_target_key} must have one or more values.") |
111 | | - |
112 | | - valid_vaccine_types = redis_client.hkeys(Constants.VACCINE_TYPE_TO_DISEASES_HASH_KEY) |
113 | | - if any(x not in valid_vaccine_types for x in vaccine_types): |
114 | | - raise ParameterException( |
115 | | - f"immunization-target must be one or more of the following: {', '.join(valid_vaccine_types)}") |
116 | | - |
117 | | - # date.from |
118 | | - date_froms = params.get(date_from_key, []) |
119 | | - |
120 | | - if len(date_froms) > 1: |
121 | | - raise ParameterException(f"Search parameter {date_from_key} may have one value at most.") |
122 | | - |
123 | | - try: |
124 | | - date_from = datetime.datetime.strptime(date_froms[0], "%Y-%m-%d").date() \ |
125 | | - if len(date_froms) == 1 else date_from_default |
126 | | - except ValueError: |
127 | | - raise ParameterException(f"Search parameter {date_from_key} must be in format: YYYY-MM-DD") |
128 | | - |
129 | | - # date.to |
130 | | - date_tos = params.get(date_to_key, []) |
131 | | - |
132 | | - if len(date_tos) > 1: |
133 | | - raise ParameterException(f"Search parameter {date_to_key} may have one value at most.") |
134 | | - |
135 | | - try: |
136 | | - date_to = datetime.datetime.strptime(date_tos[0], "%Y-%m-%d").date() \ |
137 | | - if len(date_tos) == 1 else date_to_default |
138 | | - except ValueError: |
139 | | - raise ParameterException(f"Search parameter {date_to_key} must be in format: YYYY-MM-DD") |
140 | | - |
141 | | - if date_from and date_to and date_from > date_to: |
142 | | - raise ParameterException(f"Search parameter {date_from_key} must be before {date_to_key}") |
143 | | - |
144 | | - # include |
145 | | - includes = params.get(include_key, []) |
146 | | - include = includes[0] if len(includes) > 0 else None |
147 | | - |
148 | | - return SearchParams(patient_identifier, vaccine_types, date_from, date_to, include) |
149 | | - |
150 | | - |
151 | 188 | def create_query_string(search_params: SearchParams) -> str: |
152 | 189 | params = [ |
153 | 190 | (immunization_target_key, ",".join(map(quote, search_params.immunization_targets))), |
|
0 commit comments