MIF_E31221264/app/Http/Controllers/Landingpage/HomeController.php

364 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Landingpage;
use App\Models\Pakaian;
use App\Models\Kriteria;
use App\Models\QuizHistory;
use App\Models\SubKriteria;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Models\DataAlternatif;
use App\Models\PenilaianPakaian;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
class HomeController extends Controller
{
public function index()
{
$subKriteria = SubKriteria::with('kriteria')->get()->groupBy(function ($item) {
return $item->kriteria->nama_kriteria;
});
$pakaians = Pakaian::with('subKriterias')->get();
return view('landingpage.master', [
'subKriteria' => $subKriteria,
'pakaians' => $pakaians
]);
}
public function prosesRekomendasi(Request $request)
{
// Input validation and processing
$userInput = $this->processUserInput($request->input('sub_kriteria', []));
if (empty($userInput)) {
return $this->returnNoResults('Silakan pilih minimal satu kriteria.');
}
// \Log::debug('User Input:', $userInput);
// Get selected subcriteria data
$selectedSubkriteria = SubKriteria::whereIn('id', collect($userInput)->flatten())
->get()
->keyBy('id');
// Extract criteria inputs
$criteriaInputs = $this->extractCriteriaInputs($userInput, $selectedSubkriteria);
// Apply strict filtering
$filteredClothing = $this->applyStrictFiltering($criteriaInputs);
if ($filteredClothing->isEmpty()) {
return $this->returnNoResults('Tidak ada pakaian yang sesuai dengan kriteria yang Anda pilih.');
}
// Calculate recommendations using weighted scoring
$recommendations = $this->calculateRecommendations($filteredClothing, $userInput);
return view('landingpage.hasil', compact('recommendations'));
}
/**
* Process and validate user input
*/
private function processUserInput(array $userInput): array
{
$processedInput = [];
foreach ($userInput as $kriteria_id => $subkriteria) {
$subkriteriaArray = (array) $subkriteria;
// Remove empty values and validate
$cleanedSubkriteria = array_filter($subkriteriaArray, function($value) {
return !empty($value) && is_numeric($value);
});
if (!empty($cleanedSubkriteria)) {
$processedInput[$kriteria_id] = array_map('intval', $cleanedSubkriteria);
}
}
return $processedInput;
}
/**
* Extract and organize criteria inputs
*/
private function extractCriteriaInputs(array $userInput, $selectedSubkriteria): array
{
$inputs = [
'warna' => $userInput[1] ?? [], // Criteria 1: Color
'harga' => $userInput[2] ?? [], // Criteria 2: Price
'jenis_pakaian' => $userInput[3] ?? [], // Criteria 3: Clothing Type
'jenis_acara' => $userInput[4] ?? [], // Criteria 4: Event Type
'lokasi' => $userInput[5] ?? [] // Criteria 5: Location
];
// Calculate price range if price criteria is selected
if (!empty($inputs['harga'])) {
$hargaRanges = $selectedSubkriteria->only($inputs['harga']);
$inputs['harga_min'] = $hargaRanges->min('min_value');
$inputs['harga_max'] = $hargaRanges->max('max_value');
}
// \Log::debug("Extracted Criteria Inputs:", $inputs);
return $inputs;
}
/**
* Apply strict filtering based on mandatory criteria
*/
private function applyStrictFiltering(array $criteriaInputs)
{
$query = Pakaian::with(['penilaian.subkriteria.kriteria']);
// Mandatory filter: Clothing Type (highest weight criteria)
if (!empty($criteriaInputs['jenis_pakaian'])) {
// \Log::debug('Applying clothing type filter:', $criteriaInputs['jenis_pakaian']);
$query->whereHas('penilaian.subkriteria', function ($q) use ($criteriaInputs) {
$q->where('kriteria_id', 3)
->whereIn('id', $criteriaInputs['jenis_pakaian']);
});
}
// Mandatory filter: Price Range (if specified)
if (isset($criteriaInputs['harga_min']) && isset($criteriaInputs['harga_max'])) {
// \Log::debug("Applying price filter: {$criteriaInputs['harga_min']} - {$criteriaInputs['harga_max']}");
$query->whereHas('penilaian.subkriteria', function ($q) use ($criteriaInputs) {
$q->where('kriteria_id', 2)
->where('min_value', '<=', $criteriaInputs['harga_max'])
->where('max_value', '>=', $criteriaInputs['harga_min']);
});
}
$filteredClothing = $query->get();
// Validate filtering results
$this->validateFilterResults($filteredClothing, $criteriaInputs);
// \Log::debug('Filtered clothing count: ' . $filteredClothing->count());
return $filteredClothing;
}
/**
* Validate that filtering worked correctly
*/
private function validateFilterResults($clothing, array $criteriaInputs): void
{
if (!empty($criteriaInputs['jenis_pakaian'])) {
foreach ($clothing as $item) {
$clothingTypeAssessment = $item->penilaian
->where('subkriteria.kriteria_id', 3)
->first();
if (!$clothingTypeAssessment ||
!in_array($clothingTypeAssessment->sub_kriteria_id, $criteriaInputs['jenis_pakaian'])) {
// \Log::error("❌ Filter validation failed for clothing: {$item->nama}");
}
}
}
}
/**
* Calculate recommendations using SAW method
*/
private function calculateRecommendations($clothing, array $userInput): \Illuminate\Support\Collection
{
// Get criteria with weights
$criteria = Kriteria::with('subkriteria')
->whereIn('id', [1, 2, 3, 4, 5])
->orderByDesc('bobot') // Order by weight (highest first)
->get();
// Build decision matrix
$decisionMatrix = $this->buildDecisionMatrix($clothing, $criteria, $userInput);
// Calculate min/max values for normalization
$normalizationValues = $this->calculateNormalizationValues($decisionMatrix, $criteria);
// Calculate preference scores
$results = $this->calculatePreferenceScores(
$clothing,
$decisionMatrix,
$criteria,
$normalizationValues,
$userInput
);
// Sort by score (descending)
$recommendations = collect($results)
->sortByDesc('score')
->values();
$this->logFinalRecommendations($recommendations);
return $recommendations;
}
/**
* Build decision matrix for each clothing item
*/
private function buildDecisionMatrix($clothing, $criteria, array $userInput): array
{
$matrix = [];
foreach ($clothing as $item) {
$matrix[$item->id] = [];
foreach ($criteria as $criterion) {
$userSubIds = $userInput[$criterion->id] ?? [];
if (empty($userSubIds)) {
continue; // Skip criteria not selected by user
}
// Get matching assessments for this criterion
$matchingAssessments = $item->penilaian->filter(function ($assessment) use ($userSubIds) {
return in_array($assessment->sub_kriteria_id, $userSubIds);
});
if ($matchingAssessments->isNotEmpty()) {
// Use average if multiple matches, first match for clothing type
$value = ($criterion->id == 3)
? $matchingAssessments->first()->nilai
: $matchingAssessments->avg('nilai');
$matrix[$item->id][$criterion->id] = $value;
// \Log::debug("Item {$item->id} - Criterion {$criterion->id}: {$value}");
}
}
}
return $matrix;
}
/**
* Calculate min/max values for normalization
*/
private function calculateNormalizationValues(array $decisionMatrix, $criteria): array
{
$values = ['max' => [], 'min' => []];
foreach ($criteria as $criterion) {
$criterionValues = collect($decisionMatrix)
->pluck($criterion->id)
->filter(function($value) {
return $value !== null && $value > 0;
});
if ($criterionValues->isNotEmpty()) {
$values['max'][$criterion->id] = $criterionValues->max();
$values['min'][$criterion->id] = $criterionValues->min();
} else {
// Fallback values
$values['max'][$criterion->id] = 1;
$values['min'][$criterion->id] = 1;
}
// \Log::debug("Criterion {$criterion->id} normalization - Max: {$values['max'][$criterion->id]}, Min: {$values['min'][$criterion->id]}");
}
return $values;
}
/**
* Calculate final preference scores using SAW method
*/
private function calculatePreferenceScores($clothing, array $decisionMatrix, $criteria, array $normValues, array $userInput): array
{
$results = [];
foreach ($clothing as $item) {
$preferenceScore = 0;
$totalWeight = 0;
foreach ($criteria as $criterion) {
$userSubIds = $userInput[$criterion->id] ?? [];
if (empty($userSubIds) || !isset($decisionMatrix[$item->id][$criterion->id])) {
continue;
}
$value = $decisionMatrix[$item->id][$criterion->id];
if ($value <= 0) continue;
// Normalize value based on criterion type
$normalizedValue = $this->normalizeValue(
$value,
$normValues['max'][$criterion->id],
$normValues['min'][$criterion->id],
$criterion->jenis
);
// Calculate weighted contribution
$weight = $criterion->bobot;
$contribution = $normalizedValue * $weight;
$preferenceScore += $contribution;
$totalWeight += $weight;
// \Log::debug("Item {$item->id} - Criterion {$criterion->id}: Value={$value}, Normalized={$normalizedValue}, Weight={$weight}, Contribution={$contribution}");
}
$clothingType = $item->penilaian
->firstWhere('subkriteria.kriteria_id', 3)
?->subkriteria->nama ?? 'Lainnya';
$results[] = [
'pakaian' => $item,
'score' => round($preferenceScore, 4),
'jenis_pakaian' => $clothingType,
'total_weight' => $totalWeight
];
// \Log::debug("📊 Final Score - Item {$item->id} ({$item->nama}): {$preferenceScore}");
}
return $results;
}
/**
* Normalize value based on criterion type (BENEFIT/COST)
*/
private function normalizeValue(float $value, float $max, float $min, string $type): float
{
if ($type === 'COST') {
return $value > 0 ? $min / $value : 0;
} else {
return $max > 0 ? $value / $max : 0;
}
}
/**
* Log final recommendations
*/
private function logFinalRecommendations($recommendations): void
{
// \Log::debug('🏆 FINAL RECOMMENDATIONS:');
foreach ($recommendations as $index => $item) {
$rank = $index + 1;
// \Log::debug("{$rank}. {$item['pakaian']->nama} - Score: {$item['score']} - Type: {$item['jenis_pakaian']}");
}
}
/**
* Return view with no results message
*/
private function returnNoResults(string $message)
{
return view('landingpage.hasil', [
'recommendations' => collect([]),
'message' => $message
]);
}
}