Skip to content

Commit af284e7

Browse files
feat: Cytiva Biacore T200 control - add unread data (#1120)
<img width="781" height="147" alt="image" src="https://github.com/user-attachments/assets/f23f461d-b4fd-4c12-948c-b5e888e79760" /> --------- Co-authored-by: Nathan Stender <nathan.stender@benchling.com>
1 parent da785a7 commit af284e7

13 files changed

Lines changed: 4467 additions & 674 deletions

File tree

src/allotropy/allotrope/schema_mappers/adm/binding_affinity_analyzer/benchling/_2024/_12/binding_affinity_analyzer.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class MeasurementType(Enum):
5656
class DeviceDocument:
5757
device_type: str
5858
device_identifier: str
59+
device_custom_info: DictType | None = None
5960

6061

6162
@dataclass(frozen=True)
@@ -79,6 +80,7 @@ class Metadata:
7980
lot_number: str | None = None
8081
sensor_chip_custom_info: DictType | None = None
8182
data_system_custom_info: DictType | None = None
83+
device_system_custom_info: DictType | None = None
8284

8385

8486
@dataclass(frozen=True)
@@ -172,23 +174,29 @@ def map_model(self, data: Data) -> Model:
172174
),
173175
data.metadata.data_system_custom_info,
174176
),
175-
device_system_document=DeviceSystemDocument(
176-
device_identifier=data.metadata.device_identifier,
177-
model_number=data.metadata.model_number,
178-
brand_name=data.metadata.brand_name,
179-
product_manufacturer=data.metadata.product_manufacturer,
180-
equipment_serial_number=data.metadata.equipment_serial_number,
181-
device_document=(
182-
[
183-
DeviceDocumentItem(
184-
device_type=device_document_item.device_type,
185-
device_identifier=device_document_item.device_identifier,
186-
)
187-
for device_document_item in data.metadata.device_document
188-
]
189-
if data.metadata.device_document
190-
else None
177+
device_system_document=add_custom_information_document(
178+
DeviceSystemDocument(
179+
device_identifier=data.metadata.device_identifier,
180+
model_number=data.metadata.model_number,
181+
brand_name=data.metadata.brand_name,
182+
product_manufacturer=data.metadata.product_manufacturer,
183+
equipment_serial_number=data.metadata.equipment_serial_number,
184+
device_document=(
185+
[
186+
add_custom_information_document(
187+
DeviceDocumentItem(
188+
device_type=device_document_item.device_type,
189+
device_identifier=device_document_item.device_identifier,
190+
),
191+
custom_info_doc=device_document_item.device_custom_info,
192+
)
193+
for device_document_item in data.metadata.device_document
194+
]
195+
if data.metadata.device_document
196+
else None
197+
),
191198
),
199+
data.metadata.device_system_custom_info,
192200
),
193201
binding_affinity_analyzer_document=[
194202
self._get_technique_document(measurement_group, data.metadata)

src/allotropy/parsers/cytiva_biacore_t200_control/cytiva_biacore_t200_control_data_creator.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,44 @@ def _get_sensorgram_datacube(sensorgram_data: pd.DataFrame) -> DataCube:
6868

6969

7070
def _get_device_control_custom_info(data: Data) -> DictType:
71-
custom_ifo: dict[str, Any] = {
71+
application_template_details = data.application_template_details
72+
73+
custom_info: dict[str, Any] = {
7274
"number of flow cells": data.chip_data.number_of_flow_cells,
7375
"number of spots": data.chip_data.number_of_spots,
7476
"buffer volume": quantity_or_none(
7577
TQuantityValueMilliliter, data.run_metadata.buffer_volume
7678
),
7779
}
7880
if detection_setting := data.run_metadata.detection_setting:
79-
custom_ifo.update({detection_setting.key: detection_setting.value})
80-
return custom_ifo
81+
custom_info.update({detection_setting.key: detection_setting.value})
82+
83+
detection_info = application_template_details.get_nested("detection")
84+
custom_info.update(
85+
detection_info.get(key={"FlowCellSingle", "FlowCellDual", "FlowCellMulti"})
86+
)
87+
88+
temp_info = application_template_details.get_nested("RackTemperature")
89+
custom_info.update(
90+
temp_info.get_keys_as_dict(
91+
{
92+
"minimum operating temperature": (float, "min", None),
93+
"maximum operating temperature": (float, "max", None),
94+
}
95+
)
96+
)
97+
98+
system_preparations = application_template_details.get_nested("system_preparations")
99+
custom_info.update(
100+
system_preparations.get_keys_as_dict(
101+
{
102+
"analysis temperature": (float, "AnalTemp", None),
103+
"prime": (bool, "Prime", None),
104+
"normalize": (bool, "Normalize", None),
105+
}
106+
)
107+
)
108+
return custom_info
81109

82110

83111
def create_metadata(data: Data, named_file_contents: NamedFileContents) -> Metadata:
@@ -103,15 +131,19 @@ def create_metadata(data: Data, named_file_contents: NamedFileContents) -> Metad
103131
lot_number=chip_data.lot_number,
104132
sensor_chip_identifier=chip_data.sensor_chip_identifier,
105133
device_document=[
106-
DeviceDocument(device.type_, device.identifier)
134+
DeviceDocument(device.type_, device.identifier, device.custom_info)
107135
for device in run_metadata.devices
108136
],
109137
sensor_chip_custom_info=chip_data.custom_info,
138+
data_system_custom_info=system_information.data_system_custom_info,
139+
device_system_custom_info=system_information.device_system_custom_info,
110140
)
111141

112142

113143
def create_measurements(
114-
measurements_data: list[MeasurementData], device_control_custom_info: DictType
144+
measurements_data: list[MeasurementData],
145+
device_control_custom_info: DictType,
146+
data: Data,
115147
) -> list[Measurement]:
116148
return [
117149
Measurement(
@@ -129,13 +161,25 @@ def create_measurements(
129161
flow_rate=measurement.flow_rate,
130162
contact_time=measurement.contact_time,
131163
dilution=measurement.dilution,
132-
device_control_custom_info=device_control_custom_info,
164+
device_control_custom_info=dict(
165+
device_control_custom_info
166+
), # copy to avoid modifying the original
133167
)
134168
],
135169
sample_custom_info={
170+
**data.application_template_details.get_nested(
171+
"racks"
172+
).get_keys_as_dict(
173+
{
174+
"Rack1": (str, "_Rack1", None),
175+
"Rack2": (str, "_Rack2", None),
176+
"Lock Positions": (bool, "_LockPositions", None),
177+
}
178+
),
136179
"molecular weight": quantity_or_none(
137180
TQuantityValueDalton, measurement.molecular_weight
138-
)
181+
),
182+
**measurement.sample_custom_info,
139183
},
140184
sensorgram_data_cube=_get_sensorgram_datacube(measurement.sensorgram_data),
141185
report_point_data=(
@@ -168,7 +212,7 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]:
168212
MeasurementGroup(
169213
measurement_time=system_information.measurement_time,
170214
measurements=create_measurements(
171-
measurements_data, device_control_custom_info
215+
measurements_data, device_control_custom_info, data
172216
),
173217
experiment_type=system_information.experiment_type,
174218
analytical_method_identifier=system_information.analytical_method_identifier,
@@ -181,6 +225,10 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]:
181225
TQuantityValueHertz,
182226
try_float_or_none(data.run_metadata.data_collection_rate),
183227
),
228+
**data.application_template_details.get_nested(
229+
"properties"
230+
).get_keys_as_dict({"Run Type": (str, "TypeName", None)}),
231+
**system_information.measurement_aggregate_custom_info,
184232
},
185233
)
186234
for measurements_data in data.sample_data.measurements.values()

