subDays($days); return [ 'total_requests' => ApiLog::where('created_at', '>=', $startDate)->count(), 'successful_requests' => ApiLog::successful()->where('created_at', '>=', $startDate)->count(), 'failed_requests' => ApiLog::failed()->where('created_at', '>=', $startDate)->count(), 'average_response_time' => ApiLog::where('created_at', '>=', $startDate) ->whereNotNull('response_time_ms') ->avg('response_time_ms'), 'success_rate' => $this->calculateSuccessRate($days), 'by_source' => $this->getApiMetricsBySource($days), ]; } /** * Get job fetch metrics */ public function getJobFetchMetrics(int $days = 7): array { $startDate = now()->subDays($days); return [ 'total_fetches' => JobFetchLog::where('created_at', '>=', $startDate)->count(), 'successful_fetches' => JobFetchLog::successful()->where('created_at', '>=', $startDate)->count(), 'failed_fetches' => JobFetchLog::failed()->where('created_at', '>=', $startDate)->count(), 'total_jobs_fetched' => JobFetchLog::where('created_at', '>=', $startDate)->sum('jobs_fetched'), 'total_jobs_created' => JobFetchLog::where('created_at', '>=', $startDate)->sum('jobs_created'), 'total_jobs_updated' => JobFetchLog::where('created_at', '>=', $startDate)->sum('jobs_updated'), 'total_jobs_skipped' => JobFetchLog::where('created_at', '>=', $startDate)->sum('jobs_skipped'), 'average_duration' => JobFetchLog::where('created_at', '>=', $startDate) ->whereNotNull('duration_seconds') ->avg('duration_seconds'), 'by_source' => $this->getJobFetchMetricsBySource($days), ]; } /** * Get analytics metrics */ public function getAnalyticsMetrics(int $days = 7): array { $startDate = now()->subDays($days); return [ 'total_events' => AnalyticsEvent::where('created_at', '>=', $startDate)->count(), 'job_views' => AnalyticsEvent::ofType(AnalyticsEvent::EVENT_JOB_VIEW) ->where('created_at', '>=', $startDate)->count(), 'job_apply_clicks' => AnalyticsEvent::ofType(AnalyticsEvent::EVENT_JOB_APPLY_CLICK) ->where('created_at', '>=', $startDate)->count(), 'searches' => AnalyticsEvent::ofType(AnalyticsEvent::EVENT_SEARCH) ->where('created_at', '>=', $startDate)->count(), 'unique_sessions' => AnalyticsEvent::where('created_at', '>=', $startDate) ->distinct('session_id')->count('session_id'), 'click_through_rate' => $this->calculateClickThroughRate($days), 'popular_jobs' => $this->getPopularJobs($days), 'popular_searches' => $this->getPopularSearches($days), ]; } /** * Get system health metrics */ public function getSystemHealth(): array { $recentFailures = ApiLog::failed() ->where('created_at', '>=', now()->subHours(1)) ->count(); $recentFetchFailures = JobFetchLog::failed() ->where('created_at', '>=', now()->subHours(24)) ->count(); return [ 'status' => $this->determineSystemStatus($recentFailures, $recentFetchFailures), 'api_failures_last_hour' => $recentFailures, 'fetch_failures_last_24h' => $recentFetchFailures, 'last_successful_fetch' => JobFetchLog::successful() ->latest() ->first()?->created_at, 'sources_status' => $this->getSourcesStatus(), ]; } /** * Calculate API success rate */ private function calculateSuccessRate(int $days): float { $total = ApiLog::withinDays($days)->count(); if ($total === 0) { return 0; } $successful = ApiLog::successful()->withinDays($days)->count(); return round(($successful / $total) * 100, 2); } /** * Get API metrics by source */ private function getApiMetricsBySource(int $days): array { return ApiLog::select('job_source_id') ->selectRaw('COUNT(*) as total') ->selectRaw('SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful') ->selectRaw('AVG(response_time_ms) as avg_response_time') ->withinDays($days) ->with('source:id,name') ->groupBy('job_source_id') ->get() ->map(function ($item) { return [ 'source' => $item->source->name ?? 'Unknown', 'total_requests' => $item->total, 'successful' => $item->successful, 'success_rate' => $item->total > 0 ? round(($item->successful / $item->total) * 100, 2) : 0, 'avg_response_time' => round($item->avg_response_time, 2), ]; }) ->toArray(); } /** * Get job fetch metrics by source */ private function getJobFetchMetricsBySource(int $days): array { return JobFetchLog::select('job_source_id') ->selectRaw('COUNT(*) as total_fetches') ->selectRaw('SUM(jobs_fetched) as jobs_fetched') ->selectRaw('SUM(jobs_created) as jobs_created') ->selectRaw('SUM(jobs_updated) as jobs_updated') ->selectRaw('AVG(duration_seconds) as avg_duration') ->withinDays($days) ->with('source:id,name') ->groupBy('job_source_id') ->get() ->map(function ($item) { return [ 'source' => $item->source->name ?? 'Unknown', 'total_fetches' => $item->total_fetches, 'jobs_fetched' => $item->jobs_fetched, 'jobs_created' => $item->jobs_created, 'jobs_updated' => $item->jobs_updated, 'avg_duration' => round($item->avg_duration, 2), ]; }) ->toArray(); } /** * Calculate click-through rate */ private function calculateClickThroughRate(int $days): float { $views = AnalyticsEvent::ofType(AnalyticsEvent::EVENT_JOB_VIEW) ->withinDays($days) ->count(); if ($views === 0) { return 0; } $clicks = AnalyticsEvent::ofType(AnalyticsEvent::EVENT_JOB_APPLY_CLICK) ->withinDays($days) ->count(); return round(($clicks / $views) * 100, 2); } /** * Get popular jobs */ private function getPopularJobs(int $days, int $limit = 10): array { return AnalyticsEvent::select('job_id') ->selectRaw('COUNT(*) as view_count') ->selectRaw('SUM(CASE WHEN event_type = ? THEN 1 ELSE 0 END) as apply_clicks', [AnalyticsEvent::EVENT_JOB_APPLY_CLICK]) ->whereNotNull('job_id') ->withinDays($days) ->with('job:id,title,company') ->groupBy('job_id') ->orderByDesc('view_count') ->limit($limit) ->get() ->map(function ($item) { return [ 'job_id' => $item->job_id, 'title' => $item->job->title ?? 'Unknown', 'company' => $item->job->company ?? 'Unknown', 'views' => $item->view_count, 'apply_clicks' => $item->apply_clicks, ]; }) ->toArray(); } /** * Get popular search terms */ private function getPopularSearches(int $days, int $limit = 10): array { return AnalyticsEvent::select(DB::raw('JSON_EXTRACT(event_data, "$.keywords") as search_term')) ->selectRaw('COUNT(*) as search_count') ->ofType(AnalyticsEvent::EVENT_SEARCH) ->withinDays($days) ->whereNotNull(DB::raw('JSON_EXTRACT(event_data, "$.keywords")')) ->groupBy('search_term') ->orderByDesc('search_count') ->limit($limit) ->get() ->map(function ($item) { return [ 'term' => trim($item->search_term, '"'), 'count' => $item->search_count, ]; }) ->toArray(); } /** * Determine system status */ private function determineSystemStatus(int $apiFailures, int $fetchFailures): string { if ($apiFailures > 10 || $fetchFailures > 5) { return 'critical'; } if ($apiFailures > 5 || $fetchFailures > 2) { return 'warning'; } return 'healthy'; } /** * Get status of all sources */ private function getSourcesStatus(): array { return JobSource::get()->map(function ($source) { $lastFetch = JobFetchLog::where('job_source_id', $source->id) ->latest() ->first(); return [ 'id' => $source->id, 'name' => $source->name, 'is_active' => $source->is_active, 'last_fetch_success' => $lastFetch?->success ?? null, 'last_fetch_at' => $lastFetch?->created_at ?? null, 'jobs_in_last_fetch' => $lastFetch?->jobs_created ?? 0, ]; })->toArray(); } }