feat: add validator for post fueling and car abandon
This commit is contained in:
parent
7a38933bb0
commit
4002febed2
4 changed files with 128 additions and 5 deletions
|
@ -471,10 +471,14 @@ class WebSocketHandler:
|
||||||
# Update worker state
|
# Update worker state
|
||||||
worker_state.set_progression_stage(display_identifier, stage)
|
worker_state.set_progression_stage(display_identifier, stage)
|
||||||
|
|
||||||
|
# Update tracking integration for car abandonment detection
|
||||||
|
session_id = worker_state.get_session_id(display_identifier)
|
||||||
|
if session_id:
|
||||||
|
shared_stream_manager.set_progression_stage(session_id, stage)
|
||||||
|
|
||||||
# If stage indicates session is cleared/finished, clear from tracking
|
# If stage indicates session is cleared/finished, clear from tracking
|
||||||
if stage in ['finished', 'cleared', 'idle']:
|
if stage in ['finished', 'cleared', 'idle']:
|
||||||
# Get session ID for this display and clear it
|
# Get session ID for this display and clear it
|
||||||
session_id = worker_state.get_session_id(display_identifier)
|
|
||||||
if session_id:
|
if session_id:
|
||||||
shared_stream_manager.clear_session_id(session_id)
|
shared_stream_manager.clear_session_id(session_id)
|
||||||
logger.info(f"[Tracking] Cleared session {session_id} due to progression stage: {stage}")
|
logger.info(f"[Tracking] Cleared session {session_id} due to progression stage: {stage}")
|
||||||
|
|
|
@ -389,6 +389,14 @@ class StreamManager:
|
||||||
subscription_info.tracking_integration.clear_session_id(session_id)
|
subscription_info.tracking_integration.clear_session_id(session_id)
|
||||||
logger.debug(f"Cleared session {session_id}")
|
logger.debug(f"Cleared session {session_id}")
|
||||||
|
|
||||||
|
def set_progression_stage(self, session_id: str, stage: str):
|
||||||
|
"""Set progression stage for tracking integrations."""
|
||||||
|
with self._lock:
|
||||||
|
for subscription_info in self._subscriptions.values():
|
||||||
|
if subscription_info.tracking_integration:
|
||||||
|
subscription_info.tracking_integration.set_progression_stage(session_id, stage)
|
||||||
|
logger.debug(f"Set progression stage for session {session_id}: {stage}")
|
||||||
|
|
||||||
def get_tracking_stats(self) -> Dict[str, Any]:
|
def get_tracking_stats(self) -> Dict[str, Any]:
|
||||||
"""Get tracking statistics from all subscriptions."""
|
"""Get tracking statistics from all subscriptions."""
|
||||||
stats = {}
|
stats = {}
|
||||||
|
|
|
@ -52,6 +52,12 @@ class TrackingPipelineIntegration:
|
||||||
self.cleared_sessions: Dict[str, float] = {} # session_id -> clear_time
|
self.cleared_sessions: Dict[str, float] = {} # session_id -> clear_time
|
||||||
self.pending_vehicles: Dict[str, int] = {} # display_id -> track_id (waiting for session ID)
|
self.pending_vehicles: Dict[str, int] = {} # display_id -> track_id (waiting for session ID)
|
||||||
|
|
||||||
|
# Additional validators for enhanced flow control
|
||||||
|
self.permanently_processed: Dict[int, float] = {} # track_id -> process_time (never process again)
|
||||||
|
self.progression_stages: Dict[str, str] = {} # session_id -> current_stage
|
||||||
|
self.last_detection_time: Dict[str, float] = {} # display_id -> last_detection_timestamp
|
||||||
|
self.abandonment_timeout = 3.0 # seconds to wait before declaring car abandoned
|
||||||
|
|
||||||
# Thread pool for pipeline execution
|
# Thread pool for pipeline execution
|
||||||
self.executor = ThreadPoolExecutor(max_workers=2)
|
self.executor = ThreadPoolExecutor(max_workers=2)
|
||||||
|
|
||||||
|
@ -170,6 +176,13 @@ class TrackingPipelineIntegration:
|
||||||
frame
|
frame
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update last detection time for abandonment detection
|
||||||
|
if tracked_vehicles:
|
||||||
|
self.last_detection_time[display_id] = time.time()
|
||||||
|
|
||||||
|
# Check for car abandonment (vehicle left after getting car_wait_staff stage)
|
||||||
|
await self._check_car_abandonment(display_id, subscription_id)
|
||||||
|
|
||||||
result['tracked_vehicles'] = [
|
result['tracked_vehicles'] = [
|
||||||
{
|
{
|
||||||
'track_id': v.track_id,
|
'track_id': v.track_id,
|
||||||
|
@ -207,8 +220,8 @@ class TrackingPipelineIntegration:
|
||||||
if (time.time() - clear_time) < 30: # 30 second cooldown
|
if (time.time() - clear_time) < 30: # 30 second cooldown
|
||||||
session_cleared = True
|
session_cleared = True
|
||||||
|
|
||||||
# Skip same car after session clear
|
# Skip same car after session clear or if permanently processed
|
||||||
if self.validator.should_skip_same_car(vehicle, session_cleared):
|
if self.validator.should_skip_same_car(vehicle, session_cleared, self.permanently_processed):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Validate vehicle
|
# Validate vehicle
|
||||||
|
@ -370,10 +383,13 @@ class TrackingPipelineIntegration:
|
||||||
self.tracker.mark_processed(track_id, session_id)
|
self.tracker.mark_processed(track_id, session_id)
|
||||||
self.session_vehicles[session_id] = track_id
|
self.session_vehicles[session_id] = track_id
|
||||||
|
|
||||||
|
# Mark vehicle as permanently processed (won't process again even after session clear)
|
||||||
|
self.permanently_processed[track_id] = time.time()
|
||||||
|
|
||||||
# Remove from pending
|
# Remove from pending
|
||||||
del self.pending_vehicles[display_id]
|
del self.pending_vehicles[display_id]
|
||||||
|
|
||||||
logger.info(f"Assigned session {session_id} to vehicle {track_id}, marked as processed")
|
logger.info(f"Assigned session {session_id} to vehicle {track_id}, marked as permanently processed")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"No pending vehicle found for display {display_id} when setting session {session_id}")
|
logger.warning(f"No pending vehicle found for display {display_id} when setting session {session_id}")
|
||||||
|
|
||||||
|
@ -425,6 +441,9 @@ class TrackingPipelineIntegration:
|
||||||
self.session_vehicles.clear()
|
self.session_vehicles.clear()
|
||||||
self.cleared_sessions.clear()
|
self.cleared_sessions.clear()
|
||||||
self.pending_vehicles.clear()
|
self.pending_vehicles.clear()
|
||||||
|
self.permanently_processed.clear()
|
||||||
|
self.progression_stages.clear()
|
||||||
|
self.last_detection_time.clear()
|
||||||
logger.info("Tracking pipeline integration reset")
|
logger.info("Tracking pipeline integration reset")
|
||||||
|
|
||||||
def get_statistics(self) -> Dict[str, Any]:
|
def get_statistics(self) -> Dict[str, Any]:
|
||||||
|
@ -440,6 +459,88 @@ class TrackingPipelineIntegration:
|
||||||
'cleared_sessions': len(self.cleared_sessions)
|
'cleared_sessions': len(self.cleared_sessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def _check_car_abandonment(self, display_id: str, subscription_id: str):
|
||||||
|
"""
|
||||||
|
Check if a car has abandoned the fueling process (left after getting car_wait_staff stage).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
display_id: Display identifier
|
||||||
|
subscription_id: Subscription identifier
|
||||||
|
"""
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Check all sessions in car_wait_staff stage
|
||||||
|
abandoned_sessions = []
|
||||||
|
for session_id, stage in self.progression_stages.items():
|
||||||
|
if stage == "car_wait_staff":
|
||||||
|
# Check if we have recent detections for this session's display
|
||||||
|
session_display = None
|
||||||
|
for disp_id, sess_id in self.active_sessions.items():
|
||||||
|
if sess_id == session_id:
|
||||||
|
session_display = disp_id
|
||||||
|
break
|
||||||
|
|
||||||
|
if session_display:
|
||||||
|
last_detection = self.last_detection_time.get(session_display, 0)
|
||||||
|
time_since_detection = current_time - last_detection
|
||||||
|
|
||||||
|
if time_since_detection > self.abandonment_timeout:
|
||||||
|
logger.info(f"Car abandonment detected: session {session_id}, "
|
||||||
|
f"no detection for {time_since_detection:.1f}s")
|
||||||
|
abandoned_sessions.append(session_id)
|
||||||
|
|
||||||
|
# Send abandonment detection for each abandoned session
|
||||||
|
for session_id in abandoned_sessions:
|
||||||
|
await self._send_abandonment_detection(subscription_id, session_id)
|
||||||
|
# Remove from progression stages to avoid repeated detection
|
||||||
|
if session_id in self.progression_stages:
|
||||||
|
del self.progression_stages[session_id]
|
||||||
|
|
||||||
|
async def _send_abandonment_detection(self, subscription_id: str, session_id: str):
|
||||||
|
"""
|
||||||
|
Send imageDetection with null detection to indicate car abandonment.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subscription_id: Subscription identifier
|
||||||
|
session_id: Session ID of the abandoned car
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from ..communication.messages import create_image_detection
|
||||||
|
|
||||||
|
# Create abandonment detection message with null detection
|
||||||
|
detection_message = create_image_detection(
|
||||||
|
subscription_identifier=subscription_id,
|
||||||
|
detection_data=None, # Null detection indicates abandonment
|
||||||
|
model_id=52,
|
||||||
|
model_name="front_rear_detection_v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send to backend via WebSocket if sender is available
|
||||||
|
if self.message_sender:
|
||||||
|
await self.message_sender(detection_message)
|
||||||
|
logger.info(f"[CAR ABANDONMENT] Sent null detection for session {session_id}")
|
||||||
|
else:
|
||||||
|
logger.info(f"[CAR ABANDONMENT] No message sender available, would send: {detection_message}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending abandonment detection: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def set_progression_stage(self, session_id: str, stage: str):
|
||||||
|
"""
|
||||||
|
Set progression stage for a session (from backend setProgessionStage message).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session_id: Session identifier
|
||||||
|
stage: Progression stage (e.g., "car_wait_staff")
|
||||||
|
"""
|
||||||
|
self.progression_stages[session_id] = stage
|
||||||
|
logger.info(f"Set progression stage for session {session_id}: {stage}")
|
||||||
|
|
||||||
|
# If car reaches car_wait_staff, start monitoring for abandonment
|
||||||
|
if stage == "car_wait_staff":
|
||||||
|
logger.info(f"Started monitoring session {session_id} for car abandonment")
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""Cleanup resources."""
|
"""Cleanup resources."""
|
||||||
self.executor.shutdown(wait=False)
|
self.executor.shutdown(wait=False)
|
||||||
|
|
|
@ -352,17 +352,27 @@ class StableCarValidator:
|
||||||
|
|
||||||
def should_skip_same_car(self,
|
def should_skip_same_car(self,
|
||||||
vehicle: TrackedVehicle,
|
vehicle: TrackedVehicle,
|
||||||
session_cleared: bool = False) -> bool:
|
session_cleared: bool = False,
|
||||||
|
permanently_processed: Dict[int, float] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine if we should skip processing for the same car after session clear.
|
Determine if we should skip processing for the same car after session clear.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vehicle: The tracked vehicle
|
vehicle: The tracked vehicle
|
||||||
session_cleared: Whether the session was recently cleared
|
session_cleared: Whether the session was recently cleared
|
||||||
|
permanently_processed: Dict of permanently processed vehicles
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if we should skip this vehicle
|
True if we should skip this vehicle
|
||||||
"""
|
"""
|
||||||
|
# Check if this vehicle was permanently processed (never process again)
|
||||||
|
if permanently_processed and vehicle.track_id in permanently_processed:
|
||||||
|
process_time = permanently_processed[vehicle.track_id]
|
||||||
|
time_since = time.time() - process_time
|
||||||
|
logger.debug(f"Skipping permanently processed vehicle {vehicle.track_id} "
|
||||||
|
f"(processed {time_since:.1f}s ago)")
|
||||||
|
return True
|
||||||
|
|
||||||
# If vehicle has a session_id but it was cleared, skip for a period
|
# If vehicle has a session_id but it was cleared, skip for a period
|
||||||
if vehicle.session_id is None and vehicle.processed_pipeline and session_cleared:
|
if vehicle.session_id is None and vehicle.processed_pipeline and session_cleared:
|
||||||
# Check if enough time has passed since processing
|
# Check if enough time has passed since processing
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue