275 lines
9.7 KiB
PHP
275 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\ApiLog;
|
|
use App\Models\AnalyticsEvent;
|
|
use App\Models\JobFetchLog;
|
|
use App\Models\JobSource;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class MetricsService
|
|
{
|
|
/**
|
|
* Get API performance metrics
|
|
*/
|
|
public function getApiMetrics(int $days = 7): array
|
|
{
|
|
$startDate = now()->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();
|
|
}
|
|
}
|