pychemstation 0.8.3__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. pychemstation/analysis/__init__.py +4 -0
  2. pychemstation/analysis/base_spectrum.py +9 -9
  3. pychemstation/analysis/process_report.py +13 -7
  4. pychemstation/analysis/utils.py +1 -3
  5. pychemstation/control/__init__.py +4 -0
  6. pychemstation/control/comm.py +206 -0
  7. pychemstation/control/controllers/__init__.py +6 -0
  8. pychemstation/control/controllers/comm.py +12 -5
  9. pychemstation/control/controllers/devices/column.py +12 -0
  10. pychemstation/control/controllers/devices/dad.py +0 -0
  11. pychemstation/control/controllers/devices/device.py +10 -7
  12. pychemstation/control/controllers/devices/injector.py +18 -84
  13. pychemstation/control/controllers/devices/pump.py +43 -0
  14. pychemstation/control/controllers/method.py +338 -0
  15. pychemstation/control/controllers/sequence.py +190 -0
  16. pychemstation/control/controllers/table_controller.py +266 -0
  17. pychemstation/control/controllers/tables/method.py +35 -13
  18. pychemstation/control/controllers/tables/sequence.py +46 -37
  19. pychemstation/control/controllers/tables/table.py +46 -30
  20. pychemstation/control/hplc.py +27 -11
  21. pychemstation/control/table/__init__.py +3 -0
  22. pychemstation/control/table/method.py +274 -0
  23. pychemstation/control/table/sequence.py +210 -0
  24. pychemstation/control/table/table_controller.py +201 -0
  25. pychemstation/generated/dad_method.py +1 -1
  26. pychemstation/generated/pump_method.py +1 -1
  27. pychemstation/utils/chromatogram.py +2 -5
  28. pychemstation/utils/injector_types.py +1 -1
  29. pychemstation/utils/macro.py +3 -3
  30. pychemstation/utils/method_types.py +2 -2
  31. pychemstation/utils/num_utils.py +65 -0
  32. pychemstation/utils/parsing.py +1 -0
  33. pychemstation/utils/sequence_types.py +3 -3
  34. pychemstation/utils/spec_utils.py +304 -0
  35. {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/METADATA +19 -8
  36. pychemstation-0.10.0.dist-info/RECORD +62 -0
  37. {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/WHEEL +2 -1
  38. pychemstation-0.10.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +0 -0
  40. tests/constants.py +134 -0
  41. tests/test_comb.py +136 -0
  42. tests/test_comm.py +65 -0
  43. tests/test_inj.py +39 -0
  44. tests/test_method.py +99 -0
  45. tests/test_nightly.py +80 -0
  46. tests/test_offline_stable.py +69 -0
  47. tests/test_online_stable.py +275 -0
  48. tests/test_proc_rep.py +52 -0
  49. tests/test_runs_stable.py +225 -0
  50. tests/test_sequence.py +125 -0
  51. tests/test_stable.py +276 -0
  52. pychemstation/control/README.md +0 -124
  53. pychemstation/control/controllers/README.md +0 -1
  54. pychemstation-0.8.3.dist-info/RECORD +0 -37
  55. {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,266 @@
1
+ """
2
+ Abstract module containing shared logic for Method and Sequence tables.
3
+
4
+ Authors: Lucy Hao
5
+ """
6
+
7
+ import abc
8
+ import os
9
+ from typing import Union, Optional
10
+
11
+ import polling
12
+ from result import Result, Ok, Err
13
+
14
+ from ...control.controllers.comm import CommunicationController
15
+ from ...utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
16
+ from ...utils.macro import Command, HPLCRunningStatus, Response
17
+ from ...utils.method_types import MethodDetails
18
+ from ...utils.sequence_types import SequenceDataFiles, SequenceTable
19
+ from ...utils.table_types import Table, TableOperation, RegisterFlag
20
+
21
+ TableType = Union[MethodDetails, SequenceTable]
22
+
23
+
24
+ class TableController(abc.ABC):
25
+
26
+ def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
27
+ self.controller = controller
28
+ self.table = table
29
+ self.table_state: Optional[TableType] = None
30
+
31
+ if os.path.isdir(src):
32
+ self.src: str = src
33
+ else:
34
+ raise FileNotFoundError(f"dir: {src} not found.")
35
+
36
+ if os.path.isdir(data_dir):
37
+ self.data_dir: str = data_dir
38
+ else:
39
+ raise FileNotFoundError(f"dir: {data_dir} not found.")
40
+
41
+ self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
42
+ "A": AgilentHPLCChromatogram(self.data_dir),
43
+ "B": AgilentHPLCChromatogram(self.data_dir),
44
+ "C": AgilentHPLCChromatogram(self.data_dir),
45
+ "D": AgilentHPLCChromatogram(self.data_dir),
46
+ "E": AgilentHPLCChromatogram(self.data_dir),
47
+ "F": AgilentHPLCChromatogram(self.data_dir),
48
+ "G": AgilentHPLCChromatogram(self.data_dir),
49
+ "H": AgilentHPLCChromatogram(self.data_dir),
50
+ }
51
+
52
+ self.data_files: Union[list[SequenceDataFiles], list[str]] = []
53
+
54
+ # Initialize row counter for table operations
55
+ self.send('Local Rows')
56
+
57
+ def receive(self) -> Result[Response, str]:
58
+ for _ in range(10):
59
+ try:
60
+ return self.controller.receive()
61
+ except IndexError:
62
+ continue
63
+ return Err("Could not parse response")
64
+
65
+ def send(self, cmd: Union[Command, str]):
66
+ if not self.controller:
67
+ raise RuntimeError(
68
+ "Communication controller must be initialized before sending command. It is currently in offline mode.")
69
+ self.controller.send(cmd)
70
+
71
+ def sleepy_send(self, cmd: Union[Command, str]):
72
+ self.controller.sleepy_send(cmd)
73
+
74
+ def sleep(self, seconds: int):
75
+ """
76
+ Tells the HPLC to wait for a specified number of seconds.
77
+
78
+ :param seconds: number of seconds to wait
79
+ """
80
+ self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
81
+
82
+ def get_num(self, row: int, col_name: RegisterFlag) -> float:
83
+ return self.controller.get_num_val(TableOperation.GET_ROW_VAL.value.format(register=self.table.register,
84
+ table_name=self.table.name,
85
+ row=row,
86
+ col_name=col_name.value))
87
+
88
+ def get_text(self, row: int, col_name: RegisterFlag) -> str:
89
+ return self.controller.get_text_val(TableOperation.GET_ROW_TEXT.value.format(register=self.table.register,
90
+ table_name=self.table.name,
91
+ row=row,
92
+ col_name=col_name.value))
93
+
94
+ def add_new_col_num(self,
95
+ col_name: RegisterFlag,
96
+ val: Union[int, float]):
97
+ self.sleepy_send(TableOperation.NEW_COL_VAL.value.format(
98
+ register=self.table.register,
99
+ table_name=self.table.name,
100
+ col_name=col_name,
101
+ val=val))
102
+
103
+ def add_new_col_text(self,
104
+ col_name: RegisterFlag,
105
+ val: str):
106
+ self.sleepy_send(TableOperation.NEW_COL_TEXT.value.format(
107
+ register=self.table.register,
108
+ table_name=self.table.name,
109
+ col_name=col_name,
110
+ val=val))
111
+
112
+ def edit_row_num(self,
113
+ col_name: RegisterFlag,
114
+ val: Union[int, float],
115
+ row: Optional[int] = None):
116
+ self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(
117
+ register=self.table.register,
118
+ table_name=self.table.name,
119
+ row=row if row is not None else 'Rows',
120
+ col_name=col_name,
121
+ val=val))
122
+
123
+ def edit_row_text(self,
124
+ col_name: RegisterFlag,
125
+ val: str,
126
+ row: Optional[int] = None):
127
+ self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(
128
+ register=self.table.register,
129
+ table_name=self.table.name,
130
+ row=row if row is not None else 'Rows',
131
+ col_name=col_name,
132
+ val=val))
133
+
134
+ @abc.abstractmethod
135
+ def get_row(self, row: int):
136
+ pass
137
+
138
+ def delete_row(self, row: int):
139
+ self.sleepy_send(TableOperation.DELETE_ROW.value.format(register=self.table.register,
140
+ table_name=self.table.name,
141
+ row=row))
142
+
143
+ def add_row(self):
144
+ """
145
+ Adds a row to the provided table for currently loaded method or sequence.
146
+ Import either the SEQUENCE_TABLE or METHOD_TIMETABLE from hein_analytical_control.constants.
147
+ You can also provide your own table.
148
+
149
+ :param table: the table to add a new row to
150
+ """
151
+ self.sleepy_send(TableOperation.NEW_ROW.value.format(register=self.table.register,
152
+ table_name=self.table.name))
153
+
154
+ def delete_table(self):
155
+ """
156
+ Deletes the table for the current loaded method or sequence.
157
+ Import either the SEQUENCE_TABLE or METHOD_TIMETABLE from hein_analytical_control.constants.
158
+ You can also provide your own table.
159
+
160
+ :param table: the table to delete
161
+ """
162
+ self.sleepy_send(TableOperation.DELETE_TABLE.value.format(register=self.table.register,
163
+ table_name=self.table.name))
164
+
165
+ def new_table(self):
166
+ """
167
+ Creates the table for the currently loaded method or sequence. Import either the SEQUENCE_TABLE or
168
+ METHOD_TIMETABLE from hein_analytical_control.constants. You can also provide your own table.
169
+
170
+ :param table: the table to create
171
+ """
172
+ self.send(TableOperation.CREATE_TABLE.value.format(register=self.table.register,
173
+ table_name=self.table.name))
174
+
175
+ def get_num_rows(self) -> Result[Response, str]:
176
+ self.send(TableOperation.GET_NUM_ROWS.value.format(register=self.table.register,
177
+ table_name=self.table.name,
178
+ col_name=RegisterFlag.NUM_ROWS))
179
+ self.send(Command.GET_ROWS_CMD.value.format(register=self.table.register,
180
+ table_name=self.table.name,
181
+ col_name=RegisterFlag.NUM_ROWS))
182
+ res = self.controller.receive()
183
+
184
+ if res.is_ok():
185
+ self.send("Sleep 0.1")
186
+ self.send('Print Rows')
187
+ return res
188
+ else:
189
+ return Err("No rows could be read.")
190
+
191
+ def check_hplc_is_running(self) -> bool:
192
+ started_running = polling.poll(
193
+ lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
194
+ step=5,
195
+ max_tries=100)
196
+ return started_running
197
+
198
+ def check_hplc_done_running(self,
199
+ method: Optional[MethodDetails] = None,
200
+ sequence: Optional[SequenceTable] = None) -> Result[str, str]:
201
+ """
202
+ Checks if ChemStation has finished running and can read data back
203
+
204
+ :param method: if you are running a method and want to read back data, the timeout period will be adjusted to be longer than the method's runtime
205
+ :param sequence: if you are running a sequence and want to read back data, the timeout period will be adjusted to be longer than the sequence's runtime
206
+ :return: Return True if data can be read back, else False.
207
+ """
208
+ timeout = 10 * 60
209
+ if method:
210
+ timeout = ((method.stop_time + method.post_time + 3) * 60)
211
+ if sequence:
212
+ timeout *= len(sequence.rows)
213
+
214
+ most_recent_folder = self.retrieve_recent_data_files()
215
+ finished_run = polling.poll(
216
+ lambda: self.controller.check_if_running(),
217
+ timeout=timeout,
218
+ step=50)
219
+
220
+ check_folder = self.fuzzy_match_most_recent_folder(most_recent_folder)
221
+ if check_folder.is_ok() and finished_run:
222
+ return check_folder
223
+ elif check_folder.is_ok():
224
+ finished_run = polling.poll(
225
+ lambda: self.controller.check_if_running(),
226
+ timeout=timeout,
227
+ step=50)
228
+ if finished_run:
229
+ return check_folder
230
+ return check_folder
231
+ else:
232
+ return Err("Run did not complete as expected")
233
+
234
+ def fuzzy_match_most_recent_folder(self, most_recent_folder) -> Result[str, str]:
235
+ if os.path.exists(most_recent_folder):
236
+ return Ok(most_recent_folder)
237
+
238
+ subdirs = [x[0] for x in os.walk(self.data_dir)]
239
+ potential_folders = sorted(list(filter(lambda d: most_recent_folder in d, subdirs)))
240
+ parent_dirs = []
241
+ for folder in potential_folders:
242
+ path = os.path.normpath(folder)
243
+ split_folder = path.split(os.sep)
244
+ if most_recent_folder in split_folder[-1]:
245
+ parent_dirs.append(folder)
246
+ parent_dir = sorted(parent_dirs, reverse=True)[0]
247
+ return Ok(parent_dir)
248
+
249
+ @abc.abstractmethod
250
+ def retrieve_recent_data_files(self):
251
+ pass
252
+
253
+ @abc.abstractmethod
254
+ def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
255
+ pass
256
+
257
+ def get_spectrum(self, data_file: str):
258
+ """
259
+ Load chromatogram for any channel in spectra dictionary.
260
+ """
261
+ for channel, spec in self.spectra.items():
262
+ try:
263
+ spec.load_spectrum(data_path=data_file, channel=channel)
264
+ except FileNotFoundError:
265
+ self.spectra[channel] = None
266
+ print(f"No data at channel: {channel}")
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import time
3
- from typing import List
3
+ import warnings
4
+ from typing import Dict, List, Optional, Union
4
5
 
