Skip to content

Commit f90c55f

Browse files
authored
VED-320: BugFixes-Delta-postcode (#539)
Delta Postcode Fix
1 parent 10886ab commit f90c55f

File tree

5 files changed

+110
-36
lines changed

5 files changed

+110
-36
lines changed

backend/src/models/utils/pre_validator_utils.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ def for_date_time(field_value: str, field_location: str):
114114
raise TypeError(f"{field_location} must be a string")
115115

116116
error_message = (
117-
f"{field_location} must be a valid datetime in one of the following formats:\n"
118-
"- 'YYYY-MM-DD' — Full date only\n"
119-
"- 'YYYY-MM-DDThh:mm:ss' — Full date and time without milliseconds\n"
120-
"- 'YYYY-MM-DDThh:mm:ss.f' — Full date and time with milliseconds (any level of precision)\n"
121-
"- 'YYYY-MM-DDThh:mm:ss%z' — Full date and time with timezone (e.g. +00:00 or +01:00)\n"
122-
"- 'YYYY-MM-DDThh:mm:ss.f%z' — Full date and time with milliseconds and timezone\n\n"
123-
"Only '+00:00' and '+01:00' are accepted as valid timezone offsets.\n"
117+
f"{field_location} must be a valid datetime in one of the following formats:"
118+
"- 'YYYY-MM-DD' — Full date only"
119+
"- 'YYYY-MM-DDThh:mm:ss' — Full date and time without milliseconds"
120+
"- 'YYYY-MM-DDThh:mm:ss.f' — Full date and time with milliseconds (any level of precision)"
121+
"- 'YYYY-MM-DDThh:mm:ss%z' — Full date and time with timezone (e.g. +00:00 or +01:00)"
122+
"- 'YYYY-MM-DDThh:mm:ss.f%z' — Full date and time with milliseconds and timezone"
123+
"Only '+00:00' and '+01:00' are accepted as valid timezone offsets."
124124
f"Note that partial dates are not allowed for {field_location} in this service."
125125
)
126126

backend/tests/utils/pre_validation_test_utils.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,13 @@ def test_date_time_value(
350350
)
351351

352352
expected_error_message = (
353-
f"{field_location} must be a valid datetime in one of the following formats:\n"
354-
"- 'YYYY-MM-DD' — Full date only\n"
355-
"- 'YYYY-MM-DDThh:mm:ss' — Full date and time without milliseconds\n"
356-
"- 'YYYY-MM-DDThh:mm:ss.f' — Full date and time with milliseconds (any level of precision)\n"
357-
"- 'YYYY-MM-DDThh:mm:ss%z' — Full date and time with timezone (e.g. +00:00 or +01:00)\n"
358-
"- 'YYYY-MM-DDThh:mm:ss.f%z' — Full date and time with milliseconds and timezone\n\n"
359-
"Only '+00:00' and '+01:00' are accepted as valid timezone offsets.\n"
353+
f"{field_location} must be a valid datetime in one of the following formats:"
354+
"- 'YYYY-MM-DD' — Full date only"
355+
"- 'YYYY-MM-DDThh:mm:ss' — Full date and time without milliseconds"
356+
"- 'YYYY-MM-DDThh:mm:ss.f' — Full date and time with milliseconds (any level of precision)"
357+
"- 'YYYY-MM-DDThh:mm:ss%z' — Full date and time with timezone (e.g. +00:00 or +01:00)"
358+
"- 'YYYY-MM-DDThh:mm:ss.f%z' — Full date and time with milliseconds and timezone"
359+
"Only '+00:00' and '+01:00' are accepted as valid timezone offsets."
360360
f"Note that partial dates are not allowed for {field_location} in this service."
361361
)
362362

delta_backend/src/extractor.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ def extract_person_gender(self):
254254
return ""
255255
return ""
256256

257+
def normalize(self, value):
258+
return value.lower() if isinstance(value, str) else value
259+
257260
def extract_valid_address(self):
258261
occurrence_time = self._get_occurance_date_time()
259262
patient = self._get_patient()
@@ -262,21 +265,23 @@ def extract_valid_address(self):
262265
if not isinstance(addresses, list) or not addresses:
263266
return self.DEFAULT_POSTCODE
264267

265-
valid_addresses = [a for a in addresses if "postalCode" in a and self._is_current_period(a, occurrence_time)]
266-
267-
if len(valid_addresses) > 0:
268-
selected_address = next(
269-
(a for a in valid_addresses if a.get("use") == "home" and a.get("type") != "postal"),
270-
next(
271-
(a for a in valid_addresses if a.get("use") != "old" and a.get("type") != "postal"),
272-
next((a for a in valid_addresses if a.get("use") != "old"), valid_addresses[0]),
273-
),
274-
)
275-
post_code = selected_address.get("postalCode", self.DEFAULT_POSTCODE)
276-
if post_code:
277-
return post_code
278-
279-
return addresses[0].get("postalCode", self.DEFAULT_POSTCODE) if addresses else self.DEFAULT_POSTCODE
268+
if len(addresses) == 1:
269+
return addresses[0].get("postalCode") or self.DEFAULT_POSTCODE
270+
271+
if not (valid_addresses := [
272+
addr for addr in addresses
273+
if addr.get("postalCode") and self._is_current_period(addr, occurrence_time)
274+
]):
275+
return self.DEFAULT_POSTCODE
276+
277+
selected_address = (
278+
next((a for a in valid_addresses if self.normalize(a.get("use")) == "home" and self.normalize(a.get("type")) != "postal"), None)
279+
or next((a for a in valid_addresses if self.normalize(a.get("use")) != "old" and self.normalize(a.get("type")) != "postal"), None)
280+
or next((a for a in valid_addresses if self.normalize(a.get("use")) != "old"), None)
281+
or valid_addresses[0]
282+
)
283+
284+
return selected_address.get("postalCode") or self.DEFAULT_POSTCODE
280285

281286
def extract_date_time(self) -> str:
282287
date = self.fhir_json_data.get("occurrenceDateTime","")

delta_backend/tests/sample_data/fhir_sample.json

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,41 @@
3030
"gender": "other",
3131
"birthDate": "2026-03-10",
3232
"address": [
33-
{
34-
"use": "home",
35-
"line": ["123 High Street"],
36-
"city": "London",
37-
"postalCode": "SW1A 1AA"
38-
}
39-
]
33+
{
34+
"use": "work",
35+
"type": "both",
36+
"text": "Validate Obf",
37+
"line": [
38+
"1, work_2"
39+
],
40+
"city": "work_3",
41+
"district": "work_4",
42+
"state": "work_5",
43+
"country": "work_7",
44+
"postalCode": "LS8 4ED",
45+
"period": {
46+
"start": "2000-01-01",
47+
"end": "2030-01-01"
48+
}
49+
},
50+
{
51+
"use": "Home",
52+
"type": "Physical",
53+
"text": "Validate Obf",
54+
"line": [
55+
"1, obf_2"
56+
],
57+
"city": "obf_3",
58+
"district": "obf_4",
59+
"state": "obf_5",
60+
"postalCode": "WF8 4ED",
61+
"country": "obf_7",
62+
"period": {
63+
"start": "2000-01-01",
64+
"end": "2030-01-01"
65+
}
66+
}
67+
]
4068
}
4169
],
4270
"extension": [

delta_backend/tests/test_convert_post_code.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ def test_person_postal_code_single_address(self):
2424
"type": "physical",
2525
"period": {"start": "2018-01-01", "end": "2020-12-31"},
2626
}]
27+
28+
def test_person_postal_code_single_address_only_postal_code(self):
29+
"""Test case where only one address instance exists with one postalCode"""
30+
self.request_json_data["contained"][1]["address"] = [{
31+
"postalCode": "AB12 3CD",
32+
}]
2733

