From fd3f8626c4af6416340d157063f5cd4fde27b6c1 Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Fri, 29 May 2026 17:34:58 +0100 Subject: [PATCH 1/8] adds an inspection function and a simple test of it --- nuh_helper/date_shift/validation.py | 166 +++++++++++++++++++++++ tests/data/patients2with-extra-data.xlsx | Bin 0 -> 11713 bytes tests/test_inspect.py | 54 ++++++++ 3 files changed, 220 insertions(+) create mode 100644 nuh_helper/date_shift/validation.py create mode 100644 tests/data/patients2with-extra-data.xlsx create mode 100644 tests/test_inspect.py diff --git a/nuh_helper/date_shift/validation.py b/nuh_helper/date_shift/validation.py new file mode 100644 index 0000000..88ce1f4 --- /dev/null +++ b/nuh_helper/date_shift/validation.py @@ -0,0 +1,166 @@ +"""This contains an inspection function and error record type to determine if a +spreadsheet has data in abnormal places. it's mean tot check for "little notes" which +are outside of the CDM and may have undocumented patient data""" + +from openpyxl.cell.cell import Cell +from openpyxl.worksheet.worksheet import Worksheet +from pathlib import Path + + +class Error: + """base class for the errors. has a simplified __eq__ for `assert error in list`""" + + def __eq__(self, them: object) -> bool: + if type(self) is not type(them): + return False + return str(self) == str(them) + + +class ExcessRows(Error): + """error indicating that there are extra rows in a spreadsheet that don't have a + patient id and won't be shifted""" + + def __init__(self, sheet_name: str, excess: list[int]) -> None: + self.sheet_name = sheet_name + self.excess = excess + + def __str__(self) -> str: + return f"ExcessRows('{self.sheet_name}', {self.excess})" + + +class UnlabeledColumns(Error): + """indication that there are columns with data but no header; probably notes in the + margin about missing tests or (previously) dates related to patient's treatment to + explain the data in the spreadsheet.""" + + def __init__(self, sheet_name: str, columns: list[int]) -> None: + self.sheet_name = sheet_name + self.columns = columns + + def __str__(self) -> str: + return f"UnlabeledColumns('{self.sheet_name}', {self.columns})" + + +class PatientColumnMissing(Error): + """used to indicate that the patien column wasn't found in the spreadsheet""" + + def __init__(self, sheet_name: str, label: str) -> None: + self.sheet_name = sheet_name + self.name = label + + def __str__(self) -> str: + return f"PatientColumnMissing('{self.sheet_name}', '{self.label}')" + + +def format_errors(errors: list[Error]) -> str: + """formats a collection of error objects into a human digestible string""" + message: str = "" + names = [] + + # group the errors by sheet names + for error in errors: + if error.sheet_name not in names: + names.append(error.sheet_name) + + for sheet_name in names: + message += f"on sheet {sheet_name=} ...\n" + for error in errors: + if error.sheet_name != sheet_name: + continue + match error: + case ExcessRows(): + message += ( + f"\tthere were {len(error.excess)} rows with data but no " + + "patient ID\n" + ) + message += f"\t\t{error.excess}\n" + case UnlabeledColumns(): + message += ( + f"\tthere were {len(error.columns)} columns with no data " + + "in their label\n" + ) + message += f"\t\t{error.columns}\n" + case PatientColumnMissing(): + label = error.label + message += f"\tthere was no patient column {label=}\n" + return message + + +def inspect(sheet_file: Path, sheet_configs: dict) -> list[Error]: + """Find data that's out of bounds in the spreadsheet. Uses the date-shifting + sheet_configs structure. Rather than throw exceptions, this returns a list of Error + objects that can be inspected or tested for.""" + + from openpyxl import load_workbook + + errors: list[Error] = [] + + workbook = load_workbook(sheet_file, read_only=True, rich_text=False) + for sheet_name in workbook.sheetnames: + if sheet_name not in sheet_configs: + print(f"skipping sheet {sheet_name=} since there's no config for it") + continue + + sheet = workbook[sheet_name] + + # scan the header row to find out what the bounds of the spreadsheet should be + header_row = sheet_configs[sheet_name]["header_row"] + patient_id_col_text = sheet_configs[sheet_name]["patient_id_col"] + skip_rows = sheet_configs[sheet_name]["skip_rows_after_header"] + + # we'll want to use the index in later checks + patient_id_col_index: None | int = None + + # record the "blank" columns in the + blanks: list[int] = [] + + # check each cell of the header + for col in range(0, sheet.max_column): + value = sheet.cell(header_row + 1, col + 1) + if blank_cell(value): + blanks.append(col) + elif value.value == patient_id_col_text: + patient_id_col_index = col + + if blanks: + errors.append(UnlabeledColumns(sheet_name, blanks)) + + # we can't do any further checks without the patient_id_col_index + if patient_id_col_index is None: + errors.append(PatientColumnMissing(sheet_name, patient_id_col_text)) + else: + excess = [] + + # find any rows with data but no patient id + for row in range(0, sheet.max_row): + if row in skip_rows or row == header_row: + continue + + # we will allow "blank" rows + # ... such as empty rows between groups of patients + should_be_blank = blank_cell( + sheet.cell(row + 1, patient_id_col_index + 1) + ) + + # to allow "whitespace rows" we only check rows without a patient id + if should_be_blank and not blank_row(sheet, row): + excess.append(row) + + if excess: + errors.append(ExcessRows(sheet_name, excess)) + + return errors + + +def blank_cell(cell: Cell) -> bool: + """tests if a cell value is blank""" + return str(cell.value).strip() == "" or cell.value is None + + +def blank_row(sheet: Worksheet, row: int) -> bool: + """tests if a row of a Worksheet is blank""" + for c in range(0, sheet.max_column): + cell = sheet.cell(row + 1, c + 1) + if not blank_cell(cell): + return False + return True diff --git a/tests/data/patients2with-extra-data.xlsx b/tests/data/patients2with-extra-data.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..01fb9603a675fccd78ca18f4489b6b1661339b0b GIT binary patch literal 11713 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 None: + """ + + https://github.com/Health-Informatics-UoN/nuh-helper/issues/78 + + https://github.com/Health-Informatics-UoN/nuh-helper/issues/8 + """ + + patients_src = Path(__file__).parent / "data/patients2with-extra-data.xlsx" + + errors = inspect(patients_src, sheet_configs) + + message = format_errors(errors) + print(">>>") + print(message) + print("<<<") + + assert ExcessRows("measurements", [14]) in errors + assert UnlabeledColumns("measurements", [3, 4]) in errors + + assert len(errors) == 2 + + +sheet_configs = { + "patients": { + "patient_id_col": "patient_id", + "header_row": 0, + "skip_rows_after_header": [], + "date_columns": [ + "dob", + "last_alive", + ], + }, + "results": { + "patient_id_col": "patient_id", + "header_row": 0, + "skip_rows_after_header": [], + "date_columns": [ + "date_result", + ], + }, + "measurements": { + "patient_id_col": "p_id", + "header_row": 1, + "skip_rows_after_header": [2, 3], + "date_columns": [ + "date8061", + ], + }, +} From ca299fc426fee9b3ed28c6b7a01ab4ce26dcd484 Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 15:08:10 +0100 Subject: [PATCH 2/8] explicit imports in test --- nuh_helper/date_shift/validation.py | 37 ++++++++++------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/nuh_helper/date_shift/validation.py b/nuh_helper/date_shift/validation.py index 88ce1f4..2343220 100644 --- a/nuh_helper/date_shift/validation.py +++ b/nuh_helper/date_shift/validation.py @@ -2,54 +2,41 @@ spreadsheet has data in abnormal places. it's mean tot check for "little notes" which are outside of the CDM and may have undocumented patient data""" +from dataclasses import dataclass +from pathlib import Path + from openpyxl.cell.cell import Cell from openpyxl.worksheet.worksheet import Worksheet -from pathlib import Path class Error: """base class for the errors. has a simplified __eq__ for `assert error in list`""" - def __eq__(self, them: object) -> bool: - if type(self) is not type(them): - return False - return str(self) == str(them) - +@dataclass class ExcessRows(Error): """error indicating that there are extra rows in a spreadsheet that don't have a patient id and won't be shifted""" - def __init__(self, sheet_name: str, excess: list[int]) -> None: - self.sheet_name = sheet_name - self.excess = excess - - def __str__(self) -> str: - return f"ExcessRows('{self.sheet_name}', {self.excess})" + sheet_name: str + excess: list[int] +@dataclass class UnlabeledColumns(Error): """indication that there are columns with data but no header; probably notes in the margin about missing tests or (previously) dates related to patient's treatment to explain the data in the spreadsheet.""" - def __init__(self, sheet_name: str, columns: list[int]) -> None: - self.sheet_name = sheet_name - self.columns = columns - - def __str__(self) -> str: - return f"UnlabeledColumns('{self.sheet_name}', {self.columns})" + sheet_name: str + columns: list[int] +@dataclass class PatientColumnMissing(Error): """used to indicate that the patien column wasn't found in the spreadsheet""" - - def __init__(self, sheet_name: str, label: str) -> None: - self.sheet_name = sheet_name - self.name = label - - def __str__(self) -> str: - return f"PatientColumnMissing('{self.sheet_name}', '{self.label}')" + sheet_name: str + label: str def format_errors(errors: list[Error]) -> str: From 7061e7db53de5440317126fd18d38ec37d39e381 Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 15:08:57 +0100 Subject: [PATCH 3/8] use dataclasses --- tests/test_inspect.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 368db18..1298810 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -1,7 +1,38 @@ +from nuh_helper.date_shift.validation import ( + Cell as Cell, +) +from nuh_helper.date_shift.validation import ( + Error as Error, +) +from nuh_helper.date_shift.validation import ( + ExcessRows as ExcessRows, +) +from nuh_helper.date_shift.validation import ( + Path as Path, +) +from nuh_helper.date_shift.validation import ( + PatientColumnMissing as PatientColumnMissing, +) +from nuh_helper.date_shift.validation import ( + UnlabeledColumns as UnlabeledColumns, +) +from nuh_helper.date_shift.validation import ( + Worksheet as Worksheet, +) +from nuh_helper.date_shift.validation import ( + blank_cell as blank_cell, +) +from nuh_helper.date_shift.validation import ( + blank_row as blank_row, +) +from nuh_helper.date_shift.validation import ( + format_errors as format_errors, +) +from nuh_helper.date_shift.validation import ( + inspect as inspect, +) -from nuh_helper.date_shift.validation import * - def test_inspect() -> None: """ From 6c1a3f8483f617e37758d7b3f646e9b2928aa69c Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 15:09:38 +0100 Subject: [PATCH 4/8] Potential fix for pull request finding 'Explicit returns mixed with implicit (fall through) returns' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- nuh_helper/date_shift/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuh_helper/date_shift/validation.py b/nuh_helper/date_shift/validation.py index 88ce1f4..b48bc37 100644 --- a/nuh_helper/date_shift/validation.py +++ b/nuh_helper/date_shift/validation.py @@ -83,7 +83,7 @@ def format_errors(errors: list[Error]) -> str: case PatientColumnMissing(): label = error.label message += f"\tthere was no patient column {label=}\n" - return message + return message def inspect(sheet_file: Path, sheet_configs: dict) -> list[Error]: From f259669c167d82608e853d98741e1ff600f42448 Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 15:10:11 +0100 Subject: [PATCH 5/8] fixed error --- nuh_helper/date_shift/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuh_helper/date_shift/validation.py b/nuh_helper/date_shift/validation.py index 2343220..9d30c84 100644 --- a/nuh_helper/date_shift/validation.py +++ b/nuh_helper/date_shift/validation.py @@ -70,7 +70,7 @@ def format_errors(errors: list[Error]) -> str: case PatientColumnMissing(): label = error.label message += f"\tthere was no patient column {label=}\n" - return message + return message def inspect(sheet_file: Path, sheet_configs: dict) -> list[Error]: From bb2ddf94f34380bc389b03e23ed63bfa96fc1e84 Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 19:21:25 +0100 Subject: [PATCH 6/8] Potential fix for pull request finding 'Unused import' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- tests/test_inspect.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 1298810..8241e84 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -1,6 +1,3 @@ -from nuh_helper.date_shift.validation import ( - Cell as Cell, -) from nuh_helper.date_shift.validation import ( Error as Error, ) From f3228784708b333e4ca4405e0e9b0a17d7583ffc Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 19:28:29 +0100 Subject: [PATCH 7/8] typo --- tests/test_date_shift.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_date_shift.py b/tests/test_date_shift.py index d624a16..c395d0b 100644 --- a/tests/test_date_shift.py +++ b/tests/test_date_shift.py @@ -55,7 +55,7 @@ def test_empty_string_returns_none(self) -> None: @pytest.mark.parametrize( "placeholder", - ["unknown", "Unknown", "unk", "unkown", "n/a", "none", "null"], + ["unknown", "Unknown", "unk", "unknown", "n/a", "none", "null"], ) def test_placeholder_strings_return_none(self, placeholder: str) -> None: assert _parse_date_value(placeholder) is None From de839e760e7d9d93ad8efd917e8a038882f0185f Mon Sep 17 00:00:00 2001 From: Peter LaValle Date: Tue, 2 Jun 2026 19:28:53 +0100 Subject: [PATCH 8/8] organized imports --- tests/test_inspect.py | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 8241e84..654418b 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -1,32 +1,9 @@ from nuh_helper.date_shift.validation import ( - Error as Error, -) -from nuh_helper.date_shift.validation import ( - ExcessRows as ExcessRows, -) -from nuh_helper.date_shift.validation import ( - Path as Path, -) -from nuh_helper.date_shift.validation import ( - PatientColumnMissing as PatientColumnMissing, -) -from nuh_helper.date_shift.validation import ( - UnlabeledColumns as UnlabeledColumns, -) -from nuh_helper.date_shift.validation import ( - Worksheet as Worksheet, -) -from nuh_helper.date_shift.validation import ( - blank_cell as blank_cell, -) -from nuh_helper.date_shift.validation import ( - blank_row as blank_row, -) -from nuh_helper.date_shift.validation import ( - format_errors as format_errors, -) -from nuh_helper.date_shift.validation import ( - inspect as inspect, + ExcessRows, + Path, + UnlabeledColumns, + format_errors, + inspect, )