add new function in diagnosaController

This commit is contained in:
unknown 2025-06-09 09:43:11 +07:00
parent f9ba830cfa
commit c012b08a5f
11 changed files with 149 additions and 54 deletions

View File

@ -152,6 +152,36 @@ function filterSingleSymptomPerfectMatch(results) {
return filtered; return filtered;
} }
// Helper function untuk memprioritaskan berdasarkan jumlah gejala cocok
function prioritizeBySymptomCount(results) {
// Kelompokkan hasil berdasarkan jumlah gejala cocok
const groupedBySymptoms = {};
results.forEach(result => {
const symptomCount = result.jumlah_gejala_cocok;
if (!groupedBySymptoms[symptomCount]) {
groupedBySymptoms[symptomCount] = [];
}
groupedBySymptoms[symptomCount].push(result);
});
// Ambil kelompok dengan jumlah gejala cocok terbanyak
const maxSymptomCount = Math.max(...Object.keys(groupedBySymptoms).map(Number));
const topSymptomMatches = groupedBySymptoms[maxSymptomCount];
// Jika ada lebih dari 1 hasil dengan gejala cocok terbanyak, urutkan berdasarkan probabilitas
const sortedTopMatches = topSymptomMatches.sort((a, b) => {
return b.probabilitas_persen - a.probabilitas_persen;
});
console.log(`Memprioritaskan ${sortedTopMatches.length} hasil dengan ${maxSymptomCount} gejala cocok`);
return {
prioritizedResults: sortedTopMatches,
maxSymptomCount: maxSymptomCount,
totalGroups: Object.keys(groupedBySymptoms).length
};
}
exports.diagnosa = async (req, res) => { exports.diagnosa = async (req, res) => {
const { gejala } = req.body; const { gejala } = req.body;
const userId = req.user?.id; const userId = req.user?.id;
@ -236,6 +266,26 @@ exports.diagnosa = async (req, res) => {
filterInfo.fallback_to_symptom_count = true; filterInfo.fallback_to_symptom_count = true;
filterInfo.fallback_reason = 'Semua hasil memiliki 100% akurasi dengan hanya 1 gejala cocok'; filterInfo.fallback_reason = 'Semua hasil memiliki 100% akurasi dengan hanya 1 gejala cocok';
} else if (filteredResults.length > 0) {
// ========== PRIORITAS BERDASARKAN JUMLAH GEJALA COCOK ==========
const priorityAnalysis = prioritizeBySymptomCount(filteredResults);
// Jika ada hasil dengan gejala cocok lebih banyak, prioritaskan mereka
if (priorityAnalysis.totalGroups > 1) {
console.log(`Menggunakan prioritas gejala: ${priorityAnalysis.maxSymptomCount} gejala cocok diprioritaskan`);
finalResults = priorityAnalysis.prioritizedResults;
filterInfo.symptom_priority_applied = true;
filterInfo.max_symptom_count = priorityAnalysis.maxSymptomCount;
filterInfo.prioritized_count = priorityAnalysis.prioritizedResults.length;
} else {
// Jika semua hasil memiliki jumlah gejala cocok yang sama, urutkan berdasarkan probabilitas
finalResults = filteredResults.sort((a, b) => {
return b.probabilitas_persen - a.probabilitas_persen;
});
filterInfo.sorted_by_probability = true;
}
} }
// ========== PENANGANAN AMBIGUITAS ========== // ========== PENANGANAN AMBIGUITAS ==========

View File

@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application <application
android:label="frontend" android:label="SIBAYAM"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/launcher_icon">
<activity <activity

View File

@ -7,15 +7,15 @@ import 'package:image_picker/image_picker.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
class ApiService { class ApiService {
static const String baseUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/auth'; static const String baseUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/auth';
static const String gejalaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/gejala'; static const String gejalaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/gejala';
static const String hamaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/hama'; static const String hamaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/hama';
static const String penyakitUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/penyakit'; static const String penyakitUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/penyakit';
static const String rulesPenyakitUrl ='https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/rules_penyakit'; static const String rulesPenyakitUrl ='https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/rules_penyakit';
static const String rulesHamaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/rules_hama'; static const String rulesHamaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/rules_hama';
static const String userUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/users'; static const String userUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/users';
static const String diagnosaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/diagnosa'; static const String diagnosaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/diagnosa';
static const String historiUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/histori'; static const String historiUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/histori';
static const Duration timeout = Duration(seconds: 15); static const Duration timeout = Duration(seconds: 15);
/// Fungsi untuk mengirim gejala dan menerima hasil diagnosa /// Fungsi untuk mengirim gejala dan menerima hasil diagnosa
@ -419,7 +419,7 @@ Future<List<Map<String, dynamic>>> getAllHistori() async {
Future<Uint8List?> getHamaImageBytesByFilename(String filename) async { Future<Uint8List?> getHamaImageBytesByFilename(String filename) async {
try { try {
final url = Uri.parse('https://backend-sistem-pakar-diagnosa-penya.vercel.app/image_hama/$filename'); final url = Uri.parse('https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/image_hama/$filename');
print('Fetching image from: $url'); print('Fetching image from: $url');
final response = await http.get(url); final response = await http.get(url);
@ -721,7 +721,7 @@ Future<List<Map<String, dynamic>>> getAllHistori() async {
Future<Uint8List?> getPenyakitImageBytesByFilename(String filename) async { Future<Uint8List?> getPenyakitImageBytesByFilename(String filename) async {
try { try {
final url = Uri.parse('https://backend-sistem-pakar-diagnosa-penya.vercel.app/image_penyakit/$filename'); final url = Uri.parse('https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/image_penyakit/$filename');
print('Fetching image from: $url'); print('Fetching image from: $url');
final response = await http.get(url); final response = await http.get(url);

View File

@ -41,7 +41,7 @@ class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Sistem Pakar Bayam', title: 'SIBAYAM',
home: _initialScreen, home: _initialScreen,
); );
} }

View File

@ -153,7 +153,7 @@ class DetailRiwayatPage extends StatelessWidget {
Text('Gejala: $gejalaList', style: TextStyle(fontSize: 16)), Text('Gejala: $gejalaList', style: TextStyle(fontSize: 16)),
SizedBox(height: 8), SizedBox(height: 8),
Text( Text(
'Hasil: ${(detailRiwayat['hasil'] as num?)?.toStringAsFixed(2) ?? "-"}', 'Hasil: ${detailRiwayat['hasil'] != null ? "${(((detailRiwayat['hasil'] as num) * 1000).floor() / 10).toStringAsFixed(1)}%" : "-"}',
style: TextStyle(fontSize: 16), style: TextStyle(fontSize: 16),
), ),
SizedBox(height: 8), SizedBox(height: 8),

View File

@ -56,9 +56,14 @@ class _DiagnosaPageState extends State<DiagnosaPage> {
} }
void prosesHasilDiagnosa() async { void prosesHasilDiagnosa() async {
if (gejalaTerpilihIds.isEmpty) { // Validasi minimal 3 gejala
if (gejalaTerpilihIds.length < 3) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Silakan pilih minimal satu gejala')), SnackBar(
content: Text('Silakan pilih minimal 3 gejala untuk melakukan diagnosa'),
backgroundColor: Color(0xFF9DC08D),
duration: Duration(seconds: 3),
),
); );
return; return;
} }
@ -175,10 +180,30 @@ class _DiagnosaPageState extends State<DiagnosaPage> {
), ),
), ),
Divider(color: Colors.grey), Divider(color: Colors.grey),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text( Text(
"Gejala Terpilih", "Gejala Terpilih",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: gejalaTerpilihIds.length >= 3 ? Colors.green : Colors.green,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${gejalaTerpilihIds.length}/min 3",
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
SizedBox(height: 5), SizedBox(height: 5),
SizedBox( SizedBox(
height: 100, height: 100,
@ -202,15 +227,21 @@ class _DiagnosaPageState extends State<DiagnosaPage> {
height: 30, height: 30,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, backgroundColor: gejalaTerpilihIds.length >= 3 ? Colors.green : Colors.grey,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
), ),
onPressed: prosesHasilDiagnosa, onPressed: gejalaTerpilihIds.length >= 3 ? prosesHasilDiagnosa : null,
child: Text( child: Text(
"Lihat Hasil Diagnosa", gejalaTerpilihIds.length >= 3
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ? "Lihat Hasil Diagnosa"
: "Pilih minimal 3 gejala",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
), ),
), ),
), ),

View File

@ -257,10 +257,10 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
return; return;
} }
if (passwordController.text.length < 6) { if (passwordController.text.length < 8) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Password minimal 6 karakter.'), content: Text('Password minimal 8 karakter.'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
), ),
); );

