Skip to content

Commit 4fde4d4

Browse files
committed
overview check
1 parent d1cfb2f commit 4fde4d4

File tree

5 files changed

+89
-46
lines changed

5 files changed

+89
-46
lines changed

delta_backend/src/ConversionChecker.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ def convertData(self, expressionType, expressionRule, fieldName, fieldValue):
9393
expressionRule, fieldName, fieldValue, self.summarise, self.report_unexpected_exception
9494
)
9595
case _:
96-
raise ValueError(f'Schema expression not found! Check your expression type : " + expressionType ')
97-
96+
raise ValueError("Schema expression not found! Check your expression type : " + expressionType)
97+
9898
# Utility function for logging errors
9999
def _log_error(self, fieldName, fieldValue, e, code=ExceptionMessages.RECORD_CHECK_FAILED):
100100
if isinstance(e, Exception):
@@ -181,7 +181,7 @@ def _convertToDate(self, expressionRule, fieldName, fieldValue, summarise, repor
181181
# Format and return with custom suffix
182182
formatted = dt_utc.strftime("%Y%m%dT%H%M%S%z")
183183
return formatted.replace("+0000", "00").replace("+0100", "01")
184-
184+
185185
# For all other fields, apply standard %Y%m%d processing
186186
if format_str == "%Y%m%d":
187187
fieldValue = fieldValue.replace("-", "").replace("/", "")
@@ -199,7 +199,7 @@ def _convertToDate(self, expressionRule, fieldName, fieldValue, summarise, repor
199199
today_utc = datetime.now(ZoneInfo("UTC")).date()
200200
if dt.date() > today_utc:
201201
if report_unexpected_exception:
202-
self._log_error(fieldName, fieldValue, "Date cannot be in the future")
202+
self._log_error(fieldName, fieldValue, "Birthdate cannot be in the future")
203203
return ""
204204

205205
return dt.strftime(format_str)
@@ -251,16 +251,15 @@ def _convertToDateTime(self, expressionRule, fieldName, fieldValue, summarise, r
251251
# Not Empty Validate - Returns exactly what is in the extracted fields no parsing or logic needed
252252
def _convertToNotEmpty(self, expressionRule, fieldName, fieldValue, summarise, report_unexpected_exception):
253253
try:
254-
if isinstance(fieldValue, (int, float)):
255-
return "" # Reject plain numbers
256254
if isinstance(fieldValue, str) and fieldValue.strip():
257255
return fieldValue
256+
self._log_error(fieldName, fieldValue, "Value not a String")
258257
return ""
259258
except Exception as e:
260259
if report_unexpected_exception:
261260
message = ExceptionMessages.MESSAGES[ExceptionMessages.UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)
262-
return message
263-
261+
self._log_error(fieldName, fieldValue, message)
262+
return
264263

265264
# NHSNumber Validate
266265
def _convertToNHSNumber(self, expressionRule, fieldName, fieldValue, summarise, report_unexpected_exception):
@@ -370,7 +369,7 @@ def _convertToOnlyIfTo(self, expressionRule, fieldName, fieldValue, summarise, r
370369
if report_unexpected_exception:
371370
message = ExceptionMessages.MESSAGES[ExceptionMessages.UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)
372371
return message
373-
372+
374373
# Check if Snomed code is numeric and reject other forms
375374
def _convertToSnomed(self, expressionRule, fieldName, fieldValue, summarise, report_unexpected_exception):
376375
"""
@@ -405,6 +404,6 @@ def _convertToBoolean(self, expressionRule, fieldName, fieldValue, summarise, re
405404
if report_unexpected_exception:
406405
self._log_error(fieldName, fieldValue, e)
407406
return ""
408-
407+
409408
def get_error_records(self):
410409
return self.errorRecords

delta_backend/src/ConversionLayout.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@
265265
"fieldNameFlat": "DOSE_AMOUNT",
266266
"expression": {
267267
"expressionName": "Not Empty",
268-
"expressionType": "NOTEMPTY",
268+
"expressionType": "DEFAULT",
269269
"expressionRule": ""
270270
}
271271
},

delta_backend/src/Converter.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
SchemaFile = {}
2020
imms = []
2121
Converted = {}
22-
# ErrorRecords = []
2322

2423

2524
# Converter
@@ -103,9 +102,6 @@ def runConversion(self, json_data, summarise=False, report_unexpected_exception=
103102
p = {"code": 0, "message": message}
104103
self.ErrorRecords.append(p)
105104
return p
106-
# error = self._log_error("FHIR Parser Unexpected exception [%s]: %s" % (e.__class__.__name__, e),code=0)
107-
# return error
108-
109105

110106
try:
111107
ConversionValidate = ConversionChecker(dataParser, summarise, report_unexpected_exception)
@@ -130,8 +126,8 @@ def runConversion(self, json_data, summarise=False, report_unexpected_exception=
130126
rows = self._convertData(ConversionValidate, conversion, dataParser, json_data)
131127

132128
# Collect and store any errors from ConversionChecker
133-
allErrors = ConversionValidate.get_error_records()
134-
self.ErrorRecords.extend(allErrors)
129+
all_errors = ConversionValidate.get_error_records()
130+
self.ErrorRecords.extend(all_errors)
135131

136132
# Add CONVERSION_ERRORS as the 35th field
137133
error_records = self.getErrorRecords()
@@ -146,10 +142,7 @@ def runConversion(self, json_data, summarise=False, report_unexpected_exception=
146142

147143
def getErrorRecords(self):
148144
return self.ErrorRecords
149-
150-
# def getErrorRecords(self):
151-
# return self.ErrorRecords
152-
145+
153146
def extract_patient_details(self, json_data, FlatFieldName):
154147
if not hasattr(self, "_cached_values"):
155148
self._cached_values = {}

delta_backend/tests/sample_data/fhir_sample.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"coding": [
4747
{
4848
"system": "http://snomed.info/sct",
49-
"code": "13246810000001git",
49+
"code": "13246810000001",
5050
"display": "Administration of first dose of severe acute respiratory syndrome coronavirus 2 vaccine (procedure)"
5151
}
5252
]

delta_backend/tests/test_convert_to_flat_json.py

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def test_fhir_converter_json_direct_data(self):
153153

154154
end = time.time()
155155
print(end - start)
156-
156+
157157
def test_fhir_converter_json_error_scenario(self):
158158
"""it should convert fhir json data to flat json - error scenarios"""
159159
error_test_cases = [ErrorValuesForTests.missing_json, ErrorValuesForTests.json_dob_error]
@@ -185,7 +185,7 @@ def test_fhir_converter_json_error_scenario(self):
185185

186186
end = time.time()
187187
print(end - start)
188-
188+
189189
def test_handler_imms_convert_to_flat_json(self):
190190
"""Test that the Imms field contains the correct flat JSON data for CREATE, UPDATE, and DELETE operations."""
191191
expected_action_flags = [
@@ -216,13 +216,13 @@ def test_handler_imms_convert_to_flat_json(self):
216216
result = self.table.scan()
217217
items = result.get("Items", [])
218218
self.clear_table()
219-
219+
220220
def test_conversionCount(self):
221221
parser = SchemaParser()
222222
schema_data = {"conversions": [{"conversion": "type1"}, {"conversion": "type2"}, {"conversion": "type3"}]}
223223
parser.parseSchema(schema_data)
224224
self.assertEqual(parser.conversionCount(), 3)
225-
225+
226226
def test_getConversion(self):
227227
parser = SchemaParser()
228228
schema_data = {"conversions": [{"conversion": "type1"}, {"conversion": "type2"}, {"conversion": "type3"}]}
@@ -243,7 +243,7 @@ def test_fhir_parser_exception(self, mock_fhir_parser):
243243
self.assertEqual(len(response), 2)
244244
self.assertIn("FHIR Parser Unexpected exception", converter.getErrorRecords()[0]["message"])
245245
self.assertEqual(converter.getErrorRecords()[0]["code"], 0)
246-
246+
247247
@patch("Converter.FHIRParser")
248248
@patch("Converter.SchemaParser")
249249
def test_schema_parser_exception(self, mock_schema_parser, mock_fhir_parser):
@@ -264,7 +264,7 @@ def test_schema_parser_exception(self, mock_schema_parser, mock_fhir_parser):
264264
self.assertEqual(len(errors), 1)
265265
self.assertIn("Schema Parser Unexpected exception", errors[0]["message"])
266266
self.assertEqual(errors[0]["code"], 0)
267-
267+
268268
@patch("Converter.ConversionChecker")
269269
def test_conversion_checker_exception(self, mock_conversion_checker):
270270
# Mock ConversionChecker to raise an exception
@@ -325,7 +325,7 @@ def test_conversion_exceptions(self, mock_get_key_value, mock_get_conversions):
325325
error_records[0]["message"],
326326
)
327327
self.assertEqual(error_records[0]["code"], 0)
328-
328+
329329
@patch("ConversionChecker.LookUpData")
330330
def test_log_error(self, MockLookUpData):
331331
# Instantiate ConversionChecker
@@ -350,7 +350,7 @@ def test_log_error(self, MockLookUpData):
350350
self.assertEqual(error["value"], "test_value")
351351
self.assertIn("Invalid value", error["message"])
352352
self.assertEqual(error["code"], ExceptionMessages.RECORD_CHECK_FAILED)
353-
353+
354354
@patch("ConversionChecker.LookUpData")
355355
def test_convert_to_not_empty(self, MockLookUpData):
356356

@@ -370,7 +370,7 @@ def test_convert_to_nhs_number(self, MockLookUpData):
370370
dataParser = Mock()
371371

372372
checker = ConversionChecker(dataParser, summarise=False, report_unexpected_exception=True)
373-
373+
374374
# Test empty NHS number
375375
empty_nhs_number = ""
376376
result = checker._convertToNHSNumber(None, "fieldName", empty_nhs_number, False, True)
@@ -385,7 +385,7 @@ def test_convert_to_nhs_number(self, MockLookUpData):
385385
invalid_nhs_number = "1234567890243"
386386
result = checker._convertToNHSNumber("NHSNUMBER","fieldName", invalid_nhs_number, False, True)
387387
self.assertEqual(result, "", "Invalid NHS number should return empty string")
388-
388+
389389
@patch("ConversionChecker.LookUpData")
390390
def test_convert_to_date(self, MockLookUpData):
391391
dataParser = Mock()
@@ -399,7 +399,7 @@ def test_convert_to_date(self, MockLookUpData):
399399
# 2. Partial ISO date (should trigger "Partial date not accepted")
400400
result = checker._convertToDate("%Y%m%d", "fieldName", "2022-01", False, True)
401401
self.assertEqual(result, "")
402-
402+
403403
# 3. Invalid string date format (should trigger "Date must be in YYYYMMDD format")
404404
result = checker._convertToDate("%Y%m%d", "fieldName", "invalid_date", False, True)
405405
self.assertEqual(result, "")
@@ -411,15 +411,15 @@ def test_convert_to_date(self, MockLookUpData):
411411
# 5. Not a string input (should trigger "Value is not a string")
412412
result = checker._convertToDate("%Y%m%d", "fieldName", 12345678, False, True)
413413
self.assertEqual(result, "")
414-
414+
415415
# 6. Future date for birthDate (should trigger "Date cannot be in the future")
416416
future_date = (datetime.now() + timedelta(days=365)).strftime("%Y%m%d")
417417
result = checker._convertToDate("%Y%m%d", "contained|#:Patient|birthDate", future_date, False, True)
418418
self.assertEqual(result, "")
419419

420-
# 7. Valid recorded date in the future (should also be rejected)
421-
result = checker._convertToDate("%Y%m%d", "contained|#:Patient|birthDate", future_date, False, True)
422-
self.assertEqual(result, "")
420+
# # 7. Valid recorded date in the future (should also be rejected)
421+
# result = checker._convertToDate("%Y%m%d", "recorded", future_date, False, True)
422+
# self.assertEqual(result, "")
423423

424424
# 8. Empty string
425425
result = checker._convertToDate("%Y%m%d", "fieldName", "", False, True)
@@ -438,19 +438,61 @@ def test_convert_to_date(self, MockLookUpData):
438438
result = checker._convertToDate("format:%Y-%m-%d", "recorded", "invalid_date", False, True)
439439
self.assertEqual(result, "")
440440

441-
# 12 Validate all error logs of various responses
441+
# 12. Recorded date with invalid format
442+
result = checker._convertToDate("format:%Y-%m-%d", "recorded", "invalid_date", False, True)
443+
self.assertEqual(result, "")
444+
445+
past_date = (datetime.now(ZoneInfo("UTC")) - timedelta(days=1)) \
446+
.strftime("%Y-%m-%dT%H:%M:%S")
447+
result = checker._convertToDate("format:%Y-%m-%dT%H:%M:%S",
448+
"recorded",
449+
past_date,
450+
False,
451+
True)
452+
453+
# 13 Expect it to parse naïve as UTC, then format back as “YYYYMMDDTHHMMSS”
454+
expected = datetime.strptime(past_date, "%Y-%m-%dT%H:%M:%S") \
455+
.strftime("%Y%m%dT%H%M%S")
456+
self.assertTrue(result.startswith(expected),
457+
f"Expected prefix {expected}, got {result!r}")
458+
459+
# 14. Recorded timestamp without tzinfo in the future → rejected
460+
future_naive = (datetime.now(ZoneInfo("UTC")) + timedelta(days=1)) \
461+
.strftime("%Y-%m-%dT%H:%M:%S")
462+
result = checker._convertToDate("format:%Y-%m-%dT%H:%M:%S",
463+
"recorded",
464+
future_naive,
465+
False,
466+
True)
467+
self.assertEqual(result, "")
468+
469+
# 15 Validate all error logs of various responses
442470
messages = [err["message"] for err in checker.errorRecords]
443471
print(f"Error Test Case, {messages}")
444472

445473
self.assertIn("Date must be in YYYYMMDD format", messages)
446474
self.assertIn("Value is not a string", messages)
447475
self.assertIn("Partial date not accepted", messages)
448476
self.assertIn("Date cannot be in the future", messages)
477+
self.assertIn("Birthdate cannot be in the future", messages)
449478
self.assertTrue(any(m.startswith("Unsupported offset") for m in messages))
450479
self.assertIn("Invalid date format", messages)
451480

452481
# Confirm Total Errors Per conversion
453-
self.assertEqual(len(checker.errorRecords), 6)
482+
self.assertEqual(len(checker.errorRecords), 7)
483+
484+
# Test for value Error
485+
checker._log_error = Mock()
486+
487+
# invalid date against the given format → ValueError path
488+
result = checker._convertToDate("format:%Y-%m-%d", "fieldName", "not-a-date", False, True)
489+
self.assertEqual(result, "")
490+
491+
# ensure we logged exactly that ValueError
492+
checker._log_error.assert_called_once()
493+
field, value, err = checker._log_error.call_args[0]
494+
self.assertEqual((field, value), ("fieldName", "not-a-date"))
495+
self.assertIsInstance(err, ValueError)
454496

455497
@patch("ConversionChecker.LookUpData")
456498
def test_convert_to_date_time(self, MockLookUpData):
@@ -485,27 +527,38 @@ def test_convert_to_boolean(self, MockLookUpData):
485527
checker = ConversionChecker(dataParser, summarise=False, report_unexpected_exception=True)
486528

487529
# 1. Boolean True passes through
488-
result = checker._convertToBoolean(None, "f", True, False, True)
530+
result = checker._convertToBoolean(None, "fieldName", True, False, True)
489531
self.assertTrue(result)
490532

491533
# 2. Boolean False passes through
492-
result = checker._convertToBoolean(None, "f", False, False, True)
534+
result = checker._convertToBoolean(None, "fieldName", False, False, True)
493535
self.assertFalse(result)
494536

495537
# 3. String "true" variants
496538
for val in ["true", "TRUE", " True ", "\tTrUe\n"]:
497-
result = checker._convertToBoolean(None, "f", val, False, False)
539+
result = checker._convertToBoolean(None, "fieldName", val, False, False)
498540
self.assertTrue(result)
499541

500542
# 4. String "false" variants
501543
for val in ["false", "FALSE", " False ", "\nFaLsE\t"]:
502-
result = checker._convertToBoolean(None, "f", val, False, False)
544+
result = checker._convertToBoolean(None, "fieldName", val, False, False)
503545
self.assertFalse(result)
504546

505547
# 5. Invalid string with report_unexpected_exception=False → no log
506-
result = checker._convertToBoolean(None, "f", "notbool", False, False)
548+
result = checker._convertToBoolean(None, "fieldName", "notbool", False, True)
507549
self.assertEqual(result, "")
508550

551+
# Assert exactly one error was logged
552+
self.assertEqual(len(checker.errorRecords), 1)
553+
554+
err = checker.errorRecords[0]
555+
self.assertEqual(err["field"], "fieldName")
556+
self.assertEqual(err["value"], "notbool")
557+
# message should include our literal
558+
self.assertIn("Invalid String Data", err["message"])
559+
# and code should default to UNEXPECTED_EXCEPTION
560+
self.assertEqual(err["code"], ExceptionMessages.RECORD_CHECK_FAILED)
561+
509562
#check for dose sequence
510563
@patch("ConversionChecker.LookUpData")
511564
def test_convert_to_dose(self, MockLookUpData):
@@ -533,7 +586,6 @@ def clear_table(self):
533586
result = self.table.scan()
534587
items = result.get("Items", [])
535588

536-
537589
class TestPersonForeNameToFlatJson(unittest.TestCase):
538590
def test_person_forename_multiple_names_official(self):
539591
"""Test case where multiple name instances exist, and one has use=official with period covering vaccination date"""
@@ -635,7 +687,6 @@ def _run_test(self, expected_forename):
635687
flat_json = self.converter.runConversion(request_json_data, False, True)
636688
self.assertEqual(flat_json[0]["PERSON_FORENAME"], expected_forename)
637689

638-
639690
class TestPersonSurNameToFlatJson(unittest.TestCase):
640691

641692
def test_person_surname_multiple_names_official(self):

0 commit comments

Comments
 (0)