MIF_E31230333/app/Services/CertaintyFactorService.php

177 lines
7.8 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
// ════════════════════════════════════════════════════════════════
// app/Services/CertaintyFactorService.php
//
// KEGUNAAN: Inti dari sistem pakar — mesin inferensi Certainty
// Factor. Bertugas menghitung nilai CF untuk setiap penyakit
// berdasarkan gejala yang dipilih user, lalu menyimpan hasilnya
// ke database. Semua logika perhitungan CF ada di sini,
// sehingga Controller tetap bersih dan mudah dibaca.
// ════════════════════════════════════════════════════════════════
namespace App\Services;
use App\Models\Rule;
use App\Models\Konsultasi;
use App\Models\KonsultasiGejala;
use App\Models\HasilDiagnosa;
use Illuminate\Support\Facades\DB;
class CertaintyFactorService
{
// Nilai minimum CF agar penyakit dianggap terdeteksi
private float $threshold = 0.2;
// ─────────────────────────────────────────────────────────
// FORMULA CF KOMBINASI
// Rumus: CF = CF_lama + CF_baru × (1 - CF_lama)
// ─────────────────────────────────────────────────────────
private function kombinasiCF(float $cfLama, float $cfBaru): float
{
return $cfLama + $cfBaru * (1 - $cfLama);
}
// ─────────────────────────────────────────────────────────
// INTERPRETASI NILAI CF
// Mengubah angka CF menjadi kalimat yang mudah dipahami.
// ─────────────────────────────────────────────────────────
public function interpretasi(float $cf): string
{
if ($cf >= 0.81) return 'Sangat Yakin';
if ($cf >= 0.61) return 'Yakin';
if ($cf >= 0.41) return 'Cukup Yakin';
if ($cf >= 0.21) return 'Mungkin';
if ($cf >= 0.01) return 'Tidak Yakin';
return 'Tidak Terdeteksi';
}
// ─────────────────────────────────────────────────────────
// PROSES UTAMA: HITUNG CF
// Menerima array ID gejala yang dipilih user, lalu
// menghitung nilai CF gabungan untuk setiap penyakit.
// ─────────────────────────────────────────────────────────
public function hitung(array $gejalaIds, array $cfUserMap = []): array
{
if (empty($gejalaIds)) {
return ['error' => 'Minimal 1 gejala harus dipilih.'];
}
foreach ($gejalaIds as $id) {
if (!isset($cfUserMap[$id])) {
$cfUserMap[$id] = 1.0;
}
}
$ruleCocok = Rule::with(['penyakit', 'gejala'])
->whereIn('id_gejala', $gejalaIds)
->get();
if ($ruleCocok->isEmpty()) {
return ['error' => 'Tidak ada rule yang cocok dengan gejala yang dipilih.'];
}
$hasilCF = [];
$detailLog = [];
$penyakitIds = $ruleCocok->pluck('id_penyakit')->unique();
foreach ($penyakitIds as $idPenyakit) {
$rulesPerPenyakit = $ruleCocok->where('id_penyakit', $idPenyakit);
$cfGabungan = 0.0;
$log = [];
$step = 1;
foreach ($rulesPerPenyakit as $rule) {
$cfPakar = (float) $rule->nilai_cf;
$cfUser = (float) ($cfUserMap[$rule->id_gejala] ?? 1.0);
$cfIndividu = $cfPakar * $cfUser;
$cfSebelum = $cfGabungan;
if ($cfGabungan == 0.0) {
$cfGabungan = $cfIndividu;
$rumus = number_format($cfIndividu, 4);
} else {
$cfGabungan = $this->kombinasiCF($cfGabungan, $cfIndividu);
$rumus = number_format($cfSebelum, 4)
. ' + ' . number_format($cfIndividu, 4)
. ' × (1 - ' . number_format($cfSebelum, 4) . ')'
. ' = ' . number_format($cfGabungan, 4);
}
$log[] = [
'step' => $step++,
'rule' => $rule->kode_rule,
'kode_gejala' => $rule->gejala->kode ?? '-',
'nama_gejala' => $rule->gejala->nama ?? '-',
'cf_pakar' => $cfPakar,
'cf_user' => $cfUser,
'cf_individu' => round($cfIndividu, 4),
'cf_sebelum' => round($cfSebelum, 4),
'cf_sesudah' => round($cfGabungan, 4),
'rumus' => $rumus,
];
}
$hasilCF[$idPenyakit] = round($cfGabungan, 6);
$detailLog[$idPenyakit] = $log;
}
$terdeteksi = array_filter($hasilCF, fn($cf) => $cf >= $this->threshold);
arsort($terdeteksi);
return [
'gejala_ids' => $gejalaIds,
'cf_user_map' => $cfUserMap,
'semua_cf' => $hasilCF,
'terdeteksi' => $terdeteksi,
'total_diperiksa' => count($hasilCF),
'total_terdeteksi' => count($terdeteksi),
'detail_log' => $detailLog,
];
}
// ─────────────────────────────────────────────────────────
// SIMPAN HASIL KE DATABASE
// Menyimpan sesi konsultasi, gejala yang dipilih, dan
// hasil CF ke tiga tabel sekaligus dalam satu transaksi.
// ─────────────────────────────────────────────────────────
public function simpan(int $userId, array $gejalaIds, array $cfUserMap, array $hasilInferensi): Konsultasi
{
return DB::transaction(function () use ($userId, $gejalaIds, $cfUserMap, $hasilInferensi) {
$kode = 'KON-' . date('Ymd') . '-' . str_pad(
Konsultasi::whereDate('created_at', today())->count() + 1,
4, '0', STR_PAD_LEFT
);
$status = empty($hasilInferensi['terdeteksi']) ? 'tidak_terdeteksi' : 'selesai';
$konsultasi = Konsultasi::create([
'kode_konsultasi' => $kode,
'user_id' => $userId,
'tanggal' => now(),
'status' => $status,
]);
foreach ($gejalaIds as $idGejala) {
KonsultasiGejala::create([
'id_konsultasi' => $konsultasi->id,
'id_gejala' => $idGejala,
'cf_user' => $cfUserMap[$idGejala] ?? 1.0,
]);
}
$ranking = 1;
foreach ($hasilInferensi['terdeteksi'] as $idPenyakit => $cfAkhir) {
HasilDiagnosa::create([
'id_konsultasi' => $konsultasi->id,
'id_penyakit' => $idPenyakit,
'nilai_cf_akhir' => $cfAkhir,
'persentase' => round($cfAkhir * 100, 2),
'ranking' => $ranking++,
]);
}
return $konsultasi;
});
}
}