30, 'Sedang' => 45, 'Intensif' => 80 // Base 80, bukan 90, agar lebih bervariasi ]; $baseDuration = $baseDurations[$category]; // Ambil data 7 hari terakhir untuk analisis $recentActivities = ActivityLog::where('user_id', $userId) ->with('recommendation') ->orderBy('activity_date', 'desc') ->take(7) ->get(); // Jika belum punya history, return base duration dengan variasi ringan if ($recentActivities->isEmpty()) { $initialVariation = $this->getInitialVariation($category); $finalDuration = $baseDuration + $initialVariation; $finalDuration = $this->ensureInRange($finalDuration, $category); return [ 'minutes' => $finalDuration, 'notes' => $this->generateSimpleNotes($category, $finalDuration), 'factors' => ['new_user'], 'adjustment' => $initialVariation, 'base_duration' => $baseDuration ]; } // =========================================== // FAKTOR-FAKTOR PENYESUAIAN // =========================================== $adjustment = 0; $factors = []; // 1. FAKTOR KONSISTENSI $consistencyScore = $this->calculateConsistency($recentActivities); if ($consistencyScore > 0.7) { $adjustment += 5; $factors[] = 'konsisten'; } elseif ($consistencyScore < 0.3) { $adjustment -= 5; $factors[] = 'tidak konsisten'; } // 2. FAKTOR TREND $trend = $this->calculateTrend($recentActivities); if ($trend == 'meningkat') { $adjustment += 4; $factors[] = 'trend meningkat'; } elseif ($trend == 'menurun') { $adjustment -= 4; $factors[] = 'trend menurun'; } // 3. FAKTOR MOOD $moodScore = $this->getMoodWeight($currentMood); if ($moodScore >= 1.5) { $adjustment += 3; $factors[] = 'mood positif'; } elseif ($moodScore <= 0.5) { $adjustment -= 5; $factors[] = 'mood negatif'; } // 4. FAKTOR TIDUR if ($currentSleep >= 7 && $currentSleep <= 9) { $adjustment += 3; $factors[] = 'tidur ideal'; } elseif ($currentSleep < 5) { $adjustment -= 5; $factors[] = 'kurang tidur'; } elseif ($currentSleep > 9) { $adjustment += 1; $factors[] = 'tidur panjang'; } // 5. FAKTOR BEBAN KOGNITIF $intensiveDays = $recentActivities->take(3) ->filter(function($activity) { return $activity->recommendation && $activity->recommendation->category == 'Intensif'; })->count(); if ($intensiveDays >= 2) { $adjustment -= 5; $factors[] = 'perlu recovery'; } // 6. FAKTOR ADAPTASI (Individual Differences) $avgActual = $recentActivities->avg('duration_minutes'); $avgRecommended = $recentActivities->filter(function($a) { return $a->recommendation; })->avg(function($a) { return $a->recommendation->recommended_minutes ?? 0; }); if ($avgActual > $avgRecommended * 1.2) { $adjustment += 5; $factors[] = 'kapasitas tinggi'; } elseif ($avgActual < $avgRecommended * 0.7) { $adjustment -= 5; $factors[] = 'target terlalu tinggi'; } // 7. FAKTOR STREAK (Hari beruntun) $streak = $this->calculateStreak($recentActivities); if ($streak >= 5) { $adjustment += 5; $factors[] = 'streak 5+ hari'; } elseif ($streak >= 3) { $adjustment += 3; $factors[] = 'streak 3+ hari'; } // 8. FAKTOR KESESUAIAN KATEGORI $sameCategory = $recentActivities->take(5) ->filter(function($a) use ($category) { return $a->recommendation && $a->recommendation->category == $category; })->count(); if ($sameCategory >= 4) { $adjustment += 3; $factors[] = 'konsisten di kategori'; } // =========================================== // VARIASI KHUSUS PER KATEGORI // =========================================== if ($category == 'Intensif') { // Variasi untuk Intensif (75-115) // Berdasarkan durasi aktual if ($avgActual > 100) { $adjustment += rand(5, 10); $factors[] = 'biasa belajar panjang'; } elseif ($avgActual > 80) { $adjustment += rand(0, 5); $factors[] = 'cukup terbiasa'; } else { $adjustment -= rand(0, 5); $factors[] = 'baru masuk intensif'; } // Variasi berdasarkan mood if ($currentMood == 'Bagus') { $adjustment += rand(2, 5); } elseif ($currentMood == 'Lumayan') { $adjustment += rand(0, 3); } // Random factor untuk variasi alami (±5) $adjustment += rand(-5, 5); } elseif ($category == 'Sedang') { // Variasi untuk Sedang (35-65) // Berdasarkan trend if ($trend == 'meningkat') { $adjustment += rand(3, 7); $factors[] = 'menuju intensif'; } elseif ($trend == 'menurun') { $adjustment -= rand(3, 7); $factors[] = 'menuju ringan'; } // Random factor kecil $adjustment += rand(-3, 3); } elseif ($category == 'Ringan') { // Variasi untuk Ringan (20-40) // Jika mood jelek, kurangi if ($currentMood == 'Jenuh' || $currentMood == 'Cukup Jenuh') { $adjustment -= rand(0, 5); $factors[] = 'butuh istirahat'; } // Random factor kecil $adjustment += rand(-2, 2); } // =========================================== // HITUNG DURASI FINAL // =========================================== // Batasi adjustment agar tidak terlalu ekstrim $maxAdjustment = $this->getMaxAdjustment($category); $adjustment = max(min($adjustment, $maxAdjustment), -$maxAdjustment); $finalDuration = $baseDuration + $adjustment; // Bulatkan ke 5 menit terdekat $finalDuration = round($finalDuration / 5) * 5; // Pastikan dalam range yang wajar per kategori $finalDuration = $this->ensureInRange($finalDuration, $category); // Generate notes yang personal $notes = $this->generatePersonalNotes($category, $factors, $adjustment, $finalDuration, $streak); Log::info('Recommendation calculated:', [ 'user_id' => $userId, 'category' => $category, 'base' => $baseDuration, 'adjustment' => $adjustment, 'final' => $finalDuration, 'factors' => $factors ]); return [ 'minutes' => $finalDuration, 'notes' => $notes, 'factors' => $factors, 'adjustment' => $adjustment, 'base_duration' => $baseDuration ]; } /** * Variasi awal untuk user baru */ private function getInitialVariation($category) { $variations = [ 'Ringan' => rand(-2, 5), 'Sedang' => rand(-3, 8), 'Intensif' => rand(-5, 15) ]; return $variations[$category]; } /** * Hitung skor konsistensi (0-1) */ private function calculateConsistency($activities) { if ($activities->count() < 3) return 0.5; $durations = $activities->pluck('duration_minutes')->toArray(); $avg = array_sum($durations) / count($durations); // Hitung standar deviasi $variance = 0; foreach ($durations as $dur) { $variance += pow($dur - $avg, 2); } $stdDev = sqrt($variance / count($durations)); // Konsistensi = kebalikan dari koefisien variasi $cv = $stdDev / $avg; $consistency = max(0, min(1, 1 - $cv)); return $consistency; } /** * Hitung trend (meningkat/menurun/stabil) */ private function calculateTrend($activities) { if ($activities->count() < 4) return 'stabil'; $recent = $activities->take(3)->avg('duration_minutes'); $older = $activities->slice(3, 3)->avg('duration_minutes'); $diff = $recent - $older; if ($diff > 10) return 'meningkat'; if ($diff < -10) return 'menurun'; return 'stabil'; } /** * Hitung streak hari beruntun */ private function calculateStreak($activities) { if ($activities->isEmpty()) return 0; $streak = 1; $prevDate = null; foreach ($activities as $activity) { if ($prevDate) { $diff = $prevDate->diffInDays($activity->activity_date); if ($diff == 1) { $streak++; } else { break; } } $prevDate = $activity->activity_date; } return $streak; } /** * Bobot mood (sesuai skema proposal) */ private function getMoodWeight($mood) { return match($mood) { 'Bagus' => 2.0, 'Lumayan' => 1.5, 'Biasa Saja' => 1.0, 'Cukup Jenuh' => 0.5, 'Jenuh' => 0.0, default => 1.0 }; } /** * Maksimum adjustment per kategori */ private function getMaxAdjustment($category) { return match($category) { 'Ringan' => 10, // 20-40 'Sedang' => 20, // 25-65 (lebih lebar) 'Intensif' => 35, // 45-115 (sangat lebar) default => 15 }; } /** * Pastikan durasi dalam range wajar */ private function ensureInRange($duration, $category) { $ranges = [ 'Ringan' => ['min' => 20, 'max' => 40], 'Sedang' => ['min' => 30, 'max' => 70], // Diperlebar 'Intensif' => ['min' => 60, 'max' => 120] // 60-120 ]; $range = $ranges[$category]; return max($range['min'], min($duration, $range['max'])); } /** * Generate notes sederhana untuk user baru */ private function generateSimpleNotes($category, $duration) { return match($category) { 'Ringan' => "📚 Belajar ringan {$duration} menit. Selamat datang di LearnMood!", 'Sedang' => "🎯 Belajar {$duration} menit dengan fokus. Selamat datang!", 'Intensif' => "🚀 Sesi intensif {$duration} menit. Selamat datang!", default => "Selamat datang di LearnMood!" }; } /** * Generate notes personal berdasarkan faktor-faktor */ private function generatePersonalNotes($category, $factors, $adjustment, $duration, $streak) { $notes = []; // Base note $notes[] = match($category) { 'Ringan' => "📚 Belajar ringan {$duration} menit", 'Sedang' => "🎯 Belajar {$duration} menit dengan fokus", 'Intensif' => "🚀 Sesi intensif {$duration} menit" }; // Notes berdasarkan streak if ($streak >= 7) { $notes[] = "Luar biasa! Streak 7 hari! 🌟"; } elseif ($streak >= 5) { $notes[] = "Hebat! Streak 5 hari! 🔥"; } elseif ($streak >= 3) { $notes[] = "Mantap! Streak 3 hari! ⭐"; } // Notes berdasarkan faktor if (in_array('konsisten', $factors)) { $notes[] = "Konsistensimu luar biasa!"; } if (in_array('trend meningkat', $factors)) { $notes[] = "Durasi belajarmu terus meningkat!"; } elseif (in_array('trend menurun', $factors)) { $notes[] = "Semangat! Coba tingkatkan lagi."; } if (in_array('mood positif', $factors)) { $notes[] = "Mood positif, belajar jadi lebih mudah!"; } elseif (in_array('mood negatif', $factors)) { $notes[] = "Mood sedang kurang, jangan paksakan diri."; } if (in_array('tidur ideal', $factors)) { $notes[] = "Tidur cukup mendukung konsentrasi."; } elseif (in_array('kurang tidur', $factors)) { $notes[] = "Coba tidur lebih awal nanti malam."; } if (in_array('perlu recovery', $factors)) { $notes[] = "Beberapa hari ini intensif, jaga keseimbangan."; } if (in_array('kapasitas tinggi', $factors)) { $notes[] = "Kamu mampu lebih dari target!"; } elseif (in_array('target terlalu tinggi', $factors)) { $notes[] = "Target mungkin terlalu tinggi, fokus ke kualitas."; } // Khusus Intensif if ($category == 'Intensif') { if ($duration >= 110) { $notes[] = "Wow, super intensif! Pastikan istirahat cukup."; } elseif ($duration >= 90) { $notes[] = "Sesi intensif optimal! Pertahankan."; } elseif ($duration <= 70) { $notes[] = "Intensif ringan, bagus untuk adaptasi."; } } // Khusus Sedang if ($category == 'Sedang' && $duration >= 60) { $notes[] = "Hampir masuk intensif! Tingkatkan sedikit lagi."; } // Info adjustment if ($adjustment > 5) { $notes[] = "(+{$adjustment} menit dari baseline)"; } elseif ($adjustment < -5) { $notes[] = "({$adjustment} menit dari baseline - fokus kualitas)"; } return implode(' ', $notes); } }