isar 1.20.2__py3-none-any.whl → 1.34.9__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 (124) hide show
  1. isar/apis/api.py +148 -76
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +110 -168
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +124 -162
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +1 -1
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/predefined_mission_definition/default_exr.json +0 -2
  14. isar/config/predefined_mission_definition/default_mission.json +1 -5
  15. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  16. isar/config/predefined_missions/default.json +67 -87
  17. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  18. isar/config/settings.py +76 -111
  19. isar/eventhandlers/eventhandler.py +123 -0
  20. isar/mission_planner/local_planner.py +6 -20
  21. isar/mission_planner/mission_planner_interface.py +1 -1
  22. isar/models/events.py +184 -0
  23. isar/models/status.py +18 -0
  24. isar/modules.py +118 -199
  25. isar/robot/robot.py +377 -0
  26. isar/robot/robot_battery.py +60 -0
  27. isar/robot/robot_monitor_mission.py +357 -0
  28. isar/robot/robot_pause_mission.py +74 -0
  29. isar/robot/robot_resume_mission.py +67 -0
  30. isar/robot/robot_start_mission.py +66 -0
  31. isar/robot/robot_status.py +61 -0
  32. isar/robot/robot_stop_mission.py +68 -0
  33. isar/robot/robot_upload_inspection.py +75 -0
  34. isar/script.py +57 -40
  35. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  36. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  37. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  38. isar/services/service_connections/persistent_memory.py +69 -0
  39. isar/services/utilities/mqtt_utilities.py +93 -0
  40. isar/services/utilities/robot_utilities.py +20 -0
  41. isar/services/utilities/scheduling_utilities.py +393 -65
  42. isar/state_machine/state_machine.py +219 -538
  43. isar/state_machine/states/__init__.py +0 -8
  44. isar/state_machine/states/await_next_mission.py +114 -0
  45. isar/state_machine/states/blocked_protective_stop.py +60 -0
  46. isar/state_machine/states/going_to_lockdown.py +95 -0
  47. isar/state_machine/states/going_to_recharging.py +92 -0
  48. isar/state_machine/states/home.py +115 -0
  49. isar/state_machine/states/intervention_needed.py +77 -0
  50. isar/state_machine/states/lockdown.py +38 -0
  51. isar/state_machine/states/maintenance.py +36 -0
  52. isar/state_machine/states/monitor.py +137 -247
  53. isar/state_machine/states/offline.py +51 -53
  54. isar/state_machine/states/paused.py +92 -23
  55. isar/state_machine/states/pausing.py +48 -0
  56. isar/state_machine/states/pausing_return_home.py +48 -0
  57. isar/state_machine/states/recharging.py +80 -0
  58. isar/state_machine/states/resuming.py +57 -0
  59. isar/state_machine/states/resuming_return_home.py +64 -0
  60. isar/state_machine/states/return_home_paused.py +109 -0
  61. isar/state_machine/states/returning_home.py +217 -0
  62. isar/state_machine/states/stopping.py +61 -0
  63. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  64. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  65. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  66. isar/state_machine/states/stopping_return_home.py +77 -0
  67. isar/state_machine/states/unknown_status.py +72 -0
  68. isar/state_machine/states_enum.py +21 -5
  69. isar/state_machine/transitions/mission.py +192 -0
  70. isar/state_machine/transitions/return_home.py +106 -0
  71. isar/state_machine/transitions/robot_status.py +80 -0
  72. isar/state_machine/utils/common_event_handlers.py +73 -0
  73. isar/storage/blob_storage.py +70 -52
  74. isar/storage/local_storage.py +25 -12
  75. isar/storage/storage_interface.py +28 -7
  76. isar/storage/uploader.py +174 -55
  77. isar/storage/utilities.py +32 -29
  78. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/METADATA +73 -110
  79. isar-1.34.9.dist-info/RECORD +135 -0
  80. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  81. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/entry_points.txt +1 -0
  82. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  83. robot_interface/models/initialize/__init__.py +0 -1
  84. robot_interface/models/inspection/__init__.py +0 -13
  85. robot_interface/models/inspection/inspection.py +42 -33
  86. robot_interface/models/mission/mission.py +14 -15
  87. robot_interface/models/mission/status.py +20 -26
  88. robot_interface/models/mission/task.py +154 -121
  89. robot_interface/models/robots/battery_state.py +6 -0
  90. robot_interface/models/robots/media.py +13 -0
  91. robot_interface/models/robots/robot_model.py +7 -7
  92. robot_interface/robot_interface.py +119 -84
  93. robot_interface/telemetry/mqtt_client.py +74 -12
  94. robot_interface/telemetry/payloads.py +91 -13
  95. robot_interface/utilities/json_service.py +7 -1
  96. isar/config/predefined_missions/default_turtlebot.json +0 -110
  97. isar/config/predefined_poses/__init__.py +0 -0
  98. isar/config/predefined_poses/predefined_poses.py +0 -616
  99. isar/config/settings.env +0 -25
  100. isar/mission_planner/sequential_task_selector.py +0 -23
  101. isar/mission_planner/task_selector_interface.py +0 -31
  102. isar/models/communication/__init__.py +0 -0
  103. isar/models/communication/message.py +0 -12
  104. isar/models/communication/queues/__init__.py +0 -4
  105. isar/models/communication/queues/queue_io.py +0 -12
  106. isar/models/communication/queues/queue_timeout_error.py +0 -2
  107. isar/models/communication/queues/queues.py +0 -19
  108. isar/models/communication/queues/status_queue.py +0 -20
  109. isar/models/mission_metadata/__init__.py +0 -0
  110. isar/services/readers/__init__.py +0 -0
  111. isar/services/readers/base_reader.py +0 -37
  112. isar/services/service_connections/stid/__init__.py +0 -0
  113. isar/services/utilities/queue_utilities.py +0 -39
  114. isar/state_machine/states/idle.py +0 -85
  115. isar/state_machine/states/initialize.py +0 -71
  116. isar/state_machine/states/initiate.py +0 -142
  117. isar/state_machine/states/off.py +0 -18
  118. isar/state_machine/states/stop.py +0 -95
  119. isar/storage/slimm_storage.py +0 -191
  120. isar-1.20.2.dist-info/RECORD +0 -116
  121. robot_interface/models/initialize/initialize_params.py +0 -9
  122. robot_interface/models/mission/step.py +0 -234
  123. {isar-1.20.2.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  124. {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
isar/storage/uploader.py CHANGED
@@ -1,33 +1,64 @@
1
1
  import json
2
2
  import logging
3
3
  from dataclasses import dataclass
4
- from datetime import UTC, datetime, timedelta
4
+ from datetime import datetime, timedelta, timezone
5
5
  from queue import Empty, Queue
6
- from typing import List, Union
7
-
8
- from injector import inject
6
+ from threading import Event
7
+ from typing import List
9
8
 
10
9
  from isar.config.settings import settings
11
- from isar.models.communication.queues import Queues
12
- from isar.storage.storage_interface import StorageException, StorageInterface
13
- from robot_interface.models.inspection.inspection import Inspection
10
+ from isar.models.events import Events
11
+ from isar.storage.storage_interface import (
12
+ BlobStoragePath,
13
+ LocalStoragePath,
14
+ StorageException,
15
+ StorageInterface,
16
+ StoragePaths,
17
+ )
18
+ from robot_interface.models.inspection.inspection import (
19
+ Inspection,
20
+ InspectionBlob,
21
+ InspectionValue,
22
+ )
14
23
  from robot_interface.models.mission.mission import Mission
15
24
  from robot_interface.telemetry.mqtt_client import MqttClientInterface
25
+ from robot_interface.telemetry.payloads import (
26
+ InspectionResultPayload,
27
+ InspectionValuePayload,
28
+ )
16
29
  from robot_interface.utilities.json_service import EnhancedJSONEncoder
17
30
 
18
31
 
32
+ def has_empty_blob_storage_path(storage_paths: StoragePaths):
33
+ for path in (storage_paths.data_path, storage_paths.metadata_path):
34
+ for value in (path.storage_account, path.blob_container, path.blob_name):
35
+ if not (value and value.strip()):
36
+ return True
37
+ return False
38
+
39
+
19
40
  @dataclass
20
41
  class UploaderQueueItem:
21
42
  inspection: Inspection
22
43
  mission: Mission
44
+
45
+
46
+ @dataclass
47
+ class ValueItem(UploaderQueueItem):
48
+ inspection: InspectionValue
49
+
50
+
51
+ @dataclass
52
+ class BlobItem(UploaderQueueItem):
53
+ inspection: InspectionBlob
23
54
  storage_handler: StorageInterface
24
55
  _retry_count: int
25
- _next_retry_time: datetime = datetime.now(UTC)
56
+ _next_retry_time: datetime = datetime.now(timezone.utc)
26
57
 
27
58
  def increment_retry(self, max_wait_time: int) -> None:
28
59
  self._retry_count += 1
29
60
  seconds_until_retry: int = min(2**self._retry_count, max_wait_time)
30
- self._next_retry_time = datetime.now(UTC) + timedelta(
61
+ self._next_retry_time = datetime.now(timezone.utc) + timedelta(
31
62
  seconds=seconds_until_retry
32
63
  )
33
64
 
@@ -35,17 +66,18 @@ class UploaderQueueItem:
35
66
  return self._retry_count
36
67
 
37
68
  def is_ready_for_upload(self) -> bool:
38
- return datetime.now(UTC) >= self._next_retry_time
69
+ return datetime.now(timezone.utc) >= self._next_retry_time
39
70
 
40
71
  def seconds_until_retry(self) -> int:
41
- return max(0, int((self._next_retry_time - datetime.now(UTC)).total_seconds()))
72
+ return max(
73
+ 0, int((self._next_retry_time - datetime.now(timezone.utc)).total_seconds())
74
+ )
42
75
 
43
76
 
44
77
  class Uploader:
45
- @inject
46
78
  def __init__(
47
79
  self,
48
- queues: Queues,
80
+ events: Events,
49
81
  storage_handlers: List[StorageInterface],
50
82
  mqtt_publisher: MqttClientInterface,
51
83
  max_wait_time: int = settings.UPLOAD_FAILURE_MAX_WAIT,
@@ -55,8 +87,8 @@ class Uploader:
55
87
 
56
88
  Parameters
57
89
  ----------
58
- queues : Queues
59
- Queues used for cross-thread communication.
90
+ events : Events
91
+ Events used for cross-thread communication.
60
92
  storage_handlers : List[StorageInterface]
61
93
  List of handlers for different upload options
62
94
  max_wait_time : float
@@ -64,7 +96,7 @@ class Uploader:
64
96
  max_retry_attempts : int
65
97
  Maximum attempts to retry an upload when it fails
66
98
  """
67
- self.upload_queue: Queue = queues.upload_queue
99
+ self.upload_queue: Queue = events.upload_queue
68
100
  self.storage_handlers: List[StorageInterface] = storage_handlers
69
101
  self.mqtt_publisher = mqtt_publisher
70
102
 
@@ -72,11 +104,16 @@ class Uploader:
72
104
  self.max_retry_attempts = max_retry_attempts
73
105
  self._internal_upload_queue: List[UploaderQueueItem] = []
74
106
 
107
+ self.signal_thread_quitting: Event = Event()
108
+
75
109
  self.logger = logging.getLogger("uploader")
76
110
 
111
+ def stop(self) -> None:
112
+ self.signal_thread_quitting.set()
113
+
77
114
  def run(self) -> None:
78
115
  self.logger.info("Started uploader")
79
- while True:
116
+ while not self.signal_thread_quitting.wait(0):
80
117
  inspection: Inspection
81
118
  mission: Mission
82
119
  try:
@@ -91,76 +128,158 @@ class Uploader:
91
128
  )
92
129
  continue
93
130
 
94
- # If new item from thread queue, add one per handler to internal queue:
95
- for storage_handler in self.storage_handlers:
96
- new_item: UploaderQueueItem = UploaderQueueItem(
97
- inspection, mission, storage_handler, _retry_count=-1
98
- )
131
+ new_item: UploaderQueueItem
132
+ if isinstance(inspection, InspectionValue):
133
+ new_item = ValueItem(inspection, mission)
99
134
  self._internal_upload_queue.append(new_item)
135
+
136
+ elif isinstance(inspection, InspectionBlob):
137
+ # If new item from thread queue, add one per handler to internal queue:
138
+ for storage_handler in self.storage_handlers:
139
+ new_item = BlobItem(
140
+ inspection, mission, storage_handler, _retry_count=-1
141
+ )
142
+ self._internal_upload_queue.append(new_item)
143
+ else:
144
+ self.logger.warning(
145
+ f"Unable to add UploaderQueueItem as its type {type(inspection).__name__} is unsupported"
146
+ )
100
147
  except Empty:
101
148
  continue
149
+ except Exception as e:
150
+ self.logger.error(f"Unexpected error in uploader thread: {e}")
151
+ continue
102
152
 
103
- def _upload(self, upload_item: UploaderQueueItem) -> Union[str, dict]:
104
- inspection_path: Union[str, dict] = ""
153
+ def _upload(self, item: BlobItem) -> StoragePaths:
154
+ inspection_paths: StoragePaths
105
155
  try:
106
- inspection_path = upload_item.storage_handler.store(
107
- inspection=upload_item.inspection, mission=upload_item.mission
156
+ inspection_paths = item.storage_handler.store(
157
+ inspection=item.inspection, mission=item.mission
108
158
  )
109
159
  self.logger.info(
110
- f"Storage handler: {type(upload_item.storage_handler).__name__} "
111
- f"uploaded inspection {str(upload_item.inspection.id)[:8]}"
160
+ f"Storage handler: {type(item.storage_handler).__name__} "
161
+ f"uploaded inspection {str(item.inspection.id)[:8]}"
112
162
  )
113
- self._internal_upload_queue.remove(upload_item)
114
- except StorageException:
115
- if upload_item.get_retry_count() < self.max_retry_attempts:
116
- upload_item.increment_retry(self.max_wait_time)
163
+ self._internal_upload_queue.remove(item)
164
+ except StorageException as e:
165
+ if item.get_retry_count() < self.max_retry_attempts:
166
+ item.increment_retry(self.max_wait_time)
117
167
  self.logger.warning(
118
- f"Storage handler: {type(upload_item.storage_handler).__name__} "
168
+ f"Storage handler: {type(item.storage_handler).__name__} "
119
169
  f"failed to upload inspection: "
120
- f"{str(upload_item.inspection.id)[:8]}. "
121
- f"Retrying in {upload_item.seconds_until_retry()}s."
170
+ f"{str(item.inspection.id)[:8]}. "
171
+ f"Retrying in {item.seconds_until_retry()}s."
122
172
  )
123
173
  else:
124
- self._internal_upload_queue.remove(upload_item)
125
174
  self.logger.error(
126
- f"Storage handler: {type(upload_item.storage_handler).__name__} "
175
+ f"Storage handler: {type(item.storage_handler).__name__} "
127
176
  f"exceeded max retries to upload inspection: "
128
- f"{str(upload_item.inspection.id)[:8]}. Aborting upload."
177
+ f"{str(item.inspection.id)[:8]}. Aborting upload."
129
178
  )
130
- return inspection_path
179
+ self._internal_upload_queue.remove(item)
180
+ raise e
181
+ return inspection_paths
131
182
 
132
183
  def _process_upload_queue(self) -> None:
184
+ def should_upload(_item):
185
+ if isinstance(_item, ValueItem):
186
+ return True
187
+ if _item.is_ready_for_upload():
188
+ return True
189
+ return False
190
+
133
191
  ready_items: List[UploaderQueueItem] = [
134
- item for item in self._internal_upload_queue if item.is_ready_for_upload()
192
+ item for item in self._internal_upload_queue if should_upload(item)
135
193
  ]
136
194
  for item in ready_items:
137
- inspection_path = self._upload(item)
138
- self._publish_inspection_result(
139
- inspection=item.inspection, inspection_path=inspection_path
195
+ if isinstance(item, ValueItem):
196
+ self._publish_inspection_value(item.inspection)
197
+ self.logger.info(
198
+ f"Published value for inspection {str(item.inspection.id)[:8]}"
199
+ )
200
+ self._internal_upload_queue.remove(item)
201
+ elif isinstance(item, BlobItem):
202
+ try:
203
+ inspection_paths = self._upload(item)
204
+ if isinstance(inspection_paths.data_path, LocalStoragePath):
205
+ self.logger.info("Skipping publishing when using local storage")
206
+ elif isinstance(
207
+ inspection_paths.data_path, BlobStoragePath
208
+ ) and has_empty_blob_storage_path(inspection_paths):
209
+ self.logger.warning(
210
+ "Skipping publishing: Blob storage paths are empty for inspection %s",
211
+ str(item.inspection.id)[:8],
212
+ )
213
+ else:
214
+ self._publish_inspection_result(
215
+ inspection=item.inspection,
216
+ inspection_paths=inspection_paths,
217
+ )
218
+ except StorageException:
219
+ pass
220
+ else:
221
+ self.logger.warning(
222
+ f"Unable to process upload item as its type {type(item).__name__} is not supported"
223
+ )
224
+
225
+ def _publish_inspection_value(self, inspection: InspectionValue) -> None:
226
+ if not self.mqtt_publisher:
227
+ return
228
+
229
+ if not isinstance(inspection, InspectionValue):
230
+ logging.warning(
231
+ f"Excpected type InspectionValue but got {type(inspection).__name__} instead"
140
232
  )
233
+ return
234
+
235
+ payload: InspectionValuePayload = InspectionValuePayload(
236
+ isar_id=settings.ISAR_ID,
237
+ robot_name=settings.ROBOT_NAME,
238
+ inspection_id=inspection.id,
239
+ installation_code=settings.PLANT_SHORT_NAME,
240
+ tag_id=inspection.metadata.tag_id,
241
+ inspection_type=type(inspection).__name__,
242
+ inspection_description=inspection.metadata.inspection_description,
243
+ value=inspection.value,
244
+ unit=inspection.unit,
245
+ x=inspection.metadata.robot_pose.position.x,
246
+ y=inspection.metadata.robot_pose.position.y,
247
+ z=inspection.metadata.robot_pose.position.z,
248
+ timestamp=inspection.metadata.start_time,
249
+ )
250
+ self.mqtt_publisher.publish(
251
+ topic=settings.TOPIC_ISAR_INSPECTION_VALUE,
252
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
253
+ qos=1,
254
+ retain=True,
255
+ )
141
256
 
142
257
  def _publish_inspection_result(
143
- self, inspection: Inspection, inspection_path: Union[str, dict]
258
+ self,
259
+ inspection: InspectionBlob,
260
+ inspection_paths: StoragePaths[BlobStoragePath],
144
261
  ) -> None:
145
262
  """Publishes the reference of the inspection result to the MQTT Broker
146
263
  along with the analysis type
147
264
  """
148
265
  if not self.mqtt_publisher:
149
266
  return
150
- payload: str = json.dumps(
151
- {
152
- "isar_id": settings.ISAR_ID,
153
- "robot_name": settings.ROBOT_NAME,
154
- "inspection_id": inspection.id,
155
- "inspection_path": inspection_path,
156
- "analysis_type": inspection.metadata.analysis_type,
157
- "timestamp": datetime.now(UTC),
158
- },
159
- cls=EnhancedJSONEncoder,
267
+
268
+ payload: InspectionResultPayload = InspectionResultPayload(
269
+ isar_id=settings.ISAR_ID,
270
+ robot_name=settings.ROBOT_NAME,
271
+ inspection_id=inspection.id,
272
+ blob_storage_data_path=inspection_paths.data_path,
273
+ blob_storage_metadata_path=inspection_paths.metadata_path,
274
+ installation_code=settings.PLANT_SHORT_NAME,
275
+ tag_id=inspection.metadata.tag_id,
276
+ inspection_type=type(inspection).__name__,
277
+ inspection_description=inspection.metadata.inspection_description,
278
+ timestamp=inspection.metadata.start_time,
160
279
  )
161
280
  self.mqtt_publisher.publish(
162
281
  topic=settings.TOPIC_ISAR_INSPECTION_RESULT,
163
- payload=payload,
282
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
164
283
  qos=1,
165
284
  retain=True,
166
285
  )
isar/storage/utilities.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import json
2
- import time
3
- from datetime import UTC, datetime
2
+ from datetime import datetime, timezone
4
3
  from pathlib import Path
5
4
  from typing import Tuple
6
5
 
@@ -34,34 +33,34 @@ def construct_metadata_file(
34
33
  "plant_code": settings.PLANT_CODE,
35
34
  "media_orientation_reference_system": settings.MEDIA_ORIENTATION_REFERENCE_SYSTEM, # noqa: E501
36
35
  "additional_meta": {
36
+ "inspection_id": inspection.id,
37
37
  "mission_id": mission.id,
38
38
  "mission_name": mission.name,
39
+ "mission_date": datetime.now(timezone.utc).date(),
39
40
  "plant_name": settings.PLANT_NAME,
40
- "mission_date": datetime.now(UTC).date(),
41
41
  "isar_id": settings.ISAR_ID,
42
42
  "robot_name": settings.ROBOT_NAME,
43
- "analysis_type": (
44
- inspection.metadata.additional["analysis_type"]
45
- if inspection.metadata.additional
46
- else "N/A"
47
- ),
43
+ "inspection_description": inspection.metadata.inspection_description,
44
+ "tag": inspection.metadata.tag_id,
45
+ "robot_pose": {
46
+ "position": {
47
+ "x": inspection.metadata.robot_pose.position.x,
48
+ "y": inspection.metadata.robot_pose.position.y,
49
+ "z": inspection.metadata.robot_pose.position.z,
50
+ },
51
+ "orientation": inspection.metadata.robot_pose.orientation.to_quat_array(),
52
+ },
53
+ "target_position": {
54
+ "x": inspection.metadata.target_position.x,
55
+ "y": inspection.metadata.target_position.y,
56
+ "z": inspection.metadata.target_position.z,
57
+ },
58
+ "timestamp": inspection.metadata.start_time,
48
59
  },
49
- "data": [
60
+ "data_files": [
50
61
  {
51
62
  "folder": f"/{get_foldername(mission=mission)}",
52
- "files": [
53
- {
54
- "file_name": filename,
55
- "timestamp": inspection.metadata.start_time,
56
- "x": inspection.metadata.pose.position.x,
57
- "y": inspection.metadata.pose.position.y,
58
- "z": inspection.metadata.pose.position.z,
59
- "tag": inspection.metadata.tag_id,
60
- "additional_media_metadata": {
61
- "orientation": inspection.metadata.pose.orientation.to_quat_array() # noqa: E501
62
- },
63
- }
64
- ],
63
+ "file_name": filename,
65
64
  }
66
65
  ],
67
66
  }
@@ -69,15 +68,19 @@ def construct_metadata_file(
69
68
  return json.dumps(data, cls=EnhancedJSONEncoder, indent=4).encode()
70
69
 
71
70
 
72
- def get_filename(
73
- inspection: Inspection,
74
- ) -> str:
75
- inspection_type: str = type(inspection).__name__
71
+ def get_filename(inspection: Inspection) -> str:
72
+ utc_time: str = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
76
73
  tag: str = inspection.metadata.tag_id if inspection.metadata.tag_id else "no-tag"
77
- epoch_time: int = int(time.time())
78
- return f"{tag}__{inspection_type}__{epoch_time}"
74
+ inspection_type: str = type(inspection).__name__
75
+ inspection_description: str = (
76
+ inspection.metadata.inspection_description.replace(" ", "-")
77
+ if inspection.metadata.inspection_description
78
+ else "NA"
79
+ )
80
+ return f"{tag}__{inspection_type}__{inspection_description}__{utc_time}"
79
81
 
80
82
 
81
83
  def get_foldername(mission: Mission) -> str:
84
+ utc_date: str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
82
85
  mission_name: str = mission.name.replace(" ", "-")
83
- return f"{datetime.now(UTC).date()}__{settings.PLANT_SHORT_NAME}__{mission_name}__{mission.id}"
86
+ return f"{utc_date}__{settings.PLANT_SHORT_NAME}__{mission_name}__{mission.id}"