pychemstation 0.7.0.dev1__py3-none-any.whl → 0.8.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 (49) hide show
  1. pychemstation/analysis/base_spectrum.py +3 -6
  2. pychemstation/analysis/process_report.py +248 -225
  3. pychemstation/analysis/utils.py +3 -1
  4. pychemstation/control/README.md +124 -0
  5. pychemstation/control/controllers/README.md +1 -0
  6. pychemstation/control/controllers/__init__.py +0 -2
  7. pychemstation/control/controllers/comm.py +27 -20
  8. pychemstation/control/controllers/devices/device.py +17 -4
  9. pychemstation/control/controllers/tables/method.py +57 -39
  10. pychemstation/control/controllers/tables/sequence.py +98 -31
  11. pychemstation/control/controllers/tables/table.py +121 -126
  12. pychemstation/control/hplc.py +82 -37
  13. pychemstation/generated/dad_method.py +3 -3
  14. pychemstation/generated/pump_method.py +7 -7
  15. pychemstation/out.txt +145 -0
  16. pychemstation/tests.ipynb +310 -0
  17. pychemstation/utils/chromatogram.py +5 -1
  18. pychemstation/utils/injector_types.py +2 -2
  19. pychemstation/utils/macro.py +1 -1
  20. pychemstation/utils/table_types.py +3 -0
  21. pychemstation/utils/tray_types.py +59 -39
  22. {pychemstation-0.7.0.dev1.dist-info → pychemstation-0.8.0.dist-info}/METADATA +25 -21
  23. pychemstation-0.8.0.dist-info/RECORD +39 -0
  24. {pychemstation-0.7.0.dev1.dist-info → pychemstation-0.8.0.dist-info}/WHEEL +1 -2
  25. pychemstation/control/comm.py +0 -206
  26. pychemstation/control/controllers/devices/column.py +0 -12
  27. pychemstation/control/controllers/devices/dad.py +0 -0
  28. pychemstation/control/controllers/devices/pump.py +0 -43
  29. pychemstation/control/controllers/method.py +0 -338
  30. pychemstation/control/controllers/sequence.py +0 -190
  31. pychemstation/control/controllers/table_controller.py +0 -266
  32. pychemstation/control/table/__init__.py +0 -3
  33. pychemstation/control/table/method.py +0 -274
  34. pychemstation/control/table/sequence.py +0 -210
  35. pychemstation/control/table/table_controller.py +0 -201
  36. pychemstation-0.7.0.dev1.dist-info/RECORD +0 -58
  37. pychemstation-0.7.0.dev1.dist-info/top_level.txt +0 -2
  38. tests/__init__.py +0 -0
  39. tests/constants.py +0 -88
  40. tests/test_comb.py +0 -136
  41. tests/test_comm.py +0 -65
  42. tests/test_inj.py +0 -39
  43. tests/test_method.py +0 -99
  44. tests/test_nightly.py +0 -80
  45. tests/test_proc_rep.py +0 -52
  46. tests/test_runs_stable.py +0 -125
  47. tests/test_sequence.py +0 -125
  48. tests/test_stable.py +0 -283
  49. {pychemstation-0.7.0.dev1.dist-info → pychemstation-0.8.0.dist-info/licenses}/LICENSE +0 -0
@@ -3,7 +3,5 @@
3
3
  """
4
4
 
5
5
  from .comm import CommunicationController
6
- from .devices.pump import PumpController
7
- from .devices.column import ColumnController
8
6
  from .tables.method import MethodController
9
7
  from .tables.sequence import SequenceController
@@ -9,7 +9,6 @@ been processed.
9
9
 
10
10
  Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
11
11
  """
12
- import logging
13
12
  import os
14
13
  import time
15
14
  from typing import Optional
@@ -32,12 +31,15 @@ class CommunicationController:
32
31
  comm_dir: str,
33
32
  cmd_file: str = "cmd",
