health(); return response()->json($result, $result['success'] ? 200 : 500); } public function hitungPrediksi(Request $request, PythonRecommendationService $pythonService) { $user = Auth::user(); if (! $user) { return response()->json([ 'success' => false, 'message' => 'User belum login.' ], 401); } $validated = $request->validate([ 'agama' => 'required|numeric|min:0|max:100', 'pkn' => 'required|numeric|min:0|max:100', 'bahasa_indonesia' => 'required|numeric|min:0|max:100', 'bahasa_inggris' => 'required|numeric|min:0|max:100', 'matematika' => 'required|numeric|min:0|max:100', 'fisika' => 'required|numeric|min:0|max:100', 'kimia' => 'required|numeric|min:0|max:100', 'biologi' => 'required|numeric|min:0|max:100', 'sejarah' => 'required|numeric|min:0|max:100', 'geografi' => 'required|numeric|min:0|max:100', 'ekonomi_mapel' => 'required|numeric|min:0|max:100', 'sosiologi' => 'required|numeric|min:0|max:100', 'seni_budaya' => 'required|numeric|min:0|max:100', 'pjok' => 'required|numeric|min:0|max:100', 'informatika' => 'required|numeric|min:0|max:100', 'riasec_r' => 'required|numeric|min:0|max:100', 'riasec_i' => 'required|numeric|min:0|max:100', 'riasec_a' => 'required|numeric|min:0|max:100', 'riasec_s' => 'required|numeric|min:0|max:100', 'riasec_e' => 'required|numeric|min:0|max:100', 'riasec_c' => 'required|numeric|min:0|max:100', 'gaji_ortu' => 'required|numeric|min:0', ]); $payloadPython = $validated; $payloadPython['ekonomi_orang_tua'] = self::getLabelEkonomiSingkat($validated['gaji_ortu']); unset($payloadPython['gaji_ortu']); try { $pythonResult = $pythonService->predict($payloadPython); if (! $pythonResult['success']) { return response()->json([ 'success' => false, 'message' => 'ML Engine Error: ' . ($pythonResult['message'] ?? 'Tidak dapat menghubungi Flask API.'), 'detail' => $pythonResult, ], 500); } $responseData = $pythonResult['data']; if (! is_array($responseData)) { return response()->json([ 'success' => false, 'message' => 'Respons Flask API tidak valid.' ], 500); } if (($responseData['success'] ?? true) === false) { return response()->json([ 'success' => false, 'message' => 'ML Engine Error: ' . ($responseData['message'] ?? 'Unknown Error') ], 500); } $teksJurusan = $responseData['prediksi_jurusan'] ?? $responseData['rekomendasi'] ?? $responseData['jurusan'] ?? null; if (! $teksJurusan) { return response()->json([ 'success' => false, 'message' => 'Prediksi jurusan tidak ditemukan pada respons Flask.' ], 500); } $akurasiPrediksi = self::ambilAkurasiDariResponse($responseData, $teksJurusan); $dataRiwayat = [ 'user_id' => $user->id, 'agama' => $validated['agama'], 'pkn' => $validated['pkn'], 'bahasa_indonesia' => $validated['bahasa_indonesia'], 'bahasa_inggris' => $validated['bahasa_inggris'], 'matematika' => $validated['matematika'], 'fisika' => $validated['fisika'], 'kimia' => $validated['kimia'], 'biologi' => $validated['biologi'], 'sejarah' => $validated['sejarah'], 'geografi' => $validated['geografi'], 'ekonomi_mapel' => $validated['ekonomi_mapel'], 'sosiologi' => $validated['sosiologi'], 'seni_budaya' => $validated['seni_budaya'], 'pjok' => $validated['pjok'], 'informatika' => $validated['informatika'], 'riasec_r' => $validated['riasec_r'], 'riasec_i' => $validated['riasec_i'], 'riasec_a' => $validated['riasec_a'], 'riasec_s' => $validated['riasec_s'], 'riasec_e' => $validated['riasec_e'], 'riasec_c' => $validated['riasec_c'], 'gaji_ortu' => $validated['gaji_ortu'], 'hasil_rekomendasi_jurusan' => $teksJurusan, ]; $tableName = (new RiwayatRekomendasi())->getTable(); if (Schema::hasColumn($tableName, 'akurasi_prediksi')) { $dataRiwayat['akurasi_prediksi'] = $akurasiPrediksi; } if (Schema::hasColumn($tableName, 'probabilitas')) { $dataRiwayat['probabilitas'] = $akurasiPrediksi; } $riwayat = RiwayatRekomendasi::create($dataRiwayat); return response()->json([ 'success' => true, 'message' => 'Prediksi berhasil dibuat.', 'redirect_url' => route('student.result', ['id' => $riwayat->id]), 'prediksi_jurusan' => $teksJurusan, 'akurasi_prediksi' => $akurasiPrediksi, 'top_predictions' => $responseData['top_predictions'] ?? null, 'riwayat_id' => $riwayat->id, ]); } catch (\Throwable $e) { return response()->json([ 'success' => false, 'message' => 'Koneksi ke API Flask Python gagal: ' . $e->getMessage() ], 500); } } public function getHasilRekomendasi($id) { $user = Auth::user(); if (! $user) { return response()->json([ 'success' => false, 'message' => 'User belum login.' ], 401); } $riwayat = RiwayatRekomendasi::where('user_id', $user->id)->findOrFail($id); return response()->json([ 'success' => true, 'data' => self::formatHasilData($riwayat) ]); } public static function formatHasilData(RiwayatRekomendasi $riwayat): array { return [ 'id' => $riwayat->id, 'hasil_rekomendasi_jurusan' => $riwayat->hasil_rekomendasi_jurusan, 'akurasi_prediksi' => $riwayat->akurasi_prediksi ?? $riwayat->probabilitas ?? null, 'label_ekonomi' => self::getLabelEkonomiDetail($riwayat->gaji_ortu), 'minat_utama' => self::getMinatUtamaLabel($riwayat), 'prospek_kerja' => self::getProspekKerjaText($riwayat->hasil_rekomendasi_jurusan), 'gaji_ortu' => $riwayat->gaji_ortu, 'riasec' => [ 'r' => $riwayat->riasec_r, 'i' => $riwayat->riasec_i, 'a' => $riwayat->riasec_a, 's' => $riwayat->riasec_s, 'e' => $riwayat->riasec_e, 'c' => $riwayat->riasec_c, ], ]; } private static function ambilAkurasiDariResponse(array $responseData, ?string $jurusan = null): ?float { $nilaiLangsung = $responseData['probabilitas'] ?? $responseData['confidence'] ?? $responseData['akurasi_prediksi'] ?? null; if ($nilaiLangsung !== null) { return self::normalisasiAkurasi($nilaiLangsung); } $topPredictions = $responseData['top_predictions'] ?? null; if (! is_array($topPredictions) || count($topPredictions) === 0) { return null; } if ($jurusan) { foreach ($topPredictions as $item) { if (($item['jurusan'] ?? null) === $jurusan) { return self::normalisasiAkurasi($item['probabilitas'] ?? null); } } } return self::normalisasiAkurasi($topPredictions[0]['probabilitas'] ?? null); } private static function normalisasiAkurasi($value): ?float { if ($value === null || $value === '') { return null; } if (is_string($value)) { $value = str_replace('%', '', $value); $value = str_replace(',', '.', $value); $value = trim($value); } if (! is_numeric($value)) { return null; } $number = (float) $value; if ($number >= 0 && $number <= 1) { $number = $number * 100; } return round($number, 2); } public static function getLabelEkonomiSingkat($gaji): string { $gaji = (float) $gaji; if ($gaji < 2000000) { return 'Kurang Mampu'; } if ($gaji <= 5000000) { return 'Menengah'; } return 'Mampu'; } public static function getLabelEkonomiDetail($gaji): string { $gaji = (float) $gaji; if ($gaji < 2000000) { return 'Ekonomi Kurang Mampu'; } if ($gaji <= 5000000) { return 'Ekonomi Menengah'; } return 'Ekonomi Mampu'; } public static function getMinatUtamaLabel(RiwayatRekomendasi $riwayat): string { $scores = [ 'Realistic' => (float) $riwayat->riasec_r, 'Investigative' => (float) $riwayat->riasec_i, 'Artistic' => (float) $riwayat->riasec_a, 'Social' => (float) $riwayat->riasec_s, 'Enterprising' => (float) $riwayat->riasec_e, 'Conventional' => (float) $riwayat->riasec_c, ]; arsort($scores); return array_key_first($scores) ?? 'Belum diketahui'; } public static function getProspekKerjaText(?string $jurusan): string { if (! $jurusan) { return 'Prospek kerja belum tersedia.'; } // 1. Cari data Jurusan di database berdasarkan nama jurusan hasil prediksi $dataJurusan = \App\Models\Jurusan::where('nama_jurusan', $jurusan)->first(); // 2. Jika jurusan tidak ditemukan atau tidak memiliki data prospek kerja if (! $dataJurusan || $dataJurusan->prospeks->isEmpty()) { return 'Prospek kerja untuk program studi ' . $jurusan . ' belum diinputkan oleh admin.'; } // 3. Ambil semua data prospek kerja terkait dan susun menjadi format HTML list yang rapi $htmlOutput = ''; return $htmlOutput; } }