Skip to content

Commit ddbfa62

Browse files
feat: Migrate beckman_pharmspec to use SeriesData.get_unread (#1081)
1 parent 0849e03 commit ddbfa62

9 files changed

Lines changed: 108 additions & 32 deletions

File tree

src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/rec/_2024/_09/solution_analyzer.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class DistributionDocument:
7575
differential_particle_density: float
7676
differential_count: float
7777
distribution_identifier: str
78+
custom_info: dict[str, Any] | None = None
7879

7980

8081
@dataclass(frozen=True)
@@ -122,6 +123,9 @@ class Measurement:
122123

123124
# Errors
124125
errors: list[Error] | None = None
126+
custom_info: dict[str, Any] | None = None
127+
device_control_custom_info: dict[str, Any] | None = None
128+
sample_custom_info: dict[str, Any] | None = None
125129

126130

127131
@dataclass(frozen=True)
@@ -156,6 +160,7 @@ class Metadata:
156160
detector_view_volume: float | None = None
157161
repetition_setting: int | None = None
158162
sample_volume_setting: float | None = None
163+
custom_info: dict[str, Any] | None = None
159164

160165

161166
@dataclass(frozen=True)
@@ -274,7 +279,7 @@ def _get_measurement_document_item(
274279
metadata.sample_volume_setting,
275280
),
276281
),
277-
None,
282+
measurement.device_control_custom_info,
278283
),
279284
]
280285
),
@@ -315,7 +320,7 @@ def _get_measurement_document_item(
315320
TQuantityValueMilliOsmolesPerKilogram, measurement.osmolality
316321
),
317322
),
318-
None,
323+
measurement.custom_info,
319324
)
320325

321326
def _get_sample_document(self, measurement: Measurement) -> SampleDocument:
@@ -325,7 +330,7 @@ def _get_sample_document(self, measurement: Measurement) -> SampleDocument:
325330
batch_identifier=measurement.batch_identifier,
326331
description=measurement.description,
327332
),
328-
None,
333+
measurement.sample_custom_info,
329334
)
330335

