Skip to content

Commit cf67c75

Browse files
authored
feat: Migrate cytiva_unicorn to use get_unread (#1051)
Code for [BNCH-115083](https://jira.benchling.team/browse/BNCH-115083)
1 parent 23c36a2 commit cf67c75

13 files changed

Lines changed: 865 additions & 162 deletions

File tree

src/allotropy/allotrope/schema_mappers/adm/liquid_chromatography/benchling/_2023/_09/liquid_chromatography.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ class Fraction:
146146
field_type: str | None = None
147147
retention_time: float | None = None
148148
retention_volume: float | None = None
149+
custom_info: dict[str, Any] | None = None
149150

150151

151152
@dataclass(frozen=True)
@@ -155,6 +156,7 @@ class Log:
155156
log_entry: str | None = None
156157
retention_time: float | None = None
157158
retention_volume: float | None = None
159+
custom_info: dict[str, Any] | None = None
158160

159161

160162
@dataclass(frozen=True)
@@ -236,6 +238,7 @@ class Measurement:
236238
injection_custom_info: dict[str, Any] | None = None
237239
column_custom_info: dict[str, Any] | None = None
238240
measurement_custom_info: dict[str, Any] | None = None
241+
processed_data_custom_info: dict[str, Any] | None = None
239242

240243

241244
@dataclass(frozen=True)
@@ -361,11 +364,9 @@ def _get_measurement_document_item(
361364
for device_control_doc in measurement.device_control_docs
362365
]
363366
),
364-
chromatogram_data_cube=(
365-
get_data_cube(
366-
measurement.chromatogram_data_cube,
367-
ChromatogramDataCube,
368-
)
367+
chromatogram_data_cube=get_data_cube(
368+
measurement.chromatogram_data_cube,
369+
ChromatogramDataCube,
369370
),
370371
),
371372
measurement.measurement_custom_info,
@@ -547,17 +548,27 @@ def build_data_document_item(
547548
if pdd and get_data_processing_documents(pdd)
548549
else None
549550
)
550-
item = ProcessedDataDocumentItem(
551-
chromatogram_data_cube=get_data_cube(
552-
measurement.processed_data_chromatogram_data_cube, TDatacube
553-
),
554-
derived_column_pressure_data_cube=get_data_cube(
555-
measurement.derived_column_pressure_data_cube,
556-
DerivedColumnPressureDataCube,
551+
item = add_custom_information_document(
552+
ProcessedDataDocumentItem(
553+
chromatogram_data_cube=add_custom_information_document(
554+
get_data_cube(
555+
measurement.processed_data_chromatogram_data_cube, TDatacube
556+
),
557+
getattr(
558+
measurement.processed_data_chromatogram_data_cube,
559+
"custom_info",
560+
None,
561+
),
562+
),
563+
derived_column_pressure_data_cube=get_data_cube(
564+
measurement.derived_column_pressure_data_cube,
565+
DerivedColumnPressureDataCube,
566+
),
567+
processed_data_identifier=measurement.processed_data_identifier,
568+
peak_list=build_peak_list(),
569+
data_processing_aggregate_document=data_processing,
557570
),
558-
processed_data_identifier=measurement.processed_data_identifier,
559-
peak_list=build_peak_list(),
560-
data_processing_aggregate_document=data_processing,
571+
measurement.processed_data_custom_info,
561572
)
562573
return (
563574
add_custom_information_document(item, pdd.custom_info) if pdd else item
@@ -670,7 +681,7 @@ def _get_fraction_aggregate_document(
670681
)
671682

672683
def _get_fraction_document(self, fraction_doc: Fraction) -> FractionDocumentItem:
673-
return FractionDocumentItem(
684+
fraction_doc_item = FractionDocumentItem(
674685
index=fraction_doc.index,
675686
fraction_role=fraction_doc.fraction_role,
676687
field_type=fraction_doc.field_type,
@@ -682,6 +693,10 @@ def _get_fraction_document(self, fraction_doc: Fraction) -> FractionDocumentItem
682693
),
683694
)
684695

696+
return add_custom_information_document(
697+
fraction_doc_item, fraction_doc.custom_info
698+
)
699+
685700
def _get_log_aggregate_document(
686701
self, logs: list[Log] | None
687702
) -> LogAggregateDocument | None:
@@ -693,7 +708,7 @@ def _get_log_aggregate_document(
693708
)
694709

695710
def _get_log_document(self, log_doc: Log) -> LogDocumentItem:
696-
return LogDocumentItem(
711+
log_doc_item = LogDocumentItem(
697712
index=log_doc.index,
698713
method_identifier=log_doc.method_identifier,
699714
log_entry=log_doc.log_entry,
@@ -704,3 +719,4 @@ def _get_log_document(self, log_doc: Log) -> LogDocumentItem:
704719
TQuantityValueMilliliter, log_doc.retention_volume
705720
),
706721
)
722+
return add_custom_information_document(log_doc_item, log_doc.custom_info)

src/allotropy/parsers/cytiva_unicorn/structure/measurement_group.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,34 @@ def create_measurement_groups(
6363
TemperatureMeasurement.create_or_none(handler, elements, static_docs),
6464
]
6565

66-
return [
66+
measurement_group = [
6767
MeasurementGroup(
6868
measurements=[measurement for measurement in measurements if measurement],
6969
fractions=create_fractions(event_curves) if event_curves else None,
7070
logs=create_logs(event_curves) if event_curves else None,
71+
measurement_aggregate_custom_info={},
7172
)
7273
]
7374

75+
chrom_1.mark_read({"element:TimeUnit", "element:VolumeUnit", "element:IsReadonly"})
76+
custom_info = chrom_1.get_unread()
77+
78+
if measurement_group[0].measurement_aggregate_custom_info is not None:
79+
custom_info.pop("UNICORNVersion")
80+
custom_info.update(
81+
{
82+
"RunIndex": results.get_sub_text_or_none("RunIndex"),
83+
"RunType": results.get_sub_text_or_none("RunType"),
84+
"Name": results.get_sub_text_or_none("Name"),
85+
}
86+
)
87+
custom_info_sorted = dict(sorted(custom_info.items()))
88+
measurement_group[0].measurement_aggregate_custom_info.update(
89+
custom_info_sorted
90+
)
91+
92+
return measurement_group
93+
7494

7595
def create_fractions(event_curves: StrictXmlElement) -> list[Fraction]:
7696
event_curve_fraction = None
@@ -94,6 +114,7 @@ def create_fractions(event_curves: StrictXmlElement) -> list[Fraction]:
94114
else t_min * 60
95115
),
96116
retention_volume=event.get_sub_float_or_none("EventVolume"),
117+
custom_info=event.get_unread(),
97118
)
98119
for idx, event in enumerate(events.findall("Event"), start=1)
99120
if event.get_attr_or_none("EventType") in ["Fraction", "Method"]
@@ -144,6 +165,7 @@ def create_logs(event_curves: StrictXmlElement) -> list[Log]:
144165
else t_min * 60
145166
),
146167
retention_volume=event.get_sub_float_or_none("EventVolume"),
168+
custom_info=event.get_unread(),
147169
)
148170
)
149171

