22
33from collections import defaultdict
44from dataclasses import dataclass , field
5+ from typing import Any
56
67import pandas as pd
78
1314)
1415from allotropy .parsers .constants import NOT_APPLICABLE
1516from allotropy .parsers .cytiva_biacore_t200_control import constants
17+ from allotropy .parsers .utils .dict_data import DictData
1618from allotropy .parsers .utils .pandas import map_rows , SeriesData
1719from allotropy .parsers .utils .uuids import random_uuid_str
1820from allotropy .parsers .utils .values import (
1921 assert_not_none ,
2022 try_float_or_none ,
21- try_int_or_none ,
2223)
2324from allotropy .types import DictType
2425
@@ -30,32 +31,32 @@ class ChipData:
3031 number_of_flow_cells : int | None
3132 number_of_spots : int | None
3233 lot_number : str | None
33- custom_info : DictType
34+ custom_info : dict [ str , Any ]
3435
3536 @staticmethod
36- def create (chip_data : DictType ) -> ChipData :
37+ def create (chip_data : DictData ) -> ChipData :
3738 return ChipData (
38- sensor_chip_identifier = assert_not_none (chip_data .get ("Id" ), "Chip ID" ),
39- sensor_chip_type = chip_data .get ("Name" ),
40- number_of_flow_cells = try_int_or_none ( chip_data .get ("NoFcs" ) ),
41- number_of_spots = try_int_or_none ( chip_data .get ("NoSpots" ) ),
42- lot_number = lot_no if (lot_no := chip_data .get ("LotNo" )) else None ,
39+ sensor_chip_identifier = assert_not_none (chip_data .get (str , "Id" ), "Chip ID" ),
40+ sensor_chip_type = chip_data .get (str , "Name" ),
41+ number_of_flow_cells = chip_data .get (int , "NoFcs" ),
42+ number_of_spots = chip_data .get (int , "NoSpots" ),
43+ lot_number = lot_no if (lot_no := chip_data .get (str , "LotNo" )) else None ,
4344 custom_info = {
44- "ifc identifier" : chip_data .get ("IFC" ),
45- "last modified time" : chip_data .get ("LastModTime" ),
46- "last use time" : chip_data .get ("LastUseTime" ),
47- "first dock date" : chip_data .get ("FirstDockDate" ),
45+ "ifc identifier" : chip_data .get (str , "IFC" ),
46+ "last modified time" : chip_data .get (str , "LastModTime" ),
47+ "last use time" : chip_data .get (str , "LastUseTime" ),
48+ "first dock date" : chip_data .get (str , "FirstDockDate" ),
4849 },
4950 )
5051
5152
5253@dataclass (frozen = True )
5354class DetectionSetting :
5455 key : str
55- value : float
56+ value : float | None
5657
5758 @staticmethod
58- def create (detection_setting : DictType ) -> DetectionSetting :
59+ def create (detection_setting : DictData ) -> DetectionSetting :
5960 detection_key = f"Detection{ detection_setting ['Detection' ]} "
6061 return DetectionSetting (
6162 key = detection_key .lower (),
@@ -81,35 +82,48 @@ class RunMetadata:
8182
8283 @staticmethod
8384 def create (
84- application_template_details : dict [ str , DictType ] | None ,
85+ application_template_details : DictData | None ,
8586 ) -> RunMetadata :
8687 if application_template_details is None :
8788 return RunMetadata ()
89+ baseline_flow = application_template_details .get_nested ("BaselineFlow" )
90+ baseline_flow_value = baseline_flow .get ("value" )
91+ data_collection_rate = application_template_details .get_nested (
92+ "DataCollectionRate"
93+ )
94+ data_collection_rate_value = data_collection_rate .get ("value" )
8895 return RunMetadata (
89- analyst = application_template_details ["properties" ].get ("User" ),
96+ analyst = application_template_details .get (
97+ DictData , "properties" , DictData ({})
98+ ).get (str , "User" ),
9099 compartment_temperature = try_float_or_none (
91- application_template_details [ "RackTemperature" ]. get ( "Value" )
100+ application_template_details . get_nested ( "RackTemperature" )[ "Value" ]
92101 if "sample_data" in application_template_details
93- else application_template_details ["system_preparations" ].get ("RackTemp" )
94- ),
95- baseline_flow = try_float_or_none (
96- application_template_details .get ("BaselineFlow" , {}).get ("value" )
97- ),
98- data_collection_rate = try_float_or_none (
99- application_template_details .get ("DataCollectionRate" , {}).get ("value" )
102+ else application_template_details .get (
103+ DictData ,
104+ "system_preparations" ,
105+ DictData ({}),
106+ ).get (str , "RackTemp" )
100107 ),
108+ baseline_flow = try_float_or_none (baseline_flow_value ),
109+ data_collection_rate = try_float_or_none (data_collection_rate_value ),
101110 detection_setting = (
102- DetectionSetting .create (detection_setting )
103- if (detection_setting := application_template_details .get ("detection" ))
111+ DetectionSetting .create (
112+ application_template_details .get_nested ("detection" )
113+ )
114+ if application_template_details .get_nested ("detection" )
104115 else None
105116 ),
106117 buffer_volume = try_float_or_none (
107118 next (
108- value
109- for key , value in application_template_details [
110- "prepare_run"
111- ].items ()
112- if key .startswith ("Buffer" )
119+ (
120+ value
121+ for key , value in application_template_details .get_nested (
122+ "prepare_run"
123+ ).items ()
124+ if key .startswith ("Buffer" )
125+ ),
126+ None ,
113127 )
114128 ),
115129 devices = [
@@ -131,21 +145,22 @@ class SystemInformation:
131145 software_version : str | None
132146
133147 @staticmethod
134- def create (system_information : DictType ) -> SystemInformation :
148+ def create (system_information : DictData ) -> SystemInformation :
135149 return SystemInformation (
136150 device_identifier = assert_not_none (
137- system_information .get ("InstrumentId" ), "InstrumentId"
151+ system_information .get (str , "InstrumentId" ), "InstrumentId"
138152 ),
139153 model_number = assert_not_none (
140- system_information .get ("ProcessingUnit" ), "ProcessingUnit"
154+ system_information .get (str , "ProcessingUnit" ),
155+ "ProcessingUnit" ,
141156 ),
142157 measurement_time = assert_not_none (
143- system_information .get ("Timestamp" ), "Timestamp"
158+ system_information .get (str , "Timestamp" ), "Timestamp"
144159 ),
145- experiment_type = system_information .get ("RunTypeId" ),
146- analytical_method_identifier = system_information .get ("TemplateFile" ),
147- software_name = system_information .get ("Application" ),
148- software_version = system_information .get ("Version" ),
160+ experiment_type = system_information .get (str , "RunTypeId" ),
161+ analytical_method_identifier = system_information .get (str , "TemplateFile" ),
162+ software_name = system_information .get (str , "Application" ),
163+ software_version = system_information .get (str , "Version" ),
149164 )
150165
151166
@@ -212,34 +227,39 @@ class MeasurementData:
212227 flow_rate : float | None
213228 contact_time : float | None
214229 dilution : float | None
230+ custom_info : dict [str , Any ]
215231
216232
217233@dataclass (frozen = True )
218234class SampleData :
219235 measurements : dict [str , list [MeasurementData ]]
236+ custom_info : dict [str , Any ]
220237
221238 @staticmethod
222- def create (intermediate_structured_data : DictType ) -> SampleData :
223- application_template_details : dict [
224- str , DictType
225- ] = intermediate_structured_data . get ( "application_template_details" , {} )
239+ def create (intermediate_structured_data : DictData ) -> SampleData :
240+ application_template_details = intermediate_structured_data . get_nested (
241+ "application_template_details"
242+ )
226243 measurements : dict [str , list [MeasurementData ]] = defaultdict (list )
227- for idx in range (intermediate_structured_data ["total_cycles" ]):
228- flowcell_cycle_data : DictType = application_template_details .get (
229- f"Flowcell { idx + 1 } " , {}
230- )
231- sample_data : DictType = (
232- sd [idx ]
233- if (sd := intermediate_structured_data .get ("sample_data" ))
234- else {}
244+ total_cycles = assert_not_none (
245+ intermediate_structured_data .get (int , "total_cycles" ),
246+ "total_cycles" ,
247+ )
248+ for idx in range (total_cycles ):
249+ flowcell_cycle_json = application_template_details .get_nested (
250+ f"Flowcell { idx + 1 } "
235251 )
236- cycle_data : DictType = intermediate_structured_data ["cycle_data" ][idx ]
252+ sd_list = intermediate_structured_data .get (list , "sample_data" , [])
253+ sample_data_json = sd_list [idx ] if sd_list else DictData ({})
254+
255+ cycle_data_list = intermediate_structured_data .get (list , "cycle_data" , [])
256+ cycle_data : dict [str , pd .DataFrame ] = cycle_data_list [idx ]
237257 sensorgram_data : pd .DataFrame = cycle_data ["sensorgram_data" ]
238258 # some experiments don't have report point data for some cycles (apparently just the first one)
239259 report_point_data : pd .DataFrame | None = cycle_data ["report_point_data" ]
240260
241- sample_identifier = sample_data .get ("sample_name" , NOT_APPLICABLE )
242- location_identifier = sample_data .get ("rack" )
261+ sample_identifier = sample_data_json .get (str , "sample_name" , NOT_APPLICABLE )
262+ location_identifier = sample_data_json .get (str , "rack" )
243263 sample_location_key = f"{ location_identifier } _{ sample_identifier } "
244264
245265 # Measurements are grouped by sample and location identifiers
@@ -252,12 +272,10 @@ def create(intermediate_structured_data: DictType) -> SampleData:
252272 flow_cell_identifier = str (flow_cell ),
253273 location_identifier = location_identifier ,
254274 sample_role_type = constants .SAMPLE_ROLE_TYPE .get (
255- sample_data .get ("role" , "__IVALID_KEY__" )
256- ),
257- concentration = try_float_or_none (sample_data .get ("concentration" )),
258- molecular_weight = try_float_or_none (
259- sample_data .get ("molecular_weight" )
275+ sample_data_json .get (str , "role" , "__IVALID_KEY__" )
260276 ),
277+ concentration = sample_data_json .get (float , "concentration" ),
278+ molecular_weight = sample_data_json .get (float , "molecular_weight" ),
261279 sensorgram_data = sensorgram_df ,
262280 report_point_data = (
263281 map_rows (
@@ -268,23 +286,21 @@ def create(intermediate_structured_data: DictType) -> SampleData:
268286 else None
269287 ),
270288 # for Mobilization experiments
271- method_name = flowcell_cycle_data .get ("MethodName" ),
272- ligand_identifier = flowcell_cycle_data .get ("Ligand" ),
273- flow_path = flowcell_cycle_data .get ("DetectionText" ),
274- flow_rate = try_float_or_none (flowcell_cycle_data .get ("Flow" )),
275- contact_time = try_float_or_none (
276- flowcell_cycle_data .get ("ContactTime" )
277- ),
278- dilution = try_float_or_none (
279- flowcell_cycle_data .get ("DilutePercent" )
280- ),
289+ method_name = flowcell_cycle_json .get (str , "MethodName" ),
290+ ligand_identifier = flowcell_cycle_json .get (str , "Ligand" ),
291+ flow_path = flowcell_cycle_json .get (str , "DetectionText" ),
292+ flow_rate = flowcell_cycle_json .get (float , "Flow" ),
293+ contact_time = flowcell_cycle_json .get (float , "ContactTime" ),
294+ dilution = flowcell_cycle_json .get (float , "DilutePercent" ),
295+ custom_info = {},
281296 )
282297 # group sensorgram data by Flow Cell Number (Fc in rpoint data)
283298 for flow_cell , sensorgram_df in sensorgram_data .groupby (
284299 "Flow Cell Number"
285300 )
286301 ]
287- return SampleData (measurements )
302+ custom_info : dict [str , Any ] = {}
303+ return SampleData (measurements , custom_info )
288304
289305
290306@dataclass (frozen = True )
@@ -295,14 +311,15 @@ class Data:
295311 sample_data : SampleData
296312
297313 @staticmethod
298- def create (intermediate_structured_data : DictType ) -> Data :
299- application_template_details : dict [
300- str , DictType
301- ] | None = intermediate_structured_data . get ( "application_template_details" )
302- chip_data : DictType = intermediate_structured_data [ "chip" ]
303- system_information : DictType = intermediate_structured_data [
314+ def create (intermediate_structured_data : DictData ) -> Data :
315+ application_template_details = intermediate_structured_data . get_nested (
316+ "application_template_details"
317+ )
318+ chip_data = intermediate_structured_data . get_nested ( "chip" )
319+ system_information = intermediate_structured_data . get_nested (
304320 "system_information"
305- ]
321+ )
322+
306323 return Data (
307324 run_metadata = RunMetadata .create (application_template_details ),
308325 chip_data = ChipData .create (chip_data ),
0 commit comments