startOfDay(), $to->endOfDay()); } // ---- Summary Metrics ---- public function totalPageViews(): int { return PageView::where('website_id', $this->website->id) ->whereBetween('viewed_at', [$this->from, $this->to]) ->count(); } public function uniqueVisitors(): int { return PageView::where('website_id', $this->website->id) ->whereBetween('viewed_at', [$this->from, $this->to]) ->distinct('visitor_id') ->count('visitor_id'); } public function totalSessions(): int { return AnalyticsSession::where('website_id', $this->website->id) ->whereBetween('started_at', [$this->from, $this->to]) ->count(); } public function bounceRate(): float { $total = $this->totalSessions(); if ($total === 0) { return 0; } $bounced = AnalyticsSession::where('website_id', $this->website->id) ->whereBetween('started_at', [$this->from, $this->to]) ->where('is_bounce', true) ->count(); return round(($bounced / $total) * 100, 1); } public function avgSessionDuration(): float { return round( (float) AnalyticsSession::where('website_id', $this->website->id) ->whereBetween('started_at', [$this->from, $this->to]) ->avg('duration') ?? 0, ); } public function avgPagesPerSession(): float { return round( (float) AnalyticsSession::where('website_id', $this->website->id) ->whereBetween('started_at', [$this->from, $this->to]) ->avg('pages_count') ?? 0, 1, ); } public function summary(): array { return [ 'page_views' => $this->totalPageViews(), 'visitors' => $this->uniqueVisitors(), 'sessions' => $this->totalSessions(), 'bounce_rate' => $this->bounceRate(), 'avg_duration' => $this->avgSessionDuration(), 'avg_pages' => $this->avgPagesPerSession(), ]; } // ---- Time Series ---- public function visitorsOverTime(): Collection { $days = $this->from->diffInDays($this->to); $groupFormat = $days <= 2 ? '%Y-%m-%d %H:00' : '%Y-%m-%d'; $carbonFormat = $days <= 2 ? 'Y-m-d H:00' : 'Y-m-d'; $pageViews = PageView::where('website_id', $this->website->id) ->whereBetween('viewed_at', [$this->from, $this->to]) ->select( DB::raw("strftime('{$groupFormat}', viewed_at) as period"), DB::raw('COUNT(*) as page_views'), DB::raw('COUNT(DISTINCT visitor_id) as visitors'), ) ->groupBy('period') ->orderBy('period') ->get() ->keyBy('period'); $sessions = AnalyticsSession::where('website_id', $this->website->id) ->whereBetween('started_at', [$this->from, $this->to]) ->select( DB::raw("strftime('{$groupFormat}', started_at) as period"), DB::raw('COUNT(*) as sessions'), ) ->groupBy('period') ->orderBy('period') ->get() ->keyBy('period'); $periods = collect(); $cursor = $this->from->copy(); $increment = $days <= 2 ? 'addHour' : 'addDay'; while ($cursor->lte($this->to)) { $key = $cursor->format($carbonFormat); $pv = $pageViews->get($key); $sess = $sessions->get($key); $periods->push([ 'period' => $key, 'page_views' => $pv ? $pv->page_views : 0, 'visitors' => $pv ? $pv->visitors : 0, 'sessions' => $sess ? $sess->sessions : 0, ]); $cursor->$increment(); } return $periods; } // ---- Real-Time ---- public static function activeVisitors(Website $website, int $minutes = 5): int { return AnalyticsSession::where('website_id', $website->id) ->where('ended_at', '>=', Carbon::now()->subMinutes($minutes)) ->distinct('visitor_id') ->count('visitor_id'); } }