src/allotropy/parsers/cytiva_unicorn/structure/measurements/absorbance.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def create_or_none(
5757
elements: list[StrictXmlElement],
5858
static_docs: StaticDocs,
5959
) -> UnicornMeasurement:
60-
return cls.get_measurement(
60+
measurement = cls.get_measurement(
6161
static_docs=static_docs,
6262
chromatogram_data_cube=assert_not_none(
6363
cls.get_data_cube_or_none(
@@ -75,10 +75,15 @@ def create_or_none(
7575
DeviceControlDoc(
7676
device_type=DEVICE_TYPE,
7777
start_time=static_docs.start_time,
78+
device_control_custom_info={},
7879
)
7980
],
8081
peaks=cls.get_peaks(handler),
8182
)
83+
cls.add_custom_info(
84+
measurement, cls.filter_curve_or_none(elements, cls.get_curve_regex())
85+
)
86+
return measurement
8287

8388

8489
class AbsorbanceMeasurement1(AbsorbanceMeasurement):
@@ -150,8 +155,9 @@ def get_peaks_custom_info(
150155
def get_peaks(cls, handler: UnicornZipHandler) -> list[Peak]:
151156
chrom_1 = handler.get_chrom_1()
152157
peaks = chrom_1.recursive_find_or_none(["PeakTables", "PeakTable", "Peaks"])
153-
return [
154-
Peak(
158+
output = []
159+
for idx, peak in enumerate(peaks.findall("Peak") if peaks else [], start=1):
160+
peak_obj = Peak(
155161
identifier=random_uuid_str(),
156162
index=f"Peak {idx}",
157163
end=peak.get_sub_float_or_none("EndPeakRetention"),
@@ -172,8 +178,10 @@ def get_peaks(cls, handler: UnicornZipHandler) -> list[Peak]:
172178
chromatographic_asymmetry=peak.get_sub_float_or_none("Assymetry"),
173179
custom_info=cls.get_peaks_custom_info(peak),
174180
)
175-
for idx, peak in enumerate(peaks.findall("Peak") if peaks else [], start=1)
176-
]
181+
if peak_obj.custom_info is not None:
182+
peak_obj.custom_info.update(peak.get_unread())
183+
output.append(peak_obj)
184+
return output
177185

178186

179187
class AbsorbanceMeasurement2(AbsorbanceMeasurement):

src/allotropy/parsers/cytiva_unicorn/structure/measurements/concentration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ def create_or_none(
4343
unit="%",
4444
),
4545
),
46+
device_control_custom_info={},
4647
),
4748
],
4849
)
50+
cls.add_custom_info(
51+
measurement, cls.filter_curve_or_none(elements, r"^Conc B$")
52+
)
4953
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
5054

5155
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/conductivity.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ def create_or_none(
5656
DeviceControlDoc(
5757
device_type=DEVICE_TYPE,
5858
start_time=static_docs.start_time,
59+
device_control_custom_info={},
5960
)
6061
],
6162
)
63+
cls.add_custom_info(measurement, cls.filter_curve_or_none(elements, r"^Cond$"))
6264
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
6365

