fix: penambahan logika pencocokan gejala apabila hasil perhitungan sama
This commit is contained in:
parent
f2af845287
commit
6c7e1ee53e
|
@ -9,7 +9,7 @@ function calculateBayesProbability(rules, entityType) {
|
|||
const entityName = entityData.nama;
|
||||
const entityId = entityType === 'penyakit' ? rules[0].id_penyakit : rules[0].id_hama;
|
||||
|
||||
// LANGKAH 1: Mencari nilai semesta P(E|Hi) untuk setiap gejala
|
||||
// Mencari nilai semesta P(E|Hi) untuk setiap gejala
|
||||
let nilai_semesta = 0;
|
||||
const gejalaValues = {};
|
||||
|
||||
|
@ -18,13 +18,13 @@ function calculateBayesProbability(rules, entityType) {
|
|||
nilai_semesta += rule.nilai_pakar;
|
||||
}
|
||||
|
||||
// LANGKAH 2: Mencari hasil bobot P(Hi) untuk setiap gejala
|
||||
// Mencari hasil bobot P(Hi) untuk setiap gejala
|
||||
const bobotGejala = {};
|
||||
for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
|
||||
bobotGejala[idGejala] = nilai / nilai_semesta;
|
||||
}
|
||||
|
||||
// LANGKAH 3: Hitung probabilitas H tanpa memandang Evidence P(E|Hi) × P(Hi)
|
||||
// Hitung probabilitas H tanpa memandang Evidence P(E|Hi) × P(Hi)
|
||||
const probTanpaEvidence = {};
|
||||
for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
|
||||
probTanpaEvidence[idGejala] = nilai * bobotGejala[idGejala];
|
||||
|
@ -36,13 +36,13 @@ function calculateBayesProbability(rules, entityType) {
|
|||
totalProbTanpaEvidence += nilai;
|
||||
}
|
||||
|
||||
// LANGKAH 4: Hitung probabilitas H dengan memandang Evidence P(Hi|E)
|
||||
// Hitung probabilitas H dengan memandang Evidence P(Hi|E)
|
||||
const probDenganEvidence = {};
|
||||
for (const [idGejala, nilai] of Object.entries(probTanpaEvidence)) {
|
||||
probDenganEvidence[idGejala] = nilai / totalProbTanpaEvidence;
|
||||
}
|
||||
|
||||
// LANGKAH 5: Hitung Nilai Bayes ∑bayes = ∑(P(E|Hi) × P(Hi|E))
|
||||
// Hitung Nilai Bayes ∑bayes = ∑(P(E|Hi) × P(Hi|E))
|
||||
let nilaiBayes = 0;
|
||||
const detailBayes = [];
|
||||
|
||||
|
@ -68,10 +68,72 @@ function calculateBayesProbability(rules, entityType) {
|
|||
nilai_semesta: nilai_semesta,
|
||||
detail_perhitungan: detailBayes,
|
||||
nilai_bayes: nilaiBayes,
|
||||
probabilitas_persen: nilaiBayes * 100
|
||||
probabilitas_persen: nilaiBayes * 100,
|
||||
jumlah_gejala_cocok: rules.length // Menambahkan jumlah gejala yang cocok
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function untuk mendapatkan total gejala yang tersedia untuk entity
|
||||
async function getTotalGejalaForEntity(entityId, entityType, inputGejala) {
|
||||
try {
|
||||
let totalGejala = 0;
|
||||
|
||||
if (entityType === 'penyakit') {
|
||||
const allRules = await Rule_penyakit.findAll({
|
||||
where: { id_penyakit: entityId }
|
||||
});
|
||||
totalGejala = allRules.length;
|
||||
} else if (entityType === 'hama') {
|
||||
const allRules = await Rule_hama.findAll({
|
||||
where: { id_hama: entityId }
|
||||
});
|
||||
totalGejala = allRules.length;
|
||||
}
|
||||
|
||||
return totalGejala;
|
||||
} catch (error) {
|
||||
console.error('Error getting total gejala:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function untuk menyelesaikan ambiguitas
|
||||
async function resolveAmbiguity(candidates, inputGejala) {
|
||||
// Tambahkan informasi total gejala untuk setiap kandidat
|
||||
for (let candidate of candidates) {
|
||||
const entityType = candidate.type;
|
||||
const entityId = entityType === 'penyakit' ? candidate.id_penyakit : candidate.id_hama;
|
||||
|
||||
candidate.total_gejala_entity = await getTotalGejalaForEntity(entityId, entityType, inputGejala);
|
||||
|
||||
// Hitung persentase kesesuaian gejala
|
||||
candidate.persentase_kesesuaian = candidate.total_gejala_entity > 0
|
||||
? (candidate.jumlah_gejala_cocok / candidate.total_gejala_entity) * 100
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Urutkan berdasarkan:
|
||||
// 1. Jumlah gejala yang cocok (descending)
|
||||
// 2. Persentase kesesuaian (descending)
|
||||
// 3. Total gejala entity (ascending - lebih spesifik lebih baik)
|
||||
candidates.sort((a, b) => {
|
||||
// Prioritas 1: Jumlah gejala cocok
|
||||
if (a.jumlah_gejala_cocok !== b.jumlah_gejala_cocok) {
|
||||
return b.jumlah_gejala_cocok - a.jumlah_gejala_cocok;
|
||||
}
|
||||
|
||||
// Prioritas 2: Persentase kesesuaian
|
||||
if (Math.abs(a.persentase_kesesuaian - b.persentase_kesesuaian) > 0.01) {
|
||||
return b.persentase_kesesuaian - a.persentase_kesesuaian;
|
||||
}
|
||||
|
||||
// Prioritas 3: Entity dengan total gejala lebih sedikit (lebih spesifik)
|
||||
return a.total_gejala_entity - b.total_gejala_entity;
|
||||
});
|
||||
|
||||
return candidates[0]; // Kembalikan yang terbaik
|
||||
}
|
||||
|
||||
exports.diagnosa = async (req, res) => {
|
||||
const { gejala } = req.body;
|
||||
const userId = req.user?.id;
|
||||
|
@ -133,18 +195,57 @@ exports.diagnosa = async (req, res) => {
|
|||
...sortedHama.map(h => ({ type: 'hama', ...h }))
|
||||
].sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
|
||||
|
||||
// Simpan histori diagnosa jika ada user yang login dan ada hasil diagnosa
|
||||
// ========== PENANGANAN AMBIGUITAS ==========
|
||||
let hasilTertinggi = null;
|
||||
let isAmbiguous = false;
|
||||
let ambiguityResolution = null;
|
||||
|
||||
if (allResults.length > 0) {
|
||||
const nilaiTertinggi = allResults[0].probabilitas_persen;
|
||||
|
||||
// Cari semua hasil dengan nilai probabilitas yang sama dengan yang tertinggi
|
||||
const kandidatTertinggi = allResults.filter(result =>
|
||||
Math.abs(result.probabilitas_persen - nilaiTertinggi) < 0.0001 // Toleransi untuk floating point
|
||||
);
|
||||
|
||||
if (kandidatTertinggi.length > 1) {
|
||||
// Ada ambiguitas - perlu resolusi
|
||||
isAmbiguous = true;
|
||||
console.log(`Ditemukan ${kandidatTertinggi.length} kandidat dengan nilai probabilitas sama: ${nilaiTertinggi}%`);
|
||||
|
||||
// Lakukan resolusi ambiguitas
|
||||
hasilTertinggi = await resolveAmbiguity(kandidatTertinggi, gejala);
|
||||
|
||||
ambiguityResolution = {
|
||||
total_kandidat: kandidatTertinggi.length,
|
||||
metode_resolusi: 'jumlah_gejala_cocok',
|
||||
kandidat: kandidatTertinggi.map(k => ({
|
||||
type: k.type,
|
||||
nama: k.nama,
|
||||
probabilitas_persen: k.probabilitas_persen,
|
||||
jumlah_gejala_cocok: k.jumlah_gejala_cocok,
|
||||
total_gejala_entity: k.total_gejala_entity,
|
||||
persentase_kesesuaian: k.persentase_kesesuaian
|
||||
})),
|
||||
terpilih: {
|
||||
type: hasilTertinggi.type,
|
||||
nama: hasilTertinggi.nama,
|
||||
alasan: `Memiliki ${hasilTertinggi.jumlah_gejala_cocok} gejala cocok dengan kesesuaian ${hasilTertinggi.persentase_kesesuaian?.toFixed(2)}%`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Tidak ada ambiguitas
|
||||
hasilTertinggi = allResults[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Simpan histori diagnosa jika ada user yang login dan ada hasil diagnosa
|
||||
if (!userId) {
|
||||
console.error('ID user tidak ditemukan. Histori tidak dapat disimpan.');
|
||||
} else {
|
||||
const semuaHasil = [...hasilPenyakit, ...hasilHama];
|
||||
|
||||
if (semuaHasil.length > 0) {
|
||||
const hasilTerbesar = semuaHasil.reduce((max, current) => {
|
||||
return current.probabilitas_persen > max.probabilitas_persen ? current : max;
|
||||
});
|
||||
|
||||
if (semuaHasil.length > 0 && hasilTertinggi) {
|
||||
// Dapatkan waktu saat ini dalam zona waktu Indonesia (GMT+7)
|
||||
const now = new Date();
|
||||
const jakartaTime = new Date(now.getTime() + (7 * 60 * 60 * 1000)); // GMT+7 (WIB)
|
||||
|
@ -152,14 +253,14 @@ exports.diagnosa = async (req, res) => {
|
|||
const baseHistoriData = {
|
||||
userId: userId, // harus ada
|
||||
tanggal_diagnosa: jakartaTime, // Menggunakan waktu real-time Indonesia
|
||||
hasil: hasilTerbesar.nilai_bayes, // harus ada, harus tipe FLOAT
|
||||
hasil: hasilTertinggi.nilai_bayes, // harus ada, harus tipe FLOAT
|
||||
};
|
||||
|
||||
// Tambahkan id_penyakit / id_hama jika ada
|
||||
if (hasilTerbesar.id_penyakit) {
|
||||
baseHistoriData.id_penyakit = hasilTerbesar.id_penyakit;
|
||||
} else if (hasilTerbesar.id_hama) {
|
||||
baseHistoriData.id_hama = hasilTerbesar.id_hama;
|
||||
if (hasilTertinggi.id_penyakit) {
|
||||
baseHistoriData.id_penyakit = hasilTertinggi.id_penyakit;
|
||||
} else if (hasilTertinggi.id_hama) {
|
||||
baseHistoriData.id_hama = hasilTertinggi.id_hama;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -180,7 +281,6 @@ exports.diagnosa = async (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: 'Berhasil melakukan diagnosa',
|
||||
|
@ -188,7 +288,9 @@ exports.diagnosa = async (req, res) => {
|
|||
penyakit: sortedPenyakit,
|
||||
hama: sortedHama,
|
||||
gejala_input: gejala.map(id => parseInt(id)),
|
||||
hasil_tertinggi: allResults.length > 0 ? allResults[0] : null
|
||||
hasil_tertinggi: hasilTertinggi,
|
||||
is_ambiguous: isAmbiguous,
|
||||
ambiguity_resolution: ambiguityResolution
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -64,8 +64,6 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk mengelompokkan data berdasarkan userId, diagnosa, dan waktu
|
||||
|
||||
// Fungsi untuk mengelompokkan data berdasarkan userId, diagnosa, dan waktu
|
||||
List<Map<String, dynamic>> _groupHistoriData(
|
||||
List<Map<String, dynamic>> data,
|
||||
|
@ -164,9 +162,6 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
appBar: AppBar(
|
||||
title: Text('Riwayat Diagnosa'),
|
||||
backgroundColor: Color(0xFF9DC08D),
|
||||
actions: [
|
||||
IconButton(icon: Icon(Icons.refresh), onPressed: _loadHistoriData),
|
||||
],
|
||||
),
|
||||
body:
|
||||
isLoading
|
||||
|
@ -251,7 +246,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
histori['diagnosa'] ??
|
||||
'Tidak ada diagnosa',
|
||||
style: TextStyle(
|
||||
color: _getDiagnosaColor(histori),
|
||||
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -91,6 +91,50 @@ Future<List<Map<String, dynamic>>> fetchHistoriDenganDetail(String userId) async
|
|||
// Panggil API untuk mendapatkan data histori
|
||||
final historiResponse = await getHistoriDiagnosa(userId);
|
||||
|
||||
// Tambahkan: Panggil API untuk mendapatkan data user
|
||||
final userData = await getUserById(userId);
|
||||
final String userName = userData != null ? userData['name'] ?? "User $userId" : "User $userId";
|
||||
|
||||
Future<List<Map<String, dynamic>>> fetchHistoriDenganDetail(String userId) async {
|
||||
try {
|
||||
// Panggil API untuk mendapatkan data histori
|
||||
final historiResponse = await getHistoriDiagnosa(userId);
|
||||
|
||||
// Perbaiki cara mendapatkan data user
|
||||
final userData = await getUserById(userId);
|
||||
// Pastikan data user ada dan nama diambil dengan benar
|
||||
final String userName = userData != null && userData['name'] != null
|
||||
? userData['name']
|
||||
: "User $userId";
|
||||
|
||||
print("User Data received: $userData"); // Debug log
|
||||
|
||||
// Proses data histori
|
||||
List<Map<String, dynamic>> result = historiResponse.map((histori) {
|
||||
final gejala = histori['gejala'] ?? {};
|
||||
final penyakit = histori['penyakit'] ?? {};
|
||||
final hama = histori['hama'] ?? {};
|
||||
|
||||
return {
|
||||
"id": histori['id'],
|
||||
"userId": histori['userId'],
|
||||
"name": userName, // Menggunakan nama yang sudah diambil
|
||||
"tanggal_diagnosa": histori['tanggal_diagnosa'],
|
||||
"hasil": histori['hasil'],
|
||||
"gejala_nama": gejala['nama'] ?? "Tidak diketahui",
|
||||
"penyakit_nama": penyakit['nama'],
|
||||
"hama_nama": hama['nama'],
|
||||
};
|
||||
}).toList();
|
||||
|
||||
print("Processed Histori Data with Username: $result"); // Debug log
|
||||
return result;
|
||||
} catch (e) {
|
||||
print("Error fetching histori dengan detail: $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Proses data histori
|
||||
List<Map<String, dynamic>> result = historiResponse.map((histori) {
|
||||
// Tangani properti null dengan default value
|
||||
|
@ -98,19 +142,19 @@ Future<List<Map<String, dynamic>>> fetchHistoriDenganDetail(String userId) async
|
|||
final penyakit = histori['penyakit'] ?? {};
|
||||
final hama = histori['hama'] ?? {};
|
||||
|
||||
|
||||
return {
|
||||
"id": histori['id'],
|
||||
"userId": histori['userId'],
|
||||
"name": userName, // Tambahkan nama user ke hasil
|
||||
"tanggal_diagnosa": histori['tanggal_diagnosa'],
|
||||
"hasil": histori['hasil'],
|
||||
"gejala_nama": gejala['nama'] ?? "Tidak diketahui",
|
||||
"penyakit_nama": penyakit['nama'] ,
|
||||
"hama_nama": hama['nama'] ,
|
||||
"penyakit_nama": penyakit['nama'],
|
||||
"hama_nama": hama['nama'],
|
||||
};
|
||||
}).toList();
|
||||
|
||||
print("Processed Histori Data: $result");
|
||||
print("Processed Histori Data with Username: $result");
|
||||
return result;
|
||||
} catch (e) {
|
||||
print("Error fetching histori dengan detail: $e");
|
||||
|
@ -1182,6 +1226,28 @@ Future<void> deleteUser(int id) async {
|
|||
}
|
||||
}
|
||||
|
||||
// Tambahkan fungsi untuk mendapatkan data user berdasarkan ID
|
||||
Future<Map<String, dynamic>?> getUserById(String userId) async {
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$userUrl/$userId'), // Use userUrl instead of baseUrl
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(response.body); // Direct return as backend sends user object
|
||||
} else {
|
||||
print("Error fetching user data: ${response.statusCode}");
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
print("Exception in getUserById: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,10 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
final List<dynamic> hamaList = data['hama'] ?? [];
|
||||
final Map<String, dynamic>? hasilTertinggi = data['hasil_tertinggi'];
|
||||
|
||||
// Ambiguity information from backend
|
||||
final bool isAmbiguous = data['is_ambiguous'] ?? false;
|
||||
final Map<String, dynamic>? ambiguityResolution = data['ambiguity_resolution'];
|
||||
|
||||
// Get the first penyakit and hama (if any)
|
||||
Map<String, dynamic>? firstPenyakit =
|
||||
penyakitList.isNotEmpty ? penyakitList.first : null;
|
||||
|
@ -82,387 +86,208 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
|
||||
body: Container(
|
||||
color: Color(0xFFEDF1D6),
|
||||
child:
|
||||
isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Main result display
|
||||
_buildDetailedResult(context, firstPenyakit, firstHama),
|
||||
child: isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Ambiguity notification (if applicable)
|
||||
if (isAmbiguous && ambiguityResolution != null)
|
||||
_buildAmbiguityNotification(ambiguityResolution),
|
||||
|
||||
SizedBox(height: 24),
|
||||
// Main result display - use hasil_tertinggi from backend
|
||||
_buildDetailedResultFromBackend(context, hasilTertinggi),
|
||||
|
||||
// Selected symptoms section
|
||||
_buildSection(
|
||||
context,
|
||||
'Gejala yang Dipilih',
|
||||
widget.gejalaTerpilih.isEmpty
|
||||
? _buildEmptyResult('Tidak ada gejala yang dipilih')
|
||||
: Card(
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Selected symptoms section
|
||||
_buildSection(
|
||||
context,
|
||||
'Gejala yang Dipilih',
|
||||
widget.gejalaTerpilih.isEmpty
|
||||
? _buildEmptyResult('Tidak ada gejala yang dipilih')
|
||||
: Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:
|
||||
widget.gejalaTerpilih
|
||||
.map(
|
||||
(gejala) => Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: Text(gejala)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Other possible diseases section
|
||||
_buildSection(
|
||||
context,
|
||||
'Kemungkinan Penyakit Lainnya',
|
||||
penyakitList.length <= 1
|
||||
? _buildEmptyResult(
|
||||
'Tidak ada kemungkinan penyakit lainnya',
|
||||
)
|
||||
: Column(
|
||||
children:
|
||||
penyakitList
|
||||
.skip(
|
||||
1,
|
||||
) // Skip the first one as it's already shown
|
||||
children: widget.gejalaTerpilih
|
||||
.map(
|
||||
(penyakit) => _buildItemCard(
|
||||
penyakit,
|
||||
'penyakit',
|
||||
(gejala) => Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: Text(gejala)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Other possible pests section
|
||||
_buildSection(
|
||||
context,
|
||||
'Kemungkinan Hama Lainnya',
|
||||
hamaList.length <= 1
|
||||
? _buildEmptyResult(
|
||||
'Tidak ada kemungkinan hama lainnya',
|
||||
)
|
||||
: Column(
|
||||
children:
|
||||
hamaList
|
||||
.skip(
|
||||
1,
|
||||
) // Skip the first one as it's already shown
|
||||
.map(
|
||||
(hama) => _buildItemCard(hama, 'hama'),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Other possible diseases section
|
||||
_buildSection(
|
||||
context,
|
||||
'Kemungkinan Penyakit Lainnya',
|
||||
_buildOtherPossibilities(penyakitList, hasilTertinggi, 'penyakit'),
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Other possible pests section
|
||||
_buildSection(
|
||||
context,
|
||||
'Kemungkinan Hama Lainnya',
|
||||
_buildOtherPossibilities(hamaList, hasilTertinggi, 'hama'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchAdditionalData() async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
Widget _buildAmbiguityNotification(Map<String, dynamic> ambiguityResolution) {
|
||||
final totalKandidat = ambiguityResolution['total_kandidat'] ?? 0;
|
||||
final terpilih = ambiguityResolution['terpilih'] ?? {};
|
||||
final alasan = terpilih['alasan'] ?? '';
|
||||
|
||||
try {
|
||||
print('\n=== DEBUG - STARTING DATA FETCH ===');
|
||||
print('DEBUG - hasilDiagnosa input: ${widget.hasilDiagnosa}');
|
||||
|
||||
// Fetch all disease and pest data
|
||||
print('DEBUG - Fetching all penyakit and hama data from API...');
|
||||
semuaPenyakit = await _apiService.getPenyakit();
|
||||
semuaHama = await _apiService.getHama();
|
||||
|
||||
print('\nDEBUG - API Data Summary:');
|
||||
print(
|
||||
'Fetched ${semuaPenyakit.length} penyakit and ${semuaHama.length} hama from API',
|
||||
);
|
||||
|
||||
// Get the lists from the diagnosis result
|
||||
List<dynamic> penyakitList = widget.hasilDiagnosa['penyakit'] ?? [];
|
||||
List<dynamic> hamaList = widget.hasilDiagnosa['hama'] ?? [];
|
||||
|
||||
// Process diseases
|
||||
for (var penyakit in penyakitList) {
|
||||
// Make sure the ID exists and convert to string for consistent comparison
|
||||
var penyakitId = penyakit['id_penyakit'];
|
||||
if (penyakitId == null) continue;
|
||||
|
||||
String penyakitIdStr = penyakitId.toString();
|
||||
print('DEBUG - Processing penyakit ID: $penyakitIdStr');
|
||||
|
||||
// Find the matching disease in our complete list
|
||||
var detail = semuaPenyakit.firstWhere(
|
||||
(item) => item['id'].toString() == penyakitIdStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (detail.isNotEmpty) {
|
||||
// Convert probabilitas_persen (0-100) to probabilitas (0-1)
|
||||
double probability = 0.0;
|
||||
if (penyakit.containsKey('probabilitas_persen')) {
|
||||
probability =
|
||||
(penyakit['probabilitas_persen'] as num).toDouble() / 100;
|
||||
} else if (penyakit.containsKey('nilai_bayes')) {
|
||||
probability = (penyakit['nilai_bayes'] as num).toDouble();
|
||||
}
|
||||
|
||||
// Store the complete details with normalized probability
|
||||
penyakitDetails[penyakitIdStr] = {
|
||||
...detail,
|
||||
'probabilitas': probability,
|
||||
'id_penyakit': penyakitIdStr,
|
||||
};
|
||||
|
||||
final nama =
|
||||
penyakitDetails[penyakitIdStr]?['nama'] ?? 'Nama tidak ditemukan';
|
||||
print('DEBUG - Found details for penyakit ID $penyakitIdStr: $nama');
|
||||
}
|
||||
}
|
||||
|
||||
// Process pests
|
||||
for (var hama in hamaList) {
|
||||
// Make sure the ID exists and convert to string for consistent comparison
|
||||
var hamaId = hama['id_hama'];
|
||||
if (hamaId == null) continue;
|
||||
|
||||
String hamaIdStr = hamaId.toString();
|
||||
print('DEBUG - Processing hama ID: $hamaIdStr');
|
||||
|
||||
// Find the matching pest in our complete list
|
||||
var detail = semuaHama.firstWhere(
|
||||
(item) => item['id'].toString() == hamaIdStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (detail.isNotEmpty) {
|
||||
// Convert probabilitas_persen (0-100) to probabilitas (0-1)
|
||||
double probability = 0.0;
|
||||
if (hama.containsKey('probabilitas_persen')) {
|
||||
probability = (hama['probabilitas_persen'] as num).toDouble() / 100;
|
||||
} else if (hama.containsKey('nilai_bayes')) {
|
||||
probability = (hama['nilai_bayes'] as num).toDouble();
|
||||
}
|
||||
|
||||
// Store the complete details with normalized probability
|
||||
hamaDetails[hamaIdStr] = {
|
||||
...detail,
|
||||
'probabilitas': probability,
|
||||
'id_hama': hamaIdStr,
|
||||
};
|
||||
|
||||
final nama =
|
||||
hamaDetails[hamaIdStr]?['nama'] ?? 'Nama tidak ditemukan';
|
||||
print('DEBUG - Found details for hama ID $hamaIdStr: $nama');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error fetching additional data: $e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
return Card(
|
||||
color: Colors.blue.shade50,
|
||||
elevation: 2,
|
||||
margin: EdgeInsets.only(bottom: 16),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Colors.blue.shade700,
|
||||
size: 24,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Resolusi Ambiguitas',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'Ditemukan $totalKandidat kemungkinan dengan nilai probabilitas yang sama. Sistem telah memilih hasil terbaik berdasarkan:',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'• $alasan',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Metode: Analisis kesesuaian gejala',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getCompleteItemData(
|
||||
Map<String, dynamic> item,
|
||||
String type,
|
||||
) {
|
||||
// Create a new map for the result
|
||||
Map<String, dynamic> result = {...item};
|
||||
|
||||
// Get the ID based on the correct field name from backend
|
||||
var id = type == 'penyakit' ? item['id_penyakit'] : item['id_hama'];
|
||||
|
||||
print('DEBUG - _getCompleteItemData type: $type, id: $id');
|
||||
if (id == null) {
|
||||
print('DEBUG - ID is null, returning original item');
|
||||
return result;
|
||||
}
|
||||
|
||||
String idStr = id.toString();
|
||||
|
||||
// Get the detailed information based on type
|
||||
Map<String, dynamic>? details;
|
||||
if (type == 'penyakit') {
|
||||
details = penyakitDetails[idStr];
|
||||
|
||||
// If not found in our cached details, try to find it in the API data
|
||||
if (details == null || details.isEmpty) {
|
||||
print(
|
||||
'DEBUG - No cached details for penyakit ID: $idStr, searching API data...',
|
||||
);
|
||||
details = semuaPenyakit.firstWhere(
|
||||
(p) => p['id'].toString() == idStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (details.isNotEmpty) {
|
||||
// Cache for future use
|
||||
penyakitDetails[idStr] = {...details};
|
||||
}
|
||||
}
|
||||
} else if (type == 'hama') {
|
||||
details = hamaDetails[idStr];
|
||||
|
||||
// If not found in our cached details, try to find it in the API data
|
||||
if (details == null || details.isEmpty) {
|
||||
print(
|
||||
'DEBUG - No cached details for hama ID: $idStr, searching API data...',
|
||||
);
|
||||
details = semuaHama.firstWhere(
|
||||
(h) => h['id'].toString() == idStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (details.isNotEmpty) {
|
||||
// Cache for future use
|
||||
hamaDetails[idStr] = {...details};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have details, merge them with the result
|
||||
if (details != null && details.isNotEmpty) {
|
||||
print('DEBUG - Found details for $type ID $idStr: ${details['nama']}');
|
||||
|
||||
// Calculate probability (convert from percentage if needed)
|
||||
double probability = 0.0;
|
||||
|
||||
// First check our original item
|
||||
if (item.containsKey('probabilitas_persen')) {
|
||||
probability = (item['probabilitas_persen'] as num).toDouble() / 100;
|
||||
} else if (item.containsKey('nilai_bayes')) {
|
||||
probability = (item['nilai_bayes'] as num).toDouble();
|
||||
} else if (item.containsKey('probabilitas')) {
|
||||
probability = _getProbabilitas(item);
|
||||
}
|
||||
|
||||
// Merge all the details
|
||||
result = {
|
||||
...details,
|
||||
...result,
|
||||
'probabilitas': probability,
|
||||
// Make sure these IDs are consistent
|
||||
'id': idStr,
|
||||
type == 'penyakit' ? 'id_penyakit' : 'id_hama': idStr,
|
||||
};
|
||||
|
||||
print(
|
||||
'DEBUG - Final data for $type ID $idStr (${result['nama']}): probabilitas=${result['probabilitas']}',
|
||||
);
|
||||
} else {
|
||||
print('DEBUG - No details found for $type ID $idStr');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildDetailedResult(
|
||||
Widget _buildDetailedResultFromBackend(
|
||||
BuildContext context,
|
||||
Map<String, dynamic>? penyakit,
|
||||
Map<String, dynamic>? hama,
|
||||
Map<String, dynamic>? hasilTertinggi,
|
||||
) {
|
||||
// If we have no data, show a message
|
||||
if (penyakit == null && hama == null) {
|
||||
// If no result from backend, show empty message
|
||||
if (hasilTertinggi == null) {
|
||||
return _buildEmptyResult('Tidak ada hasil diagnosa yang tersedia');
|
||||
}
|
||||
|
||||
// Determine which has higher probability
|
||||
bool isPenyakitHigher = false;
|
||||
Map<String, dynamic>? highest;
|
||||
// Determine type based on the presence of id fields
|
||||
String type = '';
|
||||
bool isPenyakit = false;
|
||||
|
||||
// Log the incoming data to debug
|
||||
print('DEBUG - Incoming penyakit: $penyakit');
|
||||
print('DEBUG - Incoming hama: $hama');
|
||||
|
||||
// Compare probabilities to determine which to show
|
||||
if (penyakit != null && hama != null) {
|
||||
double pProbabilitas = _getProbabilitas(penyakit);
|
||||
double hProbabilitas = _getProbabilitas(hama);
|
||||
|
||||
isPenyakitHigher = pProbabilitas >= hProbabilitas;
|
||||
highest = isPenyakitHigher ? penyakit : hama;
|
||||
type = isPenyakitHigher ? 'penyakit' : 'hama';
|
||||
} else if (penyakit != null) {
|
||||
highest = penyakit;
|
||||
isPenyakitHigher = true;
|
||||
if (hasilTertinggi.containsKey('id_penyakit') && hasilTertinggi['id_penyakit'] != null) {
|
||||
type = 'penyakit';
|
||||
} else if (hama != null) {
|
||||
highest = hama;
|
||||
isPenyakitHigher = false;
|
||||
isPenyakit = true;
|
||||
} else if (hasilTertinggi.containsKey('id_hama') && hasilTertinggi['id_hama'] != null) {
|
||||
type = 'hama';
|
||||
isPenyakit = false;
|
||||
} else {
|
||||
// Fallback: check type field if available
|
||||
type = hasilTertinggi['type'] ?? 'unknown';
|
||||
isPenyakit = type == 'penyakit';
|
||||
}
|
||||
|
||||
// Safety check
|
||||
if (highest == null) {
|
||||
return _buildEmptyResult('Tidak ada hasil diagnosa yang tersedia');
|
||||
}
|
||||
|
||||
// Get the complete data for the highest item
|
||||
final completeData = _getCompleteItemData(highest, type);
|
||||
|
||||
// Debug log
|
||||
print('Detail result using: $completeData');
|
||||
// Get the complete data for the result
|
||||
final completeData = _getCompleteItemData(hasilTertinggi, type);
|
||||
|
||||
// Extract the data we need with safe access
|
||||
final nama = completeData['nama'] ?? 'Tidak diketahui';
|
||||
final nama = completeData['nama'] ?? hasilTertinggi['nama'] ?? 'Tidak diketahui';
|
||||
final deskripsi = completeData['deskripsi'] ?? 'Tidak tersedia';
|
||||
final penanganan = completeData['penanganan'] ?? 'Tidak tersedia';
|
||||
final foto = completeData['foto'];
|
||||
final probabilitas = _getProbabilitas(completeData);
|
||||
final probabilitas = _getProbabilitas(hasilTertinggi);
|
||||
|
||||
// Get additional ambiguity info if available
|
||||
final jumlahGejalacocok = hasilTertinggi['jumlah_gejala_cocok'];
|
||||
final totalGejalaEntity = hasilTertinggi['total_gejala_entity'];
|
||||
final persentaseKesesuaian = hasilTertinggi['persentase_kesesuaian'];
|
||||
|
||||
// Debug log
|
||||
print('DEBUG - Building detailed result for: $nama');
|
||||
print('DEBUG - Type: $type, isPenyakit: $isPenyakit');
|
||||
print('DEBUG - Probabilitas: $probabilitas');
|
||||
|
||||
// Debug log specific fields that should be displayed
|
||||
print('DEBUG - nama: $nama (${nama.runtimeType})');
|
||||
print('DEBUG - deskripsi: $deskripsi (${deskripsi.runtimeType})');
|
||||
print('DEBUG - penanganan: $penanganan (${penanganan.runtimeType})');
|
||||
print('DEBUG - foto: $foto (${foto?.runtimeType})');
|
||||
print('DEBUG - probabilitas: $probabilitas (${probabilitas.runtimeType})');
|
||||
return Card(
|
||||
elevation: 6,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
side: BorderSide(
|
||||
color:
|
||||
isPenyakitHigher ? Colors.red.shade300 : Colors.orange.shade300,
|
||||
color: isPenyakit ? Colors.red.shade300 : Colors.orange.shade300,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
|
@ -474,13 +299,8 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isPenyakitHigher
|
||||
? Icons.coronavirus_outlined
|
||||
: Icons.bug_report,
|
||||
color:
|
||||
isPenyakitHigher
|
||||
? Colors.red.shade700
|
||||
: Colors.orange.shade700,
|
||||
isPenyakit ? Icons.coronavirus_outlined : Icons.bug_report,
|
||||
color: isPenyakit ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
size: 28,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
|
@ -490,18 +310,50 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
isPenyakitHigher
|
||||
? Colors.red.shade700
|
||||
: Colors.orange.shade700,
|
||||
color: isPenyakit ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildProbabilityIndicator(probabilitas),
|
||||
],
|
||||
),
|
||||
|
||||
// Additional info if ambiguity resolution occurred
|
||||
if (jumlahGejalacocok != null && totalGejalaEntity != null)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 8),
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.analytics_outlined, size: 16, color: Colors.grey.shade600),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Kesesuaian: $jumlahGejalacocok/$totalGejalaEntity gejala',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (persentaseKesesuaian != null)
|
||||
Text(
|
||||
' (${persentaseKesesuaian.toStringAsFixed(1)}%)',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Divider(thickness: 1, height: 24),
|
||||
|
||||
// Image section
|
||||
FutureBuilder<Uint8List?>(
|
||||
future: ApiService().getPenyakitImageBytesByFilename(
|
||||
foto.toString(),
|
||||
|
@ -522,7 +374,7 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.memory(
|
||||
snapshot.data!,
|
||||
fit: BoxFit.contain, // ✅ agar gambar tidak dipotong
|
||||
fit: BoxFit.contain,
|
||||
width: double.infinity,
|
||||
height: 180,
|
||||
),
|
||||
|
@ -573,52 +425,261 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
),
|
||||
SizedBox(height: 8),
|
||||
Text(penanganan, style: TextStyle(fontSize: 14)),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Button to see more details
|
||||
// Center(
|
||||
// child: TextButton.icon(
|
||||
// icon: Icon(Icons.info_outline),
|
||||
// label: Text('Lihat Detail Lengkap'),
|
||||
// onPressed: () => _showDetailDialog(context, completeData, type),
|
||||
// style: TextButton.styleFrom(
|
||||
// foregroundColor: isPenyakitHigher ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemCard(Map<String, dynamic> item, String type) {
|
||||
// Get the complete data for this item
|
||||
final completeData = _getCompleteItemData(item, type);
|
||||
Widget _buildOtherPossibilities(
|
||||
List<dynamic> itemList,
|
||||
Map<String, dynamic>? hasilTertinggi,
|
||||
String type,
|
||||
) {
|
||||
if (itemList.isEmpty) {
|
||||
return _buildEmptyResult('Tidak ada kemungkinan ${type} lainnya');
|
||||
}
|
||||
|
||||
// Filter out the top result that's already shown
|
||||
List<dynamic> otherItems = [];
|
||||
|
||||
if (hasilTertinggi != null) {
|
||||
// Get the ID of the top result
|
||||
String? topResultId;
|
||||
if (type == 'penyakit' && hasilTertinggi.containsKey('id_penyakit')) {
|
||||
topResultId = hasilTertinggi['id_penyakit']?.toString();
|
||||
} else if (type == 'hama' && hasilTertinggi.containsKey('id_hama')) {
|
||||
topResultId = hasilTertinggi['id_hama']?.toString();
|
||||
}
|
||||
|
||||
// Filter out the top result
|
||||
otherItems = itemList.where((item) {
|
||||
String? itemId;
|
||||
if (type == 'penyakit') {
|
||||
itemId = item['id_penyakit']?.toString();
|
||||
} else {
|
||||
itemId = item['id_hama']?.toString();
|
||||
}
|
||||
return topResultId == null || itemId != topResultId;
|
||||
}).toList();
|
||||
} else {
|
||||
// If no top result, skip the first item
|
||||
otherItems = itemList.skip(1).toList();
|
||||
}
|
||||
|
||||
if (otherItems.isEmpty) {
|
||||
return _buildEmptyResult('Tidak ada kemungkinan ${type} lainnya');
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: otherItems
|
||||
.map((item) => _buildItemCard(item, type))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchAdditionalData() async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
print('\n=== DEBUG - STARTING DATA FETCH ===');
|
||||
print('DEBUG - hasilDiagnosa input: ${widget.hasilDiagnosa}');
|
||||
|
||||
// Fetch all disease and pest data
|
||||
print('DEBUG - Fetching all penyakit and hama data from API...');
|
||||
semuaPenyakit = await _apiService.getPenyakit();
|
||||
semuaHama = await _apiService.getHama();
|
||||
|
||||
print('\nDEBUG - API Data Summary:');
|
||||
print(
|
||||
'Fetched ${semuaPenyakit.length} penyakit and ${semuaHama.length} hama from API',
|
||||
);
|
||||
|
||||
// Get the data from the new backend structure
|
||||
final data = widget.hasilDiagnosa['data'] ?? {};
|
||||
final List<dynamic> penyakitList = data['penyakit'] ?? [];
|
||||
final List<dynamic> hamaList = data['hama'] ?? [];
|
||||
|
||||
// Process diseases
|
||||
for (var penyakit in penyakitList) {
|
||||
var penyakitId = penyakit['id_penyakit'];
|
||||
if (penyakitId == null) continue;
|
||||
|
||||
String penyakitIdStr = penyakitId.toString();
|
||||
print('DEBUG - Processing penyakit ID: $penyakitIdStr');
|
||||
|
||||
var detail = semuaPenyakit.firstWhere(
|
||||
(item) => item['id'].toString() == penyakitIdStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (detail.isNotEmpty) {
|
||||
double probability = 0.0;
|
||||
if (penyakit.containsKey('probabilitas_persen')) {
|
||||
probability = (penyakit['probabilitas_persen'] as num).toDouble() / 100;
|
||||
} else if (penyakit.containsKey('nilai_bayes')) {
|
||||
probability = (penyakit['nilai_bayes'] as num).toDouble();
|
||||
}
|
||||
|
||||
penyakitDetails[penyakitIdStr] = {
|
||||
...detail,
|
||||
'probabilitas': probability,
|
||||
'id_penyakit': penyakitIdStr,
|
||||
};
|
||||
|
||||
final nama = penyakitDetails[penyakitIdStr]?['nama'] ?? 'Nama tidak ditemukan';
|
||||
print('DEBUG - Found details for penyakit ID $penyakitIdStr: $nama');
|
||||
}
|
||||
}
|
||||
|
||||
// Process pests
|
||||
for (var hama in hamaList) {
|
||||
var hamaId = hama['id_hama'];
|
||||
if (hamaId == null) continue;
|
||||
|
||||
String hamaIdStr = hamaId.toString();
|
||||
print('DEBUG - Processing hama ID: $hamaIdStr');
|
||||
|
||||
var detail = semuaHama.firstWhere(
|
||||
(item) => item['id'].toString() == hamaIdStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (detail.isNotEmpty) {
|
||||
double probability = 0.0;
|
||||
if (hama.containsKey('probabilitas_persen')) {
|
||||
probability = (hama['probabilitas_persen'] as num).toDouble() / 100;
|
||||
} else if (hama.containsKey('nilai_bayes')) {
|
||||
probability = (hama['nilai_bayes'] as num).toDouble();
|
||||
}
|
||||
|
||||
hamaDetails[hamaIdStr] = {
|
||||
...detail,
|
||||
'probabilitas': probability,
|
||||
'id_hama': hamaIdStr,
|
||||
};
|
||||
|
||||
final nama = hamaDetails[hamaIdStr]?['nama'] ?? 'Nama tidak ditemukan';
|
||||
print('DEBUG - Found details for hama ID $hamaIdStr: $nama');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error fetching additional data: $e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getCompleteItemData(
|
||||
Map<String, dynamic> item,
|
||||
String type,
|
||||
) {
|
||||
Map<String, dynamic> result = {...item};
|
||||
|
||||
var id = type == 'penyakit' ? item['id_penyakit'] : item['id_hama'];
|
||||
|
||||
print('DEBUG - _getCompleteItemData type: $type, id: $id');
|
||||
if (id == null) {
|
||||
print('DEBUG - ID is null, returning original item');
|
||||
return result;
|
||||
}
|
||||
|
||||
String idStr = id.toString();
|
||||
|
||||
Map<String, dynamic>? details;
|
||||
if (type == 'penyakit') {
|
||||
details = penyakitDetails[idStr];
|
||||
|
||||
if (details == null || details.isEmpty) {
|
||||
print('DEBUG - No cached details for penyakit ID: $idStr, searching API data...');
|
||||
details = semuaPenyakit.firstWhere(
|
||||
(p) => p['id'].toString() == idStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (details.isNotEmpty) {
|
||||
penyakitDetails[idStr] = {...details};
|
||||
}
|
||||
}
|
||||
} else if (type == 'hama') {
|
||||
details = hamaDetails[idStr];
|
||||
|
||||
if (details == null || details.isEmpty) {
|
||||
print('DEBUG - No cached details for hama ID: $idStr, searching API data...');
|
||||
details = semuaHama.firstWhere(
|
||||
(h) => h['id'].toString() == idStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (details.isNotEmpty) {
|
||||
hamaDetails[idStr] = {...details};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (details != null && details.isNotEmpty) {
|
||||
print('DEBUG - Found details for $type ID $idStr: ${details['nama']}');
|
||||
|
||||
double probability = 0.0;
|
||||
|
||||
if (item.containsKey('probabilitas_persen')) {
|
||||
probability = (item['probabilitas_persen'] as num).toDouble() / 100;
|
||||
} else if (item.containsKey('nilai_bayes')) {
|
||||
probability = (item['nilai_bayes'] as num).toDouble();
|
||||
} else if (item.containsKey('probabilitas')) {
|
||||
probability = _getProbabilitas(item);
|
||||
}
|
||||
|
||||
result = {
|
||||
...details,
|
||||
...result,
|
||||
'probabilitas': probability,
|
||||
'id': idStr,
|
||||
type == 'penyakit' ? 'id_penyakit' : 'id_hama': idStr,
|
||||
};
|
||||
|
||||
print('DEBUG - Final data for $type ID $idStr (${result['nama']}): probabilitas=${result['probabilitas']}');
|
||||
} else {
|
||||
print('DEBUG - No details found for $type ID $idStr');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildItemCard(Map<String, dynamic> item, String type) {
|
||||
final completeData = _getCompleteItemData(item, type);
|
||||
final nama = completeData['nama'] ?? 'Tidak diketahui';
|
||||
final probabilitas = _getProbabilitas(completeData);
|
||||
|
||||
// Get additional info for display
|
||||
final jumlahGejalacocok = item['jumlah_gejala_cocok'];
|
||||
final totalGejalaEntity = item['total_gejala_entity'];
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
type == 'penyakit' ? Icons.coronavirus_outlined : Icons.bug_report,
|
||||
color:
|
||||
type == 'penyakit' ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
color: type == 'penyakit' ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
),
|
||||
title: Text(nama),
|
||||
subtitle: jumlahGejalacocok != null && totalGejalaEntity != null
|
||||
? Text(
|
||||
'Kesesuaian: $jumlahGejalacocok/$totalGejalaEntity gejala',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
)
|
||||
: null,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildProbabilityIndicator(probabilitas),
|
||||
SizedBox(width: 8),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.info_outline),
|
||||
// onPressed: () => _showDetailDialog(context, completeData, type),
|
||||
// color: type == 'penyakit' ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -671,10 +732,9 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
}
|
||||
|
||||
Widget _buildProbabilityIndicator(double value) {
|
||||
final Color indicatorColor =
|
||||
value > 0.7
|
||||
? Colors.red
|
||||
: value > 0.4
|
||||
final Color indicatorColor = value > 0.7
|
||||
? Colors.red
|
||||
: value > 0.4
|
||||
? Colors.orange
|
||||
: Colors.green;
|
||||
|
||||
|
@ -695,14 +755,11 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
}
|
||||
|
||||
double _getProbabilitas(Map<String, dynamic>? item) {
|
||||
// If item is null, return 0.0
|
||||
if (item == null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Try all possible probability field names from the backend
|
||||
if (item.containsKey('probabilitas_persen')) {
|
||||
// Backend sends percentage (0-100), convert to decimal (0-1)
|
||||
var value = item['probabilitas_persen'];
|
||||
if (value is num) {
|
||||
return value.toDouble() / 100;
|
||||
|
@ -711,7 +768,6 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for nilai_bayes (already in 0-1 format)
|
||||
if (item.containsKey('nilai_bayes')) {
|
||||
var value = item['nilai_bayes'];
|
||||
if (value is num) {
|
||||
|
@ -721,7 +777,6 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
}
|
||||
}
|
||||
|
||||
// Finally check for probabilitas field that might exist in our frontend objects
|
||||
if (item.containsKey('probabilitas')) {
|
||||
var value = item['probabilitas'];
|
||||
if (value is num) {
|
||||
|
|
|
@ -309,7 +309,7 @@ class _ProfilPageState extends State<ProfilPage> {
|
|||
children: [
|
||||
// Card box untuk data pengguna
|
||||
Container(
|
||||
height: 400,
|
||||
height: 200,
|
||||
width: 450,
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
|
@ -432,8 +432,6 @@ class _ProfilPageState extends State<ProfilPage> {
|
|||
_buildProfileItem("Email: ${userData?['email'] ?? '-'}"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Alamat: ${userData?['alamat'] ?? '-'}"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Nomor Telepon: ${userData?['nomorTelepon'] ?? '-'}"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue