MIF_E31222629/app/Http/Controllers/RekomendasiController.php

260 lines
11 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Models\MobilModels as Mobil;
use App\Models\KriteriaModels as Kriteria;
use App\Models\NilaiAlternatifModels as NilaiAlternatif;
use App\Models\SubKriteriaModels as SubKriteria;
use Illuminate\Http\Request;
class RekomendasiController extends Controller
{
/**
* Menampilkan form filter untuk memilih kriteria dan bobot
* Fungsi ini untuk halaman awal sistem rekomendasi
*/
public function filterForm()
{
// Mengambil semua sub kriteria beserta relasi kriteria utama
// Diurutkan berdasarkan kriteria_id dan id sub kriteria
$subKriteria = SubKriteria::with('kriteria')
->orderBy('kriteria_id') // Urutkan berdasarkan ID kriteria utama
->orderBy('id') // Lalu urutkan berdasarkan ID sub kriteria
->get()
->groupBy(function ($item) {
// Kelompokkan sub kriteria berdasarkan nama kriteria utama
return $item->kriteria->nama_kriteria;
});
// Mengambil semua kriteria kecuali "Kapasitas Kursi"
// Kapasitas Kursi digunakan sebagai filter, bukan untuk perhitungan bobot
$kriteria = Kriteria::where('nama_kriteria', '!=', 'Kapasitas Kursi')->get();
// Mengirim data ke view untuk ditampilkan di form
return view('rekomendasi.index', compact('subKriteria', 'kriteria'));
}
public function getSubKriteriaFiltered(Request $request)
{
$kapasitasSubId = $request->input('kapasitas_sub_id');
// Ambil mobil yang cocok dengan kapasitas kursi
$mobilFiltered = Mobil::where('sub_kriteria_id', $kapasitasSubId)->pluck('id');
// Ambil nilai alternatif berdasarkan mobil yang cocok
$nilaiAlternatif = NilaiAlternatif::with('subKriteria.kriteria')
->whereIn('mobil_id', $mobilFiltered)
->get();
// Kelompokkan subkriteria yang valid berdasarkan kriteria_id
$subKriteriaByKriteria = [];
foreach ($nilaiAlternatif as $nilai) {
$sub = $nilai->subKriteria;
$kriteriaId = $sub->kriteria->id;
$kriteriaName = $sub->kriteria->nama_kriteria;
// Skip kapasitas kursi
if ($kriteriaName == 'Kapasitas Kursi') continue;
$subKriteriaByKriteria[$kriteriaName][$sub->id] = $sub->nama_subkriteria;
}
return response()->json($subKriteriaByKriteria);
}
/**
* Memproses perhitungan rekomendasi berdasarkan input user
* Menggunakan algoritma SAW (Simple Additive Weighting)
*/
public function index(Request $request)
{
// Mengambil input dari form: sub kriteria yang dipilih dan bobot
$selectedSubKriteriaIds = $request->input('sub_kriteria', []); // Sub kriteria yang dipilih user
$bobotUser = $request->input('bobot', []); // Bobot yang diinput user untuk setiap kriteria
// Debug: Uncomment untuk melihat data input
// dd($selectedSubKriteriaIds, $bobotUser);
// VALIDASI BOBOT: Total bobot harus = 1 (100%)
$total = array_sum($bobotUser); // Jumlahkan semua bobot
if (abs($total - 1) > 0.01) { // Toleransi 0.01 untuk kesalahan floating point
return back()->withErrors(['bobot' => 'Total bobot harus sama dengan 1.']);
}
// FILTER BERDASARKAN KAPASITAS KURSI
// Mencari ID sub kriteria yang termasuk dalam kriteria "Kapasitas Kursi"
$kapasitasKursiSubIds = SubKriteria::whereHas('kriteria', function ($query) {
$query->where('nama_kriteria', 'Kapasitas Kursi');
})->pluck('id')->toArray();
// Mencari sub kriteria kapasitas kursi yang dipilih user
$kapasitasYangDipilih = array_intersect($selectedSubKriteriaIds, $kapasitasKursiSubIds);
// Filter mobil berdasarkan kapasitas kursi yang dipilih
$mobilFiltered = Mobil::whereIn('sub_kriteria_id', $kapasitasYangDipilih)->pluck('id')->toArray();
// Debug: Uncomment untuk melihat mobil yang terfilter
// dd($mobilFiltered);
// Jika tidak ada mobil yang sesuai filter kapasitas
if (empty($mobilFiltered)) {
return view('rekomendasi.hasil', ['result' => [], 'message' => 'Tidak ditemukan mobil sesuai filter']);
}
// MENGAMBIL DATA NILAI ALTERNATIF
// Ambil semua nilai alternatif untuk mobil yang sudah difilter
$rows = NilaiAlternatif::with('subKriteria')
->whereIn('mobil_id', $mobilFiltered) // Hanya mobil yang sesuai filter kapasitas
->when(!empty($selectedSubKriteriaIds), function ($query) use ($selectedSubKriteriaIds) {
return $query->whereIn('sub_kriteria_id', $selectedSubKriteriaIds); // Filter berdasarkan sub kriteria yang dipilih
})
->get();
// Debug: Uncomment untuk melihat data mentah
// return response()->json($rows);
// PENGOLAHAN DATA NILAI ALTERNATIF
// Kelompokkan nilai berdasarkan mobil_id dan kriteria_id
$nilaiSementara = [];
foreach ($rows as $row) {
$kriteriaId = $row->subKriteria->kriteria_id; // ID kriteria utama
$nilaiSementara[$row->mobil_id][$kriteriaId][] = $row->nilai; // Kumpulkan nilai per mobil per kriteria
}
// Debug: Uncomment untuk melihat pengelompokan nilai
// dd($nilaiSementara);
// PERHITUNGAN RATA-RATA NILAI PER KRITERIA
// Jika satu kriteria memiliki beberapa sub kriteria, hitung rata-ratanya
$nilaiAlternatif = [];
foreach ($nilaiSementara as $mobilId => $kriteriaArray) {
foreach ($kriteriaArray as $kriteriaId => $nilaiList) {
$sum = array_sum($nilaiList); // Jumlahkan semua nilai sub kriteria
$jumlahSub = count($nilaiList); // Hitung jumlah sub kriteria
$nilaiAlternatif[$mobilId][$kriteriaId] = $sum / $jumlahSub; // Hitung rata-rata
}
}
// Debug: Uncomment untuk melihat nilai rata-rata
// dd($nilaiAlternatif);
// PERHITUNGAN SAW (Simple Additive Weighting)
$hasil = $this->hitungSAW($nilaiAlternatif, $bobotUser);
// Urutkan hasil dari yang terbesar (ranking tertinggi)
arsort($hasil);
// Ambil 3 mobil dengan skor tertinggi
$top3 = array_slice($hasil, 0, 3, true);
// PREPARE HASIL UNTUK DITAMPILKAN
$result = [];
foreach ($top3 as $mobilId => $score) {
$mobil = Mobil::find($mobilId); // Ambil data lengkap mobil
$result[] = [
'mobil' => $mobil, // Data mobil
'score' => $score, // Skor SAW
];
}
// Kirim hasil ke view
return view('rekomendasi.hasil', compact('result'));
}
/**
* IMPLEMENTASI ALGORITMA SAW (Simple Additive Weighting)
*
* Formula SAW: Si = Σ(wj × rij)
* Dimana:
* - Si = Skor alternatif ke-i
* - wj = Bobot kriteria ke-j
* - rij = Rating kinerja ternormalisasi alternatif ke-i pada kriteria ke-j
*
* Normalisasi:
* - Benefit (semakin besar semakin baik): rij = xij / max(xij)
* - Cost (semakin kecil semakin baik): rij = min(xij) / xij
*/
protected function hitungSAW(array $nilaiAlternatif, array $bobotUser): array
{
// PERSIAPAN DATA KRITERIA
$kriteriaList = Kriteria::all(); // Ambil semua kriteria
$bobot = []; // Array untuk menyimpan bobot kriteria
$tipe = []; // Array untuk menyimpan tipe kriteria (benefit/cost)
$skipKriteriaIds = []; // Array untuk kriteria yang tidak dihitung (Kapasitas Kursi)
// Loop untuk setiap kriteria
foreach ($kriteriaList as $k) {
// Skip kriteria "Kapasitas Kursi" karena hanya untuk filter
if ($k->nama_kriteria == 'Kapasitas Kursi') {
$skipKriteriaIds[] = $k->id;
continue;
}
// Ambil bobot dari input user, default 0 jika tidak ada
$bobot[$k->id] = isset($bobotUser[$k->id]) ? floatval($bobotUser[$k->id]) : 0;
$tipe[$k->id] = $k->tipe; // benefit atau cost
}
// MENCARI NILAI MAKSIMUM DAN MINIMUM PER KRITERIA
// Digunakan untuk normalisasi nilai
$maxPerKriteria = [];
$minPerKriteria = [];
// Loop untuk setiap mobil dan kriteria
foreach ($nilaiAlternatif as $mobilId => $kriteriaNilai) {
foreach ($kriteriaNilai as $kriteriaId => $nilai) {
// Skip kriteria "Kapasitas Kursi"
if (in_array($kriteriaId, $skipKriteriaIds)) continue;
// Cari nilai maksimum per kriteria
$maxPerKriteria[$kriteriaId] = max($maxPerKriteria[$kriteriaId] ?? $nilai, $nilai);
// Cari nilai minimum per kriteria
$minPerKriteria[$kriteriaId] = min($minPerKriteria[$kriteriaId] ?? $nilai, $nilai);
}
}
// Debug: Uncomment untuk melihat nilai max dan min
// dd($maxPerKriteria, $minPerKriteria);
// PERHITUNGAN SKOR SAW
$hasil = [];
// Loop untuk setiap mobil (alternatif)
foreach ($nilaiAlternatif as $mobilId => $kriteriaNilai) {
$total = 0; // Total skor untuk mobil ini
// Loop untuk setiap kriteria
foreach ($kriteriaNilai as $kriteriaId => $nilai) {
// Skip kriteria "Kapasitas Kursi"
if (in_array($kriteriaId, $skipKriteriaIds)) continue;
// Ambil nilai max dan min untuk kriteria ini
$max = $maxPerKriteria[$kriteriaId] ?? 1;
$min = $minPerKriteria[$kriteriaId] ?? 0;
// NORMALISASI NILAI berdasarkan tipe kriteria
if ($tipe[$kriteriaId] === 'benefit') {
// Untuk kriteria benefit: semakin besar semakin baik
// Formula: rij = xij / max(xij)
$normalized = $nilai / $max;
} else {
// Untuk kriteria cost: semakin kecil semakin baik
// Formula: rij = min(xij) / xij
$normalized = $min / $nilai;
}
// PERHITUNGAN SKOR: normalized value × bobot kriteria
// Formula SAW: Si = Σ(wj × rij)
$total += $normalized * ($bobot[$kriteriaId] ?? 0);
}
// Simpan total skor untuk mobil ini
$hasil[$mobilId] = $total;
}
// Return array skor SAW untuk semua mobil
return $hasil;
}
}