6466
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/flow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def create_device_control(
3636
start_time=start_time,
3737
sample_flow_data_cube=sample_flow_data_cube,
3838
system_flow_data_cube=system_flow_data_cube,
39+
device_control_custom_info={},
3940
)
4041

4142
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/generic.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,66 @@
2727

2828

2929
class UnicornMeasurement(Measurement):
30+
@classmethod
31+
def add_custom_info(
32+
cls, measurement: Measurement, base_element: StrictXmlElement | None
33+
) -> None:
34+
if base_element:
35+
device_control_custom_info = {
36+
"ColumnVolume": base_element.get_sub_text_or_none("ColumnVolume"),
37+
"ScanInterval": base_element.get_sub_text_or_none("ScanInterval"),
38+
}
39+
40+
processed_data_custom_info = {
41+
"AmplitudePrecision": base_element.get_sub_text_or_none(
42+
"AmplitudePrecision"
43+
),
44+
"AmplitudeUnit": base_element.get_sub_text_or_none("AmplitudeUnit"),
45+
"ChromatogramID": base_element.get_sub_text_or_none("ChromatogramID"),
46+
"ChromatogramName": base_element.get_sub_text_or_none(
47+
"ChromatogramName"
48+
),
49+
"ChromatogramStartTime": base_element.get_sub_text_or_none(
50+
"ChromatogramStartTime"
51+
),
52+
"ChromatogramStartTimeUtcOffsetMinutes": base_element.get_sub_text_or_none(
53+
"ChromatogramStartTimeUtcOffsetMinutes"
54+
),
55+
"ColumnVolumeUnitName": base_element.get_sub_text_or_none(
56+
"ColumnVolumeUnitName"
57+
),
58+
"CurveDataType": base_element.get_sub_text_or_none("CurveDataType"),
59+
"CurveNumber": base_element.get_sub_text_or_none("CurveNumber"),
60+
"DistanceBetweenPoints": base_element.get_sub_text_or_none(
61+
"DistanceBetweenPoints"
62+
),
63+
"DistancetoStartPoints": base_element.get_sub_text_or_none(
64+
"DistancetoStartPoints"
65+
),
66+
"IsExternal": base_element.get_sub_text_or_none("IsExternal"),
67+
"IsOriginalData": base_element.get_sub_text_or_none("IsOriginalData"),
68+
"IsReadOnly": base_element.get_sub_text_or_none("IsReadOnly"),
69+
"IsoChroneType": base_element.get_sub_text_or_none("IsoChroneType"),
70+
"MethodStartTime": base_element.get_sub_text_or_none("MethodStartTime"),
71+
"MethodStartTimeUtcOffsetMinutes": base_element.get_sub_text_or_none(
72+
"MethodStartTimeUtcOffsetMinutes"
73+
),
74+
"TimeUnit": base_element.get_sub_text_or_none("TimeUnit"),
75+
"VolumeUnit": base_element.get_sub_text_or_none("VolumeUnit"),
76+
}
77+
if (
78+
measurement.device_control_docs[0].device_control_custom_info
79+
is not None
80+
):
81+
measurement.device_control_docs[0].device_control_custom_info.update(
82+
device_control_custom_info
83+
)
84+
if measurement.processed_data_custom_info is not None:
85+
processed_data_custom_info.update(base_element.get_unread())
86+
measurement.processed_data_custom_info.update(
87+
processed_data_custom_info
88+
)
89+
3090
@classmethod
3191
def filter_curve_or_none(
3292
cls, curve_elements: list[StrictXmlElement], pattern: str
@@ -61,13 +121,15 @@ def get_data_cube_or_none(
61121

62122
if data_cube_handler := cls.get_data_cube_handler_or_none(handler, curve):
63123
name_element = curve.find("Name")
124+
64125
if name := name_element.get_text_or_none():
65-
return create_data_cube(
126+
data_cube = create_data_cube(
66127
data_cube_handler,
67128
name,
68129
data_cube_component,
69130
transformation,
70131
)
132+
return data_cube
71133
return None
72134

73135
@classmethod
@@ -104,6 +166,7 @@ def get_measurement(
104166
"sample_volume_3": static_docs.sample_volume_3,
105167
},
106168
peaks=peaks,
169+
processed_data_custom_info={},
107170
)
108171

109172
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/ph.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ def create_or_none(
4343
DeviceControlDoc(
4444
device_type=DEVICE_TYPE,
4545
start_time=static_docs.start_time,
46+
device_control_custom_info={},
4647
)
4748
],
4849
)
50+
cls.add_custom_info(measurement, cls.filter_curve_or_none(elements, r"^pH$"))
4951
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
5052

5153
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/pressure.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,13 @@ def create_or_none(
7979
unit="MPa",
8080
),
8181
),
82+
device_control_custom_info={},
8283
),
8384
],
8485
)
86+
cls.add_custom_info(
87+
measurement, cls.filter_curve_or_none(elements, r"^DeltaC pressure$")
88+
)
8589
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
8690

8791
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/temperature.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ def create_or_none(
4343
unit="degC",
4444
),
4545
),
46+
device_control_custom_info={},
4647
),
4748
],
4849
)
50+
cls.add_custom_info(
51+
measurement, cls.filter_curve_or_none(elements, r"^Cond temp$")
52+
)
4953
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
5054

5155
@classmethod

0 commit comments

Comments
 (0)