5
6
  from result import Err, Ok, Result
6
7
  from xsdata.formats.dataclass.parsers import XmlParser
@@ -13,9 +14,15 @@ from ....utils.chromatogram import (
13
14
  AgilentChannelChromatogramData,
14
15
  AgilentHPLCChromatogram,
15
16
  )
16
- from ....utils.macro import *
17
- from ....utils.method_types import *
18
- from ....utils.table_types import *
17
+ from ....utils.macro import Command
18
+ from ....utils.method_types import (
19
+ HPLCMethodParams,
20
+ MethodDetails,
21
+ Param,
22
+ PType,
23
+ TimeTableEntry,
24
+ )
25
+ from ....utils.table_types import RegisterFlag, T, Table, TableOperation
19
26
  from ..devices.injector import InjectorController
20
27
  from .table import TableController
21
28
 
@@ -129,6 +136,10 @@ class MethodController(TableController):
129
136
  register=self.table_locator.register,
130
137
  register_flag=RegisterFlag.MAX_TIME))
131
138
 
139
+ def get_total_runtime(self) -> Union[int, float]:
140
+ """Returns total method runtime in minutes."""
141
+ return self.get_post_time() + self.get_stop_time()
142
+
132
143
  def current_method(self, method_name: str):
133
144
  """
134
145
  Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
@@ -173,6 +184,7 @@ class MethodController(TableController):
173
184
  :raises FileNotFoundError: Method does not exist
174
185
  :return: method details
175
186
  """
