diff --git a/src/allotropy/allotrope/schema_mappers/adm/binding_affinity_analyzer/benchling/_2024/_12/binding_affinity_analyzer.py b/src/allotropy/allotrope/schema_mappers/adm/binding_affinity_analyzer/benchling/_2024/_12/binding_affinity_analyzer.py index 4f6bddb2c1..368b31c3f6 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/binding_affinity_analyzer/benchling/_2024/_12/binding_affinity_analyzer.py +++ b/src/allotropy/allotrope/schema_mappers/adm/binding_affinity_analyzer/benchling/_2024/_12/binding_affinity_analyzer.py @@ -27,7 +27,6 @@ DataSourceDocumentItem, DataSystemDocument, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueDegreeCelsius, TQuantityValueMicroliterPerMinute, @@ -46,6 +45,9 @@ from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, ) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import assert_not_none, quantity_or_none from allotropy.types import DictType @@ -392,29 +394,10 @@ def _get_surface_plasmon_resonance_measurement_document( def _get_calculated_data_aggregate_document( self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.uuid, - calculated_data_name=calculated_data_item.name, - calculation_description=calculated_data_item.description, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=assert_not_none(calculated_data_item.unit), - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.reference.uuid, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) diff --git a/src/allotropy/allotrope/schema_mappers/adm/cell_counting/rec/_2024/_09/cell_counting.py b/src/allotropy/allotrope/schema_mappers/adm/cell_counting/rec/_2024/_09/cell_counting.py index 4ba3bbb5f7..56eedc43eb 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/cell_counting/rec/_2024/_09/cell_counting.py +++ b/src/allotropy/allotrope/schema_mappers/adm/cell_counting/rec/_2024/_09/cell_counting.py @@ -27,10 +27,6 @@ ImageAggregateDocument, ImageDocumentItem, ) -from allotropy.allotrope.models.shared.definitions.definitions import ( - JsonFloat, - TQuantityValue, -) from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueCell, TQuantityValueMicroliter, @@ -41,7 +37,12 @@ ) from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION -from allotropy.parsers.utils.calculated_data_documents.definition import DataSource +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import quantity_or_none @@ -145,20 +146,10 @@ class Metadata: @dataclass(frozen=True) -class CalculatedDataItem: - identifier: str - name: str - value: JsonFloat - unit: str - data_sources: list[DataSource] - description: str | None = None - - -@dataclass class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None def has_value(model: object) -> bool: @@ -210,7 +201,7 @@ def _get_technique_document( self, measurement_group: MeasurementGroup, metadata: Metadata, - calculated_data: list[CalculatedDataItem] | None = None, + calculated_data: list[CalculatedDocument] | None = None, ) -> CellCountingDocumentItem: return CellCountingDocumentItem( analyst=measurement_group.analyst, @@ -418,30 +409,12 @@ def _get_error_aggregate_document( ) def _get_calculated_data_aggregate_document( - self, calculated_data_items: list[CalculatedDataItem] | None + self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.identifier, - calculated_data_name=calculated_data_item.name, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit, - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.reference.uuid, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) diff --git a/src/allotropy/allotrope/schema_mappers/adm/electrophoresis/benchling/_2024/_09/electrophoresis.py b/src/allotropy/allotrope/schema_mappers/adm/electrophoresis/benchling/_2024/_09/electrophoresis.py index fdd56c5601..2222fe94bb 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/electrophoresis/benchling/_2024/_09/electrophoresis.py +++ b/src/allotropy/allotrope/schema_mappers/adm/electrophoresis/benchling/_2024/_09/electrophoresis.py @@ -26,10 +26,7 @@ ProcessedDataDocumentItem, SampleDocument, ) -from allotropy.allotrope.models.shared.definitions.definitions import ( - JsonFloat, - TQuantityValue, -) +from allotropy.allotrope.models.shared.definitions.definitions import JsonFloat from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueDegreeCelsius, TQuantityValuePercent, @@ -38,6 +35,12 @@ ) from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import ( quantity_or_none, quantity_or_none_from_unit, @@ -67,21 +70,6 @@ class ProcessedData: data_regions: list[ProcessedDataFeature] -@dataclass(frozen=True) -class DataSource: - identifier: str - feature: str - - -@dataclass(frozen=True) -class CalculatedDataItem: - identifier: str - name: str - value: float - unit: str - data_sources: list[DataSource] - - @dataclass(frozen=True) class Error: error: str @@ -106,7 +94,7 @@ class Measurement: compartment_temperature: float | None = None # Optional processed data - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None # Errors errors: list[Error] | None = None @@ -146,7 +134,7 @@ class Metadata: class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None class Mapper(SchemaMapper[Data, Model]): @@ -309,32 +297,14 @@ def _get_data_region_agg_document( ) def _get_calculated_data_aggregate_document( - self, calculated_data_items: list[CalculatedDataItem] | None + self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.identifier, - calculated_data_name=calculated_data_item.name, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit, - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.identifier, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) def _get_error_aggregate_document( diff --git a/src/allotropy/allotrope/schema_mappers/adm/multi_analyte_profiling/benchling/_2024/_09/multi_analyte_profiling.py b/src/allotropy/allotrope/schema_mappers/adm/multi_analyte_profiling/benchling/_2024/_09/multi_analyte_profiling.py index 5742338f1d..7bed271f36 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/multi_analyte_profiling/benchling/_2024/_09/multi_analyte_profiling.py +++ b/src/allotropy/allotrope/schema_mappers/adm/multi_analyte_profiling/benchling/_2024/_09/multi_analyte_profiling.py @@ -47,6 +47,9 @@ from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, ) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import quantity_or_none @@ -350,29 +353,11 @@ def _get_error_aggregate_document( def _get_calculated_data_aggregate_document( self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.uuid, - calculated_data_name=calculated_data_item.name, - calculation_description=calculated_data_item.description, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit or Unitless.unit, - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.reference.uuid, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, + unit_fallback=Unitless.unit, ) diff --git a/src/allotropy/allotrope/schema_mappers/adm/pcr/BENCHLING/_2023/_09/dpcr.py b/src/allotropy/allotrope/schema_mappers/adm/pcr/BENCHLING/_2023/_09/dpcr.py index f905b44884..e033172b83 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/pcr/BENCHLING/_2023/_09/dpcr.py +++ b/src/allotropy/allotrope/schema_mappers/adm/pcr/BENCHLING/_2023/_09/dpcr.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, TypeVar +from typing import Any from allotropy.allotrope.converter import add_custom_information_document from allotropy.allotrope.models.adm.pcr.benchling._2023._09.dpcr import ( @@ -26,7 +26,6 @@ SampleDocument, TCalculatedDataAggregateDocument, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueNumber, TQuantityValueNumberPerMicroliter, @@ -35,30 +34,21 @@ from allotropy.allotrope.schema_mappers.data_cube import DataCube, get_data_cube from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents_for_dpcr, +) from allotropy.parsers.utils.values import quantity_or_none -@dataclass(frozen=True) -class DataSource: - identifier: str - feature: str - - @dataclass(frozen=True) class Error: error: str error_feature: str -@dataclass(frozen=True) -class CalculatedDataItem: - identifier: str - name: str - value: float - unit: str - data_sources: list[DataSource] - - @dataclass(frozen=True) class Measurement: # Measurement metadata @@ -126,10 +116,7 @@ class Metadata: class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: list[CalculatedDataItem] | None = None - - -CubeClass = TypeVar("CubeClass") + calculated_data: list[CalculatedDocument] | None = None class Mapper(SchemaMapper[Data, Model]): @@ -256,32 +243,14 @@ def _get_measurement_document( return add_custom_information_document(measurement_doc, measurement.custom_info) def _get_calculated_data_aggregate_document( - self, calculated_data_items: list[CalculatedDataItem] | None + self, calculated_data_items: list[CalculatedDocument] | None ) -> TCalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return TCalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.identifier, - calculated_data_name=calculated_data_item.name, - calculated_datum=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit, - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.identifier, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents_for_dpcr( # type: ignore[no-any-return] + calculated_data_items, + TCalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) def _get_error_aggregate_document( diff --git a/src/allotropy/allotrope/schema_mappers/adm/pcr/rec/_2024/_09/qpcr.py b/src/allotropy/allotrope/schema_mappers/adm/pcr/rec/_2024/_09/qpcr.py index 03a420d7db..4f59477246 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/pcr/rec/_2024/_09/qpcr.py +++ b/src/allotropy/allotrope/schema_mappers/adm/pcr/rec/_2024/_09/qpcr.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Any, TypeVar +from typing import Any from allotropy.allotrope.converter import add_custom_information_document from allotropy.allotrope.models.adm.core.rec._2024._09.hierarchy import ( @@ -33,7 +33,6 @@ SampleDocument, SampleRoleType as ModelSampleRoleType, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueMicroliter, TQuantityValueNumber, @@ -42,6 +41,12 @@ from allotropy.allotrope.schema_mappers.data_cube import DataCube, get_data_cube from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import assert_not_none, quantity_or_none @@ -82,32 +87,12 @@ class SampleRoleType(str, Enum): reference_sample_role = "reference sample role" -@dataclass -class DataSource: - identifier: str - feature: str - - @dataclass class Error: error: str feature: str -@dataclass -class CalculatedDataItem: - identifier: str - name: str - value: float - unit: str - data_sources: list[DataSource] - - -@dataclass -class CalculatedData: - items: list[CalculatedDataItem] - - @dataclass class ProcessedData: # Settings @@ -208,10 +193,7 @@ class Metadata: class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: CalculatedData | None = None - - -CubeClass = TypeVar("CubeClass") + calculated_data: list[CalculatedDocument] | None = None class Mapper(SchemaMapper[Data, Model]): @@ -243,7 +225,7 @@ def map_model(self, data: Data) -> Model: for measurement_group in data.measurement_groups ], calculated_data_aggregate_document=self._get_calculated_data_aggregate_document( - data + data.calculated_data ), ), data.metadata.custom_info, @@ -392,32 +374,15 @@ def _get_processed_data_aggregate_document( ) def _get_calculated_data_aggregate_document( - self, data: Data + self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not data.calculated_data or not data.calculated_data.items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calc_doc.identifier, - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=data_source.identifier, - data_source_feature=data_source.feature, - ) - for data_source in calc_doc.data_sources - ], - ), - calculated_data_name=calc_doc.name, - calculation_description=None, - calculated_result=TQuantityValue( - value=calc_doc.value, unit=calc_doc.unit - ), - ) - for calc_doc in data.calculated_data.items - ], + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, + unit_fallback="(unitless)", ) def _get_error_aggregate_document( diff --git a/src/allotropy/allotrope/schema_mappers/adm/plate_reader/benchling/_2023/_09/plate_reader.py b/src/allotropy/allotrope/schema_mappers/adm/plate_reader/benchling/_2023/_09/plate_reader.py index 7bb5ea8682..be9a19834e 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/plate_reader/benchling/_2023/_09/plate_reader.py +++ b/src/allotropy/allotrope/schema_mappers/adm/plate_reader/benchling/_2023/_09/plate_reader.py @@ -45,7 +45,6 @@ from allotropy.allotrope.models.shared.definitions.definitions import ( InvalidJsonFloat, JsonFloat, - TQuantityValue, ) from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueDegreeCelsius, @@ -62,6 +61,12 @@ from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION from allotropy.exceptions import AllotropyParserError +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import ( assert_not_none, quantity_or_none, @@ -111,16 +116,6 @@ class ProcessedData: data_processing_document: dict[str, Any] | None = None -@dataclass(frozen=True) -class CalculatedDataItem: - identifier: str - name: str - value: JsonFloat - unit: str - data_sources: list[DataSource] - description: str | None = None - - @dataclass(frozen=True) class ImageSource: identifier: str @@ -221,7 +216,7 @@ class Metadata: class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None class Mapper(SchemaMapper[Data, Model]): @@ -625,27 +620,14 @@ def _get_image_source_aggregate_document( ) def _get_calculated_data_aggregate_document( - self, calculated_data_items: list[CalculatedDataItem] | None + self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.identifier, - calculated_data_name=calculated_data_item.name, - calculation_description=calculated_data_item.description, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit, - ), - data_source_aggregate_document=self._get_data_source_aggregate_document( - calculated_data_item.data_sources - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) def _get_data_source_aggregate_document( diff --git a/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2024/_06/plate_reader.py b/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2024/_06/plate_reader.py index 32d2670770..c105b381f6 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2024/_06/plate_reader.py +++ b/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2024/_06/plate_reader.py @@ -31,7 +31,6 @@ SampleRoleType, ScanPositionSettingPlateReader as ModelScanPositionSettingPlateReader, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueDegreeCelsius, TQuantityValueMilliAbsorbanceUnit, @@ -51,6 +50,9 @@ from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, ) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import ( assert_not_none, quantity_or_none, @@ -578,34 +580,14 @@ def _get_sample_document(self, measurement: Measurement) -> SampleDocument: def _get_calculated_data_aggregate_document( self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - add_custom_information_document( - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.uuid, - calculated_data_name=calculated_data_item.name, - calculation_description=calculated_data_item.description, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit or "(unitless)", - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.reference.uuid, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ), - calculated_data_item.custom_info, - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, + unit_fallback="(unitless)", + include_custom_info=True, ) def _get_error_aggregate_document( diff --git a/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2025/_03/plate_reader.py b/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2025/_03/plate_reader.py index 5ce587418d..8597e6213f 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2025/_03/plate_reader.py +++ b/src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2025/_03/plate_reader.py @@ -36,7 +36,6 @@ SampleRoleType, ScanPositionSettingPlateReader as ModelScanPositionSettingPlateReader, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueDegreeCelsius, TQuantityValueKiloDalton, @@ -60,6 +59,9 @@ from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, ) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import ( assert_not_none, quantity_or_none, @@ -836,31 +838,13 @@ def _get_sample_document(self, measurement: Measurement) -> SampleDocument: def _get_calculated_data_aggregate_document( self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.uuid, - calculated_data_name=calculated_data_item.name, - calculation_description=calculated_data_item.description, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit or "(unitless)", - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.reference.uuid, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, + unit_fallback="(unitless)", ) def _get_error_aggregate_document( diff --git a/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/benchling/_2024/_09/solution_analyzer.py b/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/benchling/_2024/_09/solution_analyzer.py index 1af36e05b4..dd22d42e74 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/benchling/_2024/_09/solution_analyzer.py +++ b/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/benchling/_2024/_09/solution_analyzer.py @@ -29,7 +29,6 @@ SolutionAnalyzerAggregateDocument, SolutionAnalyzerDocumentItem, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueCell, TQuantityValueCountsPerMilliliter, @@ -51,6 +50,12 @@ from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION from allotropy.exceptions import AllotropeConversionError +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import quantity_or_none @@ -166,26 +171,11 @@ class Metadata: sample_volume_setting: float | None = None -@dataclass(frozen=True) -class DataSource: - identifier: str - feature: str - - -@dataclass(frozen=True) -class CalculatedDataItem: - identifier: str - name: str - value: float - unit: str - data_sources: list[DataSource] - - @dataclass(frozen=True) class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None def get_ml_hg_or_kpa_quantity_value( @@ -498,30 +488,12 @@ def _get_error_aggregate_document( ) def _get_calculated_data_aggregate_document( - self, calculated_data_items: list[CalculatedDataItem] | None + self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.identifier, - calculated_data_name=calculated_data_item.name, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit, - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.identifier, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) diff --git a/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/rec/_2024/_09/solution_analyzer.py b/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/rec/_2024/_09/solution_analyzer.py index 50c685a269..9ea82cb662 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/rec/_2024/_09/solution_analyzer.py +++ b/src/allotropy/allotrope/schema_mappers/adm/solution_analyzer/rec/_2024/_09/solution_analyzer.py @@ -29,7 +29,6 @@ SolutionAnalyzerAggregateDocument, SolutionAnalyzerDocumentItem, ) -from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue from allotropy.allotrope.models.shared.definitions.quantity_values import ( TQuantityValueCell, TQuantityValueCountsPerMilliliter, @@ -50,6 +49,12 @@ from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper from allotropy.constants import ASM_CONVERTER_VERSION from allotropy.exceptions import AllotropeConversionError +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.values import quantity_or_none @@ -165,26 +170,11 @@ class Metadata: custom_info: dict[str, Any] | None = None -@dataclass(frozen=True) -class DataSource: - identifier: str - feature: str - - -@dataclass(frozen=True) -class CalculatedDataItem: - identifier: str - name: str - value: float - unit: str - data_sources: list[DataSource] - - @dataclass(frozen=True) class Data: metadata: Metadata measurement_groups: list[MeasurementGroup] - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None class Mapper(SchemaMapper[Data, Model]): @@ -469,30 +459,12 @@ def _get_error_aggregate_document( ) def _get_calculated_data_aggregate_document( - self, calculated_data_items: list[CalculatedDataItem] | None + self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.identifier, - calculated_data_name=calculated_data_item.name, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=calculated_data_item.unit, - ), - data_source_aggregate_document=DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.identifier, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) diff --git a/src/allotropy/allotrope/schema_mappers/adm/spectrophotometry/benchling/_2023/_12/spectrophotometry.py b/src/allotropy/allotrope/schema_mappers/adm/spectrophotometry/benchling/_2023/_12/spectrophotometry.py index 939fb8b544..87c45c15ca 100644 --- a/src/allotropy/allotrope/schema_mappers/adm/spectrophotometry/benchling/_2023/_12/spectrophotometry.py +++ b/src/allotropy/allotrope/schema_mappers/adm/spectrophotometry/benchling/_2023/_12/spectrophotometry.py @@ -52,6 +52,9 @@ from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, ) +from allotropy.parsers.utils.calculated_data_documents.mapping import ( + map_calculated_data_documents, +) from allotropy.parsers.utils.units import get_quantity_class from allotropy.parsers.utils.values import assert_not_none, quantity_or_none @@ -444,32 +447,10 @@ def _get_processed_data_aggregate_document( def _get_calculated_data_aggregate_document( self, calculated_data_items: list[CalculatedDocument] | None ) -> CalculatedDataAggregateDocument | None: - if not calculated_data_items: - return None - - return CalculatedDataAggregateDocument( - calculated_data_document=[ - CalculatedDataDocumentItem( - calculated_data_identifier=calculated_data_item.uuid, - calculated_data_name=calculated_data_item.name, - calculated_result=TQuantityValue( - value=calculated_data_item.value, - unit=assert_not_none(calculated_data_item.unit), - ), - data_source_aggregate_document=( - DataSourceAggregateDocument( - data_source_document=[ - DataSourceDocumentItem( - data_source_identifier=item.reference.uuid, - data_source_feature=item.feature, - ) - for item in calculated_data_item.data_sources - ] - ) - if calculated_data_item.data_sources - else None - ), - ) - for calculated_data_item in calculated_data_items - ] + return map_calculated_data_documents( # type: ignore[no-any-return] + calculated_data_items, + CalculatedDataAggregateDocument, + CalculatedDataDocumentItem, + DataSourceAggregateDocument, + DataSourceDocumentItem, ) diff --git a/src/allotropy/calcdocs/__init__.py b/src/allotropy/calcdocs/__init__.py index e69de29bb2..2ec866aaaf 100644 --- a/src/allotropy/calcdocs/__init__.py +++ b/src/allotropy/calcdocs/__init__.py @@ -0,0 +1,36 @@ +from allotropy.calcdocs.builder import ( + build_calc_docs, + CalcDoc, + describe_graph, + Measurement, + Node, +) +from allotropy.calcdocs.extractor import Element, Extractor +from allotropy.calcdocs.view import Keys, View, ViewData +from allotropy.calcdocs.views import ( + FieldView, + SampleView, + TargetRoleView, + TargetView, + UuidView, + ViewWithReference, +) + +__all__ = [ + "CalcDoc", + "Element", + "Extractor", + "FieldView", + "Keys", + "Measurement", + "Node", + "SampleView", + "TargetRoleView", + "TargetView", + "UuidView", + "View", + "ViewData", + "ViewWithReference", + "build_calc_docs", + "describe_graph", +] diff --git a/src/allotropy/calcdocs/appbio_quantstudio_designandanalysis/config.py b/src/allotropy/calcdocs/appbio_quantstudio_designandanalysis/config.py deleted file mode 100644 index 093e7ebe38..0000000000 --- a/src/allotropy/calcdocs/appbio_quantstudio_designandanalysis/config.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterator -from dataclasses import dataclass - -from allotropy.calcdocs.config import CalculatedDataConfig -from allotropy.calcdocs.extractor import Element -from allotropy.calcdocs.view import Keys, ViewData -from allotropy.parsers.utils.calculated_data_documents.definition import ( - CalculatedDocument, - DataSource, -) - - -@dataclass(frozen=True) -class CalculatedDataConfigWithOptional(CalculatedDataConfig): - optional: bool = False - - def iter_data_sources( - self, - parent_keys: Keys, - elements: list[Element], - cache: dict[str, CalculatedDocument | None], - ) -> Iterator[DataSource]: - keys = self.view_data.filter_keys(parent_keys) - item = self.view_data.get_item(keys) - sub_keys_iterator = item.iter_keys() if isinstance(item, ViewData) else [Keys()] - - for sub_keys in sub_keys_iterator: - new_keys = keys.append(sub_keys) - if calc_doc := self.get_calc_doc(new_keys, cache): - yield DataSource( - feature=calc_doc.name, - reference=calc_doc, - value=calc_doc.value, - ) - elif self.optional: - for sub_config in self.source_configs: - yield from sub_config.iter_data_sources(new_keys, elements, cache) diff --git a/src/allotropy/calcdocs/appbio_quantstudio_designandanalysis/views.py b/src/allotropy/calcdocs/appbio_quantstudio_designandanalysis/views.py deleted file mode 100644 index c8a49ad06a..0000000000 --- a/src/allotropy/calcdocs/appbio_quantstudio_designandanalysis/views.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -from collections import defaultdict - -from allotropy.calcdocs.extractor import Element -from allotropy.calcdocs.view import Keys, View, ViewData - - -class ViewWithReference(View): - def __init__( - self, - name: str, - sub_view: View | None, - reference: str | None = None, - ): - super().__init__(name, sub_view) - self.reference = reference - - def filter_keys(self, keys: Keys) -> Keys: - filtered_keys = super().filter_keys(keys) - if self.reference is not None and filtered_keys.get_or_none(self.name): - return filtered_keys.overwrite(self.name, self.reference) - return filtered_keys - - -class SampleView(ViewWithReference): - def __init__( - self, - sub_view: View | None = None, - reference: str | None = None, - ): - super().__init__(name="sample_id", sub_view=sub_view, reference=reference) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if sample_identifier := element.get_str("sample_identifier"): - items[str(sample_identifier)].append(element) - return dict(items) - - -class TargetView(ViewWithReference): - def __init__( - self, - sub_view: View | None = None, - is_reference: bool = False, # noqa: FBT001 FBT002 - reference: str | None = None, - blacklist: list[str] | None = None, - ): - super().__init__(name="target_dna", sub_view=sub_view, reference=reference) - self.is_reference = is_reference - self.blacklist = blacklist - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if target_dna := element.get_str("target_dna_description"): - if self.blacklist is None or target_dna not in self.blacklist: - items[str(target_dna)].append(element) - return dict(items) - - def filter_keys(self, keys: Keys) -> Keys: - if self.is_reference and self.reference is None: - return Keys() - return super().filter_keys(keys) - - def apply(self, elements: list[Element]) -> ViewData: - if self.is_reference and self.reference is None: - return ViewData(view=self, name=self.name, data={}) - return super().apply(elements) - - -class UuidView(View): - def __init__(self, sub_view: ViewWithReference | None = None): - super().__init__(name="uuid", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if uuid := element.get_str("uuid"): - items[str(uuid)].append(element) - return dict(items) - - -class TargetRoleView(View): - def __init__(self, sub_view: ViewWithReference | None = None): - super().__init__(name="target_dna", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if target_dna := element.get_str_or_none("target_dna_description"): - if element.get_str("sample_role_type") == "standard sample role": - items[str(target_dna)].append(element) - return dict(items) diff --git a/src/allotropy/calcdocs/builder.py b/src/allotropy/calcdocs/builder.py new file mode 100644 index 0000000000..b33bdae879 --- /dev/null +++ b/src/allotropy/calcdocs/builder.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +from dataclasses import dataclass, field as dataclass_field +from itertools import chain + +from allotropy.calcdocs.config import ( + CalcDocsConfig, + CalculatedDataConfig, + CalculatedDataConfigWithOptional, + MeasurementConfig, +) +from allotropy.calcdocs.view import ViewData +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) + + +@dataclass(frozen=True) +class Measurement: + name: str + field: str + required: bool = False + + +@dataclass(frozen=True) +class CalcDoc: + name: str + field: str + sources: list[Measurement | CalcDoc] = dataclass_field(default_factory=list) + view: str = "" + unit: str | None = None + description: str | None = None + description_field: str | None = None + required: bool = False + optional: bool = False + source_only: bool = False + + +Node = Measurement | CalcDoc + + +def build_calc_docs( + nodes: list[Node], + views: dict[str, ViewData], +) -> list[CalculatedDocument]: + _validate_graph(nodes, views) + + measurements: dict[int, MeasurementConfig] = {} + calc_configs: dict[int, CalculatedDataConfig] = {} + + for node in nodes: + if isinstance(node, Measurement): + measurements[id(node)] = MeasurementConfig( + name=node.name, + value=node.field, + required=node.required, + ) + + for node in nodes: + if isinstance(node, CalcDoc): + _build_calc_config(node, views, measurements, calc_configs) + + top_level_configs = [ + calc_configs[id(node)] + for node in nodes + if isinstance(node, CalcDoc) + and id(node) in calc_configs + and not node.source_only + ] + + result = CalcDocsConfig(top_level_configs).construct() + return list(chain.from_iterable(doc.iter_struct() for doc in result)) + + +def _validate_graph(nodes: list[Node], views: dict[str, ViewData]) -> None: + node_set = {id(n) for n in nodes} + + for node in nodes: + if not isinstance(node, CalcDoc): + continue + if node.view and node.view not in views: + msg = ( + f"CalcDoc '{node.name}' references view '{node.view}' " + f"but available views are: {sorted(views.keys())}" + ) + raise ValueError(msg) + for source in node.sources: + if id(source) not in node_set: + msg = ( + f"CalcDoc '{node.name}' references source '{source.name}' " + f"which is not in the nodes list" + ) + raise ValueError(msg) + + +def _build_calc_config( + node: CalcDoc, + views: dict[str, ViewData], + measurements: dict[int, MeasurementConfig], + calc_configs: dict[int, CalculatedDataConfig], +) -> CalculatedDataConfig: + if id(node) in calc_configs: + return calc_configs[id(node)] + + view_data = views[node.view] + + source_configs: list[CalculatedDataConfig | MeasurementConfig] = [] + for source in node.sources: + if isinstance(source, Measurement): + source_configs.append(measurements[id(source)]) + else: + source_config = _build_calc_config( + source, views, measurements, calc_configs + ) + source_configs.append(source_config) + + config: CalculatedDataConfig + if node.optional: + config = CalculatedDataConfigWithOptional( + name=node.name, + value=node.field, + view_data=view_data, + source_configs=tuple(source_configs), + unit=node.unit, + description=node.description, + description_value_key=node.description_field, + required=node.required, + optional=True, + ) + else: + config = CalculatedDataConfig( + name=node.name, + value=node.field, + view_data=view_data, + source_configs=tuple(source_configs), + unit=node.unit, + description=node.description, + description_value_key=node.description_field, + required=node.required, + ) + calc_configs[id(node)] = config + return config + + +def describe_graph(nodes: list[Node]) -> str: + lines = [] + for node in nodes: + if isinstance(node, Measurement): + lines.append(f" [M] {node.name} <- element.{node.field}") + elif isinstance(node, CalcDoc): + prefix = "(source_only) " if node.source_only else "" + source_names = [s.name for s in node.sources] + lines.append( + f" [C] {prefix}{node.name} <- element.{node.field} " + f"| view={node.view} | sources={source_names}" + ) + return "\n".join(lines) diff --git a/src/allotropy/calcdocs/config.py b/src/allotropy/calcdocs/config.py index 8c3a7181a6..fd56b1b6bf 100644 --- a/src/allotropy/calcdocs/config.py +++ b/src/allotropy/calcdocs/config.py @@ -73,8 +73,6 @@ def _get_calc_doc_inner( if not data_sources: return None - if self.name == "B22 goodness of fit": - pass return CalculatedDocument( uuid=random_uuid_str(), name=self.name, @@ -117,6 +115,33 @@ def construct( ] +@dataclass(frozen=True) +class CalculatedDataConfigWithOptional(CalculatedDataConfig): + optional: bool = False + + def iter_data_sources( + self, + parent_keys: Keys, + elements: list[Element], + cache: dict[str, CalculatedDocument | None], + ) -> Iterator[DataSource]: + keys = self.view_data.filter_keys(parent_keys) + item = self.view_data.get_item(keys) + sub_keys_iterator = item.iter_keys() if isinstance(item, ViewData) else [Keys()] + + for sub_keys in sub_keys_iterator: + new_keys = keys.append(sub_keys) + if calc_doc := self.get_calc_doc(new_keys, cache): + yield DataSource( + feature=calc_doc.name, + reference=calc_doc, + value=calc_doc.value, + ) + elif self.optional: + for sub_config in self.source_configs: + yield from sub_config.iter_data_sources(new_keys, elements, cache) + + @dataclass(frozen=True) class MeasurementConfig: name: str diff --git a/src/allotropy/calcdocs/cytiva_biacore_t200_control/views.py b/src/allotropy/calcdocs/cytiva_biacore_t200_control/views.py deleted file mode 100644 index 879ed79e2a..0000000000 --- a/src/allotropy/calcdocs/cytiva_biacore_t200_control/views.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -from allotropy.calcdocs.extractor import Element -from allotropy.calcdocs.view import View - - -class ReportPointDataView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="report_point_data", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - return {element.uuid: [element] for element in elements} diff --git a/src/allotropy/calcdocs/docs/example/parser_calcdocs/views.py b/src/allotropy/calcdocs/docs/example/parser_calcdocs/views.py index b58f3a4f69..9691ad07b9 100644 --- a/src/allotropy/calcdocs/docs/example/parser_calcdocs/views.py +++ b/src/allotropy/calcdocs/docs/example/parser_calcdocs/views.py @@ -1,30 +1,9 @@ -from __future__ import annotations - -from collections import defaultdict - -from allotropy.calcdocs.extractor import Element -from allotropy.calcdocs.view import View - - -class MeanView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="mean", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if mean := element.get_float_or_none("mean"): - items[str(mean)].append(element) - return dict(items) - - -class SumView(View): - def __init__(self) -> None: - super().__init__(name="sum") - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if sum_ := element.get_float_or_none("sum"): - items[str(sum_)].append(element) - return dict(items) +# This file demonstrates custom views for the calcdocs framework. +# +# For simple field-based views, use FieldView directly: +# from allotropy.calcdocs import FieldView +# mean_view = FieldView("mean") +# sum_view = FieldView("sum") +# +# Custom View subclasses are only needed for complex grouping logic +# beyond simple field value matching. diff --git a/src/allotropy/calcdocs/docs/example/parser_code/parser.py b/src/allotropy/calcdocs/docs/example/parser_code/parser.py index e6af5e195a..372ec7c8aa 100644 --- a/src/allotropy/calcdocs/docs/example/parser_code/parser.py +++ b/src/allotropy/calcdocs/docs/example/parser_code/parser.py @@ -1,10 +1,11 @@ -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + FieldView, + Measurement as CalcMeasurement, + Node, ) from allotropy.calcdocs.docs.example.parser_calcdocs.extractor import ExampleExtractor -from allotropy.calcdocs.docs.example.parser_calcdocs.views import MeanView, SumView from allotropy.calcdocs.docs.example.parser_code.measurement import Measurement from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, @@ -12,6 +13,16 @@ from allotropy.parsers.utils.uuids import random_uuid_str +def _example_nodes() -> list[Node]: + m = CalcMeasurement("measurement", field="m") + summation = CalcDoc("sumation", field="sum", sources=[m], view="mean_sum") + mean = CalcDoc("sum mean", field="mean", sources=[summation], view="mean") + return [m, summation, mean] + + +EXAMPLE_NODES: list[Node] = _example_nodes() + + class ExampleParser: def read_data(self) -> list[Measurement]: return [ @@ -37,41 +48,11 @@ def create_calculated_data( self, measurements: list[Measurement] ) -> list[CalculatedDocument]: elements = ExampleExtractor.get_elements(measurements) - - mean_view_data = MeanView().apply(elements) - sum_view_data = MeanView(sub_view=SumView()).apply(elements) - - measurement_conf = MeasurementConfig( - name="measurement", - value="m", - ) - - sum_conf = CalculatedDataConfig( - name="sumation", - value="sum", - view_data=sum_view_data, - source_configs=(measurement_conf,), - ) - - mean_conf = CalculatedDataConfig( - name="sum mean", - value="mean", - view_data=mean_view_data, - source_configs=(sum_conf,), - ) - - configs = CalcDocsConfig( - [ - sum_conf, - mean_conf, - ] - ) - - return [ - calc_doc - for parent_calc_doc in configs.construct() - for calc_doc in parent_calc_doc.iter_struct() - ] + views = { + "mean": FieldView("mean").apply(elements), + "mean_sum": FieldView("mean", sub_view=FieldView("sum")).apply(elements), + } + return build_calc_docs(nodes=EXAMPLE_NODES, views=views) def create_data(self) -> None: measurements = self.read_data() diff --git a/src/allotropy/calcdocs/msd_workbench/config.py b/src/allotropy/calcdocs/msd_workbench/config.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/allotropy/calcdocs/msd_workbench/views.py b/src/allotropy/calcdocs/msd_workbench/views.py deleted file mode 100644 index 123028b94b..0000000000 --- a/src/allotropy/calcdocs/msd_workbench/views.py +++ /dev/null @@ -1,52 +0,0 @@ -from collections import defaultdict - -from allotropy.calcdocs.extractor import Element -from allotropy.calcdocs.view import View - - -class AssayIdentifierView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="assay_identifier", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - assay_identifier = element.get_str("assay_identifier") - items[assay_identifier].append(element) - return dict(items) - - -class SampleIdentifierView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="sample_identifier", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - sample_identifier = element.get_str("sample_identifier") - items[sample_identifier].append(element) - return dict(items) - - -class WellPlateIdentifierView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="well_plate_identifier", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - well_plate_identifier = element.get_str("well_plate_identifier") - items[well_plate_identifier].append(element) - return dict(items) - - -class LocationIdentifierView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="location_identifier", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - location_identifier = element.get_str("location_identifier") - items[location_identifier].append(element) - return dict(items) diff --git a/src/allotropy/calcdocs/unchained_labs_lunatic_stunner/views.py b/src/allotropy/calcdocs/unchained_labs_lunatic_stunner/views.py deleted file mode 100644 index 31fb6ded94..0000000000 --- a/src/allotropy/calcdocs/unchained_labs_lunatic_stunner/views.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -from collections import defaultdict - -from allotropy.calcdocs.extractor import Element -from allotropy.calcdocs.view import View - - -class UuidView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="uuid", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if uuid := element.get_str("uuid"): - items[uuid].append(element) - return dict(items) - - -class DetectionTypeView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="detection_type", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if detection_type := element.get_str("detection type"): - items[detection_type].append(element) - return dict(items) diff --git a/src/allotropy/calcdocs/appbio_quantstudio/views.py b/src/allotropy/calcdocs/views.py similarity index 76% rename from src/allotropy/calcdocs/appbio_quantstudio/views.py rename to src/allotropy/calcdocs/views.py index fbd62554e4..8bff0585d9 100644 --- a/src/allotropy/calcdocs/appbio_quantstudio/views.py +++ b/src/allotropy/calcdocs/views.py @@ -2,11 +2,34 @@ from collections import defaultdict -from allotropy.allotrope.schema_mappers.adm.pcr.rec._2024._09.qpcr import SampleRoleType from allotropy.calcdocs.extractor import Element from allotropy.calcdocs.view import Keys, View, ViewData +class FieldView(View): + def __init__(self, field: str, sub_view: View | None = None): + super().__init__(name=field, sub_view=sub_view) + self.field = field + + def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: + items: dict[str, list[Element]] = defaultdict(list) + for element in elements: + if val := element.get_str(self.field): + items[val].append(element) + return dict(items) + + +class UuidView(View): + def __init__(self, sub_view: View | None = None): + super().__init__(name="uuid", sub_view=sub_view) + + def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: + items: dict[str, list[Element]] = defaultdict(list) + for element in elements: + items[element.uuid].append(element) + return dict(items) + + class ViewWithReference(View): def __init__( self, @@ -29,7 +52,7 @@ def __init__(self, sub_view: View | None = None, reference: str | None = None): super().__init__(name="sample_id", sub_view=sub_view, reference=reference) def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) + items: dict[str, list[Element]] = defaultdict(list) for element in elements: if sample_identifier := element.get_str("sample_identifier"): items[sample_identifier].append(element) @@ -40,7 +63,7 @@ class TargetView(ViewWithReference): def __init__( self, sub_view: View | None = None, - is_reference: bool = False, # noqa: FBT001 FBT002 + is_reference: bool = False, # noqa: FBT001, FBT002 reference: str | None = None, blacklist: list[str] | None = None, ): @@ -49,7 +72,7 @@ def __init__( self.blacklist = blacklist def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) + items: dict[str, list[Element]] = defaultdict(list) for element in elements: if target_dna := element.get_str("target_dna_description"): if self.blacklist is None or target_dna not in self.blacklist: @@ -67,27 +90,14 @@ def apply(self, elements: list[Element]) -> ViewData: return super().apply(elements) -class UuidView(View): - def __init__(self, sub_view: View | None = None): - super().__init__(name="uuid", sub_view=sub_view) - - def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) - for element in elements: - if uuid := element.get_str("uuid"): - items[str(uuid)].append(element) - return dict(items) - - class TargetRoleView(View): def __init__(self, sub_view: View | None = None): super().__init__(name="target_dna", sub_view=sub_view) def sort_elements(self, elements: list[Element]) -> dict[str, list[Element]]: - items = defaultdict(list) + items: dict[str, list[Element]] = defaultdict(list) for element in elements: if target_dna := element.get_str("target_dna_description"): - sample_role_type = element.get_str("sample_role_type") - if sample_role_type == SampleRoleType.standard_sample_role: + if element.get_str("sample_role_type") == "standard sample role": items[target_dna].append(element) return dict(items) diff --git a/src/allotropy/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure.py b/src/allotropy/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure.py index 335d713bf5..e4a26c9cee 100644 --- a/src/allotropy/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure.py +++ b/src/allotropy/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure.py @@ -5,8 +5,6 @@ from allotropy.allotrope.models.shared.definitions.units import UNITLESS from allotropy.allotrope.schema_mappers.adm.electrophoresis.benchling._2024._09.electrophoresis import ( - CalculatedDataItem, - DataSource, Error, Measurement, MeasurementGroup, @@ -30,6 +28,11 @@ SOFTWARE_NAME, UNIT_CLASS_LOOKUP, ) +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, + DataSource, + Referenceable, +) from allotropy.parsers.utils.uuids import random_uuid_str from allotropy.parsers.utils.values import try_float_or_none from allotropy.parsers.utils.xml import ( @@ -42,7 +45,7 @@ def _get_calculated_data( element: ET.Element, excluded_tags: list[str], source_id: str, feature: str -) -> list[CalculatedDataItem]: +) -> list[CalculatedDocument]: calculated_data = [] for node in element: if (name := node.tag) in excluded_tags: @@ -50,12 +53,14 @@ def _get_calculated_data( if (value := try_float_or_none(node.text)) is None: continue calculated_data.append( - CalculatedDataItem( - identifier=random_uuid_str(), + CalculatedDocument( + uuid=random_uuid_str(), name=name, value=value, unit=UNITLESS, - data_sources=[DataSource(identifier=source_id, feature=feature)], + data_sources=[ + DataSource(reference=Referenceable(uuid=source_id), feature=feature) + ], ) ) @@ -140,9 +145,9 @@ def _create_region( def _create_measurement( sample_element: ET.Element, screen_tape: ET.Element, unit: str -) -> tuple[Measurement, list[CalculatedDataItem]]: +) -> tuple[Measurement, list[CalculatedDocument]]: measurement_id = random_uuid_str() - calculated_data: list[CalculatedDataItem] = [] + calculated_data: list[CalculatedDocument] = [] calculated_data.extend( _get_calculated_data( element=sample_element, @@ -232,7 +237,7 @@ def _get_unit_class( def create_measurement_groups( root_element: ET.Element, -) -> tuple[list[MeasurementGroup], list[CalculatedDataItem]]: +) -> tuple[list[MeasurementGroup], list[CalculatedDocument]]: screen_tapes_element = get_element_from_xml(root_element, "ScreenTapes") screen_tapes = { get_val_from_xml(screen_tape, "ScreenTapeID"): screen_tape @@ -240,7 +245,7 @@ def create_measurement_groups( } measurement_groups: list[MeasurementGroup] = [] - calculated_data: list[CalculatedDataItem] = [] + calculated_data: list[CalculatedDocument] = [] for sample_element in get_element_from_xml(root_element, "Samples").iter("Sample"): screen_tape_id = get_val_from_xml(sample_element, "ScreenTapeID") try: diff --git a/src/allotropy/parsers/appbio_absolute_q/appbio_absolute_q_structure.py b/src/allotropy/parsers/appbio_absolute_q/appbio_absolute_q_structure.py index 998555756f..9004b05f92 100644 --- a/src/allotropy/parsers/appbio_absolute_q/appbio_absolute_q_structure.py +++ b/src/allotropy/parsers/appbio_absolute_q/appbio_absolute_q_structure.py @@ -14,8 +14,6 @@ FieldComponentDatatype, ) from allotropy.allotrope.schema_mappers.adm.pcr.BENCHLING._2023._09.dpcr import ( - CalculatedDataItem, - DataSource, Error, Measurement, MeasurementGroup, @@ -42,6 +40,11 @@ NEGATIVE_ZERO, NOT_APPLICABLE, ) +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, + DataSource, + Referenceable, +) from allotropy.parsers.utils.pandas import map_rows, series_to_float_list, SeriesData from allotropy.parsers.utils.uuids import random_uuid_str @@ -61,12 +64,17 @@ def get_data_sources( ) -> list[DataSource]: if self.source == CalculatedDataSource.CALCULATED_DATA: return [ - DataSource(calculated_data_ids[source_feature], source_feature) + DataSource( + reference=Referenceable(uuid=calculated_data_ids[source_feature]), + feature=source_feature, + ) for source_feature in self.source_features ] else: return [ - DataSource(identifier, source_feature) + DataSource( + reference=Referenceable(uuid=identifier), feature=source_feature + ) for source_feature in self.source_features for identifier in measurement_ids ] @@ -171,7 +179,7 @@ class WellItem: reporter_dye_data_cube: DataCube | None = None passive_reference_dye_data_cube: DataCube | None = None errors: list[Error] | None = None - calculated_data: list[CalculatedDataItem] | None = None + calculated_data: list[CalculatedDocument] | None = None extra_data: dict[str, Any] | None = None @property @@ -199,8 +207,8 @@ def create(data: SeriesData) -> WellItem: confidence_interval__95__=data.get(float, "95%CI"), fluorescence_intensity_threshold_setting=data.get(float, "Threshold"), calculated_data=[ - CalculatedDataItem( - identifier=calc_data.identifier, + CalculatedDocument( + uuid=calc_data.identifier, name=calc_data.name, value=calc_data.value, unit=calc_data.unit, @@ -423,12 +431,12 @@ def create_measurement_groups(wells: list[Well]) -> list[MeasurementGroup]: def create_calculated_data( wells: list[Well], groups: list[Group], common_columns: list[str] -) -> list[CalculatedDataItem]: +) -> list[CalculatedDocument]: if not groups: return [] # Map measurement ids to group keys and get per-measurement calculated data items group_to_ids = defaultdict(list) - calculated_data_items: list[CalculatedDataItem] = [] + calculated_data_items: list[CalculatedDocument] = [] for well in wells: for item in well.items: group_to_ids[item.group_key].append(item.measurement_identifier) @@ -474,17 +482,14 @@ def create_calculated_data( calculated_data_items.extend( [ - CalculatedDataItem( - identifier=calculated_data.identifier, + CalculatedDocument( + uuid=calculated_data.identifier, name=calculated_data.name, value=calculated_data.value, unit=calculated_data.unit, - data_sources=[ - DataSource(source.identifier, source.feature) - for source in calculated_data.get_data_sources( - group_to_ids[group.key], group.calculated_data_ids - ) - ], + data_sources=calculated_data.get_data_sources( + group_to_ids[group.key], group.calculated_data_ids + ), ) for group in groups for calculated_data in group.calculated_data diff --git a/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_calculated_documents.py b/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_calculated_documents.py index dbf00f3238..6abe3788aa 100644 --- a/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_calculated_documents.py +++ b/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_calculated_documents.py @@ -1,18 +1,13 @@ from collections.abc import Iterator -from allotropy.calcdocs.appbio_quantstudio.extractor import AppbioQuantstudioExtractor -from allotropy.calcdocs.appbio_quantstudio.views import ( - SampleView, - TargetRoleView, - TargetView, - UuidView, -) -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + Measurement, + Node, ) -from allotropy.calcdocs.view import ViewData +from allotropy.calcdocs.appbio_quantstudio.extractor import AppbioQuantstudioExtractor +from allotropy.calcdocs.views import SampleView, TargetRoleView, TargetView, UuidView from allotropy.parsers.appbio_quantstudio.appbio_quantstudio_structure import ( WellItem, ) @@ -22,260 +17,291 @@ ) from allotropy.parsers.utils.values import assert_not_none - -def ctr() -> MeasurementConfig: - return MeasurementConfig( - name="cycle threshold result", - value="cycle_threshold_result", - ) +CTR = Measurement("cycle threshold result", field="cycle_threshold_result") +NORM_REPORTER = Measurement( + "normalized reporter result", field="normalized_reporter_result" +) -def norm_reporter_result() -> MeasurementConfig: - return MeasurementConfig( - name="normalized reporter result", - value="normalized_reporter_result", +def _comparative_ct_nodes() -> list[Node]: + y_intercept = CalcDoc( + "y intercept", + field="y_intercept", + sources=[CTR], + view="tdna_role", + source_only=True, ) - - -def amplification_score(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="amplification score", - value="amp_score", - view_data=view_data, - source_configs=(ctr(),), + slope = CalcDoc( + "slope", + field="slope", + sources=[CTR], + view="tdna_role", + source_only=True, ) - - -def cq_confidence(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="cq confidence", - value="cq_conf", - view_data=view_data, - source_configs=(ctr(),), + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR, y_intercept, slope], + view="sid_tdna_uuid", ) - - -def y_intercept(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="y intercept", - value="y_intercept", - view_data=view_data, - source_configs=(ctr(),), + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", ) - - -def slope(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="slope", - value="slope", - view_data=view_data, - source_configs=(ctr(),), + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", ) - - -def ct_mean(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="ct mean", - value="ct_mean", - view_data=view_data, - source_configs=(ctr(),), + quantity_mean = CalcDoc( + "quantity mean", field="quantity_mean", sources=[quantity], view="sid_tdna" ) - - -def ct_sd(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="ct sd", - value="ct_sd", - view_data=view_data, - source_configs=(ctr(),), + quantity_sd = CalcDoc( + "quantity sd", field="quantity_sd", sources=[quantity], view="sid_tdna" ) - - -def r_squared(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="r^2", - value="r_squared", - view_data=view_data, - source_configs=(ctr(),), + ct_mean = CalcDoc("ct mean", field="ct_mean", sources=[CTR], view="sid_tdna") + ct_sd = CalcDoc("ct sd", field="ct_sd", sources=[CTR], view="sid_tdna") + ct_mean_ref = CalcDoc( + "ct mean", + field="ct_mean", + sources=[CTR], + view="sid_tdna_ref", + source_only=True, ) - - -def efficiency(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="efficiency", - value="efficiency", - view_data=view_data, - source_configs=(ctr(),), + delta_ct_se = CalcDoc( + "delta ct se", + field="delta_ct_se", + sources=[ct_mean, ct_mean_ref], + view="sid_tdna", ) - - -def rn_mean(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rn mean", - value="rn_mean", - view_data=view_data, - source_configs=(norm_reporter_result(),), + delta_ct_mean = CalcDoc( + "delta ct mean", + field="delta_ct_mean", + sources=[ct_mean, ct_mean_ref], + view="sid_tdna", + source_only=True, ) - - -def rn_sd(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rn sd", - value="rn_sd", - view_data=view_data, - source_configs=(norm_reporter_result(),), + delta_ct_mean_ref_sample = CalcDoc( + "delta ct mean", + field="delta_ct_mean", + sources=[ct_mean, ct_mean_ref], + view="sid_ref_tdna", + source_only=True, ) - - -def quantity( - view_data: ViewData, - y_intercept_conf: CalculatedDataConfig | None = None, - slope_conf: CalculatedDataConfig | None = None, -) -> CalculatedDataConfig: - ctr_conf = ctr() - return CalculatedDataConfig( - name="quantity", - value="quantity", - view_data=view_data, - source_configs=tuple( - config for config in [ctr_conf, y_intercept_conf, slope_conf] if config - ), + delta_delta_ct = CalcDoc( + "delta delta ct", + field="delta_delta_ct", + sources=[delta_ct_mean, delta_ct_mean_ref_sample], + view="sid_tdna", + source_only=True, ) - - -def quantity_mean( - view_data: ViewData, - quantity_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="quantity mean", - value="quantity_mean", - view_data=view_data, - source_configs=(quantity_conf,), + rq = CalcDoc( + "rq", field="rq", sources=[delta_delta_ct], view="sid_tdna", source_only=True ) - - -def quantity_sd( - view_data: ViewData, - quantity_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="quantity sd", - value="quantity_sd", - view_data=view_data, - source_configs=(quantity_conf,), + rq_min = CalcDoc("rq min", field="rq_min", sources=[rq], view="sid_tdna_blacklist") + rq_max = CalcDoc("rq max", field="rq_max", sources=[rq], view="sid_tdna_blacklist") + return [ + CTR, + y_intercept, + slope, + quantity, + amp_score, + cq_conf, + quantity_mean, + quantity_sd, + ct_mean, + ct_sd, + ct_mean_ref, + delta_ct_se, + delta_ct_mean, + delta_ct_mean_ref_sample, + delta_delta_ct, + rq, + rq_min, + rq_max, + ] + + +COMPARATIVE_CT_NODES: list[Node] = _comparative_ct_nodes() + + +def _standard_curve_nodes() -> list[Node]: + y_intercept = CalcDoc( + "y intercept", + field="y_intercept", + sources=[CTR], + view="tdna_role", ) - - -def delta_ct_se( - view_data: ViewData, - ct_sd_conf: CalculatedDataConfig, - ref_ct_sd_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta ct se", - value="delta_ct_se", - view_data=view_data, - source_configs=(ct_sd_conf, ref_ct_sd_conf), + slope = CalcDoc("slope", field="slope", sources=[CTR], view="tdna_role") + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR, y_intercept, slope], + view="sid_tdna_uuid", ) - - -def relative_rq( - view_data: ViewData, - quantity_mean_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="relative rq", - value="rq", - view_data=view_data, - source_configs=(quantity_mean_conf,), + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", ) - - -def relative_rq_min( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="relative rq min", - value="rq_min", - view_data=view_data, - source_configs=(rq_conf,), + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", ) - - -def relative_rq_max( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="relative rq max", - value="rq_max", - view_data=view_data, - source_configs=(rq_conf,), + quantity_mean = CalcDoc( + "quantity mean", field="quantity_mean", sources=[quantity], view="sid_tdna" ) - - -def delta_ct_mean( - view_data: ViewData, - adj_eq_ct_mean_conf: CalculatedDataConfig, - ref_adj_eq_ct_mean_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta ct mean", - value="delta_ct_mean", - view_data=view_data, - source_configs=(adj_eq_ct_mean_conf, ref_adj_eq_ct_mean_conf), + quantity_sd = CalcDoc( + "quantity sd", field="quantity_sd", sources=[quantity], view="sid_tdna" ) - - -def delta_delta_ct( - view_data: ViewData, - delta_ct_conf: CalculatedDataConfig, - ref_delta_ct_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta delta ct", - value="delta_delta_ct", - view_data=view_data, - source_configs=(delta_ct_conf, ref_delta_ct_conf), + ct_mean = CalcDoc("ct mean", field="ct_mean", sources=[CTR], view="sid_tdna") + ct_sd = CalcDoc("ct sd", field="ct_sd", sources=[CTR], view="sid_tdna") + r_squared = CalcDoc("r^2", field="r_squared", sources=[CTR], view="tdna_role") + efficiency = CalcDoc( + "efficiency", + field="efficiency", + sources=[CTR], + view="tdna_role", ) - - -def rq( - view_data: ViewData, - delta_delta_ct_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rq", - value="rq", - view_data=view_data, - source_configs=(delta_delta_ct_conf,), + return [ + CTR, + quantity, + amp_score, + cq_conf, + quantity_mean, + quantity_sd, + ct_mean, + ct_sd, + y_intercept, + slope, + r_squared, + efficiency, + ] + + +STANDARD_CURVE_NODES: list[Node] = _standard_curve_nodes() + + +def _relative_standard_curve_nodes() -> list[Node]: + y_intercept = CalcDoc( + "y intercept", + field="y_intercept", + sources=[CTR], + view="tdna_role", ) - - -def rq_min( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rq min", - value="rq_min", - view_data=view_data, - source_configs=(rq_conf,), + slope = CalcDoc("slope", field="slope", sources=[CTR], view="tdna_role") + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR, y_intercept, slope], + view="sid_tdna_uuid", + ) + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", + ) + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", + ) + quantity_mean = CalcDoc( + "quantity mean", field="quantity_mean", sources=[quantity], view="sid_tdna" + ) + quantity_sd = CalcDoc( + "quantity sd", field="quantity_sd", sources=[quantity], view="sid_tdna" + ) + ct_mean = CalcDoc("ct mean", field="ct_mean", sources=[CTR], view="sid_tdna") + ct_sd = CalcDoc("ct sd", field="ct_sd", sources=[CTR], view="sid_tdna") + relative_rq = CalcDoc( + "relative rq", + field="rq", + sources=[quantity_mean], + view="sid_tdna", + source_only=True, + ) + relative_rq_min = CalcDoc( + "relative rq min", field="rq_min", sources=[relative_rq], view="sid_tdna" + ) + relative_rq_max = CalcDoc( + "relative rq max", field="rq_max", sources=[relative_rq], view="sid_tdna" ) + r_squared = CalcDoc("r^2", field="r_squared", sources=[CTR], view="tdna_role") + efficiency = CalcDoc( + "efficiency", + field="efficiency", + sources=[CTR], + view="tdna_role", + ) + return [ + CTR, + quantity, + amp_score, + cq_conf, + quantity_mean, + quantity_sd, + ct_mean, + ct_sd, + relative_rq, + relative_rq_min, + relative_rq_max, + y_intercept, + slope, + r_squared, + efficiency, + ] + + +RELATIVE_STANDARD_CURVE_NODES: list[Node] = _relative_standard_curve_nodes() + + +def _presence_absence_nodes() -> list[Node]: + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR], + view="sid_tdna_uuid", + ) + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", + ) + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", + ) + rn_mean = CalcDoc( + "rn mean", + field="rn_mean", + sources=[NORM_REPORTER], + view="sid_tdna", + ) + rn_sd = CalcDoc("rn sd", field="rn_sd", sources=[NORM_REPORTER], view="sid_tdna") + return [ + CTR, + NORM_REPORTER, + quantity, + amp_score, + cq_conf, + rn_mean, + rn_sd, + ] -def rq_max( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rq max", - value="rq_max", - view_data=view_data, - source_configs=(rq_conf,), - ) +PRESENCE_ABSENCE_NODES: list[Node] = _presence_absence_nodes() def iter_comparative_ct_calc_docs( @@ -283,210 +309,65 @@ def iter_comparative_ct_calc_docs( r_sample: str, r_target: str, ) -> Iterator[CalculatedDocument]: - # Y-intercept, Slope, Quantity, Amplification score, Cq confidence - # Quantity Mean, Quantity SD, Ct Mean, Ct SD - # Delta Ct SE, Delta Ct Mean, - # Delta Delta Ct, RQ, RQ min, RQ max elements = AppbioQuantstudioExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_ref_tdna_view_data = SampleView( - reference=r_sample, sub_view=TargetView() - ).apply(elements) - sid_tdna_ref_view_data = SampleView( - sub_view=TargetView(is_reference=True, reference=r_target) - ).apply(elements) - sid_tdna_blacklist_view_data = SampleView( - sub_view=TargetView(blacklist=[r_target] if r_target is not None else None) - ).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - tdna_view_data = TargetRoleView().apply(elements) - - quantity_conf = quantity( - sid_tdna_uuid_view_data, - y_intercept(tdna_view_data), - slope(tdna_view_data), - ) - - configs = CalcDocsConfig( - [ - quantity_conf, - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - quantity_mean(sid_tdna_view_data, quantity_conf), - quantity_sd(sid_tdna_view_data, quantity_conf), - ct_mean(sid_tdna_view_data), - ct_sd(sid_tdna_view_data), - delta_ct_se( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ct_mean(sid_tdna_ref_view_data), - ), - rq_min( - sid_tdna_blacklist_view_data, - rq( - sid_tdna_view_data, - delta_delta_ct( - sid_tdna_view_data, - delta_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ct_mean(sid_tdna_ref_view_data), - ), - delta_ct_mean( - sid_ref_tdna_view_data, - ct_mean(sid_tdna_view_data), - ct_mean(sid_tdna_ref_view_data), - ), - ), - ), - ), - rq_max( - sid_tdna_blacklist_view_data, - rq( - sid_tdna_view_data, - delta_delta_ct( - sid_tdna_view_data, - delta_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ct_mean(sid_tdna_ref_view_data), - ), - delta_ct_mean( - sid_ref_tdna_view_data, - ct_mean(sid_tdna_view_data), - ct_mean(sid_tdna_ref_view_data), - ), - ), - ), - ), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + "sid_ref_tdna": SampleView(reference=r_sample, sub_view=TargetView()).apply( + elements + ), + "sid_tdna_ref": SampleView( + sub_view=TargetView(is_reference=True, reference=r_target) + ).apply(elements), + "sid_tdna_blacklist": SampleView( + sub_view=TargetView(blacklist=[r_target] if r_target is not None else None) + ).apply(elements), + "tdna_role": TargetRoleView().apply(elements), + } + yield from build_calc_docs(nodes=COMPARATIVE_CT_NODES, views=views) def iter_standard_curve_calc_docs( well_items: list[WellItem], ) -> Iterator[CalculatedDocument]: - # Y-Intercept, Slope, Quantity, Amplification score, Cq confidence - # Quantity Mean, Quantity SD, Ct Mean, Ct SD, - # R(superscript 2), Efficiency elements = AppbioQuantstudioExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - tdna_view_data = TargetRoleView().apply(elements) - - quantity_conf = quantity( - sid_tdna_uuid_view_data, - y_intercept(tdna_view_data), - slope(tdna_view_data), - ) - - configs = CalcDocsConfig( - [ - quantity_conf, - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - quantity_mean(sid_tdna_view_data, quantity_conf), - quantity_sd(sid_tdna_view_data, quantity_conf), - ct_mean(sid_tdna_view_data), - ct_sd(sid_tdna_view_data), - y_intercept(tdna_view_data), - r_squared(tdna_view_data), - slope(tdna_view_data), - efficiency(tdna_view_data), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + "tdna_role": TargetRoleView().apply(elements), + } + yield from build_calc_docs(nodes=STANDARD_CURVE_NODES, views=views) def iter_relative_standard_curve_calc_docs( well_items: list[WellItem], ) -> Iterator[CalculatedDocument]: - # Y-Intercept, Slope Quantity, Amplification score, Cq confidence - # Quantity Mean, Quantity SD, Ct Mean, Ct SD - # Relative RQ, Relative RQ min, Relative RQ max, - # R(superscript 2), Efficiency elements = AppbioQuantstudioExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - tdna_view_data = TargetRoleView().apply(elements) - - quantity_conf = quantity( - sid_tdna_uuid_view_data, - y_intercept(tdna_view_data), - slope(tdna_view_data), - ) - - configs = CalcDocsConfig( - [ - quantity_conf, - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - quantity_mean(sid_tdna_view_data, quantity_conf), - quantity_sd(sid_tdna_view_data, quantity_conf), - ct_mean(sid_tdna_view_data), - ct_sd(sid_tdna_view_data), - relative_rq_min( - sid_tdna_view_data, - relative_rq( - sid_tdna_view_data, - quantity_mean(sid_tdna_view_data, quantity_conf), - ), - ), - relative_rq_max( - sid_tdna_view_data, - relative_rq( - sid_tdna_view_data, - quantity_mean(sid_tdna_view_data, quantity_conf), - ), - ), - y_intercept(tdna_view_data), - r_squared(tdna_view_data), - slope(tdna_view_data), - efficiency(tdna_view_data), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + "tdna_role": TargetRoleView().apply(elements), + } + yield from build_calc_docs(nodes=RELATIVE_STANDARD_CURVE_NODES, views=views) def iter_presence_absence_calc_docs( well_items: list[WellItem], ) -> Iterator[CalculatedDocument]: - # Quantity, Amp score, Cq confidence, Rn Mean, Rn SD elements = AppbioQuantstudioExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - - configs = CalcDocsConfig( - [ - quantity(sid_tdna_uuid_view_data), - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - rn_mean(sid_tdna_view_data), - rn_sd(sid_tdna_view_data), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + } + yield from build_calc_docs(nodes=PRESENCE_ABSENCE_NODES, views=views) def iter_calculated_data_documents( diff --git a/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_data_creator.py b/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_data_creator.py index 38883603c4..bb62ce3057 100644 --- a/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_data_creator.py +++ b/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_data_creator.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable from pathlib import Path from allotropy.allotrope.models.shared.definitions.definitions import ( @@ -7,9 +6,6 @@ ) from allotropy.allotrope.models.shared.definitions.units import UNITLESS from allotropy.allotrope.schema_mappers.adm.pcr.rec._2024._09.qpcr import ( - CalculatedData, - CalculatedDataItem, - DataSource, Error, Measurement, MeasurementGroup, @@ -29,9 +25,6 @@ WellItem, ) from allotropy.parsers.constants import get_well_count_by_well_ids, NEGATIVE_ZERO -from allotropy.parsers.utils.calculated_data_documents.definition import ( - CalculatedDocument, -) def _create_processed_data_cubes( @@ -298,29 +291,6 @@ def create_metadata(header: Header, file_path: str) -> Metadata: ) -def create_calculated_data( - calculated_data_documents: Iterable[CalculatedDocument], -) -> CalculatedData: - return CalculatedData( - items=[ - CalculatedDataItem( - identifier=cal_doc.uuid, - name=cal_doc.name, - value=cal_doc.value, - unit=UNITLESS, - data_sources=[ - DataSource( - identifier=data_source.reference.uuid, - feature=data_source.feature, - ) - for data_source in cal_doc.data_sources - ], - ) - for cal_doc in calculated_data_documents - ], - ) - - def get_well_item_results( well_item: WellItem, results_data: dict[int, dict[str, Result]], diff --git a/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_parser.py b/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_parser.py index 1783a4666f..1ab7e4ee4e 100644 --- a/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_parser.py +++ b/src/allotropy/parsers/appbio_quantstudio/appbio_quantstudio_parser.py @@ -7,7 +7,6 @@ iter_calculated_data_documents, ) from allotropy.parsers.appbio_quantstudio.appbio_quantstudio_data_creator import ( - create_calculated_data, create_measurement_groups, create_metadata, enrich_wells_with_results, @@ -93,7 +92,7 @@ def parse_data( melt_data, result_metadata, ), - calculated_data=create_calculated_data(calculated_data_documents), + calculated_data=list(calculated_data_documents) or None, ) def create_data(self, named_file_contents: NamedFileContents) -> Data: diff --git a/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_calculated_documents.py b/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_calculated_documents.py index bf7de9c866..85727b41e0 100644 --- a/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_calculated_documents.py +++ b/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_calculated_documents.py @@ -1,23 +1,15 @@ from collections.abc import Iterator -from allotropy.calcdocs.appbio_quantstudio_designandanalysis.config import ( - CalculatedDataConfigWithOptional, +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + Measurement, + Node, ) from allotropy.calcdocs.appbio_quantstudio_designandanalysis.extractor import ( AppbioQuantstudioDAExtractor, ) -from allotropy.calcdocs.appbio_quantstudio_designandanalysis.views import ( - SampleView, - TargetRoleView, - TargetView, - UuidView, -) -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, -) -from allotropy.calcdocs.view import ViewData +from allotropy.calcdocs.views import SampleView, TargetRoleView, TargetView, UuidView from allotropy.parsers.appbio_quantstudio_designandanalysis.structure.generic.structure import ( WellItem, ) @@ -25,368 +17,330 @@ CalculatedDocument, ) - -def ctr() -> MeasurementConfig: - return MeasurementConfig( - name="cycle threshold result", - value="cycle_threshold_result", - required=True, - ) - - -def norm_reporter_result() -> MeasurementConfig: - return MeasurementConfig( - name="normalized reporter result", - value="normalized_reporter_result", - ) - - -def amplification_score(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="amplification score", - value="amp_score", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def cq_confidence(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="cq confidence", - value="cq_conf", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def y_intercept(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="y intercept", - value="y_intercept", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def slope(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="slope", - value="slope", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def ct_mean(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="ct mean", - value="ct_mean", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def ct_sd(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="ct sd", - value="ct_sd", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def ct_se(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="ct se", - value="ct_se", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def r_squared(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="r^2", - value="r_squared", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def efficiency(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="efficiency", - value="efficiency", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def standard_deviation(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="standard deviation", - value="standard_deviation", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def standard_error(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="standard error", - value="standard_error", - view_data=view_data, - source_configs=(ctr(),), - ) - - -def rn_mean(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rn mean", - value="rn_mean", - view_data=view_data, - source_configs=(norm_reporter_result(),), - ) - - -def rn_sd(view_data: ViewData) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rn sd", - value="rn_sd", - view_data=view_data, - source_configs=(norm_reporter_result(),), - ) - - -def quantity( - view_data: ViewData, - y_intercept_conf: CalculatedDataConfig | None = None, - slope_conf: CalculatedDataConfig | None = None, -) -> CalculatedDataConfig: - ctr_conf = ctr() - return CalculatedDataConfig( - name="quantity", - value="quantity", - view_data=view_data, - source_configs=tuple( - config for config in [ctr_conf, y_intercept_conf, slope_conf] if config - ), - ) - - -def quantity_mean( - view_data: ViewData, - quantity_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="quantity mean", - value="quantity_mean", - view_data=view_data, - source_configs=(quantity_conf,), - ) - - -def quantity_sd( - view_data: ViewData, - quantity_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="quantity sd", - value="quantity_sd", - view_data=view_data, - source_configs=(quantity_conf,), - ) - - -def delta_ct_sd( - view_data: ViewData, - ct_sd_conf: CalculatedDataConfig, - ref_ct_sd_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta equivalent ct sd", - value="delta_ct_sd", - view_data=view_data, - source_configs=(ct_sd_conf, ref_ct_sd_conf), - ) - - -def delta_ct_se( - view_data: ViewData, - ct_sd_conf: CalculatedDataConfig, - ref_ct_sd_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta equivalent ct se", - value="delta_ct_se", - view_data=view_data, - source_configs=(ct_sd_conf, ref_ct_sd_conf), - ) - - -def relative_rq( - view_data: ViewData, - quantity_mean_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="relative rq", - value="rq", - view_data=view_data, - source_configs=(quantity_mean_conf,), - ) - - -def relative_rq_min( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="relative rq min", - value="rq_min", - view_data=view_data, - source_configs=(rq_conf,), - ) - - -def relative_rq_max( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="relative rq max", - value="rq_max", - view_data=view_data, - source_configs=(rq_conf,), - ) - - -def eq_ct_mean( - view_data: ViewData, - ct_mean_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="equivalent ct mean", - value="eq_ct_mean", - view_data=view_data, - source_configs=(ct_mean_conf,), - ) +CTR = Measurement( + "cycle threshold result", field="cycle_threshold_result", required=True +) +NORM_REPORTER = Measurement( + "normalized reporter result", field="normalized_reporter_result" +) -def adj_eq_ct_mean( - view_data: ViewData, - eq_ct_mean_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfigWithOptional( - name="adjusted equivalent ct mean", - value="adj_eq_ct_mean", - view_data=view_data, +def _standard_curve_nodes() -> list[Node]: + y_intercept = CalcDoc( + "y intercept", + field="y_intercept", + sources=[CTR], + view="tdna_role", + ) + slope = CalcDoc("slope", field="slope", sources=[CTR], view="tdna_role") + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR, y_intercept, slope], + view="sid_tdna_uuid", + ) + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", + ) + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", + ) + quantity_mean = CalcDoc( + "quantity mean", field="quantity_mean", sources=[quantity], view="sid_tdna" + ) + quantity_sd = CalcDoc( + "quantity sd", field="quantity_sd", sources=[quantity], view="sid_tdna" + ) + ct_mean = CalcDoc("ct mean", field="ct_mean", sources=[CTR], view="sid_tdna") + ct_sd = CalcDoc("ct sd", field="ct_sd", sources=[CTR], view="sid_tdna") + r_squared = CalcDoc("r^2", field="r_squared", sources=[CTR], view="tdna_role") + efficiency = CalcDoc( + "efficiency", + field="efficiency", + sources=[CTR], + view="tdna_role", + ) + standard_deviation = CalcDoc( + "standard deviation", + field="standard_deviation", + sources=[CTR], + view="sid_tdna_uuid", + ) + standard_error = CalcDoc( + "standard error", + field="standard_error", + sources=[CTR], + view="sid_tdna_uuid", + ) + return [ + CTR, + quantity, + amp_score, + cq_conf, + quantity_mean, + quantity_sd, + ct_mean, + ct_sd, + y_intercept, + r_squared, + slope, + efficiency, + standard_deviation, + standard_error, + ] + + +STANDARD_CURVE_NODES: list[Node] = _standard_curve_nodes() + + +def _relative_standard_curve_nodes() -> list[Node]: + y_intercept = CalcDoc( + "y intercept", + field="y_intercept", + sources=[CTR], + view="tdna_role", + source_only=True, + ) + slope = CalcDoc( + "slope", + field="slope", + sources=[CTR], + view="tdna_role", + source_only=True, + ) + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR, y_intercept, slope], + view="sid_tdna_uuid", + ) + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", + ) + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", + ) + ct_mean = CalcDoc("ct mean", field="ct_mean", sources=[CTR], view="sid_tdna") + ct_sd = CalcDoc("ct sd", field="ct_sd", sources=[CTR], view="sid_tdna") + ct_sd_ref = CalcDoc( + "ct sd", + field="ct_sd", + sources=[CTR], + view="sid_tdna_ref", + source_only=True, + ) + ct_se = CalcDoc( + "ct se", + field="ct_se", + sources=[CTR], + view="sid_tdna", + source_only=True, + ) + ct_se_ref = CalcDoc( + "ct se", + field="ct_se", + sources=[CTR], + view="sid_tdna_ref", + source_only=True, + ) + delta_equivalent_ct_sd = CalcDoc( + "delta equivalent ct sd", + field="delta_ct_sd", + sources=[ct_sd, ct_sd_ref], + view="sid_tdna", + ) + delta_equivalent_ct_se = CalcDoc( + "delta equivalent ct se", + field="delta_ct_se", + sources=[ct_se, ct_se_ref], + view="sid_tdna", + ) + quantity_mean = CalcDoc( + "quantity mean", + field="quantity_mean", + sources=[quantity], + view="sid_tdna", + source_only=True, + ) + relative_rq = CalcDoc( + "relative rq", + field="rq", + sources=[quantity_mean], + view="sid_tdna", + source_only=True, + ) + relative_rq_min = CalcDoc( + "relative rq min", field="rq_min", sources=[relative_rq], view="sid_tdna" + ) + relative_rq_max = CalcDoc( + "relative rq max", field="rq_max", sources=[relative_rq], view="sid_tdna" + ) + # Deep dependency chain for rq_min/rq_max + equivalent_ct_mean = CalcDoc( + "equivalent ct mean", + field="eq_ct_mean", + sources=[ct_mean], + view="sid_tdna", + source_only=True, + ) + adjusted_equivalent_ct_mean = CalcDoc( + "adjusted equivalent ct mean", + field="adj_eq_ct_mean", + sources=[equivalent_ct_mean], + view="sid_tdna", + source_only=True, optional=True, - source_configs=(eq_ct_mean_conf,), - ) - - -def delta_ct( - view_data: ViewData, - adj_eq_ct_mean_conf: CalculatedDataConfig, - ref_adj_eq_ct_mean_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta equivalent ct mean", - value="delta_ct_mean", - view_data=view_data, - source_configs=(adj_eq_ct_mean_conf, ref_adj_eq_ct_mean_conf), - ) - - -def delta_delta_ct( - view_data: ViewData, - delta_ct_conf: CalculatedDataConfig, - ref_delta_ct_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="delta delta equivalent ct", - value="delta_delta_ct", - view_data=view_data, - source_configs=(delta_ct_conf, ref_delta_ct_conf), ) - - -def rq( - view_data: ViewData, - delta_delta_ct_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rq", - value="rq", - view_data=view_data, - source_configs=(delta_delta_ct_conf,), - ) - - -def rq_min( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rq min", - value="rq_min", - view_data=view_data, - source_configs=(rq_conf,), - ) - - -def rq_max( - view_data: ViewData, - rq_conf: CalculatedDataConfig, -) -> CalculatedDataConfig: - return CalculatedDataConfig( - name="rq max", - value="rq_max", - view_data=view_data, - source_configs=(rq_conf,), + adjusted_equivalent_ct_mean_ref = CalcDoc( + "adjusted equivalent ct mean", + field="adj_eq_ct_mean", + sources=[equivalent_ct_mean], + view="sid_tdna_ref", + source_only=True, + optional=True, ) + delta_equivalent_ct_mean = CalcDoc( + "delta equivalent ct mean", + field="delta_ct_mean", + sources=[adjusted_equivalent_ct_mean, adjusted_equivalent_ct_mean_ref], + view="sid_tdna", + source_only=True, + ) + delta_equivalent_ct_mean_ref_sample = CalcDoc( + "delta equivalent ct mean", + field="delta_ct_mean", + sources=[adjusted_equivalent_ct_mean, adjusted_equivalent_ct_mean_ref], + view="sid_ref_tdna", + source_only=True, + ) + delta_delta_equivalent_ct = CalcDoc( + "delta delta equivalent ct", + field="delta_delta_ct", + sources=[delta_equivalent_ct_mean, delta_equivalent_ct_mean_ref_sample], + view="sid_tdna", + source_only=True, + ) + rq = CalcDoc( + "rq", + field="rq", + sources=[delta_delta_equivalent_ct], + view="sid_tdna", + source_only=True, + ) + rq_min = CalcDoc("rq min", field="rq_min", sources=[rq], view="sid_tdna_blacklist") + rq_max = CalcDoc("rq max", field="rq_max", sources=[rq], view="sid_tdna_blacklist") + return [ + CTR, + quantity, + amp_score, + cq_conf, + ct_mean, + ct_sd, + ct_sd_ref, + ct_se, + ct_se_ref, + delta_equivalent_ct_sd, + delta_equivalent_ct_se, + quantity_mean, + relative_rq, + relative_rq_min, + relative_rq_max, + equivalent_ct_mean, + adjusted_equivalent_ct_mean, + adjusted_equivalent_ct_mean_ref, + delta_equivalent_ct_mean, + delta_equivalent_ct_mean_ref_sample, + delta_delta_equivalent_ct, + rq, + rq_min, + rq_max, + y_intercept, + slope, + ] + + +RELATIVE_STANDARD_CURVE_NODES: list[Node] = _relative_standard_curve_nodes() + + +def _presence_absence_nodes() -> list[Node]: + quantity = CalcDoc( + "quantity", + field="quantity", + sources=[CTR], + view="sid_tdna_uuid", + ) + amp_score = CalcDoc( + "amplification score", + field="amp_score", + sources=[CTR], + view="sid_tdna_uuid", + ) + cq_conf = CalcDoc( + "cq confidence", + field="cq_conf", + sources=[CTR], + view="sid_tdna_uuid", + ) + rn_mean = CalcDoc( + "rn mean", + field="rn_mean", + sources=[NORM_REPORTER], + view="sid_tdna", + ) + rn_sd = CalcDoc("rn sd", field="rn_sd", sources=[NORM_REPORTER], view="sid_tdna") + return [ + CTR, + NORM_REPORTER, + quantity, + amp_score, + cq_conf, + rn_mean, + rn_sd, + ] + + +PRESENCE_ABSENCE_NODES: list[Node] = _presence_absence_nodes() + + +def _primary_analysis_nodes() -> list[Node]: + ct_mean = CalcDoc("ct mean", field="ct_mean", sources=[CTR], view="sid_tdna") + ct_sd = CalcDoc("ct sd", field="ct_sd", sources=[CTR], view="sid_tdna") + ct_se = CalcDoc("ct se", field="ct_se", sources=[CTR], view="sid_tdna") + return [ + CTR, + ct_mean, + ct_sd, + ct_se, + ] + + +PRIMARY_ANALYSIS_NODES: list[Node] = _primary_analysis_nodes() def iter_standard_curve_calc_docs( well_items: list[WellItem], ) -> Iterator[CalculatedDocument]: - # Y-intercept, Slope, Quantity, Amp score, Cq confidence, - # Quantity Mean, Quantity SD, Ct Mean, Ct SD - # R^2, Efficiency, Standard Deviation, Standard Error elements = AppbioQuantstudioDAExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - tdna_view_data = TargetRoleView().apply(elements) - - quantity_conf = quantity( - sid_tdna_uuid_view_data, - y_intercept(tdna_view_data), - slope(tdna_view_data), - ) - - configs = CalcDocsConfig( - [ - quantity_conf, - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - quantity_mean(sid_tdna_view_data, quantity_conf), - quantity_sd(sid_tdna_view_data, quantity_conf), - ct_mean(sid_tdna_view_data), - ct_sd(sid_tdna_view_data), - y_intercept(tdna_view_data), - r_squared(tdna_view_data), - slope(tdna_view_data), - efficiency(tdna_view_data), - standard_deviation(sid_tdna_uuid_view_data), - standard_error(sid_tdna_uuid_view_data), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + "tdna_role": TargetRoleView().apply(elements), + } + yield from build_calc_docs(nodes=STANDARD_CURVE_NODES, views=views) def iter_relative_standard_curve_calc_docs( @@ -394,199 +348,44 @@ def iter_relative_standard_curve_calc_docs( r_sample: str | None, r_target: str | None, ) -> Iterator[CalculatedDocument]: - # Y-Intercept, Slope, Quantity, Amp score, Cq confidence, - # Ct Mean, Ct SD, Ct SE, - # Delta Ct SD, Delta Ct SE, - # Relative RQ min, Relative RQ max, Relative RQ, Quantity Mean, - # RQ min, RQ max, RQ, Delta Delta Ct, Delta Ct, - # Adjusted equivalent Ct mean, equivalent Ct mean elements = AppbioQuantstudioDAExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_ref_tdna_view_data = SampleView( - reference=r_sample, sub_view=TargetView() - ).apply(elements) - sid_tdna_ref_view_data = SampleView( - sub_view=TargetView(is_reference=True, reference=r_target) - ).apply(elements) - sid_tdna_blacklist_view_data = SampleView( - sub_view=TargetView(blacklist=[r_target] if r_target is not None else None) - ).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - tdna_view_data = TargetRoleView().apply(elements) - - quantity_conf = quantity( - sid_tdna_uuid_view_data, - y_intercept(tdna_view_data), - slope(tdna_view_data), - ) - - configs = CalcDocsConfig( - [ - quantity_conf, - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - ct_mean(sid_tdna_view_data), - ct_sd(sid_tdna_view_data), - delta_ct_sd( - sid_tdna_view_data, - ct_sd(sid_tdna_view_data), - ct_sd(sid_tdna_ref_view_data), - ), - delta_ct_se( - sid_tdna_view_data, - ct_se(sid_tdna_view_data), - ct_se(sid_tdna_ref_view_data), - ), - relative_rq_min( - sid_tdna_view_data, - relative_rq( - sid_tdna_view_data, - quantity_mean(sid_tdna_view_data, quantity_conf), - ), - ), - relative_rq_max( - sid_tdna_view_data, - relative_rq( - sid_tdna_view_data, - quantity_mean(sid_tdna_view_data, quantity_conf), - ), - ), - rq_min( - sid_tdna_blacklist_view_data, - rq( - sid_tdna_view_data, - delta_delta_ct( - sid_tdna_view_data, - delta_ct( - sid_tdna_view_data, - adj_eq_ct_mean( - sid_tdna_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - adj_eq_ct_mean( - sid_tdna_ref_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - ), - delta_ct( - sid_ref_tdna_view_data, - adj_eq_ct_mean( - sid_tdna_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - adj_eq_ct_mean( - sid_tdna_ref_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - ), - ), - ), - ), - rq_max( - sid_tdna_blacklist_view_data, - rq( - sid_tdna_view_data, - delta_delta_ct( - sid_tdna_view_data, - delta_ct( - sid_tdna_view_data, - adj_eq_ct_mean( - sid_tdna_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - adj_eq_ct_mean( - sid_tdna_ref_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - ), - delta_ct( - sid_ref_tdna_view_data, - adj_eq_ct_mean( - sid_tdna_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - adj_eq_ct_mean( - sid_tdna_ref_view_data, - eq_ct_mean( - sid_tdna_view_data, - ct_mean(sid_tdna_view_data), - ), - ), - ), - ), - ), - ), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + "sid_ref_tdna": SampleView(reference=r_sample, sub_view=TargetView()).apply( + elements + ), + "sid_tdna_ref": SampleView( + sub_view=TargetView(is_reference=True, reference=r_target) + ).apply(elements), + "sid_tdna_blacklist": SampleView( + sub_view=TargetView(blacklist=[r_target] if r_target is not None else None) + ).apply(elements), + "tdna_role": TargetRoleView().apply(elements), + } + yield from build_calc_docs(nodes=RELATIVE_STANDARD_CURVE_NODES, views=views) def iter_presence_absence_calc_docs( well_items: list[WellItem], ) -> Iterator[CalculatedDocument]: - # Quantity, Amp score, Cq confidence, Rn Mean, Rn SD elements = AppbioQuantstudioDAExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - sid_tdna_uuid_view_data = SampleView( - sub_view=TargetView(sub_view=UuidView()) - ).apply(elements) - - configs = CalcDocsConfig( - [ - quantity(sid_tdna_uuid_view_data), - amplification_score(sid_tdna_uuid_view_data), - cq_confidence(sid_tdna_uuid_view_data), - rn_mean(sid_tdna_view_data), - rn_sd(sid_tdna_view_data), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + "sid_tdna_uuid": SampleView(sub_view=TargetView(sub_view=UuidView())).apply( + elements + ), + } + yield from build_calc_docs(nodes=PRESENCE_ABSENCE_NODES, views=views) def iter_primary_analysis_calc_docs( well_items: list[WellItem], ) -> Iterator[CalculatedDocument]: - # Ct Mean, Ct SD, Ct SE elements = AppbioQuantstudioDAExtractor.get_elements(well_items) - - sid_tdna_view_data = SampleView(sub_view=TargetView()).apply(elements) - - configs = CalcDocsConfig( - [ - ct_mean(sid_tdna_view_data), - ct_sd(sid_tdna_view_data), - ct_se(sid_tdna_view_data), - ] - ) - - for calc_doc in configs.construct(): - yield from calc_doc.iter_struct() + views = { + "sid_tdna": SampleView(sub_view=TargetView()).apply(elements), + } + yield from build_calc_docs(nodes=PRIMARY_ANALYSIS_NODES, views=views) diff --git a/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_data_creator.py b/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_data_creator.py index b72193676b..d0c73a452a 100644 --- a/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_data_creator.py +++ b/src/allotropy/parsers/appbio_quantstudio_designandanalysis/appbio_quantstudio_designandanalysis_data_creator.py @@ -6,9 +6,6 @@ ) from allotropy.allotrope.models.shared.definitions.units import UNITLESS from allotropy.allotrope.schema_mappers.adm.pcr.rec._2024._09.qpcr import ( - CalculatedData, - CalculatedDataItem, - DataSource, Error, Measurement, MeasurementGroup, @@ -57,6 +54,9 @@ StandardCurveCreator, ) from allotropy.parsers.constants import NEGATIVE_ZERO +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) def create_metadata( @@ -243,25 +243,8 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]: ] -def create_calculated_data(data: Data) -> CalculatedData: - return CalculatedData( - items=[ - CalculatedDataItem( - identifier=calc_doc.uuid, - name=calc_doc.name, - value=calc_doc.value, - unit=UNITLESS, - data_sources=[ - DataSource( - identifier=data_source.reference.uuid, - feature=data_source.feature, - ) - for data_source in calc_doc.data_sources - ], - ) - for calc_doc in data.calculated_documents - ], - ) +def create_calculated_data(data: Data) -> list[CalculatedDocument] | None: + return data.calculated_documents or None def create_data(reader: DesignQuantstudioReader) -> Data: diff --git a/src/allotropy/parsers/beckman_pharmspec/beckman_pharmspec_structure.py b/src/allotropy/parsers/beckman_pharmspec/beckman_pharmspec_structure.py index 1d568bf88d..1abf0b7c35 100644 --- a/src/allotropy/parsers/beckman_pharmspec/beckman_pharmspec_structure.py +++ b/src/allotropy/parsers/beckman_pharmspec/beckman_pharmspec_structure.py @@ -10,9 +10,7 @@ import pandas as pd from allotropy.allotrope.schema_mappers.adm.solution_analyzer.rec._2024._09.solution_analyzer import ( - CalculatedDataItem, DataProcessing, - DataSource, DistributionDocument, Error, Measurement, @@ -27,6 +25,11 @@ VALID_CALCS, ) from allotropy.parsers.constants import NEGATIVE_ZERO, NOT_APPLICABLE +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, + DataSource, + Referenceable, +) from allotropy.parsers.utils.pandas import ( map_rows, SeriesData, @@ -197,7 +200,7 @@ def create_measurement_groups( def create_calculated_data( distributions: list[Distribution], -) -> list[CalculatedDataItem]: +) -> list[CalculatedDocument]: particle_size_sources = defaultdict(list) for source in [ feature @@ -208,14 +211,14 @@ def create_calculated_data( particle_size_sources[source.particle_size].append(source) return [ - CalculatedDataItem( - identifier=random_uuid_str(), + CalculatedDocument( + uuid=random_uuid_str(), name=f"{distribution.name}_{name}".lower(), value=value, unit=UNIT_LOOKUP[name], data_sources=[ DataSource( - identifier=x.distribution_identifier, + reference=Referenceable(uuid=x.distribution_identifier), feature=name.replace("_", " "), ) for x in particle_size_sources[feature.particle_size] diff --git a/src/allotropy/parsers/bmg_labtech_smart_control/bmg_labtech_smart_control_structure.py b/src/allotropy/parsers/bmg_labtech_smart_control/bmg_labtech_smart_control_structure.py index 0773d8608c..d6127b8d33 100644 --- a/src/allotropy/parsers/bmg_labtech_smart_control/bmg_labtech_smart_control_structure.py +++ b/src/allotropy/parsers/bmg_labtech_smart_control/bmg_labtech_smart_control_structure.py @@ -15,6 +15,12 @@ Metadata, ScanPositionSettingPlateReader, ) +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + Measurement as CalcMeasurement, + Node, +) from allotropy.calcdocs.bmg_labtech_smart_control.extractor import ( BmgLabtechSmartControlExtractor, ) @@ -22,11 +28,6 @@ BlankRoleTypeView, CorrectedView, ) -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, -) from allotropy.parsers.bmg_labtech_smart_control.bmg_labtech_smart_control_reader import ( BmgLabtechSmartControlReader, ) @@ -193,34 +194,24 @@ def create_calculated_data_documents( ) elements = BmgLabtechSmartControlExtractor.get_elements(calc_data_measurements) - corrected_view_data = CorrectedView().apply(elements=elements) - role_type_view_data = BlankRoleTypeView().apply(elements=elements) - - measurement_conf = MeasurementConfig( - name="fluorescence", - value="fluorescence", - ) - average_of_blank_used_conf = CalculatedDataConfig( - name="Average of all blanks used", - value="average_of_blank_used", - view_data=role_type_view_data, - source_configs=(measurement_conf,), + fluorescence = CalcMeasurement("fluorescence", field="fluorescence") + average_of_blanks = CalcDoc( + "Average of all blanks used", + field="average_of_blank_used", + sources=[fluorescence], + view="role_type", unit=RelativeFluorescenceUnit.unit, ) - - corrected_conf = CalculatedDataConfig( - name="Blank corrected based on Raw Data (480-14/520-30)", - value="corrected_value", - view_data=corrected_view_data, - source_configs=(measurement_conf, average_of_blank_used_conf), + blank_corrected = CalcDoc( + "Blank corrected based on Raw Data (480-14/520-30)", + field="corrected_value", + sources=[fluorescence, average_of_blanks], + view="corrected", unit=RelativeFluorescenceUnit.unit, ) - - configs = CalcDocsConfig([average_of_blank_used_conf, corrected_conf]) - - calc_docs = [ - calc_doc - for parent_calc_doc in configs.construct() - for calc_doc in parent_calc_doc.iter_struct() - ] - return calc_docs + nodes: list[Node] = [fluorescence, average_of_blanks, blank_corrected] + views = { + "role_type": BlankRoleTypeView().apply(elements=elements), + "corrected": CorrectedView().apply(elements=elements), + } + return build_calc_docs(nodes=nodes, views=views) diff --git a/src/allotropy/parsers/chemometec_nucleoview/nucleoview_structure.py b/src/allotropy/parsers/chemometec_nucleoview/nucleoview_structure.py index 7d2e656f9c..8b5b91da5a 100644 --- a/src/allotropy/parsers/chemometec_nucleoview/nucleoview_structure.py +++ b/src/allotropy/parsers/chemometec_nucleoview/nucleoview_structure.py @@ -9,7 +9,6 @@ Percent, ) from allotropy.allotrope.schema_mappers.adm.cell_counting.rec._2024._09.cell_counting import ( - CalculatedDataItem, Error, Measurement, MeasurementGroup, @@ -29,6 +28,7 @@ NOT_APPLICABLE, ) from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, DataSource, Referenceable, ) @@ -63,7 +63,7 @@ def create_metadata(data: SeriesData, file_path: str) -> Metadata: def create_measurement_groups( data: SeriesData, -) -> tuple[MeasurementGroup, list[CalculatedDataItem] | None]: +) -> tuple[MeasurementGroup, list[CalculatedDocument] | None]: timestamp = data.get(str, "Date time") errors = [] if timestamp: @@ -140,13 +140,13 @@ def _get_calculated_data( groups: list[MeasurementGroup], cell_diameter_standard_deviation: float | None, percentage_of_cells_with_five_or_more: float | None, -) -> list[CalculatedDataItem] | None: +) -> list[CalculatedDocument] | None: result = [] for group in groups: if cell_diameter_standard_deviation: result.append( - CalculatedDataItem( - identifier=random_uuid_str(), + CalculatedDocument( + uuid=random_uuid_str(), name="Cell diameter standard deviation (um)", value=cell_diameter_standard_deviation, data_sources=[ @@ -162,8 +162,8 @@ def _get_calculated_data( ) if percentage_of_cells_with_five_or_more: result.append( - CalculatedDataItem( - identifier=random_uuid_str(), + CalculatedDocument( + uuid=random_uuid_str(), name="Percentage of cells with five or more", value=percentage_of_cells_with_five_or_more, data_sources=[ diff --git a/src/allotropy/parsers/cytiva_biacore_t200_control/cytiva_biacore_t200_control_data_creator.py b/src/allotropy/parsers/cytiva_biacore_t200_control/cytiva_biacore_t200_control_data_creator.py index 9932c9713b..4eaabd91db 100644 --- a/src/allotropy/parsers/cytiva_biacore_t200_control/cytiva_biacore_t200_control_data_creator.py +++ b/src/allotropy/parsers/cytiva_biacore_t200_control/cytiva_biacore_t200_control_data_creator.py @@ -29,15 +29,16 @@ ReportPoint, ) from allotropy.allotrope.schema_mappers.data_cube import DataCube, DataCubeComponent -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + Measurement as CalcMeasurement, + Node, + UuidView, ) from allotropy.calcdocs.cytiva_biacore_t200_control.extractor import ( CytivaBiacoreExtractor, ) -from allotropy.calcdocs.cytiva_biacore_t200_control.views import ReportPointDataView from allotropy.named_file_contents import NamedFileContents from allotropy.parsers.constants import NOT_APPLICABLE from allotropy.parsers.cytiva_biacore_t200_control import constants @@ -253,57 +254,54 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]: ] -def create_calculated_data(data: Data) -> list[CalculatedDocument]: - report_point_data_view = ReportPointDataView().apply( - CytivaBiacoreExtractor.sample_data_to_elements(data.sample_data) +def _get_biacore_nodes() -> list[Node]: + absolute_resonance = CalcMeasurement( + "Absolute Resonance", field="absolute_resonance" ) - absolute_resonance_conf = MeasurementConfig( - name="Absolute Resonance", - value="absolute_resonance", + min_resonance = CalcDoc( + "Min Resonance", + field="min_resonance", + sources=[absolute_resonance], + view="uuid", + unit=ResponseUnit.unit, ) - - configs = CalcDocsConfig( - [ - CalculatedDataConfig( - name="Min Resonance", - value="min_resonance", - view_data=report_point_data_view, - source_configs=(absolute_resonance_conf,), - unit=ResponseUnit.unit, - ), - CalculatedDataConfig( - name="Max Resonance", - value="max_resonance", - view_data=report_point_data_view, - source_configs=(absolute_resonance_conf,), - unit=ResponseUnit.unit, - ), - CalculatedDataConfig( - name="LRSD", - value="lrsd", - view_data=report_point_data_view, - source_configs=(absolute_resonance_conf,), - unit=Unitless.unit, - ), - CalculatedDataConfig( - name="Slope", - value="slope", - view_data=report_point_data_view, - source_configs=(absolute_resonance_conf,), - unit=ResponseUnitPerSecond.unit, - ), - CalculatedDataConfig( - name="SD", - value="sd", - view_data=report_point_data_view, - source_configs=(absolute_resonance_conf,), - unit=Unitless.unit, - ), - ] + max_resonance = CalcDoc( + "Max Resonance", + field="max_resonance", + sources=[absolute_resonance], + view="uuid", + unit=ResponseUnit.unit, + ) + lrsd = CalcDoc( + "LRSD", + field="lrsd", + sources=[absolute_resonance], + view="uuid", + unit=Unitless.unit, + ) + slope = CalcDoc( + "Slope", + field="slope", + sources=[absolute_resonance], + view="uuid", + unit=ResponseUnitPerSecond.unit, ) + sd = CalcDoc( + "SD", + field="sd", + sources=[absolute_resonance], + view="uuid", + unit=Unitless.unit, + ) + return [absolute_resonance, min_resonance, max_resonance, lrsd, slope, sd] - return [ - calc_doc - for parent_calc_doc in configs.construct() - for calc_doc in parent_calc_doc.iter_struct() - ] + +_BIACORE_NODES: list[Node] = _get_biacore_nodes() + + +def create_calculated_data(data: Data) -> list[CalculatedDocument]: + elements = CytivaBiacoreExtractor.sample_data_to_elements(data.sample_data) + return build_calc_docs( + nodes=_BIACORE_NODES, + views={"uuid": UuidView().apply(elements)}, + ) diff --git a/src/allotropy/parsers/msd_workbench/msd_workbench_calculated_data_mapping.py b/src/allotropy/parsers/msd_workbench/msd_workbench_calculated_data_mapping.py index 8317cbd73e..240c022dc8 100644 --- a/src/allotropy/parsers/msd_workbench/msd_workbench_calculated_data_mapping.py +++ b/src/allotropy/parsers/msd_workbench/msd_workbench_calculated_data_mapping.py @@ -5,18 +5,14 @@ from allotropy.allotrope.schema_mappers.adm.plate_reader.rec._2024._06.plate_reader import ( Measurement, ) -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + FieldView, + Measurement as CalcMeasurement, + Node, ) from allotropy.calcdocs.msd_workbench.extractor import MsdWorkbenchExtractor -from allotropy.calcdocs.msd_workbench.views import ( - AssayIdentifierView, - LocationIdentifierView, - SampleIdentifierView, - WellPlateIdentifierView, -) from allotropy.parsers.msd_workbench.calculdated_data_structure import ( CalculatedDataMeasurementStructure, ) @@ -93,96 +89,88 @@ def create_calculated_data_groups( # we do not need additional data for the calculated data documents row_series.get_unread() elements = MsdWorkbenchExtractor.get_elements(calc_data_measurements) - sample_assay_view = SampleIdentifierView(sub_view=AssayIdentifierView()).apply( - elements - ) - assay_view = AssayIdentifierView().apply(elements) - sample_well_plate_location_id_view = AssayIdentifierView( - sub_view=WellPlateIdentifierView( - sub_view=LocationIdentifierView(SampleIdentifierView()) - ) - ).apply(elements) - measurement_conf = MeasurementConfig( - name="luminescence", - value="luminescence", - ) - adj_signal_config = CalculatedDataConfig( - name=CalculatedDataColumns.ADJUSTED_SIGNAL.value, - value="adjusted_signal", - view_data=sample_assay_view, - source_configs=(measurement_conf,), - ) - mean_config = CalculatedDataConfig( - name=CalculatedDataColumns.MEAN.value, - value="mean", - view_data=assay_view, - source_configs=(measurement_conf,), - ) - adj_signal_mean_config = CalculatedDataConfig( - name=CalculatedDataColumns.ADJ_SIG_MEAN.value, - value="adj_sig_mean", - view_data=assay_view, - source_configs=(mean_config,), + views = { + "sample_assay": FieldView( + "sample_identifier", sub_view=FieldView("assay_identifier") + ).apply(elements), + "assay": FieldView("assay_identifier").apply(elements), + "assay_plate_loc_sample": FieldView( + "assay_identifier", + sub_view=FieldView( + "well_plate_identifier", + sub_view=FieldView( + "location_identifier", sub_view=FieldView("sample_identifier") + ), + ), + ).apply(elements), + } + luminescence = CalcMeasurement("luminescence", field="luminescence") + mean = CalcDoc("Mean", field="mean", sources=[luminescence], view="assay") + adjusted_signal = CalcDoc( + CalculatedDataColumns.ADJUSTED_SIGNAL.value, + field="adjusted_signal", + sources=[luminescence], + view="sample_assay", ) - rsquared_config = CalculatedDataConfig( - name="R-Squared", - value="fit_statistic_rsquared", - view_data=assay_view, - source_configs=(measurement_conf,), + adj_sig_mean = CalcDoc( + CalculatedDataColumns.ADJ_SIG_MEAN.value, + field="adj_sig_mean", + sources=[mean], + view="assay", ) - cv_config = CalculatedDataConfig( - name=CalculatedDataColumns.CV.value, - value="cv", - view_data=assay_view, - source_configs=(measurement_conf,), + r_squared = CalcDoc( + "R-Squared", + field="fit_statistic_rsquared", + sources=[luminescence], + view="assay", ) - percent_recovery_config = CalculatedDataConfig( - name=CalculatedDataColumns.PERCENT_RECOVERY.value, - value="percent_recovery", - view_data=sample_well_plate_location_id_view, - source_configs=(measurement_conf,), + cv = CalcDoc( + CalculatedDataColumns.CV.value, + field="cv", + sources=[luminescence], + view="assay", ) - percent_recovery_mean_config = CalculatedDataConfig( - name=CalculatedDataColumns.PERCENT_RECOVERY_MEAN.value, - value="percent_recovery_mean", - view_data=assay_view, - source_configs=(percent_recovery_config,), + percent_recovery = CalcDoc( + CalculatedDataColumns.PERCENT_RECOVERY.value, + field="percent_recovery", + sources=[luminescence], + view="assay_plate_loc_sample", ) - calc_concentration_config = CalculatedDataConfig( - name=CalculatedDataColumns.CALC_CONCENTRATION.value, - value="calc_concentration", - view_data=sample_well_plate_location_id_view, - source_configs=(measurement_conf,), + percent_recovery_mean = CalcDoc( + CalculatedDataColumns.PERCENT_RECOVERY_MEAN.value, + field="percent_recovery_mean", + sources=[percent_recovery], + view="assay", ) - calc_conc_mean_config = CalculatedDataConfig( - name="Calc. Concentration Mean", - value="calc_conc_mean", - view_data=assay_view, - source_configs=(calc_concentration_config,), + calc_concentration = CalcDoc( + CalculatedDataColumns.CALC_CONCENTRATION.value, + field="calc_concentration", + sources=[luminescence], + view="assay_plate_loc_sample", ) - calc_conc_cv_config = CalculatedDataConfig( - name=CalculatedDataColumns.CALC_CONC_CV.value, - value="calc_conc_cv", - view_data=assay_view, - source_configs=(calc_concentration_config,), + calc_conc_mean = CalcDoc( + "Calc. Concentration Mean", + field="calc_conc_mean", + sources=[calc_concentration], + view="assay", ) - configs = CalcDocsConfig( - [ - mean_config, - adj_signal_config, - adj_signal_mean_config, - rsquared_config, - cv_config, - percent_recovery_config, - percent_recovery_mean_config, - calc_concentration_config, - calc_conc_mean_config, - calc_conc_cv_config, - ] + calc_conc_cv = CalcDoc( + CalculatedDataColumns.CALC_CONC_CV.value, + field="calc_conc_cv", + sources=[calc_concentration], + view="assay", ) - calc_docs = [ - calc_doc - for parent_calc_doc in configs.construct() - for calc_doc in parent_calc_doc.iter_struct() + nodes: list[Node] = [ + luminescence, + mean, + adjusted_signal, + adj_sig_mean, + r_squared, + cv, + percent_recovery, + percent_recovery_mean, + calc_concentration, + calc_conc_mean, + calc_conc_cv, ] - return calc_docs + return build_calc_docs(nodes=nodes, views=views) diff --git a/src/allotropy/parsers/qiacuity_dpcr/qiacuity_dpcr_calculated_data.py b/src/allotropy/parsers/qiacuity_dpcr/qiacuity_dpcr_calculated_data.py index 86e0d7e874..c4dd330beb 100644 --- a/src/allotropy/parsers/qiacuity_dpcr/qiacuity_dpcr_calculated_data.py +++ b/src/allotropy/parsers/qiacuity_dpcr/qiacuity_dpcr_calculated_data.py @@ -2,41 +2,37 @@ from collections.abc import Iterable -from allotropy.allotrope.schema_mappers.adm.pcr.BENCHLING._2023._09.dpcr import ( - CalculatedDataItem, - DataSource, -) from allotropy.parsers.qiacuity_dpcr.constants import CALCULATED_DATA_CONFIGS from allotropy.parsers.utils.calculated_data_documents.definition import ( - CalculatedDocument as UtilsCalculatedDocument, - DataSource as UtilsDataSource, - Referenceable as UtilsReferenceable, + CalculatedDocument, + DataSource, + Referenceable, ) from allotropy.parsers.utils.pandas import SeriesData from allotropy.parsers.utils.uuids import random_uuid_str -def _iter_row_calculated_docs(row: SeriesData) -> Iterable[UtilsCalculatedDocument]: +def _iter_row_calculated_docs(row: SeriesData) -> Iterable[CalculatedDocument]: measurement_identifier = row.get(str, "_measurement_identifier") if not measurement_identifier: return [] - docs: list[UtilsCalculatedDocument] = [] + docs: list[CalculatedDocument] = [] - measurement_ref = UtilsReferenceable(uuid=measurement_identifier) + measurement_ref = Referenceable(uuid=measurement_identifier) for conf in CALCULATED_DATA_CONFIGS: value = row.get(float, conf["keys"]) if value is None: continue docs.append( - UtilsCalculatedDocument( + CalculatedDocument( uuid=random_uuid_str(), name=conf["name"], value=float(value), unit=conf["unit"], data_sources=[ - UtilsDataSource( + DataSource( feature=conf.get("feature", conf["name"]), reference=measurement_ref, ) @@ -47,32 +43,8 @@ def _iter_row_calculated_docs(row: SeriesData) -> Iterable[UtilsCalculatedDocume return docs -def _docs_to_benchling_items( - docs: Iterable[UtilsCalculatedDocument], -) -> list[CalculatedDataItem]: - items: list[CalculatedDataItem] = [] - for doc in docs: - for flat_doc in doc.iter_struct(): - items.append( - CalculatedDataItem( - identifier=flat_doc.uuid, - name=flat_doc.name, - value=flat_doc.value, - unit=flat_doc.unit or "", - data_sources=[ - DataSource( - identifier=ds.reference.uuid, - feature=ds.feature, - ) - for ds in flat_doc.data_sources - ], - ) - ) - return items - - -def create_calculated_data(rows: list[SeriesData]) -> list[CalculatedDataItem]: - docs: list[UtilsCalculatedDocument] = [] +def create_calculated_data(rows: list[SeriesData]) -> list[CalculatedDocument]: + docs: list[CalculatedDocument] = [] for row in rows: docs.extend(list(_iter_row_calculated_docs(row))) - return _docs_to_benchling_items(docs) + return docs diff --git a/src/allotropy/parsers/unchained_labs_lunatic_stunner/unchained_labs_lunatic_stunner_calcdocs.py b/src/allotropy/parsers/unchained_labs_lunatic_stunner/unchained_labs_lunatic_stunner_calcdocs.py index 49db7e4066..86b5d2668f 100644 --- a/src/allotropy/parsers/unchained_labs_lunatic_stunner/unchained_labs_lunatic_stunner_calcdocs.py +++ b/src/allotropy/parsers/unchained_labs_lunatic_stunner/unchained_labs_lunatic_stunner_calcdocs.py @@ -2,20 +2,291 @@ from allotropy.allotrope.schema_mappers.adm.plate_reader.rec._2025._03.plate_reader import ( MeasurementGroup, ) -from allotropy.calcdocs.config import ( - CalcDocsConfig, - CalculatedDataConfig, - MeasurementConfig, -) -from allotropy.calcdocs.unchained_labs_lunatic_stunner.extractor import LunaticExtractor -from allotropy.calcdocs.unchained_labs_lunatic_stunner.views import ( - DetectionTypeView, +from allotropy.calcdocs import ( + build_calc_docs, + CalcDoc, + FieldView, + Measurement as CalcMeasurement, + Node, UuidView, ) +from allotropy.calcdocs.unchained_labs_lunatic_stunner.extractor import LunaticExtractor from allotropy.parsers.utils.calculated_data_documents.definition import ( CalculatedDocument, ) +_ABSORBANCE = CalcMeasurement("absorbance", field="absorbance") +_DLS = CalcMeasurement("dynamic light scattering", field="absorbance") + +_LUNATIC_NODES: list[Node] = [ + _ABSORBANCE, + _DLS, + # a260 + CalcDoc( + "Concentration", + field="a260 concentration (ng/ul)", + sources=[_ABSORBANCE], + view="uuid", + unit="ng/µL", + ), + CalcDoc( + "Concentration", + field="concentration (ng/ul)", + sources=[_ABSORBANCE], + view="uuid", + unit="ng/µL", + ), + CalcDoc( + "Background (A260)", + field="background (a260)", + sources=[_ABSORBANCE], + view="uuid", + unit="mAU", + ), + CalcDoc( + "A260/A230", + field="a260/a230", + sources=[_ABSORBANCE], + view="uuid", + unit=UNITLESS, + ), + CalcDoc( + "A260/A280", + field="a260/a280", + sources=[_ABSORBANCE], + view="uuid", + unit=UNITLESS, + ), + # a280 + CalcDoc( + "Concentration", + field="concentration (mg/ml)", + sources=[_ABSORBANCE], + view="uuid", + unit="mg/mL", + ), + CalcDoc( + "Background (A280)", + field="background (a280)", + sources=[_ABSORBANCE], + view="uuid", + unit="mAU", + ), + CalcDoc( + "A260/A280", + field="a260/a280", + sources=[_ABSORBANCE], + view="uuid", + unit=UNITLESS, + ), + # Dynamic Light Scattering + CalcDoc( + "KC/R (mol/g)", + field="kc/r (mol/g)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "kD (ml/g)", + field="kd (ml/g)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "B22", + field="b22 (ml.mol/g^2)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Optical contrast constant K", + field="optical contrast constant k (m^2 mol kg^-2)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Z Average Diameter", + field="z ave. dia (nm)", + sources=[_DLS], + view="detection_uuid", + unit="nm", + ), + CalcDoc( + "Polydispersity Index", + field="pdi", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Diameter Standard Deviation", + field="sd dia (nm)", + sources=[_DLS], + view="detection_uuid", + unit="nm", + ), + CalcDoc( + "Diffusion coefficient", + field="diffusion coefficient (um^2/s)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Peak of Interest Mean Diameter", + field="peak of interest mean dia (nm)", + sources=[_DLS], + view="detection_uuid", + unit="nm", + ), + CalcDoc( + "Peak of Interest Mode Diameter", + field="peak of interest mode dia (nm)", + sources=[_DLS], + view="detection_uuid", + unit="nm", + ), + CalcDoc( + "Peak of Interest Est. MW", + field="peak of interest est. mw (kda)", + sources=[_DLS], + view="detection_uuid", + unit="kDa", + ), + CalcDoc( + "Peak of Interest Intensity", + field="peak of interest intensity (%)", + sources=[_DLS], + view="detection_uuid", + unit="%", + ), + CalcDoc( + "Peak of Interest Mass", + field="peak of interest mass (%)", + sources=[_DLS], + view="detection_uuid", + unit="%", + ), + CalcDoc( + "Peak of Interest Diffusion coefficient", + field="peak of interest diffusion coefficient (um^2/s)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Peak of Interest Mass Mean Diameter", + field="peak of interest mass mean dia (nm)", + sources=[_DLS], + view="detection_uuid", + unit="nm", + ), + CalcDoc( + "Peak of Interest Rayleigh Ratio R", + field="peak of interest rayleigh ratio r (cm^-1)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Derived intensity (cps)", + field="derived intensity (cps)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Rayleigh ratio R", + field="rayleigh ratio r (cm^-1)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "kD goodness of fit", + field="kd goodness of fit", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + description_field="kd linear fit", + ), + CalcDoc( + "B22 goodness of fit", + field="b22 goodness of fit", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + description_field="b22 linear fit", + ), + CalcDoc( + "Viscosity at T (cP)", + field="viscosity at t (cp)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Viscosity at 20°C (cP)", + field="viscosity at 20°c (cp)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "RI at T", + field="ri at t", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "RI at 20°C", + field="ri at 20°c", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Diameter @ C=0", + field="diameter @ c=0 (nm)", + sources=[_DLS], + view="detection_uuid", + unit="nm", + ), + CalcDoc( + "Number of Peaks", + field="number of peaks", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Number of Angles", + field="number of angles", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Angles Measured", + field="angles measured (°)", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), + CalcDoc( + "Intercept", + field="intercept", + sources=[_DLS], + view="detection_uuid", + unit=UNITLESS, + ), +] + def create_calculated_data( measurement_groups: list[MeasurementGroup], @@ -24,291 +295,10 @@ def create_calculated_data( for measurement_group in measurement_groups: elements += LunaticExtractor.get_elements(measurement_group.measurements) - lunatic_view_data = UuidView().apply(elements) - - absorbance_conf = MeasurementConfig( - name="absorbance", - value="absorbance", - ) - - dynamic_light_scattering_conf = MeasurementConfig( - name="dynamic light scattering", - value="absorbance", - ) - - detection_type_view = DetectionTypeView(sub_view=UuidView()).apply(elements) - - configs = CalcDocsConfig( - [ - # a260 - CalculatedDataConfig( - name="Concentration", - value="a260 concentration (ng/ul)", - unit="ng/µL", - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - CalculatedDataConfig( - name="Concentration", - value="concentration (ng/ul)", - unit="ng/µL", - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - CalculatedDataConfig( - name="Background (A260)", - value="background (a260)", - unit="mAU", - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - CalculatedDataConfig( - name="A260/A230", - value="a260/a230", - unit=UNITLESS, - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - CalculatedDataConfig( - name="A260/A280", - value="a260/a280", - unit=UNITLESS, - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - # a280 - CalculatedDataConfig( - name="Concentration", - value="concentration (mg/ml)", - unit="mg/mL", - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - CalculatedDataConfig( - name="Background (A280)", - value="background (a280)", - unit="mAU", - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - CalculatedDataConfig( - name="A260/A280", - value="a260/a280", - unit=UNITLESS, - view_data=lunatic_view_data, - source_configs=(absorbance_conf,), - ), - # Dynamic Light Scattering related calculations - CalculatedDataConfig( - name="KC/R (mol/g)", - value="kc/r (mol/g)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="kD (ml/g)", - value="kd (ml/g)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="B22", - value="b22 (ml.mol/g^2)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Optical contrast constant K", - value="optical contrast constant k (m^2 mol kg^-2)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Z Average Diameter", - value="z ave. dia (nm)", - unit="nm", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Polydispersity Index", - value="pdi", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Diameter Standard Deviation", - value="sd dia (nm)", - unit="nm", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Diffusion coefficient", - value="diffusion coefficient (um^2/s)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Mean Diameter", - value="peak of interest mean dia (nm)", - unit="nm", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Mode Diameter", - value="peak of interest mode dia (nm)", - unit="nm", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Est. MW", - value="peak of interest est. mw (kda)", - unit="kDa", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Intensity", - value="peak of interest intensity (%)", - unit="%", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Mass", - value="peak of interest mass (%)", - unit="%", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Diffusion coefficient", - value="peak of interest diffusion coefficient (um^2/s)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Mass Mean Diameter", - value="peak of interest mass mean dia (nm)", - unit="nm", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Peak of Interest Rayleigh Ratio R", - value="peak of interest rayleigh ratio r (cm^-1)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Derived intensity (cps)", - value="derived intensity (cps)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Rayleigh ratio R", - value="rayleigh ratio r (cm^-1)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="kD goodness of fit", - value="kd goodness of fit", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - description_value_key="kd linear fit", - ), - CalculatedDataConfig( - name="B22 goodness of fit", - value="b22 goodness of fit", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - description_value_key="b22 linear fit", - ), - CalculatedDataConfig( - name="Viscosity at T (cP)", - value="viscosity at t (cp)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Viscosity at 20°C (cP)", - value="viscosity at 20°c (cp)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="RI at T", - value="ri at t", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="RI at 20°C", - value="ri at 20°c", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Diameter @ C=0", - value="diameter @ c=0 (nm)", - unit="nm", - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Number of Peaks", - value="number of peaks", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Number of Angles", - value="number of angles", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Angles Measured", - value="angles measured (°)", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - CalculatedDataConfig( - name="Intercept", - value="intercept", - unit=UNITLESS, - view_data=detection_type_view, - source_configs=(dynamic_light_scattering_conf,), - ), - ] - ) - - return [ - calc_doc - for parent_calc_doc in configs.construct() - for calc_doc in parent_calc_doc.iter_struct() - ] + views = { + "uuid": UuidView().apply(elements), + "detection_uuid": FieldView("detection type", sub_view=UuidView()).apply( + elements + ), + } + return build_calc_docs(nodes=_LUNATIC_NODES, views=views) diff --git a/src/allotropy/parsers/utils/calculated_data_documents/definition.py b/src/allotropy/parsers/utils/calculated_data_documents/definition.py index 8dee36a9ce..9db8750771 100644 --- a/src/allotropy/parsers/utils/calculated_data_documents/definition.py +++ b/src/allotropy/parsers/utils/calculated_data_documents/definition.py @@ -4,6 +4,8 @@ from dataclasses import dataclass from typing import Any +from allotropy.allotrope.models.shared.definitions.definitions import JsonFloat + @dataclass class Referenceable: @@ -14,13 +16,13 @@ class Referenceable: class DataSource: feature: str reference: CalculatedDocument | Referenceable - value: float | None = None + value: JsonFloat | None = None @dataclass class CalculatedDocument(Referenceable): name: str - value: float + value: JsonFloat data_sources: list[DataSource] unit: str | None = None description: str | None = None diff --git a/src/allotropy/parsers/utils/calculated_data_documents/mapping.py b/src/allotropy/parsers/utils/calculated_data_documents/mapping.py new file mode 100644 index 0000000000..ede85fe70e --- /dev/null +++ b/src/allotropy/parsers/utils/calculated_data_documents/mapping.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from typing import Any + +from allotropy.allotrope.converter import add_custom_information_document +from allotropy.allotrope.models.shared.definitions.definitions import TQuantityValue +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, +) + + +def map_calculated_data_documents( + calculated_data: list[CalculatedDocument] | None, + aggregate_document_cls: type, + document_item_cls: type, + data_source_aggregate_cls: type, + data_source_item_cls: type, + *, + unit_fallback: str | None = None, + include_custom_info: bool = False, +) -> Any: + if not calculated_data: + return None + + return aggregate_document_cls( + calculated_data_document=[ + _map_item( + item, + document_item_cls=document_item_cls, + data_source_aggregate_cls=data_source_aggregate_cls, + data_source_item_cls=data_source_item_cls, + unit_fallback=unit_fallback, + include_custom_info=include_custom_info, + ) + for item in calculated_data + ] + ) + + +def _map_item( + item: CalculatedDocument, + document_item_cls: type, + data_source_aggregate_cls: type, + data_source_item_cls: type, + *, + unit_fallback: str | None = None, + include_custom_info: bool = False, +) -> Any: + unit = item.unit or unit_fallback or "(unitless)" + data_source_agg = ( + data_source_aggregate_cls( + data_source_document=[ + data_source_item_cls( + data_source_identifier=source.reference.uuid, + data_source_feature=source.feature, + ) + for source in item.data_sources + ] + ) + if item.data_sources + else None + ) + doc_item = document_item_cls( + calculated_data_identifier=item.uuid, + calculated_data_name=item.name, + calculation_description=item.description, + calculated_result=TQuantityValue(value=item.value, unit=unit), + data_source_aggregate_document=data_source_agg, + ) + if include_custom_info: + return add_custom_information_document(doc_item, item.custom_info) + return doc_item + + +def map_calculated_data_documents_for_dpcr( + calculated_data: list[CalculatedDocument] | None, + aggregate_document_cls: type, + document_item_cls: type, + data_source_aggregate_cls: type, + data_source_item_cls: type, +) -> Any: + if not calculated_data: + return None + + return aggregate_document_cls( + calculated_data_document=[ + document_item_cls( + calculated_data_identifier=item.uuid, + calculated_data_name=item.name, + calculated_datum=TQuantityValue( + value=item.value, unit=item.unit or "(unitless)" + ), + data_source_aggregate_document=data_source_aggregate_cls( + data_source_document=[ + data_source_item_cls( + data_source_identifier=source.reference.uuid, + data_source_feature=source.feature, + ) + for source in item.data_sources + ] + ), + ) + for item in calculated_data + ] + ) diff --git a/tests/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure_test.py b/tests/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure_test.py index 62ecfc41b3..da792ca95b 100644 --- a/tests/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure_test.py +++ b/tests/parsers/agilent_tapestation_analysis/agilent_tapestation_analysis_structure_test.py @@ -4,8 +4,6 @@ import pytest from allotropy.allotrope.schema_mappers.adm.electrophoresis.benchling._2024._09.electrophoresis import ( - CalculatedDataItem, - DataSource, Measurement, MeasurementGroup, Metadata, @@ -26,6 +24,11 @@ SCREEN_TAPE_MISMATCH_ERROR, SOFTWARE_NAME, ) +from allotropy.parsers.utils.calculated_data_documents.definition import ( + CalculatedDocument, + DataSource, + Referenceable, +) from allotropy.parsers.utils.values import assert_not_none from allotropy.testing.utils import mock_uuid_generation from tests.parsers.agilent_tapestation_analysis.agilent_tapestation_test_data import ( @@ -181,62 +184,68 @@ def testcreate_measurement_groups_with_calculated_data() -> None: get_samples_xml(with_calculated_data=True) ) - sample_data_source = DataSource(feature="sample", identifier="TEST_ID_0") - peak_1_data_source = DataSource(feature="peak", identifier="TEST_ID_2") - peak_2_data_source = DataSource(feature="peak", identifier="TEST_ID_7") + sample_data_source = DataSource( + feature="sample", reference=Referenceable(uuid="TEST_ID_0") + ) + peak_1_data_source = DataSource( + feature="peak", reference=Referenceable(uuid="TEST_ID_2") + ) + peak_2_data_source = DataSource( + feature="peak", reference=Referenceable(uuid="TEST_ID_7") + ) assert calc_docs == [ - CalculatedDataItem( - identifier="TEST_ID_1", + CalculatedDocument( + uuid="TEST_ID_1", name="Concentration", unit="(unitless)", value=58.2, data_sources=[sample_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_3", + CalculatedDocument( + uuid="TEST_ID_3", name="AssignedQuantity", unit="(unitless)", value=8.50, data_sources=[peak_1_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_4", + CalculatedDocument( + uuid="TEST_ID_4", name="FromPercent", unit="(unitless)", value=80.6, data_sources=[peak_1_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_5", + CalculatedDocument( + uuid="TEST_ID_5", name="Molarity", unit="(unitless)", value=131.0, data_sources=[peak_1_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_6", + CalculatedDocument( + uuid="TEST_ID_6", name="ToPercent", unit="(unitless)", value=85.4, data_sources=[peak_1_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_8", + CalculatedDocument( + uuid="TEST_ID_8", name="CalibratedQuantity", unit="(unitless)", value=11.3, data_sources=[peak_2_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_9", + CalculatedDocument( + uuid="TEST_ID_9", name="FromPercent", unit="(unitless)", value=41.1, data_sources=[peak_2_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_10", + CalculatedDocument( + uuid="TEST_ID_10", name="RunDistance", unit="(unitless)", value=46.5, @@ -256,8 +265,12 @@ def testcreate_measurement_groups_with_regions() -> None: get_samples_xml(with_regions=True) ) - region_1_data_source = DataSource(feature="data region", identifier="TEST_ID_3") - region_2_data_source = DataSource(feature="data region", identifier="TEST_ID_6") + region_1_data_source = DataSource( + feature="data region", reference=Referenceable(uuid="TEST_ID_3") + ) + region_2_data_source = DataSource( + feature="data region", reference=Referenceable(uuid="TEST_ID_6") + ) # Note: Data regions are ordered by (region_start) ascending assert groups[0].measurements[0].processed_data.data_regions == [ @@ -285,29 +298,29 @@ def testcreate_measurement_groups_with_regions() -> None: ), ] assert calc_docs == [ - CalculatedDataItem( - identifier="TEST_ID_4", + CalculatedDocument( + uuid="TEST_ID_4", name="AverageSize", unit="(unitless)", value=1944.0, data_sources=[region_1_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_5", + CalculatedDocument( + uuid="TEST_ID_5", name="Molarity", unit="(unitless)", value=0.765, data_sources=[region_1_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_7", + CalculatedDocument( + uuid="TEST_ID_7", name="AverageSize", unit="(unitless)", value=395.0, data_sources=[region_2_data_source], ), - CalculatedDataItem( - identifier="TEST_ID_8", + CalculatedDocument( + uuid="TEST_ID_8", name="Concentration", unit="(unitless)", value=1.11,