phase 7
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user