nedo-vision-worker 1.2.9__py3-none-any.whl → 1.3.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.
@@ -6,5 +6,5 @@ A library for running worker agents in the Nedo Vision platform.
6
6
 
7
7
  from .worker_service import WorkerService
8
8
 
9
- __version__ = "1.2.9"
9
+ __version__ = "1.3.0"
10
10
  __all__ = ["WorkerService"]
@@ -19,19 +19,22 @@ class PPEDetectionRepository:
19
19
  os.makedirs(self.storage_dir, exist_ok=True)
20
20
 
21
21
 
22
- def get_latest_5_detections(self) -> list:
22
+ def get_latest_detections(self, limit: int = 50) -> list:
23
23
  """
24
- Retrieves the latest 5 PPE detections ordered by the 'created_at' timestamp.
24
+ Retrieves the latest PPE detections ordered by the 'created_at' timestamp.
25
+
26
+ Args:
27
+ limit (int): Maximum number of detections to retrieve. Default is 50.
25
28
 
26
29
  Returns:
27
- list: A list of PPEDetectionEntity objects.
30
+ list: A list of PPE detection dictionaries.
28
31
  """
29
32
  try:
30
33
  latest_detections = (
31
34
  self.session.query(PPEDetectionEntity)
32
35
  .options(joinedload(PPEDetectionEntity.ppe_labels))
33
36
  .order_by(asc(PPEDetectionEntity.created_at))
34
- .limit(5)
37
+ .limit(limit)
35
38
  .all()
36
39
  )
37
40
 
@@ -67,9 +70,22 @@ class PPEDetectionRepository:
67
70
 
68
71
  except SQLAlchemyError as e:
69
72
  self.session.rollback()
70
- logging.error(f"❌ Database error while retrieving latest 5 detections: {e}")
73
+ logging.error(f"❌ Database error while retrieving detections: {e}")
71
74
  return []
72
75
 
76
+ def get_total_pending_count(self) -> int:
77
+ """
78
+ Returns the total count of pending PPE detections in the database.
79
+
80
+ Returns:
81
+ int: Total count of pending detections.
82
+ """
83
+ try:
84
+ return self.session.query(PPEDetectionEntity).count()
85
+ except SQLAlchemyError as e:
86
+ logging.error(f"❌ Error counting pending detections: {e}")
87
+ return 0
88
+
73
89
  def delete_records_from_db(self, detection_data: list):
74
90
  """
75
91
  Deletes PPE detection records from the database based on detection data.
@@ -103,7 +119,6 @@ class PPEDetectionRepository:
103
119
  if os.path.exists(image_path):
104
120
  try:
105
121
  os.remove(image_path)
106
- logging.info(f"Deleted image file: {image_path}")
107
122
  except OSError as e:
108
123
  logging.warning(f"Failed to delete image file {image_path}: {e}")
109
124
  else:
@@ -117,7 +132,6 @@ class PPEDetectionRepository:
117
132
  if os.path.exists(image_tile_path):
118
133
  try:
119
134
  os.remove(image_tile_path)
120
- logging.info(f"Deleted image tile file: {image_tile_path}")
121
135
  except OSError as e:
122
136
  logging.warning(f"Failed to delete image tile file {image_tile_path}: {e}")
123
137
 
@@ -16,9 +16,12 @@ class RestrictedAreaRepository:
16
16
  self.session: Session = self.db_manager.get_session("default")
17
17
  os.makedirs(self.storage_dir, exist_ok=True)
18
18
 
19
- def get_latest_5_violations(self) -> list:
19
+ def get_latest_violations(self, limit: int = 50) -> list:
20
20
  """
21
- Retrieves the latest 5 restricted area violations ordered by the 'created_at' timestamp.
21
+ Retrieves the latest restricted area violations ordered by the 'created_at' timestamp.
22
+
23
+ Args:
24
+ limit (int): Maximum number of violations to retrieve. Default is 50.
22
25
 
23
26
  Returns:
24
27
  list: A list of dictionaries representing restricted area violations.
@@ -27,7 +30,7 @@ class RestrictedAreaRepository:
27
30
  latest_violations = (
28
31
  self.session.query(RestrictedAreaViolationEntity)
29
32
  .order_by(asc(RestrictedAreaViolationEntity.created_at))
30
- .limit(5)
33
+ .limit(limit)
31
34
  .all()
32
35
  )
