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
 | 
			
		||||
        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 in ['finished', 'cleared', 'idle']:
 | 
			
		||||
            # Get session ID for this display and clear it
 | 
			
		||||
            session_id = worker_state.get_session_id(display_identifier)
 | 
			
		||||
            if session_id:
 | 
			
		||||
                shared_stream_manager.clear_session_id(session_id)
 | 
			
		||||
                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)
 | 
			
		||||
                    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]:
 | 
			
		||||
        """Get tracking statistics from all subscriptions."""
 | 
			
		||||
        stats = {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,6 +52,12 @@ class TrackingPipelineIntegration:
 | 
			
		|||
        self.cleared_sessions: Dict[str, float] = {}  # session_id -> clear_time
 | 
			
		||||
        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
 | 
			
		||||
        self.executor = ThreadPoolExecutor(max_workers=2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +176,13 @@ class TrackingPipelineIntegration:
 | 
			
		|||
                    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'] = [
 | 
			
		||||
                    {
 | 
			
		||||
                        'track_id': v.track_id,
 | 
			
		||||
| 
						 | 
				
			
			@ -207,8 +220,8 @@ class TrackingPipelineIntegration:
 | 
			
		|||
                        if (time.time() - clear_time) < 30:  # 30 second cooldown
 | 
			
		||||
                            session_cleared = True
 | 
			
		||||
 | 
			
		||||
                    # Skip same car after session clear
 | 
			
		||||
                    if self.validator.should_skip_same_car(vehicle, session_cleared):
 | 
			
		||||
                    # Skip same car after session clear or if permanently processed
 | 
			
		||||
                    if self.validator.should_skip_same_car(vehicle, session_cleared, self.permanently_processed):
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    # Validate vehicle
 | 
			
		||||
| 
						 | 
				
			
			@ -370,10 +383,13 @@ class TrackingPipelineIntegration:
 | 
			
		|||
            self.tracker.mark_processed(track_id, session_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
 | 
			
		||||
            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:
 | 
			
		||||
            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.cleared_sessions.clear()
 | 
			
		||||
        self.pending_vehicles.clear()
 | 
			
		||||
        self.permanently_processed.clear()
 | 
			
		||||
        self.progression_stages.clear()
 | 
			
		||||
        self.last_detection_time.clear()
 | 
			
		||||
        logger.info("Tracking pipeline integration reset")
 | 
			
		||||
 | 
			
		||||
    def get_statistics(self) -> Dict[str, Any]:
 | 
			
		||||
| 
						 | 
				
			
			@ -440,6 +459,88 @@ class TrackingPipelineIntegration:
 | 
			
		|||
            '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):
 | 
			
		||||
        """Cleanup resources."""
 | 
			
		||||
        self.executor.shutdown(wait=False)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -352,17 +352,27 @@ class StableCarValidator:
 | 
			
		|||
 | 
			
		||||
    def should_skip_same_car(self,
 | 
			
		||||
                            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.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            vehicle: The tracked vehicle
 | 
			
		||||
            session_cleared: Whether the session was recently cleared
 | 
			
		||||
            permanently_processed: Dict of permanently processed vehicles
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            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.session_id is None and vehicle.processed_pipeline and session_cleared:
 | 
			
		||||
            # Check if enough time has passed since processing
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue