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
299 lines
10 KiB
PHP
299 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\{Budget, LlmRequest};
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class BudgetController extends Controller
|
|
{
|
|
/**
|
|
* Get current budget status
|
|
*
|
|
* Returns comprehensive budget information including total budget, used budget,
|
|
* remaining budget, projections, and breakdowns by provider.
|
|
*
|
|
* ## Example Response
|
|
*
|
|
* ```json
|
|
* {
|
|
* "data": {
|
|
* "total_budget": 100.00,
|
|
* "used_budget": 45.67,
|
|
* "remaining_budget": 54.33,
|
|
* "budget_percentage": 45.67,
|
|
* "currency": "USD",
|
|
* "period": "monthly",
|
|
* "period_start": "2025-11-01T00:00:00Z",
|
|
* "period_end": "2025-11-30T23:59:59Z",
|
|
* "days_remaining": 11,
|
|
* "projected_spend": 95.34,
|
|
* "projected_overspend": false,
|
|
* "limits": {
|
|
* "daily_limit": 10.00,
|
|
* "daily_used": 2.45,
|
|
* "daily_remaining": 7.55
|
|
* },
|
|
* "alerts": {
|
|
* "threshold_50_percent": false,
|
|
* "threshold_75_percent": false,
|
|
* "threshold_90_percent": false
|
|
* },
|
|
* "breakdown_by_provider": [
|
|
* {
|
|
* "provider": "openai",
|
|
* "provider_name": "OpenAI",
|
|
* "spent": 25.30,
|
|
* "percentage": 55.4,
|
|
* "requests": 850
|
|
* }
|
|
* ]
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @tags Budget
|
|
*
|
|
* @param Request $request
|
|
* @return JsonResponse
|
|
*/
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$user = $request->user();
|
|
|
|
// Get budget configuration
|
|
$budget = Budget::where('gateway_user_id', $user->user_id)->first();
|
|
|
|
if (!$budget) {
|
|
return response()->json([
|
|
'error' => [
|
|
'code' => 'not_found',
|
|
'message' => 'No budget configured for this user',
|
|
'status' => 404,
|
|
],
|
|
], 404);
|
|
}
|
|
|
|
// Calculate period dates
|
|
$now = now();
|
|
$periodStart = $now->copy()->startOfMonth();
|
|
$periodEnd = $now->copy()->endOfMonth();
|
|
$daysRemaining = $now->diffInDays($periodEnd, false);
|
|
$daysInMonth = $periodStart->daysInMonth;
|
|
$daysElapsed = $now->diffInDays($periodStart);
|
|
|
|
// Get current month's spending
|
|
$monthlySpending = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->whereYear('created_at', $now->year)
|
|
->whereMonth('created_at', $now->month)
|
|
->where('status', 'success')
|
|
->sum('total_cost') ?? 0;
|
|
|
|
// Get today's spending
|
|
$dailySpending = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->whereDate('created_at', $now->toDateString())
|
|
->where('status', 'success')
|
|
->sum('total_cost') ?? 0;
|
|
|
|
// Calculate projections
|
|
$dailyAverage = $daysElapsed > 0 ? ($monthlySpending / $daysElapsed) : 0;
|
|
$projectedSpend = $dailyAverage * $daysInMonth;
|
|
|
|
// Calculate remaining budget
|
|
$remainingBudget = $budget->monthly_limit - $monthlySpending;
|
|
$budgetPercentage = $budget->monthly_limit > 0
|
|
? ($monthlySpending / $budget->monthly_limit) * 100
|
|
: 0;
|
|
|
|
// Get breakdown by provider
|
|
$providerBreakdown = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->whereYear('created_at', $now->year)
|
|
->whereMonth('created_at', $now->month)
|
|
->where('status', 'success')
|
|
->select('provider', DB::raw('SUM(total_cost) as spent'), DB::raw('COUNT(*) as requests'))
|
|
->groupBy('provider')
|
|
->orderByDesc('spent')
|
|
->get()
|
|
->map(function ($item) use ($monthlySpending) {
|
|
$percentage = $monthlySpending > 0 ? ($item->spent / $monthlySpending) * 100 : 0;
|
|
return [
|
|
'provider' => $item->provider,
|
|
'provider_name' => $this->getProviderName($item->provider),
|
|
'spent' => round($item->spent, 4),
|
|
'percentage' => round($percentage, 1),
|
|
'requests' => $item->requests,
|
|
];
|
|
});
|
|
|
|
// Calculate daily limit (monthly limit / days in month)
|
|
$dailyLimit = $budget->monthly_limit / $daysInMonth;
|
|
|
|
return response()->json([
|
|
'data' => [
|
|
'total_budget' => round($budget->monthly_limit, 2),
|
|
'used_budget' => round($monthlySpending, 4),
|
|
'remaining_budget' => round($remainingBudget, 4),
|
|
'budget_percentage' => round($budgetPercentage, 2),
|
|
'currency' => 'USD',
|
|
'period' => 'monthly',
|
|
'period_start' => $periodStart->toIso8601String(),
|
|
'period_end' => $periodEnd->toIso8601String(),
|
|
'days_remaining' => max(0, $daysRemaining),
|
|
'projected_spend' => round($projectedSpend, 2),
|
|
'projected_overspend' => $projectedSpend > $budget->monthly_limit,
|
|
'limits' => [
|
|
'daily_limit' => round($dailyLimit, 2),
|
|
'daily_used' => round($dailySpending, 4),
|
|
'daily_remaining' => round($dailyLimit - $dailySpending, 4),
|
|
],
|
|
'alerts' => [
|
|
'threshold_50_percent' => $budgetPercentage >= 50,
|
|
'threshold_75_percent' => $budgetPercentage >= 75,
|
|
'threshold_90_percent' => $budgetPercentage >= 90,
|
|
'approaching_daily_limit' => ($dailySpending / $dailyLimit) >= 0.8,
|
|
],
|
|
'breakdown_by_provider' => $providerBreakdown,
|
|
'updated_at' => now()->toIso8601String(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get budget history over time
|
|
*
|
|
* Returns historical budget data with daily, weekly, or monthly aggregation.
|
|
*
|
|
* ## Query Parameters
|
|
*
|
|
* - `period` (optional) - Aggregation period: daily, weekly, monthly (default: daily)
|
|
* - `days` (optional) - Number of days to look back (default: 30)
|
|
*
|
|
* ## Example Response
|
|
*
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "date": "2025-11-19",
|
|
* "total_cost": 2.45,
|
|
* "total_requests": 45,
|
|
* "total_tokens": 45000,
|
|
* "breakdown": [
|
|
* {
|
|
* "provider": "openai",
|
|
* "cost": 1.35,
|
|
* "requests": 25
|
|
* }
|
|
* ]
|
|
* }
|
|
* ],
|
|
* "meta": {
|
|
* "period": "daily",
|
|
* "days": 30,
|
|
* "total_cost": 45.67,
|
|
* "total_requests": 1250,
|
|
* "avg_daily_cost": 1.52,
|
|
* "avg_daily_requests": 41.7
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @tags Budget
|
|
*
|
|
* @param Request $request
|
|
* @return JsonResponse
|
|
*/
|
|
public function history(Request $request): JsonResponse
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'period' => 'sometimes|string|in:daily,weekly,monthly',
|
|
'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();
|
|
$period = $request->input('period', 'daily');
|
|
$days = $request->input('days', 30);
|
|
|
|
$startDate = now()->subDays($days)->startOfDay();
|
|
|
|
// Get daily aggregated data
|
|
$dailyData = LlmRequest::where('gateway_user_id', $user->user_id)
|
|
->where('created_at', '>=', $startDate)
|
|
->where('status', 'success')
|
|
->select(
|
|
DB::raw('DATE(created_at) as date'),
|
|
'provider',
|
|
DB::raw('SUM(total_cost) as cost'),
|
|
DB::raw('COUNT(*) as requests'),
|
|
DB::raw('SUM(total_tokens) as tokens')
|
|
)
|
|
->groupBy('date', 'provider')
|
|
->orderBy('date')
|
|
->get();
|
|
|
|
// Group by date
|
|
$groupedByDate = $dailyData->groupBy('date')->map(function ($items, $date) {
|
|
return [
|
|
'date' => $date,
|
|
'total_cost' => round($items->sum('cost'), 4),
|
|
'total_requests' => $items->sum('requests'),
|
|
'total_tokens' => $items->sum('tokens'),
|
|
'breakdown' => $items->map(function ($item) {
|
|
return [
|
|
'provider' => $item->provider,
|
|
'cost' => round($item->cost, 4),
|
|
'requests' => $item->requests,
|
|
];
|
|
})->values(),
|
|
];
|
|
})->values();
|
|
|
|
// Calculate meta statistics
|
|
$totalCost = $groupedByDate->sum('total_cost');
|
|
$totalRequests = $groupedByDate->sum('total_requests');
|
|
$dataPoints = $groupedByDate->count();
|
|
|
|
return response()->json([
|
|
'data' => $groupedByDate,
|
|
'meta' => [
|
|
'period' => $period,
|
|
'days' => $days,
|
|
'total_cost' => round($totalCost, 4),
|
|
'total_requests' => $totalRequests,
|
|
'avg_daily_cost' => $dataPoints > 0 ? round($totalCost / $dataPoints, 4) : 0,
|
|
'avg_daily_requests' => $dataPoints > 0 ? round($totalRequests / $dataPoints, 1) : 0,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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),
|
|
};
|
|
}
|
|
}
|