33
36
 
@@ -51,9 +54,22 @@ class RestrictedAreaRepository:
51
54
 
52
55
  except SQLAlchemyError as e:
53
56
  self.session.rollback()
54
- logging.error(f"❌ Database error while retrieving latest 5 violations: {e}")
57
+ logging.error(f"❌ Database error while retrieving violations: {e}")
55
58
  return []
56
59
 
60
+ def get_total_pending_count(self) -> int:
61
+ """
62
+ Returns the total count of pending restricted area violations in the database.
63
+
64
+ Returns:
65
+ int: Total count of pending violations.
66
+ """
67
+ try:
68
+ return self.session.query(RestrictedAreaViolationEntity).count()
69
+ except SQLAlchemyError as e:
70
+ logging.error(f"❌ Error counting pending violations: {e}")
71
+ return 0
72
+
57
73
  def delete_records_from_db(self, violation_data: list):
58
74
  """
59
75
  Deletes restricted area violation records from the database based on the provided data.
@@ -81,7 +97,6 @@ class RestrictedAreaRepository:
81
97
  if os.path.exists(image_path):
82
98
  try:
83
99
  os.remove(image_path)
84
- logging.info(f"Deleted image file: {image_path}")
85
100
  except OSError as e:
86
101
  logging.warning(f"Failed to delete image file {image_path}: {e}")
87
102
  else:
@@ -95,7 +110,6 @@ class RestrictedAreaRepository:
95
110
  if os.path.exists(image_tile_path):
96
111
  try:
97
112
  os.remove(image_tile_path)
98
- logging.info(f"Deleted image tile file: {image_tile_path}")
99
113
  except OSError as e:
100
114
  logging.warning(f"Failed to delete image tile file {image_tile_path}: {e}")
101
115
 
@@ -118,14 +118,63 @@ class DataSenderWorker:
118
118
  try:
119
119
  while not self.stop_event.is_set():
120
120
  self.system_usage_manager.process_system_usage()
121
- self.ppe_detection_manager.send_ppe_detection_batch()
122
- self.restricted_area_manager.send_violation_batch()
121
+
122
+ ppe_pending = self._send_with_adaptive_interval(
123
+ self.ppe_detection_manager.send_ppe_detection_batch,
124
+ self.ppe_detection_manager.ppe_detection_repo.get_total_pending_count
125
+ )
126
+
127
+ violation_pending = self._send_with_adaptive_interval(
128
+ self.restricted_area_manager.send_violation_batch,
129
+ self.restricted_area_manager.repo.get_total_pending_count
130
+ )
131
+
123
132
  self._process_image_upload()
124
133
  self._process_dataset_frames()
125
- time.sleep(self.send_interval)
134
+
135
+ sleep_time = self._calculate_sleep_interval(ppe_pending, violation_pending)
136
+ if sleep_time < self.send_interval and (ppe_pending + violation_pending) > 0:
137
+ logger.debug(f"⚡ [APP] Catch-up mode: {ppe_pending + violation_pending} pending, sleeping {sleep_time}s")
138
+ time.sleep(sleep_time)
126
139
  except Exception as e:
127
140
  logger.error("🚨 [APP] Unexpected error in main worker loop.", exc_info=True)
128
141
 
142
+ def _send_with_adaptive_interval(self, send_func, count_func):
143
+ """Send data with adaptive interval based on backlog."""
144
+ try:
145
+ pending = count_func()
146
+ if pending > 0:
147
+ send_func()
148
+ return pending
149
+ except Exception as e:
150
+ logger.error(f"🚨 [APP] Error in adaptive sending: {e}", exc_info=True)
151
+ return 0
152
+
153
+ def _calculate_sleep_interval(self, ppe_pending: int, violation_pending: int) -> float:
154
+ """
155
+ Calculate optimal sleep interval based on pending data.
156
+ Conservative approach to prevent server overload.
157
+
158
+ Args:
159
+ ppe_pending: Number of pending PPE detections
160
+ violation_pending: Number of pending violations
161
+
162
+ Returns:
163
+ float: Sleep interval in seconds
164
+ """
165
+ total_pending = ppe_pending + violation_pending
166
+
167
+ if total_pending == 0:
168
+ return self.send_interval
169
+ elif total_pending < 50:
170
+ return self.send_interval
171
+ elif total_pending < 200:
172
+ return 3.0
173
+ elif total_pending < 500:
174
+ return 2.0
175
+ else:
176
+ return 1.5
177
+
129
178
  def _run_worker_source_updater(self):
130
179
  """Dedicated loop for updating worker sources at a different interval."""
131
180
  try:
@@ -50,14 +50,42 @@ class PPEDetectionManager:
50
50
 
51
51
  logger.info("📡 [APP] PPE detection monitoring started.")
52
52
 
53
+ def _calculate_batch_size(self, pending_count: int) -> int:
54
+ """
55
+ Calculates optimal batch size based on pending items.
56
+ Conservative limits to prevent server overload.
57
+
58
+ Args:
59
+ pending_count (int): Number of pending detections
60
+
61
+ Returns:
62
+ int: Optimal batch size
63
+ """
64
+ if pending_count < 20:
65
+ return 10
66
+ elif pending_count < 100:
67
+ return 30
68
+ elif pending_count < 500:
69
+ return 50
70
+ else:
71
+ return 100
53
72
 
54
73
  def send_ppe_detection_batch(self):
55
- """Sends a batch of collected PPE detection data to the server."""
74
+ """Sends a batch of collected PPE detection data to the server with dynamic batch sizing."""
56
75
  try:
57
- self.ppe_detection_data = self.ppe_detection_repo.get_latest_5_detections()
76
+ pending_count = self.ppe_detection_repo.get_total_pending_count()
77
+
78
+ if pending_count == 0:
79
+ return
80
+
81
+ batch_size = self._calculate_batch_size(pending_count)
82
+ self.ppe_detection_data = self.ppe_detection_repo.get_latest_detections(batch_size)
83
+
58
84
  if not self.ppe_detection_data:
59
85
  return
60
86
 
87
+ logger.info(f"📤 [APP] Sending {len(self.ppe_detection_data)} PPE detections ({pending_count} pending)")
88
+
61
89
  response = self.ppe_detection_client.send_upsert_batch(
62
90
  worker_id=self.worker_id,
63
91
  worker_source_id=self.worker_source_id,
@@ -66,7 +94,7 @@ class PPEDetectionManager:
66
94
  )
67
95
 
68
96
  if response.get("success"):
69
- logger.info("✅ [APP] Successfully sent PPE detection batch.")
97
+ logger.info(f"✅ [APP] Successfully sent {len(self.ppe_detection_data)} PPE detections")
70
98
  self.ppe_detection_data.clear()
71
99
  else:
72
100
  logger.error(f"❌ [APP] Failed to send PPE detection batch: {response.get('message')}")
@@ -50,13 +50,42 @@ class RestrictedAreaManager:
50
50
 
51
51
  logger.info("📡 [APP] Restricted area violation monitoring started.")
52
52
 
53
+ def _calculate_batch_size(self, pending_count: int) -> int:
54
+ """
55
+ Calculates optimal batch size based on pending items.
56
+ Conservative limits to prevent server overload.
57
+
58
+ Args:
59
+ pending_count (int): Number of pending violations
60
+
61
+ Returns:
62
+ int: Optimal batch size
63
+ """
64
+ if pending_count < 20:
65
+ return 10
66
+ elif pending_count < 100:
67
+ return 30
68
+ elif pending_count < 500:
69
+ return 50
70
+ else:
71
+ return 100
72
+
53
73
  def send_violation_batch(self):
54
- """Sends a batch of collected violation data to the server."""
74
+ """Sends a batch of collected violation data to the server with dynamic batch sizing."""
55
75
  try:
56
- self.violations_data = self.repo.get_latest_5_violations()
76
+ pending_count = self.repo.get_total_pending_count()
77
+
78
+ if pending_count == 0:
79
+ return
80
+
81
+ batch_size = self._calculate_batch_size(pending_count)
82
+ self.violations_data = self.repo.get_latest_violations(batch_size)
83
+
57
84
  if not self.violations_data:
58
85
  return
59
86
 
87
+ logger.info(f"📤 [APP] Sending {len(self.violations_data)} violations ({pending_count} pending)")
88
+
60
89
  response = self.client.send_upsert_batch(
61
90
  worker_id=self.worker_id,
62
91
  worker_source_id=self.worker_source_id,
@@ -65,7 +94,7 @@ class RestrictedAreaManager:
65
94
  )
66
95
 
67
96
  if response.get("success"):
68
- logger.info("✅ [APP] Successfully sent restricted area violation batch.")
97
+ logger.info(f"✅ [APP] Successfully sent {len(self.violations_data)} violations")
69
98
  self.violations_data.clear()
70
99
  else:
71
100
  logger.error(f"❌ [APP] Failed to send restricted area violation batch: {response.get('message')}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedo-vision-worker
3
- Version: 1.2.9
3
+ Version: 1.3.0
4
4
  Summary: Nedo Vision Worker Service Library for AI Vision Processing
5
5
  Author-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
6
6
  Maintainer-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
@@ -1,4 +1,4 @@
1
- nedo_vision_worker/__init__.py,sha256=oLzUhGDH-bezYa_Smc3pWfjWYzwQGUZ0LiTwVuu5AYA,203
1
+ nedo_vision_worker/__init__.py,sha256=lM4SwuHDgc4TdI91GAS_FS--pQKK2RFKliCp8sJY1-o,203
2
2
  nedo_vision_worker/cli.py,sha256=ddWspJmSgVkcUYvRdkvTtMNuMTDvNCqLLuMVU9KE3Ik,7457
3
3
  nedo_vision_worker/doctor.py,sha256=wNkpe8gLVd76Y_ViyK2h1ZFdqeSl37MnzZN5frWKu30,48410
4
4
  nedo_vision_worker/worker_service.py,sha256=9zz8hKwDwqwpfS0KPQfftGJtRci0uj_wiwcr_TGf-E0,11039
@@ -40,8 +40,8 @@ nedo_vision_worker/protos/WorkerSourceService_pb2_grpc.py,sha256=23Zj3WSogfhmp_Y
40
40
  nedo_vision_worker/protos/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
41
41
  nedo_vision_worker/repositories/AIModelRepository.py,sha256=35cQ2EnJvMdArewumPrVIaiwueGy6cXU_g5J1Qcj6_Y,1415
42
42
  nedo_vision_worker/repositories/DatasetSourceRepository.py,sha256=MOUbXY1-7WUPhY15aMpdXl6rl0afSvu3Cq8jNtp9nzM,8595
43
- nedo_vision_worker/repositories/PPEDetectionRepository.py,sha256=s4iCaTNYoJJgGr9ojWgoj6wtUQYFbloxSpnuntX26yw,5792
44
- nedo_vision_worker/repositories/RestrictedAreaRepository.py,sha256=XudFBscLCXCheUTAPC_CbwBkJMn2uIx2HCnU0NH4riE,4766
43
+ nedo_vision_worker/repositories/PPEDetectionRepository.py,sha256=WYPrmMs3wSsaALR4ixsgMNOUbHbmYDGPbjMQdjSnIE4,6161
44
+ nedo_vision_worker/repositories/RestrictedAreaRepository.py,sha256=nk8DV5dkBfAm2AT8wY9la6yv6Q7pFi00YXLbvofqNkc,5158
45
45
  nedo_vision_worker/repositories/WorkerSourcePipelineDebugRepository.py,sha256=kOlVEnPOoDRZdZIm8uWXlc89GMvBPI-36QyKecX7ucE,3350
46
46
  nedo_vision_worker/repositories/WorkerSourcePipelineDetectionRepository.py,sha256=cbgg_7p0eNUIgCHoPDZBaRZ1b2Y68p_dfSxpvuGMtRE,1773
47
47
  nedo_vision_worker/repositories/WorkerSourcePipelineRepository.py,sha256=xfmEvgnyt-DdfSApGyFfy0H0dXjFFkjeo4LMr0fVFXU,10053
@@ -80,22 +80,22 @@ nedo_vision_worker/util/SystemMonitor.py,sha256=2kkqj9mOlywAS2fHdN1TaIXSXvCApcIH
80
80
  nedo_vision_worker/util/VideoProbeUtil.py,sha256=cF-vJ7hIDlXfEJby2a0s9tqwkPGVz_6B3Vv4D5pMmIw,12876
81
81
  nedo_vision_worker/util/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
82
82
  nedo_vision_worker/worker/CoreActionWorker.py,sha256=lb7zPY3yui6I3F4rX4Ii7JwpWZahLEO72rh3iWOgFmg,5441
83
- nedo_vision_worker/worker/DataSenderWorker.py,sha256=9FudRRItiMOcQx5UfVyu4p0Enb9BbgwZZ5EgX6Ho2U4,7160
83
+ nedo_vision_worker/worker/DataSenderWorker.py,sha256=BNA5mL2guBVp0O5MKULEWd78dY32YjiyWZX7ECFjVFc,9089
84
84
  nedo_vision_worker/worker/DataSyncWorker.py,sha256=LmDPt2J1frmXwuR46L6b0MjlFOHfgG-4_0MGQa78zF4,6288
85
85
  nedo_vision_worker/worker/DatasetFrameSender.py,sha256=1SFYj8LJFNi-anBTapsbq8U_NGMM7mnoMKg9NeFAHys,8087
86
86
  nedo_vision_worker/worker/DatasetFrameWorker.py,sha256=Hh_wZuMjwovxsEKFqXSuTRin9eYRBZCbcFKm3CKLMbE,19335
87
- nedo_vision_worker/worker/PPEDetectionManager.py,sha256=fAolWlrsY5SQAWygvjNBNU56IlC0HLlhPgpz7shL-gk,3588
87
+ nedo_vision_worker/worker/PPEDetectionManager.py,sha256=sO0ygzUKNNkUsrv-QJk9b-r9QnTw80VsniD5--OQJi0,4569
88
88
  nedo_vision_worker/worker/PipelineActionWorker.py,sha256=xgvryjKtEsMj4BKqWzDIaK_lFny-DfMCj5Y2DxHnWww,5651
89
89
  nedo_vision_worker/worker/PipelineImageWorker.py,sha256=J8VBUG0cwcH3qOJp2zTl30B-XhmPFyvJLjxitKJYq0E,5642
90
90
  nedo_vision_worker/worker/PipelinePreviewWorker.py,sha256=owFiBbktcOZkdImQeykZSeBIR2-mpt6HNkmYIkLRKzE,6397
91
91
  nedo_vision_worker/worker/RabbitMQListener.py,sha256=9gR49MDplgpyb-D5HOH0K77-DJQFvhS2E7biL92SjSU,6950
92
- nedo_vision_worker/worker/RestrictedAreaManager.py,sha256=3yoXgQ459tV1bOa5choEzR9gE6LklrtHR_e0472U3L0,3521
92
+ nedo_vision_worker/worker/RestrictedAreaManager.py,sha256=Y739KzbPzm2UF1a9oT3YEk7trfKqQjGUQ6MTwEDMkj4,4463
93
93
  nedo_vision_worker/worker/SystemUsageManager.py,sha256=mkh4sT-HkIEY1CJHMEG6LP9ATu39YXvLRLyf995OkoQ,5315
94
94
  nedo_vision_worker/worker/VideoStreamWorker.py,sha256=5n6v1PNO7IB-jj_McALLkUP-cBjJoIEw4UiSAs3vTb0,7606
95
95
  nedo_vision_worker/worker/WorkerManager.py,sha256=2bxXi19fp3p1qjYBStYRdVVgko8dnevXx1_M_sqH5og,5521
96
96
  nedo_vision_worker/worker/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
97
- nedo_vision_worker-1.2.9.dist-info/METADATA,sha256=ySyI3I8lzUmhonotTgOrhH2Tt-AxL_l9oYifd_qaHcU,14728
98
- nedo_vision_worker-1.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
- nedo_vision_worker-1.2.9.dist-info/entry_points.txt,sha256=LrglS-8nCi8C_PL_pa6uxdgCe879hBETHDVXAckvs-8,60
100
- nedo_vision_worker-1.2.9.dist-info/top_level.txt,sha256=vgilhlkyD34YsEKkaBabmhIpcKSvF3XpzD2By68L-XI,19
101
- nedo_vision_worker-1.2.9.dist-info/RECORD,,
97
+ nedo_vision_worker-1.3.0.dist-info/METADATA,sha256=MiQg-EA4V0jEqMkrupbyhwLvIDD1UfTpQ_cub90gh8E,14728
98
+ nedo_vision_worker-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
+ nedo_vision_worker-1.3.0.dist-info/entry_points.txt,sha256=LrglS-8nCi8C_PL_pa6uxdgCe879hBETHDVXAckvs-8,60
100
+ nedo_vision_worker-1.3.0.dist-info/top_level.txt,sha256=vgilhlkyD34YsEKkaBabmhIpcKSvF3XpzD2By68L-XI,19
101
+ nedo_vision_worker-1.3.0.dist-info/RECORD,,