187
+ warnings.warn("This method is not actively maintained.")
176
188
  method_folder = f"{method_name}.M"
177
189
  method_path = os.path.join(self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml")
178
190
  dad_path = os.path.join(self.src, method_folder, "Agilent1200erDadDriver1.RapidControl.MethodXML.xml")
@@ -347,29 +359,39 @@ class MethodController(TableController):
347
359
  raise RuntimeError("Method failed to start.")
348
360
 
349
361
  self.data_files.append(os.path.join(self.data_dirs[0], folder_name))
362
+ self.timeout = (self.get_total_runtime()) * 60
350
363
 
351
364
  if stall_while_running:
352
- self.timeout = (self.get_stop_time() + self.get_post_time()) * 60
353
365
  run_completed = self.check_hplc_done_running()
354
366
  if run_completed.is_ok():
355
367
  self.data_files[-1] = run_completed.ok_value
356
368
  else:
357
369
  raise RuntimeError("Run error has occurred.")
358
370
  else:
359
- self.data_files[-1] = self.fuzzy_match_most_recent_folder(folder_name).ok_value
371
+ folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
372
+ while folder.is_err():
373
+ folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
374
+ if folder.is_ok():
375
+ self.data_files[-1] = folder.ok_value
376
+ else:
377
+ warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
378
+ warnings.warn(warning)
379
+
360
380
 
361
381
  def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
362
382
  if os.path.exists(most_recent_folder):
363
383
  return Ok(most_recent_folder)
364
384
  return Err("Folder not found!")
365
385
 
366
- def get_data(self, custom_path: Optional[str] = None,
367
- read_uv: bool = False) -> AgilentChannelChromatogramData:
368
- if not custom_path:
369
- self.get_spectrum(self.data_files[-1], read_uv)
370
- else:
371
- self.get_spectrum(custom_path, read_uv)
372
- return AgilentChannelChromatogramData(**self.spectra) if not read_uv else self.uv
386
+ def get_data(self, custom_path: Optional[str] = None) -> AgilentChannelChromatogramData:
387
+ custom_path = custom_path if custom_path else self.data_files[-1]
388
+ self.get_spectrum_at_channels(custom_path)
389
+ return AgilentChannelChromatogramData(**self.spectra)
390
+
391
+ def get_data_uv(self, custom_path: Optional[str] = None) -> Dict[str, AgilentHPLCChromatogram]:
392
+ custom_path = custom_path if custom_path else self.data_files[-1]
393
+ self.get_uv_spectrum(custom_path)
394
+ return self.uv
373
395
 
374
396
  def get_report(self, custom_path: Optional[str] = None,
375
397
  report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
@@ -1,19 +1,29 @@
1
1
  import os
2
2
  import time
3
- from typing import Optional, List, Dict
3
+ from typing import Dict, List, Optional
4
4
 
5
- from result import Result, Ok, Err
5
+ from result import Err, Ok, Result
6
6
  from typing_extensions import override
7
7
 
8
- from .table import TableController
9
- from .. import MethodController
10
- from ....analysis.process_report import ReportType, AgilentReport
8
+ from ....analysis.process_report import AgilentReport, ReportType
11
9
  from ....control.controllers.comm import CommunicationController
12
- from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData, AgilentHPLCChromatogram
10
+ from ....utils.chromatogram import (
11
+ SEQUENCE_TIME_FORMAT,
12
+ AgilentChannelChromatogramData,
13
+ AgilentHPLCChromatogram,
14
+ )
13
15
  from ....utils.macro import Command
14
- from ....utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
16
+ from ....utils.sequence_types import (
17
+ InjectionSource,
18
+ SampleType,
19
+ SequenceDataFiles,
20
+ SequenceEntry,
21
+ SequenceTable,
22
+ )
15
23
  from ....utils.table_types import RegisterFlag, Table
16
- from ....utils.tray_types import TenVialColumn, FiftyFourVialPlate
24
+ from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
25
+ from .. import MethodController
26
+ from .table import TableController
17
27
 
18
28
 
19
29
  class SequenceController(TableController):
@@ -82,7 +92,7 @@ class SequenceController(TableController):
82
92
  time.sleep(2)
83
93
  self.send(Command.GET_SEQUENCE_CMD)
84
94
  time.sleep(2)
85
- parsed_response = self.receive().value.string_response
95
+ parsed_response = self.receive().ok_value.string_response
86
96
 
87
97
  assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
88
98
  self.table_state = None
@@ -97,7 +107,7 @@ class SequenceController(TableController):
97
107
  self.table_state = sequence_table
98
108
  rows = self.get_num_rows()
99
109
  if rows.is_ok():
100
- existing_row_num = rows.value.num_response
110
+ existing_row_num = rows.ok_value.num_response
101
111
  wanted_row_num = len(sequence_table.rows)
102
112
  while existing_row_num != wanted_row_num:
103
113
  if wanted_row_num > existing_row_num:
@@ -127,7 +137,6 @@ class SequenceController(TableController):
127
137
  self.add_row()
128
138
  self.send(Command.SAVE_SEQUENCE_CMD)
129
139
  num_rows = self.get_num_rows()
130
-
131
140
  if row.vial_location:
132
141
  loc = row.vial_location
133
142
  if isinstance(loc, TenVialColumn):
@@ -135,7 +144,6 @@ class SequenceController(TableController):
135
144
  elif isinstance(loc, FiftyFourVialPlate):
136
145
  loc = row.vial_location.value()
137
146
  self._edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
138
-
139
147
  if row.method:
140
148
  method_dir = self.method_controller.src
141
149
  possible_path = os.path.join(method_dir, row.method) + ".M\\"
@@ -143,23 +151,18 @@ class SequenceController(TableController):
143
151
  if os.path.exists(possible_path):
144
152
  method = os.path.join(method_dir, row.method)
145
153
  self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
146
-
147
154
  if row.num_inj:
148
155
  self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj)
149
-
150
156
  if row.inj_vol:
151
157
  self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol)