View File

@ -614,6 +614,12 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
return _buildEmptyResult('Tidak ada kemungkinan ${type} lainnya'); return _buildEmptyResult('Tidak ada kemungkinan ${type} lainnya');
} }
// Get the probability of the main diagnosis result
double? mainDiagnosisProbability;
if (hasilTertinggi != null) {
mainDiagnosisProbability = _getProbabilitas(hasilTertinggi);
}
// Filter out items with 100% probability and the top result // Filter out items with 100% probability and the top result
List otherItems = []; List otherItems = [];
@ -626,7 +632,7 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
topResultId = hasilTertinggi['id_hama']?.toString(); topResultId = hasilTertinggi['id_hama']?.toString();
} }
// Filter out the top result AND items with 100% probability // Filter out the top result AND items with 100% probability AND items with higher probability than main diagnosis
otherItems = itemList.where((item) { otherItems = itemList.where((item) {
String? itemId; String? itemId;
if (type == 'penyakit') { if (type == 'penyakit') {
@ -640,12 +646,19 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
return false; return false;
} }
// Skip if this item has 100% probability // Get item probability
double itemProbabilitas = _getProbabilitas(item); double itemProbabilitas = _getProbabilitas(item);
// Skip if this item has 100% probability
if ((itemProbabilitas * 100).round() == 100) { if ((itemProbabilitas * 100).round() == 100) {
return false; return false;
} }
// Skip if this item has higher probability than main diagnosis
if (mainDiagnosisProbability != null && itemProbabilitas > mainDiagnosisProbability) {
return false;
}
return true; return true;
}).toList(); }).toList();
} else { } else {
@ -975,7 +988,7 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
), ),
child: Center( child: Center(
child: Text( child: Text(
'${(value * 100).toStringAsFixed(0)}%', '${((value * 1000).floor()/10).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
), ),
), ),
@ -1055,7 +1068,7 @@ Widget _buildProbabilityIndicator(double value) {
), ),
child: Center( child: Center(
child: Text( child: Text(
'${(value * 100).toStringAsFixed(0)}%', '${((value * 1000).floor()/10).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
), ),
), ),