331336
def _create_analyte_document(self, analyte: Analyte) -> AnalyteDocument:
@@ -410,23 +415,26 @@ def _create_processed_data_document(
410415
else None,
411416
distribution_aggregate_document=DistributionAggregateDocument(
412417
distribution_document=[
413-
DistributionDocumentItem(
414-
distribution_identifier=distribution.distribution_identifier,
415-
particle_size=TQuantityValueMicrometer(
416-
value=distribution.particle_size
417-
),
418-
cumulative_count=TQuantityValueUnitless(
419-
value=distribution.cumulative_count
420-
),
421-
cumulative_particle_density=TQuantityValueCountsPerMilliliter(
422-
value=distribution.cumulative_particle_density
423-
),
424-
differential_particle_density=TQuantityValueCountsPerMilliliter(
425-
value=distribution.differential_particle_density
426-
),
427-
differential_count=TQuantityValueUnitless(
428-
value=distribution.differential_count
418+
add_custom_information_document(
419+
DistributionDocumentItem(
420+
distribution_identifier=distribution.distribution_identifier,
421+
particle_size=TQuantityValueMicrometer(
422+
value=distribution.particle_size
423+
),
424+
cumulative_count=TQuantityValueUnitless(
425+
value=distribution.cumulative_count
426+
),
427+
cumulative_particle_density=TQuantityValueCountsPerMilliliter(
428+
value=distribution.cumulative_particle_density
429+
),
430+
differential_particle_density=TQuantityValueCountsPerMilliliter(
431+
value=distribution.differential_particle_density
432+
),
433+
differential_count=TQuantityValueUnitless(
434+
value=distribution.differential_count
435+
),
429436
),
437+
distribution.custom_info,
430438
)
431439
for distribution in measurement.distribution_documents
432440
]

src/allotropy/parsers/beckman_pharmspec/beckman_pharmspec_reader.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,23 @@ def __init__(self, named_file_contents: NamedFileContents) -> None:
3737
raw_header = df.loc[: start - 1].T
3838
header_data = pd.concat([raw_header.loc[2], raw_header.loc[5]])
3939
header_columns = pd.concat([raw_header.loc[0], raw_header.loc[3]])
40+
41+
# Store the software version string before filtering (it's usually at index 0)
42+
software_version_string = (
43+
str(header_data.iloc[0]) if len(header_data) > 0 else "Unknown"
44+
)
45+
46+
# Filter out nan values from header_columns to avoid nan keys in the Series
47+
valid_mask = ~pd.isna(header_columns)
48+
header_data = header_data[valid_mask]
49+
header_columns = header_columns[valid_mask]
50+
4051
header_data.index = pd.Index(header_columns)
41-
self.header = SeriesData(header_data)
52+
53+
# Create a SeriesData with the software version string preserved
54+
series_data = SeriesData(header_data)
55+
series_data._software_version_string = software_version_string # type: ignore
56+
self.header = series_data
4257

4358
data = df.loc[start:end]
4459
data = parse_header_row(data)

src/allotropy/parsers/beckman_pharmspec/beckman_pharmspec_structure.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import math
66
from pathlib import Path
77
import re
8+
from typing import Any
89

910
import pandas as pd
1011

@@ -46,6 +47,7 @@ def _create_processed_data(data: SeriesData) -> DistributionDocument:
4647
float, "Differential Counts/mL", NEGATIVE_ZERO
4748
),
4849
differential_count=data.get(float, "Differential Count", NEGATIVE_ZERO),
50+
custom_info=data.get_unread(skip={"Run No.", "nan"}),
4951
)
5052

5153

@@ -83,9 +85,23 @@ class Header:
8385
analyst: str
8486
equipment_serial_number: str
8587
software_version: str
88+
custom_info: dict[str, Any]
8689

8790
@staticmethod
88-
def _get_software_version_report_string(report_string: str) -> str:
91+
def _get_software_version_report_string(data: SeriesData | str) -> str:
92+
# Handle both SeriesData objects and string inputs (for unit tests)
93+
if isinstance(data, str):
94+
report_string = data
95+
elif hasattr(data, "_software_version_string"):
96+
# Use the preserved software version string from the reader
97+
report_string = data._software_version_string
98+
else:
99+
# Fallback to the old method (for backward compatibility)
100+
report_string = (
101+
str(data.series.iloc[0]) if len(data.series) > 0 else "Unknown"
102+
)
103+
104+
# Extract version from the report string
89105
match = re.search(r"v(\d+(?:\.\d+)?(?:\.\d+)?)", report_string)
90106
if match:
91107
return match.group(1)
@@ -102,10 +118,9 @@ def create(data: SeriesData) -> Header:
102118
sample_identifier=data[str, "Probe"],
103119
dilution_factor_setting=data[float, "Dilution Factor"],
104120
analyst=data[str, "Operator Name"],
105-
software_version=Header._get_software_version_report_string(
106-
data.series.iloc[0]
107-
),
121+
software_version=Header._get_software_version_report_string(data),
108122
equipment_serial_number=data[str, "Sensor Serial Number"],
123+
custom_info=data.get_unread(),
109124
)
110125

111126

@@ -124,6 +139,7 @@ def create_metadata(header: Header, file_path: str) -> Metadata:
124139
repetition_setting=header.repetition_setting,
125140
sample_volume_setting=header.sample_volume_setting,
126141
device_type=DEVICE_TYPE,
142+
custom_info=header.custom_info,
127143
)
128144

129145

@@ -152,6 +168,25 @@ def create_measurement_groups(
152168
if key in REQUIRED_DISTRIBUTION_DOCUMENT_KEYS
153169
and is_negative_zero(feature.__dict__[key])
154170
],
171+
device_control_custom_info={
172+
"model number": header.custom_info.pop("Sensor Model", None),
173+
},
174+
sample_custom_info={
175+
"batch identifier": header.custom_info.pop("Batch-Nr", None)
176+
if header.custom_info.get("Batch-Nr", None) != "-"
177+
else None,
178+
},
179+
custom_info={
180+
"Ro-Nr": header.custom_info.pop("Ro-Nr", None)
181+
if header.custom_info.get("Ro-Nr", None) != "-"
182+
else None,
183+
"observation 1": header.custom_info.pop("Bemerkungen 1", None)
184+
if header.custom_info.get("Bemerkungen 1", None) != "-"
185+
else None,
186+
"observation 2": header.custom_info.pop("Bemerkungen 2", None)
187+
if header.custom_info.get("Bemerkungen 2", None) != "-"
188+
else None,
189+
},
155190
)
156191
for distribution in [x for x in distributions if not x.is_calculated]
157192
],
@@ -189,9 +224,9 @@ def create_calculated_data(
189224
for distribution in distributions
190225
for feature in distribution.features
191226
if distribution.is_calculated
192-
# Ignore "distribution_identifier" attribute, and skip empty or -0.0 values
227+
# Ignore "distribution_identifier" and "custom_info" attributes, and skip empty or -0.0 values
193228
for name, value in feature.__dict__.items()
194-
if name != "distribution_identifier"
229+
if name not in ("distribution_identifier", "custom_info")
195230
and value is not None
196231
and not is_negative_zero(value)
197232
]

