'date', 'call_count' => 'integer', 'success_count' => 'integer', 'error_count' => 'integer', 'total_duration_ms' => 'integer', 'min_duration_ms' => 'integer', 'max_duration_ms' => 'integer', ]; // Relationships public function workspace(): BelongsTo { return $this->belongsTo(Workspace::class); } // Scopes public function scopeForServer(Builder $query, string $serverId): Builder { return $query->where('server_id', $serverId); } public function scopeForTool(Builder $query, string $toolName): Builder { return $query->where('tool_name', $toolName); } public function scopeForDate(Builder $query, Carbon|string $date): Builder { $date = $date instanceof Carbon ? $date->toDateString() : $date; return $query->where('date', $date); } public function scopeForDateRange(Builder $query, Carbon|string $start, Carbon|string $end): Builder { $start = $start instanceof Carbon ? $start->toDateString() : $start; $end = $end instanceof Carbon ? $end->toDateString() : $end; return $query->whereBetween('date', [$start, $end]); } public function scopeLast7Days(Builder $query): Builder { return $query->forDateRange(now()->subDays(6), now()); } public function scopeLast30Days(Builder $query): Builder { return $query->forDateRange(now()->subDays(29), now()); } /** * Increment stats for a tool call. */ public static function incrementForCall(McpToolCall $call): void { $stat = static::firstOrCreate([ 'date' => $call->created_at->toDateString(), 'server_id' => $call->server_id, 'tool_name' => $call->tool_name, 'workspace_id' => $call->workspace_id, ], [ 'call_count' => 0, 'success_count' => 0, 'error_count' => 0, 'total_duration_ms' => 0, ]); $stat->call_count++; if ($call->success) { $stat->success_count++; } else { $stat->error_count++; } if ($call->duration_ms) { $stat->total_duration_ms += $call->duration_ms; if ($stat->min_duration_ms === null || $call->duration_ms < $stat->min_duration_ms) { $stat->min_duration_ms = $call->duration_ms; } if ($stat->max_duration_ms === null || $call->duration_ms > $stat->max_duration_ms) { $stat->max_duration_ms = $call->duration_ms; } } $stat->save(); } // Computed attributes public function getSuccessRateAttribute(): float { if ($this->call_count === 0) { return 0; } return round(($this->success_count / $this->call_count) * 100, 1); } public function getAvgDurationMsAttribute(): ?float { if ($this->call_count === 0 || $this->total_duration_ms === 0) { return null; } return round($this->total_duration_ms / $this->call_count, 1); } public function getAvgDurationForHumansAttribute(): string { $avg = $this->avg_duration_ms; if ($avg === null) { return '-'; } if ($avg < 1000) { return round($avg).'ms'; } return round($avg / 1000, 2).'s'; } /** * Get top tools by call count. */ public static function getTopTools(int $days = 7, int $limit = 10, ?int $workspaceId = null): Collection { $query = static::query() ->select('server_id', 'tool_name') ->selectRaw('SUM(call_count) as total_calls') ->selectRaw('SUM(success_count) as total_success') ->selectRaw('SUM(error_count) as total_errors') ->selectRaw('AVG(total_duration_ms / NULLIF(call_count, 0)) as avg_duration') ->forDateRange(now()->subDays($days - 1), now()) ->groupBy('server_id', 'tool_name') ->orderByDesc('total_calls') ->limit($limit); if ($workspaceId !== null) { $query->where('workspace_id', $workspaceId); } return $query->get() ->map(function ($item) { $item->success_rate = $item->total_calls > 0 ? round(($item->total_success / $item->total_calls) * 100, 1) : 0; return $item; }); } /** * Get daily trend data. */ public static function getDailyTrend(int $days = 7, ?int $workspaceId = null): Collection { $query = static::query() ->select('date') ->selectRaw('SUM(call_count) as total_calls') ->selectRaw('SUM(success_count) as total_success') ->selectRaw('SUM(error_count) as total_errors') ->forDateRange(now()->subDays($days - 1), now()) ->groupBy('date') ->orderBy('date'); if ($workspaceId !== null) { $query->where('workspace_id', $workspaceId); } return $query->get() ->map(function ($item) { $item->success_rate = $item->total_calls > 0 ? round(($item->total_success / $item->total_calls) * 100, 1) : 0; return $item; }); } /** * Get server-level statistics. */ public static function getServerStats(int $days = 7, ?int $workspaceId = null): Collection { $query = static::query() ->select('server_id') ->selectRaw('SUM(call_count) as total_calls') ->selectRaw('SUM(success_count) as total_success') ->selectRaw('SUM(error_count) as total_errors') ->selectRaw('COUNT(DISTINCT tool_name) as unique_tools') ->forDateRange(now()->subDays($days - 1), now()) ->groupBy('server_id') ->orderByDesc('total_calls'); if ($workspaceId !== null) { $query->where('workspace_id', $workspaceId); } return $query->get() ->map(function ($item) { $item->success_rate = $item->total_calls > 0 ? round(($item->total_success / $item->total_calls) * 100, 1) : 0; return $item; }); } }