34
33
  reply_file: str = "reply",
34
+ debug: bool = False
35
35
  ):
36
36
  """
37
37
  :param comm_dir:
38
38
  :param cmd_file: Name of command file
39
39
  :param reply_file: Name of reply file
40
+ :param debug: whether to save log of sent commands
40
41
  """
42
+ self.debug = debug
41
43
  if os.path.isdir(comm_dir):
42
44
  self.cmd_file = os.path.join(comm_dir, cmd_file)
43
45
  self.reply_file = os.path.join(comm_dir, reply_file)
@@ -52,21 +54,26 @@ class CommunicationController:
52
54
 
53
55
  self.reset_cmd_counter()
54
56
 
55
- def get_num_val(self, cmd: str) -> Union[int, float, Err]:
56
- self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
57
- res = self.receive()
58
- if res.is_ok():
59
- return res.ok_value.num_response
60
- else:
61
- raise RuntimeError("Failed to get number.")
57
+ # Initialize row counter for table operations
58
+ self.send('Local Rows')
59
+
60
+ def get_num_val(self, cmd: str) -> Union[int, float]:
61
+ tries = 5
62
+ for _ in range(tries):
63
+ self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
64
+ res = self.receive()
65
+ if res.is_ok():
66
+ return res.ok_value.num_response
67
+ raise RuntimeError("Failed to get number.")
62
68
 
63
69
  def get_text_val(self, cmd: str) -> str:
64
- self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
65
- res = self.receive()
66
- if res.is_ok():
67
- return res.ok_value.string_response
68
- else:
69
- raise RuntimeError("Failed to get string")
70
+ tries = 5
71
+ for _ in range(tries):
72
+ self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
73
+ res = self.receive()
74
+ if res.is_ok():
75
+ return res.ok_value.string_response
76
+ raise RuntimeError("Failed to get string")
70
77
 
71
78
  def get_status(self) -> Status:
72
79
  """Get device status(es).
@@ -89,16 +96,15 @@ class CommunicationController:
89
96
  """Updates current status of HPLC machine"""
90
97
  self._most_recent_hplc_status = self.get_status()
91
98
 
92
- def check_if_running(self) -> bool:
99
+ def check_if_not_running(self) -> bool:
93
100
  """Checks if HPLC machine is in an available state, meaning a state that data is not being written.
94
101
 
95
102
  :return: whether the HPLC machine is in a safe state to retrieve data back."""
96
103
  self.set_status()
97
104
  hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
98
- time.sleep(30)
105
+ time.sleep(5)
99
106
  self.set_status()
100
107
  hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
101
- logging.info("Still running")
102
108
  return hplc_avail and hplc_actually_avail
103
109
 
104
110
  def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
@@ -173,9 +179,10 @@ class CommunicationController:
173
179
  cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
174
180
  self.cmd_no += 1
175
181
  self._send(cmd_to_send, self.cmd_no)
176
- f = open("out.txt", "a")
177
- f.write(cmd_to_send + "\n")
178
- f.close()
182
+ if self.debug:
183
+ f = open("out.txt", "a")
184
+ f.write(cmd_to_send + "\n")
185
+ f.close()
179
186
 
180
187
  def receive(self) -> Result[Response, str]:
181
188
  """Returns messages received in reply file.
@@ -1,16 +1,23 @@
1
1
  import abc
2
- from typing import Union
2
+ from typing import Union, List
3
3
 
4
+ from result import Result
5
+
6
+ from ....analysis.process_report import ReportType, AgilentReport
4
7
  from ....control.controllers import CommunicationController
5
8
  from ....control.controllers.tables.table import TableController
6
9
  from ....utils.chromatogram import AgilentChannelChromatogramData
7
- from ....utils.table_types import Table
10
+ from ....utils.table_types import Table, T
8
11
 
9
12
 
10
13
  class DeviceController(TableController, abc.ABC):
11
14
 