src/allotropy/parsers/cytiva_biacore_t200_control/cytiva_biacore_t200_control_parser.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import json
2+
from pathlib import Path, PureWindowsPath
3+
import warnings
4+
15
from allotropy.allotrope.models.adm.binding_affinity_analyzer.wd._2024._12.binding_affinity_analyzer import (
26
Model,
37
)
@@ -30,9 +34,26 @@ class CytivaBiacoreT200ControlParser(VendorParser[MapperData, Model]):
3034
SCHEMA_MAPPER = Mapper
3135

3236
def create_data(self, named_file_contents: NamedFileContents) -> MapperData:
33-
data = Data.create(DictData(decode_data(named_file_contents)))
34-
return MapperData(
37+
base_data = DictData(decode_data(named_file_contents))
38+
data = Data.create(base_data)
39+
mapper_data = MapperData(
3540
metadata=create_metadata(data, named_file_contents),
3641
measurement_groups=create_measurement_groups(data),
3742
calculated_data=create_calculated_data(data),
3843
)
44+
try:
45+
unread = base_data.get_unread_deep()
46+
original_path = named_file_contents.original_file_path
47+
# Derive file stem robustly for both POSIX and Windows-style paths
48+
stem = (
49+
PureWindowsPath(original_path).stem
50+
if "\\" in original_path and "/" not in original_path
51+
else Path(original_path).stem
52+
)
53+
out_name = f"{stem}data_unread_2.json"
54+
with open(out_name, "w", encoding="utf-8") as f:
55+
json.dump(unread, f, ensure_ascii=False, indent=2)
56+
except Exception as e:
57+
# Best-effort debug artifact; do not break parsing on failure
58+
warnings.warn(f"Failed to write unread debug file: {e!s}", stacklevel=1)
59+
return mapper_data

0 commit comments

Comments
 (0)