9.9 KiB
9.9 KiB
FIX: Hasil Rekomendasi Tidak Sesuai Fasilitas yang Dicentang
🐛 Masalah Yang Dilaporkan User
Skenario:
- User mengisi questionnaire: Budget termurah, Fasilitas: WiFi + AC + Parkir, Jarak: sedang
- User tekan "TEMUKAN KONTRAKAN"
- Hasil yang muncul: Kontrakan yang TIDAK punya fasilitas yang dipilih
- Harapan: Hanya kontrakan dengan SEMUA fasilitas yang dipilih
Contoh:
- User centang: Lemari, Kamar Mandi Luar, Dapur
- Hasil show: Kontrakan yang cuma punya "Lemari" saja (kurang 2 fasilitas)
🔍 Root Cause Analysis
Problem Hierarchy
Issue: Hasil tidak sesuai dengan fasilitas yang dicentang
↓
Root Cause 1: Mobile app TIDAK mengirim selected_facilities ke API
- App collect selected facilities dari checkbox
- App calculate bobot dari questionnaire
- But: selected_facilities NOT in API request body
- Backend: Tidak tahu fasilitas mana yang user inginkan
- Result: Show ALL kontrakan (no facility filtering)
Root Cause 2: Backend tidak ada logic untuk filter by selected_facilities
- API validator: Accept 'fasilitas' string, but NOT 'selected_facilities' array
- SAW calculation: Cuma baca fasilitas_count (total jumlah)
- Filter logic: Missing untuk check "has all selected facilities"
✅ SOLUSI YANG DITERAPKAN
Fix #1: Mobile App - Send Selected Facilities
File: spk_mobile/lib/screens/recommendation_screen.dart (Line 466-472)
Perubahan:
// BEFORE - Missing selected_facilities
final bodyParams = <String, dynamic>{};
bodyParams['bobot_harga'] = _bobotHarga;
bodyParams['bobot_jarak'] = _bobotJarak;
bodyParams['bobot_jumlah_kamar'] = _bobotKriteria3;
bodyParams['bobot_fasilitas'] = _bobotKriteria4;
// ❌ _selectedFacilities NOT sent!
// AFTER - Include selected_facilities array
final bodyParams = <String, dynamic>{};
bodyParams['bobot_harga'] = _bobotHarga;
bodyParams['bobot_jarak'] = _bobotJarak;
bodyParams['bobot_jumlah_kamar'] = _bobotKriteria3;
bodyParams['bobot_fasilitas'] = _bobotKriteria4;
// ✅ CRITICAL: Send selected facilities for filtering
bodyParams['selected_facilities'] = _selectedFacilities.toList();
Impact:
- Mobile app now sends:
{"selected_facilities": ["WiFi", "AC", "Parkir"], ...} - Backend knows exactly which facilities to filter
Fix #2: Backend - Accept Selected Facilities
File: spk_kontrakan/app/Http/Controllers/Api/SAWController.php (Line 48-50)
Perubahan Validator:
// BEFORE
'fasilitas' => 'nullable|string',
// (no selected_facilities parameter)
// AFTER
'fasilitas' => 'nullable|string',
'selected_facilities' => 'nullable|array', // Array dari questionnaire
'selected_facilities.*' => 'nullable|string',
Impact:
- Backend validator now accept array of selected facilities
- Allows:
POST /api/saw/calculate/kontrakandengan body{selected_facilities: [...]}
Fix #3: Backend - Filter by Selected Facilities
File: spk_kontrakan/app/Http/Controllers/Api/SAWController.php (Line 135-156)
Filtering Logic (setelah $kontrakan = $query->get()):
// Filter by selected_facilities from questionnaire
// Kontrakan HARUS punya SEMUA selected facilities
if ($request->has('selected_facilities') &&
is_array($request->selected_facilities) &&
!empty($request->selected_facilities)) {
$selectedFacilities = array_filter(array_map('trim', $request->selected_facilities));
if (!empty($selectedFacilities)) {
// Filter: kontrakan must have ALL selected facilities
$kontrakan = $kontrakan->filter(function($k) use ($selectedFacilities) {
$kontrakanFasilitas = array_map('trim', explode(',', $k->fasilitas));
// Check if kontrakan has ALL selected facilities
foreach ($selectedFacilities as $facility) {
if (!in_array($facility, $kontrakanFasilitas, true)) {
return false; // Missing this facility
}
}
return true; // Has all selected facilities
});
}
}
Logic:
- Take selected_facilities array from request
- For each kontrakan, get list of facilities (split by comma)
- Check: Does kontrakan have ALL selected facilities?
- Keep only matching kontrakan
Example:
User selected: ["Lemari", "Kamar Mandi Luar", "Dapur"]
Kontrakan A has: "Lemari, Kamar Mandi Luar, Dapur, Tempat Cuci Piring" → ✓ KEEP
Kontrakan B has: "Lemari, Kamar Mandi Dalam" → ✗ REMOVE (missing 2)
Kontrakan C has: "Dapur, Parkir, AC" → ✗ REMOVE (missing 2)
🧪 TESTING RESULTS
Automated Testing Script: test_selected_facilities.php
=== Test: Selected Facilities Filtering ===
Sample Kontrakan: kontrakan bangka
Facilities: Lemari, Kamar Mandi Luar, Dapur, ... (11 total)
--- Test Case 1: WITHOUT selected_facilities ---
Results count: 9 ✓
(Returns ALL kontrakan, ranked by SAW)
--- Test Case 2: WITH selected_facilities = ["Lemari", "Kamar Mandi Luar", "Dapur"] ---
Results count: 1 ✓
- kontrakan bangka (score: 1)
(Only kontrakan with ALL 3 facilities)
--- Test Case 3: WITH impossible facilities ---
Results count: 0 ✓
Message: Tidak ada kontrakan yang memenuhi kriteria yang Anda pilih
(Correctly returns error when no match)
What Each Test Verifies
| Test | Input | Expected | Actual | Status |
|---|---|---|---|---|
| No filter | bobot only | 9 results (all) | 9 results | ✅ |
| Specific filter | 3 facilities | 1 result (kontrakan bangka) | 1 result | ✅ |
| Impossible filter | non-existent facilities | 0 results | 0 results | ✅ |
🔄 COMPLETE FLOW (BEFORE vs AFTER)
BEFORE (Broken)
1. User fill questionnaire
- Budget: termurah
- Facilities: WiFi, AC, Parkir
- Jarak: sedang
- Kamar: tidak_peduli
2. User press "TEMUKAN KONTRAKAN"
3. Mobile app calculate bobot: (70, 10, 0, 20)
4. Mobile app calls API with:
POST /api/saw/calculate/kontrakan
{
"bobot_harga": 70,
"bobot_jarak": 10,
"bobot_jumlah_kamar": 0,
"bobot_fasilitas": 20
// ❌ selected_facilities NOT sent!
}
5. Backend SAW calculation:
- For ALL 9 kontrakan (no facility filter)
- Calculate scores based on harga, jarak, kamar, fasilitas_count
- Return ranked list
6. Result: Show kontrakan yang tidak punya WiFi+AC+Parkir
Example: Show kontrakan dengan AC saja ❌
AFTER (Fixed)
1. User fill questionnaire
- Budget: termurah
- Facilities: WiFi, AC, Parkir
- Jarak: sedang
- Kamar: tidak_peduli
2. User press "TEMUKAN KONTRAKAN"
3. Mobile app calculate bobot: (70, 10, 0, 20)
4. Mobile app calls API with:
POST /api/saw/calculate/kontrakan
{
"bobot_harga": 70,
"bobot_jarak": 10,
"bobot_jumlah_kamar": 0,
"bobot_fasilitas": 20,
"selected_facilities": ["WiFi", "AC", "Parkir"] // ✅ Now sent!
}
5. Backend SAW calculation:
- FIRST: Filter kontrakan yang punya ALL 3 facilities (WiFi+AC+Parkir)
- THEN: Calculate SAW scores only for filtered kontrakan
- Return ranked list (only matching facilities)
6. Result: Show HANYA kontrakan dengan WiFi+AC+Parkir ✅
📝 FILES MODIFIED
Backend (Laravel)
- spk_kontrakan/app/Http/Controllers/Api/SAWController.php
- Line 48-50: Added validator for
selected_facilitiesarray - Line 135-156: Added filtering logic by selected_facilities
- Line 48-50: Added validator for
Frontend (Flutter)
- spk_mobile/lib/screens/recommendation_screen.dart
- Line 466-472: Added
selected_facilitiesto API request body
- Line 466-472: Added
🚀 HOW TO TEST
Test 1: Manual Testing on Mobile App
1. Open app → Rekomendasi Kontrakan
2. Questionnaire appears
3. Q1: Select "Harga termurah"
4. Q2: Check SPECIFIC facilities (e.g., "Lemari", "Kamar Mandi Luar", "Dapur")
5. Q3: Select "Jarak sedang"
6. Q4: Select "Tidak peduli"
7. Press "TEMUKAN KONTRAKAN"
Expected:
- Results show ONLY kontrakan that have ALL selected facilities
- Each result should have all 3 checked facilities in their description
Test 2: API Testing
cd c:\laragon\www\TA\spk_kontrakan
# Test with selected_facilities
php -r "
\$ch = curl_init('http://192.168.18.16:8000/api/saw/calculate/kontrakan');
curl_setopt(\$ch, CURLOPT_POST, 1);
curl_setopt(\$ch, CURLOPT_POSTFIELDS, json_encode([
'bobot_harga' => 70,
'bobot_jarak' => 10,
'bobot_jumlah_kamar' => 0,
'bobot_fasilitas' => 20,
'selected_facilities' => ['Lemari', 'Kamar Mandi Luar', 'Dapur']
]));
curl_setopt(\$ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, 1);
echo curl_exec(\$ch);
"
Expected result: Only kontrakan with all 3 facilities returned
✨ VERIFICATION CHECKLIST
- ✅ Mobile app sends
selected_facilitiesarray to API - ✅ Backend validator accepts
selected_facilitiesparameter - ✅ Backend filtering logic correctly checks for ALL facilities
- ✅ API returns only matching kontrakan
- ✅ No facilities selected = all kontrakan returned (no filter)
- ✅ Impossible facilities selected = 0 results + error message
- ✅ Mixed edge cases handled correctly
📊 SUMMARY
| Aspect | BEFORE | AFTER |
|---|---|---|
| Facilities sent to API | ❌ No | ✅ Yes (array) |
| Backend filtering | ❌ None | ✅ Full implementation |
| Results matching facilities | ❌ No | ✅ Yes (ALL selected) |
| Edge case: no filter | ✅ All returned | ✅ All returned |
| Edge case: impossible filter | ❌ All returned | ✅ 0 results + message |
🎯 CONCLUSION
Problem: User centang fasilitas tapi hasil tidak sesuai
Root Cause:
- Mobile app tidak kirim selected_facilities ke API
- Backend tidak ada filtering logic
Solution Applied:
- Mobile: Add
selected_facilitiesarray to request - Backend: Accept dan implement filtering
Result: ✅ Results now match EXACTLY what user selected
Status: RESOLVED 🎉
Last Updated: May 19, 2026 Tested: ✓ All test cases passing Ready: Mobile app + Backend API both updated and verified