152
-
153
158
  if row.inj_source:
154
159
  self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value)
155
-
156
160
  if row.sample_name:
157
161
  self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
158
162
  if row.data_file:
159
163
  self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file)
160
164
  else:
161
165
  self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
162
-
163
166
  if row.sample_type:
164
167
  self._edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
165
168
 
@@ -179,15 +182,15 @@ class SequenceController(TableController):
179
182
 
180
183
  total_runtime = 0
181
184
  for entry in self.table_state.rows:
182
- curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
183
- loaded_method = self.method_controller.get_method_name()[:-2]
185
+ curr_method_runtime = self.method_controller.get_total_runtime()
186
+ loaded_method = self.method_controller.get_method_name().removesuffix(".M")
184
187
  method_path = entry.method.split(sep="\\")
185
188
  method_name = method_path[-1]
186
189
  if loaded_method != method_name:
187
- method_dir = os.path.join(*method_path[:-1]) if len(method_path) > 1 else None
190
+ method_dir = "\\".join(method_path[:-1])+"\\" if len(method_path) > 1 else None
188
191
  self.method_controller.switch(method_name=method_name,
189
192
  alt_method_dir=method_dir)
190
- curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
193
+ curr_method_runtime = self.method_controller.get_total_runtime()
191
194
  total_runtime += curr_method_runtime
