insertOrIgnore([ 'id' => (string) str()->ulid(), 'organization_id' => $event->organization_id, 'learning_event_id' => $event->getKey(), 'processor' => self::PROCESSOR, 'processor_version' => self::VERSION, 'processed_at' => now(), 'created_at' => now(), 'updated_at' => now(), ]); if ($claimed === 0) { return false; } $this->refreshDate($event->organization_id, $event->occurred_at->toDateString()); return true; }); } public function rebuild(string $organizationId): int { DB::table('analytics_event_projections')->where('organization_id', $organizationId)->delete(); DB::table('analytics_daily_metrics')->where('organization_id', $organizationId)->delete(); $count = 0; LearningEvent::query()->where('organization_id', $organizationId)->orderBy('occurred_at')->orderBy('id')->each(function (LearningEvent $event) use (&$count): void { if ($this->process($event)) { $count++; } }); return $count; } private function refreshDate(string $organizationId, string $date): void { $events = LearningEvent::query()->where('organization_id', $organizationId)->whereDate('occurred_at', $date)->get(); $durations = $events->sum(fn (LearningEvent $event) => max(0, min(21600, (int) ($event->payload['durationSeconds'] ?? 0)))); $assessmentScores = $events->where('event_type', 'assessment.completed')->map(fn (LearningEvent $event) => $event->payload['score'] ?? null)->filter(fn ($score) => is_numeric($score)); $metrics = [ 'events' => $events->count(), 'activeLearners' => $events->pluck('learner_id')->unique()->count(), 'sessions' => $events->pluck('session_id')->filter()->unique()->count(), 'learningMinutes' => round($durations / 60, 1), 'coursesOpened' => $events->where('event_type', 'course.opened')->count(), 'coursesCompleted' => $events->where('event_type', 'course.completed')->count(), 'lessonsStarted' => $events->where('event_type', 'lesson.started')->count(), 'lessonsCompleted' => $events->where('event_type', 'lesson.completed')->count(), 'blocksViewed' => $events->where('event_type', 'block.viewed')->count(), 'blocksInteracted' => $events->whereIn('event_type', ['block.interacted', 'block.completed'])->count(), 'videoStarted' => $events->where('event_type', 'video.started')->count(), 'videoCompleted' => $events->where('event_type', 'video.completed')->count(), 'assessmentsCompleted' => $assessmentScores->count(), 'assessmentAverage' => $assessmentScores->isEmpty() ? null : round((float) $assessmentScores->average(), 1), ]; DB::table('analytics_daily_metrics')->updateOrInsert( ['organization_id' => $organizationId, 'metric_date' => $date, 'scope_type' => 'organization', 'scope_id' => $organizationId], ['id' => (string) str()->ulid(), 'metrics' => json_encode($metrics, JSON_THROW_ON_ERROR), 'calculated_at' => now(), 'created_at' => now(), 'updated_at' => now()], ); } }