2834
expected_postal_code = "AB12 3CD"
2935
self._run_postal_code_test(expected_postal_code)
@@ -94,6 +100,25 @@ def test_person_postal_code_fallback_first_non_old(self):
94100
expected_postal_code = "KK20 2KK"
95101
self._run_postal_code_test(expected_postal_code)
96102

103+
def test_person_postal_code_case_insensitive_match(self):
104+
"""Test case where 'use' and 'type' values require case-insensitive comparison"""
105+
self.request_json_data["contained"][1]["address"] = [
106+
{
107+
"postalCode": "LS8 4ED",
108+
"use": "work",
109+
"type": "both",
110+
"period": {"start": "2000-01-01", "end": "2023-01-01"},
111+
},
112+
{
113+
"postalCode": "WF8 4ED",
114+
"use": "Home", # capital H
115+
"type": "Physical", # capital P
116+
"period": {"start": "2000-01-01", "end": "2023-01-01"},
117+
}
118+
]
119+
expected_postal_code = "WF8 4ED"
120+
self._run_postal_code_test(expected_postal_code)
121+
97122
def test_person_postal_code_default_to_ZZ99_3CZ(self):
98123
"""Test case where no valid postalCode is found, should default to ZZ99 3CZ"""
99124
self.request_json_data["contained"][1]["address"] = [
@@ -103,6 +128,22 @@ def test_person_postal_code_default_to_ZZ99_3CZ(self):
103128
expected_postal_code = "ZZ99 3CZ"
104129
self._run_postal_code_test(expected_postal_code)
105130

131+
def test_person_postal_code_blank_string_should_fallback(self):
132+
"""Test case where postalCode is an empty string — should fallback to ZZ99 3CZ"""
133+
self.request_json_data["contained"][1]["address"] = [
134+
{"postalCode": "",
135+
"use": "home",
136+
"type": "physical",
137+
"period": {"start": "2018-01-01", "end": "2030-12-31"},
138+
},
139+
]
140+
expected_postal_code = "ZZ99 3CZ"
141+
self._run_postal_code_test(expected_postal_code)
142+
assert "postalCode" in self.request_json_data["contained"][1]["address"][0]
143+
assert self.request_json_data["contained"][1]["address"][0]["postalCode"] == ""
144+
145+
146+
106147
def _run_postal_code_test(self, expected_postal_code):
107148
"""Helper function to run the test"""
108149
self.converter = Converter(json.dumps(self.request_json_data))

0 commit comments

Comments
 (0)