Skip to content

Commit bdb8dca

Browse files
refactor: Agilent gen5 - refactor adapter (#1038)
Co-authored-by: Nathan Stender <nathan.stender@benchling.com>
1 parent 456ed01 commit bdb8dca

4 files changed

Lines changed: 454 additions & 234 deletions

File tree

src/allotropy/parsers/agilent_gen5/agilent_gen5_parser.py

Lines changed: 5 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,11 @@
99
from allotropy.named_file_contents import NamedFileContents
1010
from allotropy.parsers.agilent_gen5.agilent_gen5_reader import AgilentGen5Reader
1111
from allotropy.parsers.agilent_gen5.agilent_gen5_structure import (
12-
create_kinetic_results,
1312
create_metadata,
14-
create_results,
15-
create_spectrum_results,
16-
get_concentrations,
17-
get_identifiers,
18-
get_kinetic_measurements,
19-
get_results_section,
20-
get_temperature,
21-
HeaderData,
22-
KineticData,
23-
ReadData,
13+
get_processor,
2414
)
2515
from allotropy.parsers.agilent_gen5.constants import (
2616
NO_MEASUREMENTS_ERROR,
27-
ReadType,
2817
)
2918
from allotropy.parsers.release_state import ReleaseState
3019
from allotropy.parsers.vendor_parser import VendorParser
@@ -38,83 +27,16 @@ class AgilentGen5Parser(VendorParser[Data, Model]):
3827

3928
def create_data(self, named_file_contents: NamedFileContents) -> Data:
4029
reader = AgilentGen5Reader(named_file_contents)
30+
context = reader.extract_data_context(named_file_contents.original_file_path)
4131

42-
if (results_section := get_results_section(reader)) is None:
43-
reader.header_data.get_unread()
44-
raise AllotropeConversionError(NO_MEASUREMENTS_ERROR)
45-
46-
header_data = HeaderData.create(
47-
reader.header_data, named_file_contents.original_file_path
48-
)
49-
read_data = ReadData.create(reader.sections["Procedure Details"])
50-
kinetic_data = KineticData.create(reader.sections["Procedure Details"])
51-
52-
sample_identifiers = get_identifiers(reader.sections.get("Layout"))
53-
concentration_values = get_concentrations(reader.sections.get("Layout"))
54-
actual_temperature = get_temperature(reader.sections.get("Actual Temperature"))
55-
kinetic_result = get_kinetic_measurements(reader.sections.get("Time"))
56-
kinetic_measurements, kinetic_elapsed_time, kinetic_errors = kinetic_result or (
57-
{},
58-
[],
59-
{},
60-
)
61-
62-
if kinetic_data and not (kinetic_measurements and kinetic_elapsed_time):
63-
msg = "Kinetic data is present in the file but no kinetic measurements data is found."
64-
raise AllotropeConversionError(msg)
65-
66-
read_is_spectral = read_data[0].read_type == ReadType.SPECTRUM
67-
if read_is_spectral and reader.sections.get("Wavelength"):
68-
(
69-
wavelength_measurements,
70-
wavelength_calculated_data,
71-
) = create_spectrum_results(
72-
header_data,
73-
read_data_list=read_data,
74-
wavelengths_sections=reader.sections.get("Wavelength"),
75-
sample_identifiers=sample_identifiers,
76-
actual_temperature=actual_temperature,
77-
results_section=results_section,
78-
concentration_values=concentration_values,
79-
)
80-
81-
if not wavelength_measurements:
82-
raise AllotropeConversionError(NO_MEASUREMENTS_ERROR)
83-
84-
return Data(
85-
metadata=create_metadata(header_data),
86-
measurement_groups=wavelength_measurements,
87-
calculated_data=wavelength_calculated_data,
88-
)
89-
90-
if kinetic_data:
91-
measurement_groups, calculated_data = create_kinetic_results(
92-
results_section,
93-
header_data,
94-
read_data,
95-
sample_identifiers,
96-
actual_temperature,
97-
kinetic_data,
98-
kinetic_measurements,
99-
kinetic_elapsed_time,
100-
kinetic_errors,
101-
concentration_values,
102-
)
103-
else:
104-
measurement_groups, calculated_data = create_results(
105-
results_section,
106-
header_data,
107-
read_data,
108-
sample_identifiers,
109-
actual_temperature,
110-
concentration_values,
111-
)
32+
processor = get_processor(context)
33+
measurement_groups, calculated_data = processor.process(context)
11234

11335
if not measurement_groups:
11436
raise AllotropeConversionError(NO_MEASUREMENTS_ERROR)
11537

11638
return Data(
117-
metadata=create_metadata(header_data),
39+
metadata=create_metadata(context.header_data),
11840
measurement_groups=measurement_groups,
11941
calculated_data=calculated_data,
12042
)

src/allotropy/parsers/agilent_gen5/agilent_gen5_reader.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,19 @@
22

33
from allotropy.exceptions import AllotropeConversionError
44
from allotropy.named_file_contents import NamedFileContents
5+
from allotropy.parsers.agilent_gen5.agilent_gen5_structure import (
6+
Gen5DataContext,
7+
get_concentrations,
8+
get_identifiers,
9+
get_kinetic_measurements,
10+
get_temperature,
11+
HeaderData,
12+
KineticData,
13+
ReadData,
14+
)
515
from allotropy.parsers.agilent_gen5.constants import (
616
MULTIPLATE_FILE_ERROR,
17+
NO_MEASUREMENTS_ERROR,
718
NO_PLATE_DATA_ERROR,
819
)
920
from allotropy.parsers.lines_reader import SectionLinesReader
@@ -59,3 +70,107 @@ def __init__(self, named_file_contents: NamedFileContents) -> None:
5970
lines = list(plate_reader.pop_until_empty())
6071
self.sections[lines[0].split("\t")[0].strip(":")] = lines
6172
plate_reader.drop_empty()
73+
74+
def get_required_section(self, section_name: str) -> list[str]:
75+
"""Get a required section, raises error if not found."""
76+
section = self.sections.get(section_name)
77+
if section is None:
78+
msg = f"Required section '{section_name}' not found."
79+
raise AllotropeConversionError(msg)
80+
return section
81+
82+
@property
83+
def procedure_details(self) -> list[str]:
84+
return self.get_required_section("Procedure Details")
85+
86+
@property
87+
def layout_section(self) -> list[str] | None:
88+
return self.sections.get("Layout")
89+
90+
@property
91+
def wavelength_section(self) -> list[str] | None:
92+
return self.sections.get("Wavelength")
93+
94+
@property
95+
def time_section(self) -> list[str] | None:
96+
return self.sections.get("Time")
97+
98+
@property
99+
def actual_temperature_section(self) -> list[str] | None:
100+
return self.sections.get("Actual Temperature")
101+
102+
def _validate_result_sections(self, result_sections: list[list[str]]) -> None:
103+
"""Validates whether all the result sections dimensions are consistent."""
104+
first_section = result_sections[0]
105+
106+
for section in result_sections[1:]:
107+
if not first_section[0] == section[0] and len(first_section) == len(
108+
section
109+
):
110+
msg = "All result tables should have the same dimensions."
111+
raise AllotropeConversionError(msg)
112+
113+
def get_results_section(self) -> list[str] | None:
114+
"""Returns a valid Results Matrix from the reader sections if found.
115+
116+
Checks for Results in the reader sections, if not found, creates the results matrix with all
117+
sections that are correctly formatted as a results table (excluding the Layout section). If
118+
no tables with results are found, returns None
119+
"""
120+
if "Results" in self.sections:
121+
return self.sections["Results"]
122+
123+
def is_results(section: list[str]) -> bool:
124+
return (
125+
len(section) > 2
126+
and section[1].startswith("\t1")
127+
and section[2].startswith("A\t")
128+
)
129+
130+
result_sections = []
131+
for name, section in self.sections.items():
132+
if name == "Layout":
133+
continue
134+
if is_results(section):
135+
result_sections.append(section[1:])
136+
137+
if result_sections:
138+
self._validate_result_sections(result_sections)
139+
return [
140+
"Results",
141+
result_sections[0][0],
142+
*[
143+
section[i + 1]
144+
for i in range(len(result_sections[0]) - 1)
145+
for section in result_sections
146+
],
147+
]
148+
149+
return None
150+
151+
def extract_data_context(self, file_path: str) -> Gen5DataContext:
152+
results_section = self.get_results_section()
153+
if not results_section:
154+
self.header_data.get_unread()
155+
raise AllotropeConversionError(NO_MEASUREMENTS_ERROR)
156+
157+
kinetic_result = get_kinetic_measurements(self.time_section)
158+
kinetic_measurements, kinetic_elapsed_time, kinetic_errors = kinetic_result or (
159+
{},
160+
[],
161+
{},
162+
)
163+
164+
return Gen5DataContext(
165+
header_data=HeaderData.create(self.header_data, file_path),
166+
read_data=ReadData.create(self.procedure_details),
167+
kinetic_data=KineticData.create(self.procedure_details),
168+
results_section=results_section,
169+
sample_identifiers=get_identifiers(self.layout_section),
170+
concentration_values=get_concentrations(self.layout_section),
171+
actual_temperature=get_temperature(self.actual_temperature_section),
172+
kinetic_measurements=kinetic_measurements,
173+
kinetic_elapsed_time=kinetic_elapsed_time,
174+
kinetic_errors=kinetic_errors,
175+
wavelength_section=self.wavelength_section,
176+
)

0 commit comments

Comments
 (0)