tests/parsers/beckman_pharmspec/testdata/hiac_example_1.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"sample volume setting": {
2323
"value": 0.2,
2424
"unit": "mL"
25+
},
26+
"custom information document": {
27+
"model number": "HRLD150@10ml"
2528
}
2629
}
2730
]
@@ -725,7 +728,7 @@
725728
"file name": "hiac_example_1.xlsx",
726729
"UNC path": "tests/parsers/beckman_pharmspec/testdata/hiac_example_1.xlsx",
727730
"ASM converter name": "allotropy_beckman_coulter_pharmspec",
728-
"ASM converter version": "0.1.69",
731+
"ASM converter version": "0.1.105",
729732
"software name": "PharmSpec",
730733
"software version": "3.0"
731734
},

tests/parsers/beckman_pharmspec/testdata/hiac_example_2.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"sample volume setting": {
2323
"value": 0.2,
2424
"unit": "mL"
25+
},
26+
"custom information document": {
27+
"model number": "HRLD150@10ml"
2528
}
2629
}
2730
]
@@ -987,7 +990,7 @@
987990
"file name": "hiac_example_2.xlsx",
988991
"UNC path": "tests/parsers/beckman_pharmspec/testdata/hiac_example_2.xlsx",
989992
"ASM converter name": "allotropy_beckman_coulter_pharmspec",
990-
"ASM converter version": "0.1.69",
993+
"ASM converter version": "0.1.105",
991994
"software name": "PharmSpec",
992995
"software version": "3.0"
993996
},

tests/parsers/beckman_pharmspec/testdata/hiac_example_3.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"sample volume setting": {
2323
"value": 0.2,
2424
"unit": "mL"
25+
},
26+
"custom information document": {
27+
"model number": "HRLD150@10ml"
2528
}
2629
}
2730
]
@@ -463,7 +466,7 @@
463466
"file name": "hiac_example_3.xlsx",
464467
"UNC path": "tests/parsers/beckman_pharmspec/testdata/hiac_example_3.xlsx",
465468
"ASM converter name": "allotropy_beckman_coulter_pharmspec",
466-
"ASM converter version": "0.1.69",
469+
"ASM converter version": "0.1.105",
467470
"software name": "PharmSpec",
468471
"software version": "3.0"
469472
},

tests/parsers/beckman_pharmspec/testdata/hiac_example_4.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"sample volume setting": {
2323
"value": 0.2,
2424
"unit": "mL"
25+
},
26+
"custom information document": {
27+
"model number": "HRLD150@10ml"
2528
}
2629
}
2730
]
@@ -219,7 +222,7 @@
219222
"file name": "hiac_example_4.xlsx",
220223
"UNC path": "tests/parsers/beckman_pharmspec/testdata/hiac_example_4.xlsx",
221224
"ASM converter name": "allotropy_beckman_coulter_pharmspec",
222-
"ASM converter version": "0.1.69",
225+
"ASM converter version": "0.1.105",
223226
"software name": "PharmSpec",
224227
"software version": "3.0"
225228
},

tests/parsers/beckman_pharmspec/testdata/hiac_example_5.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"sample volume setting": {
2323
"value": 0.2,
2424
"unit": "mL"
25+
},
26+
"custom information document": {
27+
"model number": "HRLD150@10ml"
2528
}
2629
}
2730
]
@@ -623,7 +626,7 @@
623626
"file name": "hiac_example_5.xlsx",
624627
"UNC path": "tests/parsers/beckman_pharmspec/testdata/hiac_example_5.xlsx",
625628
"ASM converter name": "allotropy_beckman_coulter_pharmspec",
626-
"ASM converter version": "0.1.69",
629+
"ASM converter version": "0.1.105",
627630
"software name": "PharmSpec",
628631
"software version": "3.0"
629632
},

tests/parsers/beckman_pharmspec/testdata/pharmspec_example_01.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"sample volume setting": {
2323
"value": 0.2,
2424
"unit": "mL"
25+
},
26+
"custom information document": {
27+
"model number": "HRLD150@10ml"
2528
}
2629
}
2730
]
@@ -725,7 +728,7 @@
725728
"file name": "pharmspec_example_01.xlsx",
726729
"UNC path": "tests/parsers/beckman_pharmspec/testdata/pharmspec_example_01.xlsx",
727730
"ASM converter name": "allotropy_beckman_coulter_pharmspec",
728-
"ASM converter version": "0.1.69",
731+
"ASM converter version": "0.1.105",
729732
"software name": "PharmSpec",
730733
"software version": "3.0"
731734
},

0 commit comments

Comments
 (0)