6.2 KiB
Analisis Akurasi & Konsistensi Scoring Rekomendasi
Status: ✅ DETERMINISTIC (Konsisten)
Algoritma Naive Bayes yang diimplementasikan bersifat deterministik - input yang sama akan selalu menghasilkan output yang sama.
Alur Scoring:
1️⃣ Input Parsing (Deterministic ✅)
$minatRaw = strtolower($minatInput); // Selalu sama
$prestasiRaw = strtolower($prestasiInput); // Selalu sama
$citaMapped = strtolower($citaRaw); // Selalu sama
- Semua input di-normalize ke lowercase
- String di-trim
- Hasil: Deterministic
2️⃣ Nilai Kategorisasi (Deterministic ✅)
// Kategori Nilai:
$nilaiCategories = [
'Tinggi' => ['min' => 85, 'max' => 100], // Range jelas, non-overlapping
'Sedang' => ['min' => 70, 'max' => 84],
'Rendah' => ['min' => 0, 'max' => 69],
]
- Rata-rata nilai dihitung dari input
- Dikategorisasi berdasarkan range tetap
- Hasil: Deterministic
3️⃣ Minat Mapping (Deterministic ✅ + Order-dependent)
// Gunakan elseif - HANYA FIRST MATCH yang dikembalikan
if (preg_match('/(coding|komputer|...)/', $minatRaw)) return 'Logika & Komputer';
elseif (preg_match('/(tanam|kebun|...)/', $minatRaw)) return 'Alam & Tanaman';
// ...
Catatan: Order-dependent tetapi deterministic
- Input "coding" → Selalu 'Logika & Komputer'
- Input "bisnis web" → Selalu 'Logika & Komputer' (karena 'web' matched first)
- Hasil: Deterministic ✅
4️⃣ Naive Bayes Calculation (Deterministic ✅)
// Log-Sum-Exp formula (numerically stable)
$logLikelihood = sum(weight[i] * log(prob[i]))
$logPosterior = logPrior + logLikelihood
// Softmax conversion
$probability = exp(logPosterior - maxLog) / sumExp
- Menggunakan log probability untuk stabilitas numerik
- Softmax conversion deterministic
- Rounding ke 4 desimal (round($prob, 4))
- Hasil: Deterministic ✅
Potensi Issues & Solutions:
⚠️ Issue 1: Keyword Order Dependency di mapMinat
Skenario: Input "bisnis teknik"
- Sekarang: → 'Manajemen & Bisnis' (karena 'bisnis' matched first)
- Alternatif: Bisa return 'Mesin & Listrik'
Solusi: Gunakan scoring berbasis coverage daripada elseif
private function mapMinat(string $minatRaw): string
{
$categories = [
'Logika & Komputer' => ['coding', 'komputer', ...],
'Alam & Tanaman' => ['tanam', 'kebun', ...],
// ...
];
$scores = [];
foreach ($categories as $category => $keywords) {
$scores[$category] = $this->keywordCoverage($minatRaw, $keywords);
}
return array_key_first($scores) ? key($scores) : 'Umum';
}
⚠️ Issue 2: Overlapping Keywords
Beberapa keyword mungkin muncul di multiple categories:
- "teknik" - bisa berarti Mesin & Listrik, atau Teknologi Informasi
- "bisnis" - bisa berarti Manajemen & Bisnis, atau Agribisnis
Saat ini: Dianggap sebagai ambiguitas yang acceptable (first match wins) Solusi: Gunakan keyword yang lebih specific atau scoring berbasis coverage
⚠️ Issue 3: Database Query Konsistensi
$majorMap = PolijeMajor::all()->keyBy('nama_jurusan');
Potensi masalah: Jika data di database berubah antara requests Solusi saat ini: Sudah safe karena keyBy menggunakan primary key mapping Rekomendasi: Cache hasil selama session atau use database transaction
Kesimpulan Akurasi:
✅ Scoring Adalah KONSISTEN
- Same input → Same output (deterministic)
- Tidak ada randomness dalam algoritma
- Tidak ada race condition
- Proses mathematical semuanya deterministic
⏱️ Kemungkinan Perbedaan:
-
Input Parse Berbeda: Misal spasi/capitalization berbeda
- ✅ Sudah di-handle dengan lowercase + trim
-
Database Data Berubah: Jika bobot_mapel atau criteria config berubah
- ⚠️ Akan menyebabkan hasil berbeda (expected)
- ✅ Ini feature, bukan bug
-
Nilai yang di-input berbeda: Misal 85.5 vs 85.0
- ✅ Akan di-round konsisten dalam kategori
-
Preferensi/Keywords overlap: Misal "bisnis teknik"
- ⚠️ Bersifat order-dependent
- ✅ Tetap deterministic (always first match)
Rekomendasi untuk Improved Accuracy:
1. Gunakan Coverage-Based Mapping (bukan binary matching)
Alih-alih hanya first match, score setiap category dan ambil highest:
'Logika & Komputer' → coverage 80%
'Manajemen & Bisnis' → coverage 40%
→ Hasilnya 'Logika & Komputer' (highest coverage)
2. Tambahkan Logging untuk Audit Trail
Simpan semua intermediate scores untuk dapat trace keputusan:
$recommendation->debug_scores = [
'nilai' => $p_nilai,
'minat' => $p_minat,
'pref' => $p_pref,
// ... semua intermediate values
];
3. Implement Caching untuk Consistency
Cache hasil config untuk menghindari potential changes:
$cfg = Cache::remember('polije.criteria', now()->addHours(24),
fn() => config('polije.criteria')
);
4. Add Input Validation/Normalization Layer
Normalize similar inputs (e.g., "programmer" vs "programming" vs "code"):
'programmer' → 'coding'
'programming' → 'coding'
'coder' → 'coding'
Test Cases untuk Verify Consistency:
Test 1: Standard IPA Input
Input: mtk=85, fisika=80, kimia=82, biologi=78, minat="coding", pref="Sains & Teknologi"
Expected: Top major should be IT/Programming related
Consistency: ✅ Same result on 2nd run
Test 2: Ambiguous Minat
Input: minat="bisnis teknologi"
Expected: Deterministic order-dependent result (bisnis matched first)
Consistency: ✅ Same result on 2nd run
Test 3: Edge Cases
Input: minat="", prestasi=""
Expected: Default scoring dengan default probabilities
Consistency: ✅ Same result on 2nd run
Final Verdict:
🎯 Scoring Algorithm: ACCURATE & CONSISTENT
- Semua input akan menghasilkan output yang identical jika input nya identical
- Algoritma mathematically sound (Naive Bayes)
- Numerically stable (log-sum-exp)
- Deterministic (no randomness)
- Order-dependent mapping adalah acceptable behavior
Jika ada perbedaan hasil untuk input yang sama, kemungkinan penyebabnya:
- Input nya actually tidak identical (e.g., spasi berbeda, typo)
- Database configuration berubah (criteria or bobot_mapel diupdate)
- Browser cache issue (load stale version)