192
195
 
193
196
  timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
@@ -196,8 +199,8 @@ class SequenceController(TableController):
196
199
 
197
200
  if self.check_hplc_is_running():
198
201
  folder_name = f"{self.table_state.name} {timestamp}"
199
- self.data_files.append(SequenceDataFiles(dir=folder_name,
200
- sequence_name=self.table_state.name))
202
+ data_file = SequenceDataFiles(dir=folder_name, sequence_name=self.table_state.name)
203
+ self.data_files.append(data_file)
201
204
 
202
205
  if stall_while_running:
203
206
  run_completed = self.check_hplc_done_running()
@@ -205,10 +208,8 @@ class SequenceController(TableController):
205
208
  self.data_files[-1] = run_completed.ok_value
206
209
  else:
207
210
  raise RuntimeError("Run error has occurred.")
208
- else:
209
- self.data_files[-1] = SequenceDataFiles(dir=folder_name,
210
- child_dirs=[],
211
- sequence_name=self.table_state.name)
211
+ else:
212
+ raise RuntimeError("Sequence run did not start.")
212
213
 
213
214
  @override
214
215
  def fuzzy_match_most_recent_folder(self, most_recent_folder: SequenceDataFiles) -> Result[SequenceDataFiles, str]:
@@ -248,17 +249,25 @@ class SequenceController(TableController):
248
249
  except Exception:
