341 lines
12 KiB
PHP
341 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Student;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\RiwayatRekomendasi;
|
|
use App\Models\Jurusan;
|
|
use App\Services\PythonRecommendationService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class RecommendationApiController extends Controller
|
|
{
|
|
public function health(PythonRecommendationService $pythonService)
|
|
{
|
|
$result = $pythonService->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 = '<ul class="space-y-3">';
|
|
|
|
foreach ($dataJurusan->prospeks as $prospek) {
|
|
$htmlOutput .= '<li class="border-b border-slate-100 pb-2 last:border-0 last:pb-0">';
|
|
$htmlOutput .= ' <strong class="text-slate-800 block text-base">' . e($prospek->nama_profesi) . '</strong>';
|
|
|
|
// Tampilkan range gaji jika admin mengisinya
|
|
if (!empty($prospek->range_gaji)) {
|
|
$htmlOutput .= ' <span class="inline-block bg-amber-50 text-amber-700 text-xs px-2 py-0.5 rounded-md font-medium mt-1 mb-1">💰 Estimasi: ' . e($prospek->range_gaji) . '</span>';
|
|
}
|
|
|
|
$htmlOutput .= ' <p class="text-slate-500 text-sm mt-0.5 leading-relaxed">' . e($prospek->deskripsi_pekerjaan) . '</p>';
|
|
$htmlOutput .= '</li>';
|
|
}
|
|
|
|
$htmlOutput .= '</ul>';
|
|
|
|
return $htmlOutput;
|
|
}
|
|
} |