12
15
  def __init__(self, controller: CommunicationController, table: Table, offline: bool):
13
- super().__init__(controller, None, None, table, offline)
16
+ super().__init__(controller=controller,
17
+ src=None,
18
+ data_dirs=[],
19
+ table=table,
20
+ offline=offline)
14
21
 
15
22
  @abc.abstractmethod
16
23
  def get_row(self, row: int):
@@ -19,5 +26,11 @@ class DeviceController(TableController, abc.ABC):
19
26
  def retrieve_recent_data_files(self):
20
27
  raise NotImplementedError
21
28
 
22
- def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
29
+ def get_data(self) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
30
+ raise NotImplementedError
31
+
32
+ def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
33
+ raise NotImplementedError
34
+
35
+ def get_report(self, report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
23
36
  raise NotImplementedError
@@ -2,13 +2,15 @@ import os
2
2
  import time
3
3
  from typing import List
4
4
 
5
+ from result import Result, Ok, Err
5
6
  from xsdata.formats.dataclass.parsers import XmlParser
6
7
 
7
8
  from .table import TableController
8
9
  from ..devices.injector import InjectorController
10
+ from ....analysis.process_report import AgilentReport, ReportType
9
11
  from ....control.controllers import CommunicationController
10
12
  from ....generated import PumpMethod, DadMethod, SolventElement
11
- from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
13
+ from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData, AgilentHPLCChromatogram
12
14
  from ....utils.macro import *
13
15
  from ....utils.method_types import *
14
16
  from ....utils.table_types import *
@@ -26,7 +28,8 @@ class MethodController(TableController):
26
28
  offline: bool,
27
29
  injector_controller: InjectorController):
28
30
  self.injector_controller = injector_controller
29
- super().__init__(controller, src, data_dirs, table, offline=offline)
31
+ self.data_files: List[str] = []
32
+ super().__init__(controller=controller, src=src, data_dirs=data_dirs, table=table, offline=offline)
30
33
 
31
34
  def check(self) -> str:
32
35
  time.sleep(2)
@@ -40,13 +43,13 @@ class MethodController(TableController):
40
43
  def get_method_params(self) -> HPLCMethodParams:
41
44
  return HPLCMethodParams(organic_modifier=self.controller.get_num_val(
42
45
  cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
43
- register=self.table.register,
46
+ register=self.table_locator.register,
44
47
  register_flag=RegisterFlag.SOLVENT_B_COMPOSITION
45
48
  )
46
49
  ),
47
50
  flow=self.controller.get_num_val(
48
51
  cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
49
- register=self.table.register,
52
+ register=self.table_locator.register,
50
53
  register_flag=RegisterFlag.FLOW
51
54
  )
52
55
  ),
@@ -90,19 +93,11 @@ class MethodController(TableController):
90
93
  def load(self) -> MethodDetails:
91
94
  rows = self.get_num_rows()
92
95
  if rows.is_ok():
93
- self.send(Command.GET_METHOD_CMD)
94
- res = self.receive()
95
- method_name = res.ok_value.string_response
96
+ method_name = self.get_method_name()
96
97
  timetable_rows = self.get_timetable(int(rows.ok_value.num_response))
97
98
  params = self.get_method_params()