249
250
  return Err("Failed to get sequence folder")
250
251
 
251
- def get_data(self, custom_path: Optional[str] = None,
252
- read_uv: bool = False) -> List[AgilentChannelChromatogramData]:
253
- if len(self.data_files[-1].child_dirs) == 0:
254
- self.data_files[-1] = self.fuzzy_match_most_recent_folder(self.data_files[-1]).ok_value
255
- spectra: list[AgilentChannelChromatogramData] = []
256
- all_w_spectra: list[Dict[str, AgilentHPLCChromatogram]] = []
252
+ def get_data_uv(self,custom_path: Optional[str] = None) -> List[Dict[str, AgilentHPLCChromatogram]]:
253
+ custom_path = SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="") if custom_path else self.data_files[-1]
254
+ if len(custom_path.child_dirs) == 0:
255
+ self.data_files[-1] = self.fuzzy_match_most_recent_folder(custom_path).ok_value
256
+ all_w_spectra: List[Dict[str, AgilentHPLCChromatogram]] = []
257
257
  for row in self.data_files[-1].child_dirs:
258
- self.get_spectrum(row, read_uv)
259
- spectra.append(AgilentChannelChromatogramData(**self.spectra))
258
+ self.get_uv_spectrum(row)
260
259
  all_w_spectra.append(self.uv)
261
- return spectra if not read_uv else all_w_spectra
260
+ return all_w_spectra
261
+
262
+ def get_data(self, custom_path: Optional[str] = None) -> List[AgilentChannelChromatogramData]:
263
+ custom_path = SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="") if custom_path else self.data_files[-1]
264
+ if len(custom_path.child_dirs) == 0:
265
+ self.data_files[-1] = self.fuzzy_match_most_recent_folder(custom_path).ok_value
266
+ spectra: List[AgilentChannelChromatogramData] = []
267
+ for row in self.data_files[-1].child_dirs:
268
+ self.get_spectrum_at_channels(row)
269
+ spectra.append(AgilentChannelChromatogramData(**self.spectra))
270
+ return spectra
262
271
 
263
272
  def get_report(self, custom_path: Optional[str] = None,
264
273
  report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
@@ -268,7 +277,7 @@ class SequenceController(TableController):
268
277
  child_dirs=[],
269
278
  sequence_name="NA")).ok_value)
270
279
  parent_dir = self.data_files[-1]
271
- spectra = self.get_data(custom_path=custom_path)
280
+ spectra = self.get_data()
272
281
  reports = []
273
282
  for i, child_dir in enumerate(parent_dir.child_dirs):
274
283
  metd_report = self.get_report_details(child_dir, report_type)