Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 28 additions & 19 deletions src/pmotools/pmo_builder/metatable_to_pmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
75 changes: 75 additions & 0 deletions tests/test_pmo_builder/test_metatable_to_pmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down
Loading