98
- stop_time = self.controller.get_num_val(
99
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
100
- register=self.table.register,
101
- register_flag=RegisterFlag.MAX_TIME))
102
- post_time = self.controller.get_num_val(
103
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
104
- register=self.table.register,
105
- register_flag=RegisterFlag.POST_TIME))
99
+ stop_time = self.get_stop_time()
100
+ post_time = self.get_post_time()
106
101
  self.table_state = MethodDetails(name=method_name,
107
102
  timetable=timetable_rows,
108
103
  stop_time=stop_time,
@@ -112,6 +107,24 @@ class MethodController(TableController):
112
107
  else:
113
108
  raise RuntimeError(rows.err_value)
114
109
 
110
+ def get_method_name(self):
111
+ self.send(Command.GET_METHOD_CMD)
112
+ res = self.receive()
113
+ method_name = res.ok_value.string_response
114
+ return method_name
115
+
116
+ def get_post_time(self) -> Union[int, float]:
117
+ return self.controller.get_num_val(
118
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
119
+ register=self.table_locator.register,
120
+ register_flag=RegisterFlag.POST_TIME))
121
+
122
+ def get_stop_time(self) -> Union[int, float]:
123
+ return self.controller.get_num_val(
124
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
125
+ register=self.table_locator.register,
126
+ register_flag=RegisterFlag.MAX_TIME))
127
+
115
128
  def current_method(self, method_name: str):
116
129
  """
117
130
  Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
@@ -123,18 +136,19 @@ class MethodController(TableController):
123
136
  parsed_response = self.receive()
124
137
  return method_name in parsed_response
125
138
 
126
- def switch(self, method_name: str):
139
+ def switch(self, method_name: str, alt_method_dir: Optional[str] = None):
127
140
  """
128
141
  Allows the user to switch between pre-programmed methods. No need to append '.M'
129
142
  to the end of the method name. For example. for the method named 'General-Poroshell.M',
130
143
  only 'General-Poroshell' is needed.
131
144
 
132
145
  :param method_name: any available method in Chemstation method directory
146
+ :param alt_method_dir: directory where the method resides
133
147
  :raise IndexError: Response did not have expected format. Try again.
134
148
  :raise AssertionError: The desired method is not selected. Try again.
135
149
  """
136
- self.send(Command.SWITCH_METHOD_CMD_SPECIFIC.value.format(method_dir=self.src,
137
- method_name=method_name))
150
+ method_dir = self.src if not alt_method_dir else alt_method_dir
151
+ self.send(Command.SWITCH_METHOD_CMD_SPECIFIC.value.format(method_dir=method_dir, method_name=method_name))
138
152
 
139
153
  time.sleep(2)
140
154
  self.send(Command.GET_METHOD_CMD)
@@ -239,7 +253,7 @@ class MethodController(TableController):
239
253
 
240
254
  :param method_param: a parameter to update for currently loaded method.
241
255
  """
242
- register = self.table.register
256
+ register = self.table_locator.register
243
257
  setting_command = TableOperation.UPDATE_OBJ_HDR_VAL if method_param.ptype == PType.NUM else TableOperation.UPDATE_OBJ_HDR_TEXT
244
258
  if isinstance(method_param.chemstation_key, list):
245
259
  for register_flag in method_param.chemstation_key:
@@ -287,9 +301,8 @@ class MethodController(TableController):
287
301
  self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
288
302
  self.download()
289
303
 
290
- def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
304
+ def _update_method_timetable(self, timetable_rows: List[TimeTableEntry]):
291
305
  self.get_num_rows()
292
-
293
306
  self.delete_table()
294
307
  res = self.get_num_rows()
295
308
  while not res.is_err():
@@ -304,19 +317,15 @@ class MethodController(TableController):
304
317
 
305
318
  def stop(self):
306
319
  """
307
- Stops the method run. A dialog window will pop up and manual intervention may be required.\
320
+ Stops the method run. A dialog window will pop up and manual intervention may be required.
308
321
  """
309
322
  self.send(Command.STOP_METHOD_CMD)
310
323
 
311
- def run(self, experiment_name: str, stall_while_running: bool = True):
324
+ def run(self, experiment_name: str, add_timestamp: bool = True, stall_while_running: bool = True):
312
325
  """
313
- This is the preferred method to trigger a run.
314
- Starts the currently selected method, storing data
315
- under the <data_dir>/<experiment_name>.D folder.
316
- The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
317
- Device must be ready.
318
-
319
326
  :param experiment_name: Name of the experiment
327
+ :param stall_while_running: whether to stall or immediately return
328
+ :param add_timestamp: if should add timestamp to experiment name
320
329
  """
