'datetime', 'expires_at' => 'datetime', 'call_started_at' => 'datetime', 'enable_tab_switch_screenshot' => 'boolean', 'assigned_interviewers' => 'array', 'proctor_logs' => 'array', 'recordings' => 'array', 'tab_switch_screenshots' => 'array', 'active_peers' => 'array', ]; public function creator() { return $this->belongsTo(User::class, 'created_by'); } public function callStarter() { return $this->belongsTo(User::class, 'call_started_by'); } public function warnings() { return $this->hasMany(InterviewWarning::class, 'interview_id'); } public function isExpired(): bool { return now()->greaterThan($this->expires_at); } /** * Check if the interview call is active and unexpired. */ public function isCallActive(): bool { if ($this->isExpired() || in_array($this->status, ['completed', 'expired'])) { return false; } return $this->call_status === 'active'; } /** * Check if the current time is within the interview timeline. */ public function isActiveTimeline(): bool { $now = now(); $start = $this->scheduled_at ?? $this->created_at; if ($now->lessThan($start)) { return false; } if ($now->greaterThan($this->expires_at)) { return false; } return !in_array($this->status, ['completed', 'expired']); } /** * Check if a specific user is assigned as an interviewer. */ public function isAssignedInterviewer($userId): bool { if (empty($this->assigned_interviewers)) { return false; } $interviewers = array_map('strval', (array) $this->assigned_interviewers); return in_array((string) $userId, $interviewers, true); } /** * Get computed initials for candidate (e.g. "John Doe" -> "JD"). */ public function getCandidateInitialsAttribute(): string { $parts = preg_split('/\s+/', trim($this->candidate_name ?? '')); if (count($parts) >= 2) { return strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1)); } $nameStr = trim($this->candidate_name ?? ''); return strtoupper(substr($nameStr, 0, 1) . (strlen($nameStr) > 1 ? substr($nameStr, -1) : '')); } /** * Get proctoring alert metrics summary. */ public function getProctorMetricsAttribute(): array { $logs = $this->proctor_logs ?? []; return [ 'tab_switches' => count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'tab_switch')), 'focus_lost' => count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'focus_lost')), 'gaze_alerts' => count(array_filter($logs, fn($l) => in_array($l['type'] ?? '', ['gaze_anomaly', 'gaze_fixed_staring', 'looking_away']))), 'lower_gaze' => count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'gaze_lower_device')), 'question_repeat' => count(array_filter($logs, fn($l) => in_array($l['type'] ?? '', ['question_repeat_lower', 'question_repetition']))), 'external_ai' => count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'external_ai_detected')), 'paste_events' => count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'paste_event')), 'total_incidents' => count($logs), ]; } /** * Format timeline string: "M d, H:i – H:i". */ public function getFormattedTimelineAttribute(): string { $start = $this->scheduled_at ?? $this->created_at; return $start->format('M d, H:i') . ' – ' . $this->expires_at->format('H:i'); } }