Fügt 21 neue API-Endpoints in 4 Phasen hinzu:
Phase 1 - Foundation (Provider & Models):
- GET /api/providers - Liste aller Provider
- GET /api/providers/{provider} - Provider-Details
- GET /api/models - Liste aller Models mit Filtering/Sorting
- GET /api/models/{provider}/{model} - Model-Details
Phase 2 - Core Features (Credentials, Budget, Pricing):
- GET/POST/PUT/DELETE /api/credentials - Credential-Management
- POST /api/credentials/{id}/test - Connection Testing
- GET /api/budget - Budget-Status mit Projektionen
- GET /api/budget/history - Budget-Historie
- GET /api/pricing - Model-Pricing-Listen
- GET /api/pricing/calculator - Kosten-Kalkulator
- GET /api/pricing/compare - Preis-Vergleich
Phase 3 - Analytics (Usage Statistics):
- GET /api/usage/summary - Umfassende Statistiken
- GET /api/usage/requests - Request-History mit Pagination
- GET /api/usage/requests/{id} - Request-Details
- GET /api/usage/charts - Chart-Daten (4 Typen)
Phase 4 - Account (Account Info & Activity):
- GET /api/account - User-Informationen
- GET /api/account/activity - Activity-Log
Features:
- Vollständige Scramble/Swagger-Dokumentation
- Consistent Error-Handling
- API-Key Authentication
- Filtering, Sorting, Pagination
- Budget-Tracking mit Alerts
- Provider-Breakdown
- Performance-Metriken
- Chart-Ready-Data
Controller erstellt:
- ProviderController
- ModelController
- CredentialController
- BudgetController
- PricingController
- UsageController
- AccountController
Dokumentation:
- API_KONZEPT.md - Vollständiges API-Konzept
- API_IMPLEMENTATION_STATUS.txt - Implementation-Tracking
- API_IMPLEMENTATION_SUMMARY.md - Zusammenfassung und Workflows
637 lines
23 KiB
PHP
637 lines
23 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\LlmRequest;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class UsageController extends Controller
|
|
{
|
|
/**
|
|
* Get usage summary statistics
|
|
*
|
|
* Returns comprehensive usage statistics for the authenticated user,
|
|
* including requests, tokens, costs, and breakdowns by provider and model.
|
|
*
|
|
* ## Query Parameters
|
|
*
|
|
* - `period` (optional) - Time period: today, week, month, all (default: month)
|
|
* - `provider` (optional) - Filter by provider
|
|
*
|
|
* ## Example Response
|
|
*
|
|
* ```json
|
|
* {
|
|
* "data": {
|
|
* "period": "month",
|
|
* "period_start": "2025-11-01T00:00:00Z",
|
|
* "period_end": "2025-11-30T23:59:59Z",
|
|
* "summary": {
|
|
* "total_requests": 1250,
|
|
* "successful_requests": 1235,
|
|
* "failed_requests": 15,
|
|
* "success_rate": 98.8,
|
|
* "total_tokens": 2500000,
|
|
* "prompt_tokens": 1800000,
|
|
* "completion_tokens": 700000,
|
|
* "total_cost": 45.67,
|
|
* "avg_cost_per_request": 0.0365,
|
|
* "avg_tokens_per_request": 2000,
|
|
* "avg_response_time_ms": 1450
|
|
* },
|
|
* "by_provider": [...],
|
|
* "by_model": [...],
|
|
* "top_hours": [...]
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @tags Usage
|
|
*
|
|
* @param Request $request
|
|
* @return JsonResponse
|
|
*/
|
|
public function summary(Request $request): JsonResponse
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'period' => 'sometimes|string|in:today,week,month,all',
|
|
'provider' => 'sometimes|string|in:openai,anthropic,gemini,deepseek,mistral',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'error' => [
|
|
'code' => 'validation_error',
|
|
'message' => 'Invalid query parameters',
|
|
'status' => 422,
|
|
'details' => $validator->errors(),
|
|
],
|
|
], 422);
|
|
}
|
|
|
|
$user = $request->user();
|
|
$period = $request->input('period', 'month');
|
|
|
|
// Calculate date range
|
|
$dateRange = $this->getDateRange($period);
|
|
|
|
// Base query
|
|
$query = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $dateRange['start']);
|
|
|
|
if ($dateRange['end']) {
|
|
$query->where('created_at', '<=', $dateRange['end']);
|
|
}
|
|
|
|
// Apply provider filter
|
|
if ($request->has('provider')) {
|
|
$query->where('provider', $request->input('provider'));
|
|
}
|
|
|
|
// Get summary statistics
|
|
$summary = $query->selectRaw('
|
|
COUNT(*) as total_requests,
|
|
SUM(CASE WHEN status = "success" THEN 1 ELSE 0 END) as successful_requests,
|
|
SUM(CASE WHEN status != "success" THEN 1 ELSE 0 END) as failed_requests,
|
|
SUM(prompt_tokens) as prompt_tokens,
|
|
SUM(completion_tokens) as completion_tokens,
|
|
SUM(total_tokens) as total_tokens,
|
|
SUM(total_cost) as total_cost,
|
|
AVG(total_tokens) as avg_tokens_per_request,
|
|
AVG(total_cost) as avg_cost_per_request,
|
|
AVG(response_time_ms) as avg_response_time_ms
|
|
')->first();
|
|
|
|
$successRate = $summary->total_requests > 0
|
|
? ($summary->successful_requests / $summary->total_requests) * 100
|
|
: 0;
|
|
|
|
// Get breakdown by provider
|
|
$byProvider = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $dateRange['start'])
|
|
->where('status', 'success')
|
|
->select(
|
|
'provider',
|
|
DB::raw('COUNT(*) as requests'),
|
|
DB::raw('SUM(total_tokens) as tokens'),
|
|
DB::raw('SUM(total_cost) as cost'),
|
|
DB::raw('AVG(response_time_ms) as avg_response_time_ms')
|
|
)
|
|
->groupBy('provider')
|
|
->orderByDesc('requests')
|
|
->get()
|
|
->map(function ($item) use ($summary) {
|
|
$successRate = LlmRequest::where('gateway_user_id', request()->user()->user_id)
|
|
->where('provider', $item->provider)
|
|
->where('created_at', '>=', $this->getDateRange(request()->input('period', 'month'))['start'])
|
|
->selectRaw('
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN status = "success" THEN 1 ELSE 0 END) as successful
|
|
')
|
|
->first();
|
|
|
|
$rate = $successRate->total > 0 ? ($successRate->successful / $successRate->total) * 100 : 0;
|
|
|
|
return [
|
|
'provider' => $item->provider,
|
|
'provider_name' => $this->getProviderName($item->provider),
|
|
'requests' => $item->requests,
|
|
'tokens' => $item->tokens,
|
|
'cost' => round($item->cost, 4),
|
|
'avg_response_time_ms' => round($item->avg_response_time_ms),
|
|
'success_rate' => round($rate, 1),
|
|
];
|
|
});
|
|
|
|
// Get breakdown by model (top 10)
|
|
$byModel = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $dateRange['start'])
|
|
->where('status', 'success')
|
|
->select(
|
|
'model',
|
|
'provider',
|
|
DB::raw('COUNT(*) as requests'),
|
|
DB::raw('SUM(total_tokens) as tokens'),
|
|
DB::raw('SUM(total_cost) as cost'),
|
|
DB::raw('AVG(total_tokens) as avg_tokens_per_request')
|
|
)
|
|
->groupBy('model', 'provider')
|
|
->orderByDesc('requests')
|
|
->limit(10)
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'model' => $item->model,
|
|
'provider' => $item->provider,
|
|
'requests' => $item->requests,
|
|
'tokens' => $item->tokens,
|
|
'cost' => round($item->cost, 4),
|
|
'avg_tokens_per_request' => round($item->avg_tokens_per_request),
|
|
];
|
|
});
|
|
|
|
// Get top hours
|
|
$topHours = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $dateRange['start'])
|
|
->where('status', 'success')
|
|
->select(
|
|
DB::raw('HOUR(created_at) as hour'),
|
|
DB::raw('COUNT(*) as requests'),
|
|
DB::raw('SUM(total_cost) as cost')
|
|
)
|
|
->groupBy('hour')
|
|
->orderByDesc('requests')
|
|
->limit(5)
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'hour' => $item->hour,
|
|
'requests' => $item->requests,
|
|
'cost' => round($item->cost, 4),
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'data' => [
|
|
'period' => $period,
|
|
'period_start' => $dateRange['start']->toIso8601String(),
|
|
'period_end' => $dateRange['end']?->toIso8601String() ?? now()->toIso8601String(),
|
|
'summary' => [
|
|
'total_requests' => $summary->total_requests ?? 0,
|
|
'successful_requests' => $summary->successful_requests ?? 0,
|
|
'failed_requests' => $summary->failed_requests ?? 0,
|
|
'success_rate' => round($successRate, 1),
|
|
'total_tokens' => $summary->total_tokens ?? 0,
|
|
'prompt_tokens' => $summary->prompt_tokens ?? 0,
|
|
'completion_tokens' => $summary->completion_tokens ?? 0,
|
|
'total_cost' => round($summary->total_cost ?? 0, 4),
|
|
'avg_cost_per_request' => round($summary->avg_cost_per_request ?? 0, 6),
|
|
'avg_tokens_per_request' => round($summary->avg_tokens_per_request ?? 0),
|
|
'avg_response_time_ms' => round($summary->avg_response_time_ms ?? 0),
|
|
],
|
|
'by_provider' => $byProvider,
|
|
'by_model' => $byModel,
|
|
'top_hours' => $topHours,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get list of individual requests
|
|
*
|
|
* Returns paginated list of requests with filtering and sorting options.
|
|
*
|
|
* ## Query Parameters
|
|
*
|
|
* - `page` (optional) - Page number (default: 1)
|
|
* - `per_page` (optional) - Items per page (default: 20, max: 100)
|
|
* - `provider` (optional) - Filter by provider
|
|
* - `model` (optional) - Filter by model
|
|
* - `status` (optional) - Filter by status: success, failed, all (default: all)
|
|
* - `date_from` (optional) - From date (ISO 8601)
|
|
* - `date_to` (optional) - To date (ISO 8601)
|
|
* - `sort` (optional) - Sort field: created_at, cost, tokens, response_time (default: -created_at)
|
|
*
|
|
* @tags Usage
|
|
*
|
|
* @param Request $request
|
|
* @return JsonResponse
|
|
*/
|
|
public function requests(Request $request): JsonResponse
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'page' => 'sometimes|integer|min:1',
|
|
'per_page' => 'sometimes|integer|min:1|max:100',
|
|
'provider' => 'sometimes|string|in:openai,anthropic,gemini,deepseek,mistral',
|
|
'model' => 'sometimes|string',
|
|
'status' => 'sometimes|string|in:success,failed,all',
|
|
'date_from' => 'sometimes|date',
|
|
'date_to' => 'sometimes|date',
|
|
'sort' => 'sometimes|string|in:created_at,-created_at,cost,-cost,tokens,-tokens,response_time,-response_time',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'error' => [
|
|
'code' => 'validation_error',
|
|
'message' => 'Invalid query parameters',
|
|
'status' => 422,
|
|
'details' => $validator->errors(),
|
|
],
|
|
], 422);
|
|
}
|
|
|
|
$user = $request->user();
|
|
$perPage = $request->input('per_page', 20);
|
|
|
|
// Build query
|
|
$query = LlmRequest::where('gateway_user_id', $user->user_id);
|
|
|
|
// Apply filters
|
|
if ($request->has('provider')) {
|
|
$query->where('provider', $request->input('provider'));
|
|
}
|
|
|
|
if ($request->has('model')) {
|
|
$query->where('model', $request->input('model'));
|
|
}
|
|
|
|
$status = $request->input('status', 'all');
|
|
if ($status === 'success') {
|
|
$query->where('status', 'success');
|
|
} elseif ($status === 'failed') {
|
|
$query->where('status', '!=', 'success');
|
|
}
|
|
|
|
if ($request->has('date_from')) {
|
|
$query->where('created_at', '>=', $request->input('date_from'));
|
|
}
|
|
|
|
if ($request->has('date_to')) {
|
|
$query->where('created_at', '<=', $request->input('date_to'));
|
|
}
|
|
|
|
// Apply sorting
|
|
$sort = $request->input('sort', '-created_at');
|
|
$sortField = ltrim($sort, '-');
|
|
$sortDirection = str_starts_with($sort, '-') ? 'desc' : 'asc';
|
|
$query->orderBy($sortField, $sortDirection);
|
|
|
|
// Get summary for filtered results
|
|
$summary = $query->clone()->selectRaw('
|
|
SUM(total_cost) as total_cost,
|
|
SUM(total_tokens) as total_tokens,
|
|
AVG(response_time_ms) as avg_response_time_ms
|
|
')->first();
|
|
|
|
// Paginate
|
|
$paginated = $query->paginate($perPage);
|
|
|
|
$data = $paginated->map(function ($request) {
|
|
return [
|
|
'id' => $request->request_id,
|
|
'provider' => $request->provider,
|
|
'model' => $request->model,
|
|
'status' => $request->status,
|
|
'prompt_tokens' => $request->prompt_tokens,
|
|
'completion_tokens' => $request->completion_tokens,
|
|
'total_tokens' => $request->total_tokens,
|
|
'input_cost' => round($request->prompt_tokens * ($request->input_price_per_token ?? 0), 6),
|
|
'output_cost' => round($request->completion_tokens * ($request->output_price_per_token ?? 0), 6),
|
|
'total_cost' => round($request->total_cost, 6),
|
|
'response_time_ms' => $request->response_time_ms,
|
|
'created_at' => $request->created_at->toIso8601String(),
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'data' => $data,
|
|
'meta' => [
|
|
'current_page' => $paginated->currentPage(),
|
|
'per_page' => $paginated->perPage(),
|
|
'total' => $paginated->total(),
|
|
'total_pages' => $paginated->lastPage(),
|
|
'has_more' => $paginated->hasMorePages(),
|
|
],
|
|
'links' => [
|
|
'first' => $paginated->url(1),
|
|
'last' => $paginated->url($paginated->lastPage()),
|
|
'prev' => $paginated->previousPageUrl(),
|
|
'next' => $paginated->nextPageUrl(),
|
|
],
|
|
'summary' => [
|
|
'total_cost' => round($summary->total_cost ?? 0, 4),
|
|
'total_tokens' => $summary->total_tokens ?? 0,
|
|
'avg_response_time_ms' => round($summary->avg_response_time_ms ?? 0),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get details of a specific request
|
|
*
|
|
* Returns complete information about a single request including
|
|
* full request and response data.
|
|
*
|
|
* @tags Usage
|
|
*
|
|
* @param Request $request
|
|
* @param string $id
|
|
* @return JsonResponse
|
|
*/
|
|
public function show(Request $request, string $id): JsonResponse
|
|
{
|
|
$user = $request->user();
|
|
|
|
$llmRequest = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('request_id', $id)
|
|
->first();
|
|
|
|
if (!$llmRequest) {
|
|
return response()->json([
|
|
'error' => [
|
|
'code' => 'not_found',
|
|
'message' => 'Request not found',
|
|
'status' => 404,
|
|
],
|
|
], 404);
|
|
}
|
|
|
|
return response()->json([
|
|
'data' => [
|
|
'id' => $llmRequest->request_id,
|
|
'gateway_user_id' => $llmRequest->gateway_user_id,
|
|
'provider' => $llmRequest->provider,
|
|
'provider_name' => $this->getProviderName($llmRequest->provider),
|
|
'model' => $llmRequest->model,
|
|
'status' => $llmRequest->status,
|
|
'request' => $llmRequest->request_data,
|
|
'response' => $llmRequest->response_data,
|
|
'usage' => [
|
|
'prompt_tokens' => $llmRequest->prompt_tokens,
|
|
'completion_tokens' => $llmRequest->completion_tokens,
|
|
'total_tokens' => $llmRequest->total_tokens,
|
|
],
|
|
'cost' => [
|
|
'input_cost' => round($llmRequest->prompt_tokens * ($llmRequest->input_price_per_token ?? 0), 6),
|
|
'output_cost' => round($llmRequest->completion_tokens * ($llmRequest->output_price_per_token ?? 0), 6),
|
|
'total_cost' => round($llmRequest->total_cost, 6),
|
|
'currency' => 'USD',
|
|
],
|
|
'performance' => [
|
|
'response_time_ms' => $llmRequest->response_time_ms,
|
|
],
|
|
'metadata' => [
|
|
'ip_address' => $llmRequest->ip_address,
|
|
'user_agent' => $llmRequest->user_agent,
|
|
],
|
|
'created_at' => $llmRequest->created_at->toIso8601String(),
|
|
'completed_at' => $llmRequest->created_at->addMilliseconds($llmRequest->response_time_ms)->toIso8601String(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get chart data for visualizations
|
|
*
|
|
* Returns data formatted for chart visualizations.
|
|
*
|
|
* ## Query Parameters
|
|
*
|
|
* - `type` (required) - Chart type: daily_cost, provider_distribution, model_usage, hourly_pattern
|
|
* - `days` (optional) - Number of days to look back (default: 30)
|
|
*
|
|
* @tags Usage
|
|
*
|
|
* @param Request $request
|
|
* @return JsonResponse
|
|
*/
|
|
public function charts(Request $request): JsonResponse
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'type' => 'required|string|in:daily_cost,provider_distribution,model_usage,hourly_pattern',
|
|
'days' => 'sometimes|integer|min:1|max:365',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'error' => [
|
|
'code' => 'validation_error',
|
|
'message' => 'Invalid query parameters',
|
|
'status' => 422,
|
|
'details' => $validator->errors(),
|
|
],
|
|
], 422);
|
|
}
|
|
|
|
$user = $request->user();
|
|
$type = $request->input('type');
|
|
$days = $request->input('days', 30);
|
|
$startDate = now()->subDays($days);
|
|
|
|
$chartData = match ($type) {
|
|
'daily_cost' => $this->getDailyCostChart($user, $startDate),
|
|
'provider_distribution' => $this->getProviderDistributionChart($user, $startDate),
|
|
'model_usage' => $this->getModelUsageChart($user, $startDate),
|
|
'hourly_pattern' => $this->getHourlyPatternChart($user, $startDate),
|
|
};
|
|
|
|
return response()->json([
|
|
'data' => $chartData,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Calculate date range for period
|
|
*/
|
|
private function getDateRange(string $period): array
|
|
{
|
|
$now = now();
|
|
|
|
return match ($period) {
|
|
'today' => [
|
|
'start' => $now->copy()->startOfDay(),
|
|
'end' => $now->copy()->endOfDay(),
|
|
],
|
|
'week' => [
|
|
'start' => $now->copy()->startOfWeek(),
|
|
'end' => $now->copy()->endOfWeek(),
|
|
],
|
|
'month' => [
|
|
'start' => $now->copy()->startOfMonth(),
|
|
'end' => $now->copy()->endOfMonth(),
|
|
],
|
|
'all' => [
|
|
'start' => $now->copy()->subYears(10), // 10 years back
|
|
'end' => null,
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get daily cost chart data
|
|
*/
|
|
private function getDailyCostChart($user, $startDate): array
|
|
{
|
|
$dailyData = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $startDate)
|
|
->where('status', 'success')
|
|
->select(
|
|
DB::raw('DATE(created_at) as date'),
|
|
DB::raw('SUM(total_cost) as cost'),
|
|
DB::raw('COUNT(*) as requests')
|
|
)
|
|
->groupBy('date')
|
|
->orderBy('date')
|
|
->get();
|
|
|
|
return [
|
|
'type' => 'daily_cost',
|
|
'labels' => $dailyData->pluck('date')->toArray(),
|
|
'datasets' => [
|
|
[
|
|
'label' => 'Daily Cost',
|
|
'data' => $dailyData->pluck('cost')->map(fn($v) => round($v, 4))->toArray(),
|
|
'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
|
|
'borderColor' => 'rgba(59, 130, 246, 1)',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get provider distribution chart data
|
|
*/
|
|
private function getProviderDistributionChart($user, $startDate): array
|
|
{
|
|
$providerData = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $startDate)
|
|
->where('status', 'success')
|
|
->select('provider', DB::raw('SUM(total_cost) as cost'))
|
|
->groupBy('provider')
|
|
->orderByDesc('cost')
|
|
->get();
|
|
|
|
return [
|
|
'type' => 'provider_distribution',
|
|
'labels' => $providerData->pluck('provider')->map(fn($p) => $this->getProviderName($p))->toArray(),
|
|
'datasets' => [
|
|
[
|
|
'label' => 'Cost by Provider',
|
|
'data' => $providerData->pluck('cost')->map(fn($v) => round($v, 4))->toArray(),
|
|
'backgroundColor' => [
|
|
'rgba(59, 130, 246, 0.8)',
|
|
'rgba(239, 68, 68, 0.8)',
|
|
'rgba(34, 197, 94, 0.8)',
|
|
'rgba(251, 146, 60, 0.8)',
|
|
'rgba(168, 85, 247, 0.8)',
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get model usage chart data
|
|
*/
|
|
private function getModelUsageChart($user, $startDate): array
|
|
{
|
|
$modelData = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $startDate)
|
|
->where('status', 'success')
|
|
->select('model', DB::raw('COUNT(*) as requests'))
|
|
->groupBy('model')
|
|
->orderByDesc('requests')
|
|
->limit(10)
|
|
->get();
|
|
|
|
return [
|
|
'type' => 'model_usage',
|
|
'labels' => $modelData->pluck('model')->toArray(),
|
|
'datasets' => [
|
|
[
|
|
'label' => 'Requests by Model',
|
|
'data' => $modelData->pluck('requests')->toArray(),
|
|
'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
|
|
'borderColor' => 'rgba(59, 130, 246, 1)',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get hourly pattern chart data
|
|
*/
|
|
private function getHourlyPatternChart($user, $startDate): array
|
|
{
|
|
$hourlyData = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $startDate)
|
|
->where('status', 'success')
|
|
->select(
|
|
DB::raw('HOUR(created_at) as hour'),
|
|
DB::raw('COUNT(*) as requests')
|
|
)
|
|
->groupBy('hour')
|
|
->orderBy('hour')
|
|
->get();
|
|
|
|
// Fill missing hours with 0
|
|
$allHours = collect(range(0, 23))->map(function ($hour) use ($hourlyData) {
|
|
$data = $hourlyData->firstWhere('hour', $hour);
|
|
return $data?->requests ?? 0;
|
|
});
|
|
|
|
return [
|
|
'type' => 'hourly_pattern',
|
|
'labels' => range(0, 23),
|
|
'datasets' => [
|
|
[
|
|
'label' => 'Requests by Hour',
|
|
'data' => $allHours->toArray(),
|
|
'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
|
|
'borderColor' => 'rgba(59, 130, 246, 1)',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get human-readable provider name
|
|
*/
|
|
private function getProviderName(string $provider): string
|
|
{
|
|
return match ($provider) {
|
|
'openai' => 'OpenAI',
|
|
'anthropic' => 'Anthropic',
|
|
'gemini' => 'Google Gemini',
|
|
'deepseek' => 'DeepSeek',
|
|
'mistral' => 'Mistral AI',
|
|
default => ucfirst($provider),
|
|
};
|
|
}
|
|
}
|