321
330
 
322
331
  folder_name = ""
@@ -325,9 +334,8 @@ class MethodController(TableController):
325
334
  while tries < 10 and not hplc_is_running:
326
335
  timestamp = time.strftime(TIME_FORMAT)
327
336
  self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dirs[0],
328
- experiment_name=experiment_name,
329
- timestamp=timestamp))
330
- folder_name = f"{experiment_name}_{timestamp}.D"
337
+ experiment_name=f"{experiment_name}_{timestamp}" if add_timestamp else experiment_name))
338
+ folder_name = f"{experiment_name}_{timestamp}.D" if add_timestamp else f"{experiment_name}.D"
331
339
  hplc_is_running = self.check_hplc_is_running()
332
340
  tries += 1
333
341
 
@@ -337,11 +345,8 @@ class MethodController(TableController):
337
345
  self.data_files.append(os.path.join(self.data_dirs[0], folder_name))
338
346
 
339
347
  if stall_while_running:
340
-
341
- if not self.table_state:
342
- self.table_state = self.load()
343
-
344
- run_completed = self.check_hplc_done_running(method=self.table_state)
348
+ self.timeout = (self.get_stop_time() + self.get_post_time()) * 60
349
+ run_completed = self.check_hplc_done_running()
345
350
  if run_completed.is_ok():
346
351
  self.data_files[-1] = run_completed.ok_value
347
352
  else:
@@ -349,8 +354,10 @@ class MethodController(TableController):
349
354
  else:
350
355
  self.data_files[-1] = self.fuzzy_match_most_recent_folder(folder_name).ok_value
351
356
 
352
- def retrieve_recent_data_files(self) -> str:
353
- return self.data_files[-1]
357
+ def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
358
+ if os.path.exists(most_recent_folder):
359
+ return Ok(most_recent_folder)
360
+ return Err("Folder not found!")
354
361
 
355
362
  def get_data(self, custom_path: Optional[str] = None,
356
363
  read_uv: bool = False) -> AgilentChannelChromatogramData:
@@ -359,3 +366,14 @@ class MethodController(TableController):
359
366
  else:
360
367
  self.get_spectrum(custom_path, read_uv)
361
368
  return AgilentChannelChromatogramData(**self.spectra) if not read_uv else self.uv
