```python:inference/core/workflows/core_steps/analytics/detection_event_log/v1.py
from dataclasses import dataclass
from typing import Dict

from inference.core.workflows.execution_engine.entities.base import \
    VideoMetadata
from inference.core.workflows.prototypes.block import WorkflowBlock


@dataclass
class DetectionEvent:
    """Stores event data for a tracked detection."""

    tracker_id: int
    class_name: str
    first_seen_frame: int
    first_seen_timestamp: float  # Unix wall-clock time (frame_timestamp or time.time())
    last_seen_frame: int
    last_seen_timestamp: float  # Unix wall-clock time (frame_timestamp or time.time())
    first_seen_relative: float = 0.0  # seconds since video start
    last_seen_relative: float = 0.0  # seconds since video start
    frame_count: int = 1
    logged: bool = False


class DetectionEventLogBlockV1(WorkflowBlock):

    def __init__(self):
        # Dict[video_id, Dict[tracker_id, DetectionEvent]]
        self._event_logs: Dict[str, Dict[int, DetectionEvent]] = {}
        # Dict[video_id, last_flush_frame]
        self._last_flush_frame: Dict[str, int] = {}
        # Dict[video_id, frame_count] - internal frame counter (increments each run)
        self._frame_count: Dict[str, int] = {}
        # Dict[video_id, last_access_frame] - tracks when each video was last accessed (global frame count)
        self._last_access: Dict[str, int] = {}
        # Dict[video_id, first_frame_timestamp] - stores the first frame's wall-clock timestamp
        # Used as the anchor for frame_timestamp-based relative time calculation
        self._first_frame_timestamps: Dict[str, float] = {}
        # Global frame counter for tracking video access order
        self._global_frame: int = 0

    def _get_relative_time(
        self,
        video_id: str,
        current_frame: int,
        metadata: VideoMetadata,
        fallback_fps: float,
    ) -> float:
        """Calculate relative time in seconds since video started.

        Uses frame_timestamp from metadata when available for accurate timing,
        even when inference doesn't run at the camera's reported FPS (e.g. due
        to dropped frames or processing lag). Falls back to internal frame
        counter / FPS when frame_timestamp is not available.
        """
        if metadata.frame_timestamp is not None:
            frame_ts = metadata.frame_timestamp.timestamp()
            first_ts = self._first_frame_timestamps.get(video_id)
            if first_ts is None:
                # Store the first-seen timestamp and return zero elapsed time
                self._first_frame_timestamps[video_id] = frame_ts
                return 0.0
            return frame_ts - first_ts

        # Fallback: use internal frame counter and FPS
        fps = metadata.fps or fallback_fps
        return (current_frame - 1) / fps
```