260 lines
11 KiB
PHP
260 lines
11 KiB
PHP
<?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;
|
||
}
|
||
} |