369
+
370
+ def get_report(self, custom_path: Optional[str] = None,
371
+ report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
372
+ metd_report = self.get_report_details(self.data_files[-1] if not custom_path else custom_path,
373
+ report_type)
374
+ chrom_data: List[AgilentHPLCChromatogram] = list(self.get_data(custom_path).__dict__.values())
375
+ for i, signal in enumerate(metd_report.signals):
376
+ possible_data = chrom_data[i]
377
+ if len(possible_data.x) > 0:
378
+ signal.data = possible_data
379
+ return [metd_report]
@@ -1,10 +1,15 @@
1
1
  import os
2
2
  import time
3
- from typing import Optional, Union, List
3
+ from typing import Optional, List, Dict
4
4
 
5
- from .table import TableController, ChromData
5
+ from result import Result, Ok, Err
6
+ from typing_extensions import override
7
+
8
+ from .table import TableController
9
+ from .. import MethodController
10
+ from ....analysis.process_report import ReportType, AgilentReport
6
11
  from ....control.controllers.comm import CommunicationController
7
- from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData
12
+ from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData, AgilentHPLCChromatogram
8
13
  from ....utils.macro import Command
9
14
  from ....utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
10
15
  from ....utils.table_types import RegisterFlag, Table
@@ -17,13 +22,14 @@ class SequenceController(TableController):
17
22
  """
18
23
 
19
24
  def __init__(self, controller: CommunicationController,
25
+ method_controller: MethodController,
20
26
  src: str,
21
27
  data_dirs: List[str],
22
28
  table: Table,
23
- method_dir: str,
24
29
  offline: bool):
25
- self.method_dir = method_dir
26
- super().__init__(controller, src, data_dirs, table, offline=offline)
30
+ self.method_controller = method_controller
31
+ self.data_files: List[SequenceDataFiles] = []
32
+ super().__init__(controller=controller, src=src, data_dirs=data_dirs, table=table, offline=offline)
27
33
 
28
34
  def load(self) -> SequenceTable:
29
35
  rows = self.get_num_rows()
@@ -42,7 +48,7 @@ class SequenceController(TableController):
42
48
  vial_location = int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
43
49
  method = self.get_text(row, RegisterFlag.METHOD)
44
50
  num_inj = int(self.get_num(row, RegisterFlag.NUM_INJ))
45
- inj_vol = int(self.get_text(row, RegisterFlag.INJ_VOL))
51
+ inj_vol = float(self.get_text(row, RegisterFlag.INJ_VOL))
46
52
  inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
47
53
  sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
48
54
  vial_enum = TenVialColumn(vial_location) if vial_location <= 10 else FiftyFourVialPlate.from_int(
@@ -131,10 +137,11 @@ class SequenceController(TableController):
131
137
  self._edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
132
138
 
133
139
  if row.method:
134
- possible_path = os.path.join(self.method_dir, row.method) + ".M\\"
140
+ method_dir = self.method_controller.src
141
+ possible_path = os.path.join(method_dir, row.method) + ".M\\"
135
142
  method = row.method
136
143
  if os.path.exists(possible_path):
137
- method = os.path.join(self.method_dir, row.method)
144
+ method = os.path.join(method_dir, row.method)
138
145
  self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
139
146
 
140
147
  if row.num_inj:
@@ -165,11 +172,27 @@ class SequenceController(TableController):
165
172
  Device must be ready.
166
173
  """
167
174
  self.controller.send(Command.SAVE_METHOD_CMD)
175
+ self.controller.send(Command.SAVE_SEQUENCE_CMD)
176
+
168
177
  if not self.table_state:
169
178
  self.table_state = self.load()
170
179
 
180
+ total_runtime = 0
181
+ 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]
184
+ method_path = entry.method.split(sep="\\")
185
+ method_name = method_path[-1]
186
+ if loaded_method != method_name:
187
+ method_dir = os.path.join(*method_path[:-1]) if len(method_path) > 1 else None
188
+ self.method_controller.switch(method_name=method_name,
189
+ alt_method_dir=method_dir)
190
+ curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
191
+ total_runtime += curr_method_runtime
192
+
171
193
  timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
172
194
  self.send(Command.RUN_SEQUENCE_CMD.value)
195
+ self.timeout = total_runtime * 60
173
196
 
174
197
  if self.check_hplc_is_running():
175
198
  folder_name = f"{self.table_state.name} {timestamp}"
@@ -177,38 +200,82 @@ class SequenceController(TableController):
177
200
  sequence_name=self.table_state.name))
178
201
 
179
202
  if stall_while_running:
180
- run_completed = self.check_hplc_done_running(sequence=self.table_state)
203
+ run_completed = self.check_hplc_done_running()
181
204
  if run_completed.is_ok():
182
- self.data_files[-1].dir = run_completed.value
205
+ self.data_files[-1].dir = run_completed.ok_value
183
206
  else:
184
207
  raise RuntimeError("Run error has occurred.")
185
208
  else:
