From dd1d770c34d01dc77d82529ecc1fe224d9830ab1 Mon Sep 17 00:00:00 2001 From: kathryn1995 Date: Fri, 2 Oct 2026 21:33:48 -0700 Subject: [PATCH] skip blanks in add_plate_info --- src/pmotools/pmo_builder/metatable_to_pmo.py | 47 +++++++----- .../test_pmo_builder/test_metatable_to_pmo.py | 75 +++++++++++++++++++ 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/pmotools/pmo_builder/metatable_to_pmo.py b/src/pmotools/pmo_builder/metatable_to_pmo.py index 6169646..e6ee1fa 100644 --- a/src/pmotools/pmo_builder/metatable_to_pmo.py +++ b/src/pmotools/pmo_builder/metatable_to_pmo.py @@ -487,6 +487,14 @@ def check_columns_exist(df, columns): ) +def _is_blank(value): + if value is None: + return True + if isinstance(value, str): + return not value.strip() + return bool(pd.isna(value)) + + def add_plate_info( plate_col_col, plate_name_col, @@ -516,38 +524,39 @@ def add_plate_info( plate_row_col = "plate_row" plate_col_col = "plate_col" - try: - df[plate_row_col] = ( - df[plate_position_col].str.extract(r"(?i)^([A-H])")[0].str.upper() - ) - df[plate_col_col] = ( - df[plate_position_col] - .str.extract(r"(?i)^[A-H]0*([1-9]|1[0-2])$")[0] - .astype(int) - ) - except (AttributeError, ValueError, IndexError, KeyError) as e: + positions = df[plate_position_col] + blank = positions.map(_is_blank) + parsed = ( + positions[~blank] + .astype(str) + .str.strip() + .str.extract(r"(?i)^([A-H])0*([1-9]|1[0-2])$") + ) + if parsed.isna().any().any(): raise ValueError( f"Values in '{plate_position_col}' must start with a single letter A-H/a-h followed by number 1-12." - ) from e + ) + df[plate_row_col] = None + df.loc[~blank, plate_row_col] = parsed[0].str.upper() + df[plate_col_col] = pd.Series(pd.NA, index=df.index, dtype="Int64") + df.loc[~blank, plate_col_col] = parsed[1].astype(int) for row in meta_json: content_row = df[df[match_col] == row[match_col]] plate_name_val = content_row[plate_name_col].iloc[0] if plate_name_col else None - plate_row_val = ( - content_row[plate_row_col].iloc[0].upper() if plate_row_col else None - ) + plate_row_val = content_row[plate_row_col].iloc[0] if plate_row_col else None plate_col_val = content_row[plate_col_col].iloc[0] if plate_col_col else None - if plate_col_val is not None and not pd.isna(plate_col_val): + if not _is_blank(plate_col_val): try: plate_col_val = int(plate_col_val) except (TypeError, ValueError): plate_col_val = plate_col_val plate_info = {} - if plate_name_val: + if not _is_blank(plate_name_val): plate_info["plate_name"] = plate_name_val - if plate_row_val: - plate_info["plate_row"] = plate_row_val - if plate_col_val is not None and not pd.isna(plate_col_val): + if not _is_blank(plate_row_val): + plate_info["plate_row"] = str(plate_row_val).strip().upper() + if not _is_blank(plate_col_val): plate_info["plate_col"] = plate_col_val if plate_info: diff --git a/tests/test_pmo_builder/test_metatable_to_pmo.py b/tests/test_pmo_builder/test_metatable_to_pmo.py index 7223cca..50b992f 100644 --- a/tests/test_pmo_builder/test_metatable_to_pmo.py +++ b/tests/test_pmo_builder/test_metatable_to_pmo.py @@ -219,6 +219,81 @@ def test_add_plate_info_adds_nothing(self): result, [{"specimen_name": "sample1"}, {"specimen_name": "sample2"}] ) + def test_add_plate_info_row_col_skips_blank_cells(self): + df = pd.DataFrame( + { + "specimen_name": ["sample1", "sample2"], + "plate_row": ["A", None], + "plate_col": [1, None], + "plate_name": ["Plate1", None], + } + ) + result = add_plate_info( + "plate_col", + "plate_name", + "plate_row", + None, + self.small_json_example, + df, + "specimen_name", + ) + self.assertEqual( + result[0]["plate_info"], + {"plate_name": "Plate1", "plate_row": "A", "plate_col": 1}, + ) + self.assertNotIn("plate_info", result[1]) + + def test_add_plate_info_position_skips_blank_cells(self): + df = pd.DataFrame( + { + "specimen_name": ["sample1", "sample2", "sample3"], + "plate_position": ["A01", None, " "], + "plate_name": ["Plate1", float("nan"), ""], + } + ) + json_example = [ + {"specimen_name": "sample1"}, + {"specimen_name": "sample2"}, + {"specimen_name": "sample3"}, + ] + result = add_plate_info( + None, + "plate_name", + None, + "plate_position", + json_example, + df, + "specimen_name", + ) + self.assertEqual( + result[0]["plate_info"], + {"plate_name": "Plate1", "plate_row": "A", "plate_col": 1}, + ) + self.assertNotIn("plate_info", result[1]) + self.assertNotIn("plate_info", result[2]) + + def test_add_plate_info_position_fails_with_invalid_value_among_blanks(self): + df = pd.DataFrame( + { + "specimen_name": ["sample1", "sample2"], + "plate_position": [None, "K10"], + } + ) + with self.assertRaises(ValueError) as context: + add_plate_info( + None, + None, + None, + "plate_position", + self.small_json_example, + df, + "specimen_name", + ) + self.assertEqual( + "Values in 'plate_position' must start with a single letter A-H/a-h followed by number 1-12.", + str(context.exception), + ) + def test_add_parasite_density_info_single_value(self): df = pd.DataFrame( {