Skip to content

Commit 2cdfe1e

Browse files
feat: Thermo Fisher Scientific NanoDrop 8000 - add unread data to asm (#1094)
<img width="1805" height="162" alt="image" src="https://github.com/user-attachments/assets/567064c5-c482-4c2d-b2f6-de45a7809d0b" />
1 parent cb5f80c commit 2cdfe1e

12 files changed

Lines changed: 2045 additions & 57 deletions

File tree

src/allotropy/allotrope/converter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"=": "_EQUALS_",
113113
"@": "_AT_",
114114
"'": "_QUOTE_",
115+
"*": "_ASTERISK_",
115116
",": "_COMMA_",
116117
"&": "_AMPERSAND_",
117118
# NOTE: this MUST be at the end, or it will break other key replacements.

src/allotropy/allotrope/models/shared/definitions/custom.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
PartsPerBillion,
5555
PartsPerMillion,
5656
Percent,
57+
PerMolarPerCentimeter,
5758
PerMolarPerSecond,
5859
PerSecond,
5960
PH,
@@ -610,6 +611,18 @@ class TNullableQuantityValuePartsPerMillion(PartsPerMillion, TNullableQuantityVa
610611
pass
611612

612613

614+
@dataclass(frozen=True, kw_only=True)
615+
class TQuantityValuePerMolarPerCentimeter(PerMolarPerCentimeter, TQuantityValue):
616+
pass
617+
618+
619+
@dataclass(frozen=True, kw_only=True)
620+
class TNullableQuantityValuePerMolarPerCentimeter(
621+
PerMolarPerCentimeter, TNullableQuantityValue
622+
):
623+
pass
624+
625+
613626
@dataclass(frozen=True, kw_only=True)
614627
class TQuantityValuePerMolarPerSecond(PerMolarPerSecond, TQuantityValue):
615628
pass

src/allotropy/allotrope/models/shared/definitions/units.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ class PartsPerMillion(HasUnit):
249249
unit: str = "ppm"
250250

251251

252+
@dataclass(frozen=True, kw_only=True)
253+
class PerMolarPerCentimeter(HasUnit):
254+
unit: str = "M-1cm-1"
255+
256+
252257
@dataclass(frozen=True, kw_only=True)
253258
class PerMolarPerSecond(HasUnit):
254259
unit: str = "M-1s-1"

src/allotropy/allotrope/schema_mappers/adm/spectrophotometry/benchling/_2023/_12/spectrophotometry.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
UltravioletAbsorbanceSpectrumDetectionMeasurementDocumentItems,
3030
)
3131
from allotropy.allotrope.models.shared.definitions.custom import (
32+
TQuantityValueKiloDalton,
3233
TQuantityValueMicroliter,
3334
TQuantityValueMilliAbsorbanceUnit,
35+
TQuantityValueNanogramPerMicroliter,
3436
TQuantityValueNanometer,
37+
TQuantityValuePerMolarPerCentimeter,
3538
TQuantityValueRelativeFluorescenceUnit,
3639
TQuantityValueUnitless,
3740
)
@@ -77,6 +80,7 @@ class ProcessedDataFeature:
7780
class ProcessedData:
7881
features: list[ProcessedDataFeature]
7982
identifier: str | None = None
83+
custom_info: dict[str, Any] | None = None
8084

8185

8286
@dataclass
@@ -102,6 +106,8 @@ class Measurement:
102106
original_sample_concentration_unit: str | None = None
103107
baseline_absorbance: float | None = None
104108
electronic_absorbance_reference_wavelength_setting: float | None = None
109+
cursor_position: float | None = None
110+
cursor_absorbance: float | None = None
105111

106112
# Measurements
107113
absorbance: JsonFloat | None = None
@@ -269,6 +275,14 @@ def _get_ultraviolet_absorbance_measurement_document(
269275
TQuantityValueMilliAbsorbanceUnit,
270276
measurement.baseline_absorbance,
271277
),
278+
"340 raw": quantity_or_none(
279+
TQuantityValueMilliAbsorbanceUnit,
280+
(measurement.custom_info or {}).get("340 raw"),
281+
),
282+
"Cursor Abs.": quantity_or_none(
283+
TQuantityValueMilliAbsorbanceUnit,
284+
measurement.cursor_absorbance,
285+
),
272286
}
273287
return add_custom_information_document(doc, custom_info_doc)
274288

@@ -341,6 +355,9 @@ def _get_device_control_custom_document(
341355
"dilution factor": quantity_or_none(
342356
TQuantityValueUnitless, measurement.dilution_factor_setting
343357
),
358+
"Cursor Pos.": quantity_or_none(
359+
TQuantityValueNanometer, measurement.cursor_position
360+
),
344361
}
345362
return (measurement.device_control_custom_info or {}) | custom_info
346363

@@ -363,14 +380,19 @@ def _get_sample_document(self, measurement: Measurement) -> SampleDocument:
363380
measurement.sample_custom_info["last read standards"]
364381
)
365382

383+
sample_custom_info = measurement.sample_custom_info or {}
384+
sample_custom_info["Mol. Wt. kda"] = quantity_or_none(
385+
TQuantityValueKiloDalton, sample_custom_info.get("Mol. Wt. kda")
386+
)
387+
366388
return add_custom_information_document(
367389
SampleDocument(
368390
sample_identifier=measurement.sample_identifier,
369391
batch_identifier=measurement.batch_identifier,
370392
location_identifier=measurement.location_identifier,
371393
well_plate_identifier=measurement.well_plate_identifier,
372394
),
373-
(measurement.sample_custom_info or {}) | custom_info_doc,
395+
sample_custom_info | custom_info_doc,
374396
)
375397