186
- self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
187
-
188
- def retrieve_recent_data_files(self) -> str:
189
- sequence_data_files: SequenceDataFiles = self.data_files[-1]
190
- return sequence_data_files.dir
191
-
192
- def get_data(self, custom_path: Optional[str] = None,
193
- read_uv: bool = False) -> list[AgilentChannelChromatogramData]:
194
- parent_dir = self.data_files[-1].dir if not custom_path else custom_path
195
- child_dirs = self.data_files[-1].child_dirs
196
- if len(child_dirs) == 0:
197
- subdirs = []
209
+ self.data_files[-1] = SequenceDataFiles(dir=folder_name,
210
+ child_dirs=[],
211
+ sequence_name=self.table_state.name)
212
+
213
+ @override
214
+ def fuzzy_match_most_recent_folder(self, most_recent_folder: SequenceDataFiles) -> Result[SequenceDataFiles, str]:
215
+ if os.path.isdir(most_recent_folder.dir):
216
+ subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
217
+ potential_folders = sorted(list(filter(lambda d: most_recent_folder.dir in d, subdirs)))
218
+ most_recent_folder.child_dirs = [f for f in potential_folders if
219
+ most_recent_folder.dir in f and ".M" not in f and ".D" in f]
220
+ return Ok(most_recent_folder)
221
+
222
+ try:
223
+ potential_folders = []
198
224
  for d in self.data_dirs:
199
225
  subdirs = [x[0] for x in os.walk(d)]
200
- if len(subdirs) > 0:
226
+ potential_folders = sorted(list(filter(lambda d: most_recent_folder.dir in d, subdirs)))
227
+ if len(potential_folders) > 0:
201
228
  break
202
- assert len(subdirs) > 0
203
- potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
204
- self.data_files[-1].child_dirs = [f for f in potential_folders if
205
- parent_dir in f and ".M" not in f and ".D" in f]
229
+ assert len(potential_folders) > 0
230
+ parent_dirs = []
231
+ for folder in potential_folders:
232
+ path = os.path.normpath(folder)
233
+ split_folder = path.split(os.sep)
234
+ if most_recent_folder.dir in split_folder[-1]:
235
+ parent_dirs.append(folder)
236
+ parent_dir = sorted(parent_dirs, reverse=True)[0]
237
+
238
+ potential_folders = []
239
+ for d in self.data_dirs:
240
+ subdirs = [x[0] for x in os.walk(d)]
241
+ potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
242
+ if len(potential_folders) > 0:
243
+ break
244
+ assert len(potential_folders) > 0
245
+ most_recent_folder.child_dirs = [f for f in potential_folders if
246
+ parent_dir in f and ".M" not in f and ".D" in f]
247
+ return Ok(most_recent_folder)
248
+ except Exception:
249
+ return Err("Failed to get sequence folder")
206
250
 
207
- spectra: list[Union[AgilentChannelChromatogramData, ChromData]] = []
208
- all_w_spectra: list[Union[AgilentChannelChromatogramData, ChromData]] = []
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]] = []
209
257
  for row in self.data_files[-1].child_dirs:
210
258
  self.get_spectrum(row, read_uv)
211
259
  spectra.append(AgilentChannelChromatogramData(**self.spectra))
212
260
  all_w_spectra.append(self.uv)
213
-
214
261
  return spectra if not read_uv else all_w_spectra
262
+
263
+ def get_report(self, custom_path: Optional[str] = None,
264
+ report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
265
+ if custom_path:
266
+ self.data_files.append(
267
+ self.fuzzy_match_most_recent_folder(most_recent_folder=SequenceDataFiles(dir=custom_path,
268
+ child_dirs=[],
269
+ sequence_name="NA")).ok_value)
270
+ parent_dir = self.data_files[-1]
271
+ spectra = self.get_data(custom_path=custom_path)
272
+ reports = []
273
+ for i, child_dir in enumerate(parent_dir.child_dirs):
274
+ metd_report = self.get_report_details(child_dir, report_type)
275
+ child_spectra: List[AgilentHPLCChromatogram] = list(spectra[i].__dict__.values())
276
+ for j, signal in enumerate(metd_report.signals):
277
+ possible_data = child_spectra[j]
278
+ if len(possible_data.x) > 0:
279
+ signal.data = possible_data
280
+ reports.append(metd_report)
281
+ return reports