View File

@ -62,7 +62,7 @@ class _LoginPageState extends State<LoginPage> {
} }
try { try {
var url = Uri.parse("https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/auth/login"); var url = Uri.parse("https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/auth/login");
var response = await http.post( var response = await http.post(
url, url,
headers: {"Content-Type": "application/json"}, headers: {"Content-Type": "application/json"},

View File

@ -63,7 +63,7 @@ class _ProfilPageState extends State<ProfilPage> {
} }
// Buat URL untuk endpoint user API // Buat URL untuk endpoint user API
var url = Uri.parse("https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/users"); var url = Uri.parse("https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/users");
// Kirim permintaan GET dengan token autentikasi // Kirim permintaan GET dengan token autentikasi
var response = await http.get( var response = await http.get(
@ -135,7 +135,6 @@ class _ProfilPageState extends State<ProfilPage> {
_nameController.text = userData?['name'] ?? ''; _nameController.text = userData?['name'] ?? '';
_emailController.text = userData?['email'] ?? ''; _emailController.text = userData?['email'] ?? '';
_alamatController.text = userData?['alamat'] ?? ''; _alamatController.text = userData?['alamat'] ?? '';
_nomorTeleponController.text = userData?['nomorTelepon'] ?? '';
_passwordController.text = ''; // Empty for security _passwordController.text = ''; // Empty for security
showDialog( showDialog(
@ -247,26 +246,26 @@ class _ProfilPageState extends State<ProfilPage> {
: null, : null,
), ),
SizedBox(height: 16), SizedBox(height: 16),
TextFormField( // TextFormField(
controller: _nomorTeleponController, // controller: _nomorTeleponController,
decoration: InputDecoration( // decoration: InputDecoration(
labelText: 'Nomor Telepon', // labelText: 'Nomor Telepon',
prefixIcon: Icon(Icons.phone, color: Color(0xFF9DC08D)), // prefixIcon: Icon(Icons.phone, color: Color(0xFF9DC08D)),
border: OutlineInputBorder( // border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // borderRadius: BorderRadius.circular(8),
), // ),
focusedBorder: OutlineInputBorder( // focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Color(0xFF9DC08D)), // borderSide: BorderSide(color: Color(0xFF9DC08D)),
), // ),
), // ),
keyboardType: TextInputType.phone, // keyboardType: TextInputType.phone,
validator: // validator:
(value) => // (value) =>
value?.isEmpty ?? true // value?.isEmpty ?? true
? 'Nomor telepon tidak boleh kosong' // ? 'Nomor telepon tidak boleh kosong'
: null, // : null,
), // ),
], ],
), ),
), ),

View File

@ -94,7 +94,9 @@ class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
Future<void> _fetchUserData() async { Future<void> _fetchUserData() async {
try { try {
// Buat URL untuk endpoint user API // Buat URL untuk endpoint user API
var url = Uri.parse("https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/users"); var url = Uri.parse(
"https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/users",
);
// Kirim permintaan GET dengan token autentikasi // Kirim permintaan GET dengan token autentikasi
var response = await http.get( var response = await http.get(
@ -489,7 +491,7 @@ class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
), ),
SizedBox(height: 4), SizedBox(height: 4),
Text( Text(
'Hasil: ${(riwayat['hasil'] as num?)?.toStringAsFixed(2) ?? "-"}', 'Hasil: ${riwayat['hasil'] != null ? "${(((riwayat['hasil'] as num) * 1000).floor() / 10).toStringAsFixed(1)}%" : "-"}',
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
), ),
SizedBox(height: 12), SizedBox(height: 12),