376398
def _get_processed_data_aggregate_document(
@@ -379,14 +401,38 @@ def _get_processed_data_aggregate_document(
379401
if not data:
380402
return None
381403

404+
# Build custom info for processed data with proper TQuantityValue wrappers
405+
processed_data_custom_info = {}
406+
if data.custom_info:
407+
processed_data_custom_info = {
408+
"E1%": quantity_or_none(
409+
TQuantityValueUnitless, data.custom_info.get("E1%")
410+
),
411+
"ext. coeff x10e3": quantity_or_none(
412+
TQuantityValuePerMolarPerCentimeter,
413+
data.custom_info.get("ext. coeff x10e3"),
414+
),
415+
"ext.c.": quantity_or_none(
416+
TQuantityValuePerMolarPerCentimeter,
417+
data.custom_info.get("ext.c. (l/(mol*cm))"),
418+
),
419+
"conc. factor": quantity_or_none(
420+
TQuantityValueNanogramPerMicroliter,
421+
data.custom_info.get("conc. factor (ng/ul)"),
422+
),
423+
}
424+
382425
return ProcessedDataAggregateDocument(
383426
processed_data_document=[
384-
ProcessedDataDocumentItem(
385-
# TODO(nstender): figure out how to limit possible classes from get_quantity_class for typing.
386-
mass_concentration=quantity_or_none(
387-
get_quantity_class(feature.unit), feature.result # type: ignore[arg-type]
427+
add_custom_information_document(
428+
ProcessedDataDocumentItem(
429+
# TODO(nstender): figure out how to limit possible classes from get_quantity_class for typing.
430+
mass_concentration=quantity_or_none(
431+
get_quantity_class(feature.unit), feature.result # type: ignore[arg-type]
432+
),
433+
processed_data_identifier=data.identifier,
388434
),
389-
processed_data_identifier=data.identifier,
435+
processed_data_custom_info,
390436
)
391437
for feature in data.features
392438
]

src/allotropy/allotrope/schemas/shared/definitions/custom.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,26 @@
959959
}
960960
]
961961
},
962+
"tQuantityValuePerMolarPerCentimeter": {
963+
"allOf": [
964+
{
965+
"$ref": "#/$defs/tQuantityValue"
966+
},
967+
{
968+
"$ref": "#/$defs/PerMolarPerCentimeter"
969+
}
970+
]
971+
},
972+
"tNullableQuantityValuePerMolarPerCentimeter": {
973+
"allOf": [
974+
{
975+
"$ref": "#/$defs/tNullableQuantityValue"
976+
},
977+
{
978+
"$ref": "#/$defs/PerMolarPerCentimeter"
979+
}
980+
]
981+
},
962982
"tQuantityValuePerMolarPerSecond": {
963983
"allOf": [
964984
{

src/allotropy/allotrope/schemas/shared/definitions/units.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,18 @@
575575
"unit"
576576
]
577577
},
578+
"PerMolarPerCentimeter": {
579+
"properties": {
580+
"unit": {
581+
"type": "string",
582+
"const": "M-1cm-1",
583+
"$asm.unit-iri": "http://purl.allotrope.org/ontology/qudt-ext/unit#PerMolarPerCentimeter"
584+
}
585+
},
586+
"required": [
587+
"unit"
588+
]
589+
},
578590
"PerMolarPerSecond": {
579591
"properties": {
580592
"unit": {

src/allotropy/parsers/thermo_fisher_nanodrop_8000/nanodrop_8000_structure.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ def create(data: SeriesData) -> SpectroscopyRow:
166166
)
167167
unit = data.get(str, "units")
168168

169+
# Read fields for Data Processing Document custom info
170+
processed_data_custom_info = {
171+
"E1%": data.get(float, "e 1%"),
172+
"ext. coeff x10e3": data.get(float, "ext. coeff x10e3"),
173+
"ext.c. (l/(mol*cm))": data.get(float, "ext.c. (l/(mol*cm))"),
174+
"conc. factor (ng/ul)": data.get(float, "conc. factor (ng/ul)"),
175+
}
176+
169177
measurements: list[Measurement] = []
170178
for wavelength, absorbance in absorbances.items():
171179
if absorbance is None:
@@ -190,11 +198,30 @@ def create(data: SeriesData) -> SpectroscopyRow:
190198
unit=unit,
191199
)
192200
],
201+
custom_info=processed_data_custom_info or None,
193202
)
194203
if mass_concentration_capture_wavelength == wavelength
195204
and mass_concentration
196205
and unit
197206
else None,
207+
cursor_position=data.get(float, "cursor pos."),
208+
cursor_absorbance=data.get(float, "cursor abs."),
209+
sample_custom_info={
210+
"Mol. Wt. kda": data.get(float, "mol. wt. kda"),
211+
},
212+
custom_info=data.get_unread(
213+
skip={
214+
"date",
215+
"time",
216+
"user id",
217+
"formula value 1",
218+
"formula name 1",
219+
"formula 1",
220+
"260/230",
221+
"260/280",
222+
"m.w. (g/mol)",
223+
}
224+
),
198225
)
199226
)
200227

0 commit comments

Comments
 (0)