From ecd5c87368e66f6cf03f84bf5818132809a8047c Mon Sep 17 00:00:00 2001 From: Joshua Hernandez Date: Thu, 4 Sep 2025 10:57:35 -0500 Subject: [PATCH 1/2] add unread data to plate reader asm --- .../softmax_pro_data_creator.py | 38 ++++++++++++++++++- .../softmax_pro_structure.py | 13 +++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_data_creator.py b/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_data_creator.py index 07dda53c7d..ffd5f23e79 100644 --- a/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_data_creator.py +++ b/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_data_creator.py @@ -249,7 +249,10 @@ def _create_measurements(plate_block: PlateBlock, position: str) -> list[Measure def _create_measurement_group( - plate_block: PlateBlock, position: str, date_last_saved: str | None + plate_block: PlateBlock, + position: str, + date_last_saved: str | None, + structure_data: StructureData, ) -> MeasurementGroup | None: if not (measurements := _create_measurements(plate_block, position)): @@ -260,10 +263,41 @@ def _create_measurement_group( delta = datetime.timedelta(seconds=plate_block.header.read_time or 0) measurement_time = (parser.parse(date_last_saved) + delta).isoformat() + # Gather unread group fields for this plate/position + unread_rows: list[dict[str, float | str | None]] = [] + single_plate_name: str | None = None + if len(structure_data.block_list.plate_blocks) == 1: + single_plate_name = next(iter(structure_data.block_list.plate_blocks.keys())) + + for group_block in structure_data.block_list.group_blocks: + for group_sample_data in group_block.group_data.sample_data: + for ( + plate_key, + positions_map, + ) in group_sample_data.unread_by_plate_and_position.items(): + effective_plate = ( + plate_key if plate_key is not None else single_plate_name + ) + # filters out unread entries that do not belong to the current plate + if effective_plate != plate_block.header.name: + continue + if position in positions_map: + unread_rows.extend(positions_map[position]) + + # Flatten unread rows into a single custom_info dict + custom_info = None + if unread_rows: + merged = {} + for row_dict in unread_rows: + # Only include keys with non-None values + merged.update({k: v for k, v in row_dict.items() if v is not None}) + custom_info = merged + return MeasurementGroup( measurements=measurements, plate_well_count=plate_block.header.num_wells, measurement_time=measurement_time, + custom_info=custom_info, ) @@ -274,7 +308,7 @@ def create_measurement_groups(data: StructureData) -> list[MeasurementGroup]: for position in plate_block.iter_wells() if ( measurement_group := _create_measurement_group( - plate_block, position, data.date_last_saved + plate_block, position, data.date_last_saved, data ) ) ] diff --git a/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py b/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py index 16cf476c28..5f24bf5858 100644 --- a/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py +++ b/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py @@ -128,6 +128,10 @@ class GroupDataElement: class GroupSampleData: identifier: str data_elements: list[GroupDataElement] + # Capture unread fields per plate and well position. The None key is used when plate is unspecified. + unread_by_plate_and_position: dict[ + str | None, dict[str, list[dict[str, float | str | None]]] + ] @classmethod def create(cls, data: pd.DataFrame, calc_data_cols: list[str]) -> GroupSampleData: @@ -141,6 +145,9 @@ def create(cls, data: pd.DataFrame, calc_data_cols: list[str]) -> GroupSampleDat data_elements: dict[str, list[GroupDataElement]] = { column: [] for column in data_columns } + unread_by_plate_and_position: dict[ + str | None, dict[str, list[dict[str, float | str | None]]] + ] = {} for row in row_data: row_results = cls._get_row_results(row, data_columns) position = row[str, ["Well", "Wells"]] @@ -170,12 +177,18 @@ def create(cls, data: pd.DataFrame, calc_data_cols: list[str]) -> GroupSampleDat ) elif data_elements[column]: data_elements[column][-1].positions.append(position) + row.mark_read("Sample") + unread = row.get_unread() + if unread: + by_position = unread_by_plate_and_position.setdefault(plate, {}) + by_position.setdefault(str(position), []).append(unread) return GroupSampleData( identifier=identifier, data_elements=[ elem for elements in data_elements.values() for elem in elements ], + unread_by_plate_and_position=unread_by_plate_and_position, ) @classmethod From f4694a6938ee01e567e398b3bdcd9ff0d4138bd9 Mon Sep 17 00:00:00 2001 From: Joshua Hernandez Date: Mon, 15 Sep 2025 09:42:51 -0500 Subject: [PATCH 2/2] move skip to get unread call --- .../parsers/moldev_softmax_pro/softmax_pro_structure.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py b/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py index 5f24bf5858..b81c75e5d1 100644 --- a/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py +++ b/src/allotropy/parsers/moldev_softmax_pro/softmax_pro_structure.py @@ -177,8 +177,7 @@ def create(cls, data: pd.DataFrame, calc_data_cols: list[str]) -> GroupSampleDat ) elif data_elements[column]: data_elements[column][-1].positions.append(position) - row.mark_read("Sample") - unread = row.get_unread() + unread = row.get_unread(skip={"Sample"}) if unread: by_position = unread_by_plate_and_position.setdefault(plate, {}) by_position.setdefault(str(position), []).append(unread)