44import itertools
55from pathlib import Path
66import re
7+ from typing import Any
78
89import pandas as pd
910
3940)
4041from allotropy .parsers .utils .pandas import map_rows , SeriesData
4142from allotropy .parsers .utils .uuids import random_uuid_str
42- from allotropy .parsers .utils .values import assert_not_none , try_float
43+ from allotropy .parsers .utils .values import (
44+ assert_not_none ,
45+ try_float ,
46+ try_non_nan_float_or_negative_zero ,
47+ try_non_nan_float_or_none ,
48+ )
4349
4450
4551@dataclass (frozen = True )
@@ -163,6 +169,7 @@ def create(
163169 location = str (count_data .series .name )
164170 dilution_factor_data = results_data ["Dilution Factor" ]
165171 errors_data = results_data .get ("Warnings/Errors" )
172+ measurement_identifier = random_uuid_str ()
166173
167174 if location not in dilution_factor_data .index :
168175 msg = f"Could not find 'Dilution Factor' data for: '{ location } '."
@@ -177,31 +184,49 @@ def create(
177184 )
178185 errors : list [Error ] = []
179186 data_errors = cls ._get_errors (errors_data , well_location ) or []
180- for error in data_errors :
181- errors .append (Error (error = error ))
187+ for data_error in data_errors :
188+ errors .append (Error (error = data_error ))
182189
183190 if dilution_factor_setting == NEGATIVE_ZERO :
184191 errors .append (
185192 Error (error = "Not reported in file" , feature = "dilution factor setting" )
186193 )
187194
195+ def get_statistics_error (
196+ raw_value : Any , analyte : str , role : str
197+ ) -> Error | None :
198+ """Check if the raw value is empty or NaN and return an Error if so."""
199+ error = None
200+ if raw_value == "" :
201+ error = "Not reported"
202+ elif try_non_nan_float_or_none (raw_value ) is None :
203+ error = "NaN"
204+ return Error (error = error , feature = f"{ analyte } <{ role } >" ) if error else None
205+
188206 def get_statistic_dimensions (analyte : str ) -> list [StatisticDimension ]:
189- return [
190- StatisticDimension (
191- value = try_float (statistic_table .at [location , analyte ], analyte ),
192- unit = statistic_conf .unit ,
193- statistic_datum_role = statistic_conf .role ,
207+ statistic_dimensions = []
208+ for section , statistic_conf in STATISTIC_SECTIONS_CONF .items ():
209+ if (statistic_table := results_data .get (section )) is None :
210+ continue
211+ raw_value = statistic_table .at [location , analyte ]
212+ role = statistic_conf .role
213+ if error := get_statistics_error (raw_value , analyte , role .value ):
214+ errors .append (error )
215+ statistic_dimensions .append (
216+ StatisticDimension (
217+ value = try_non_nan_float_or_negative_zero (raw_value ),
218+ unit = statistic_conf .unit ,
219+ statistic_datum_role = role ,
220+ )
194221 )
195- for section , statistic_conf in STATISTIC_SECTIONS_CONF .items ()
196- if (statistic_table := results_data .get (section )) is not None
197- ]
222+ return statistic_dimensions
198223
199- measurement_id = random_uuid_str ()
200224 analytes = []
201225 calculated_data = []
202- for analyte in [
226+ analyte_keys = [
203227 key for key in count_data .series .index if key not in metadata_keys
204- ]:
228+ ]
229+ for analyte in analyte_keys :
205230 analytes .append (
206231 Analyte (
207232 identifier = (analyte_identifier := random_uuid_str ()),
@@ -217,14 +242,21 @@ def get_statistic_dimensions(analyte: str) -> list[StatisticDimension]:
217242 )
218243 )
219244
220- calculated_data .extend (
221- [
245+ for section_name , unit in CALCULATED_DATA_SECTIONS .items ():
246+ calculated_data_section = results_data .get (section_name , pd .DataFrame ())
247+ # Some sections don't include the location as an index, in those cases we cannot associate
248+ # the calculated data with the measurement.
249+ if location not in calculated_data_section .index :
250+ continue
251+ raw_value = calculated_data_section .at [location , analyte ]
252+ if error := get_statistics_error (raw_value , analyte , section_name ):
253+ errors .append (error )
254+ calculated_data_id = random_uuid_str ()
255+ calculated_data .append (
222256 CalculatedDocument (
223- uuid = random_uuid_str () ,
257+ uuid = calculated_data_id ,
224258 name = section_name ,
225- value = try_float (
226- calculated_data_section .at [location , analyte ], analyte
227- ),
259+ value = try_non_nan_float_or_negative_zero (raw_value ),
228260 unit = unit ,
229261 data_sources = [
230262 DataSource (
@@ -233,15 +265,10 @@ def get_statistic_dimensions(analyte: str) -> list[StatisticDimension]:
233265 )
234266 ],
235267 )
236- for section_name , unit in CALCULATED_DATA_SECTIONS .items ()
237- if section_name in results_data
238- and location
239- in (calculated_data_section := results_data [section_name ]).index
240- ]
241- )
268+ )
242269
243270 return Measurement (
244- identifier = measurement_id ,
271+ identifier = measurement_identifier ,
245272 sample_identifier = count_data [str , "Sample" ],
246273 location_identifier = location_id ,
247274 dilution_factor_setting = dilution_factor_setting ,
0 commit comments