add new function in diagnosaController
This commit is contained in:
parent
f9ba830cfa
commit
c012b08a5f
|
@ -152,6 +152,36 @@ function filterSingleSymptomPerfectMatch(results) {
|
|||
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) => {
|
||||
const { gejala } = req.body;
|
||||
const userId = req.user?.id;
|
||||
|
@ -236,6 +266,26 @@ exports.diagnosa = async (req, res) => {
|
|||
|
||||
filterInfo.fallback_to_symptom_count = true;
|
||||
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 ==========
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="frontend"
|
||||
android:label="SIBAYAM"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
|
|
|
@ -7,15 +7,15 @@ import 'package:image_picker/image_picker.dart';
|
|||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
class ApiService {
|
||||
static const String baseUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/auth';
|
||||
static const String gejalaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/gejala';
|
||||
static const String hamaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/hama';
|
||||
static const String penyakitUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/penyakit';
|
||||
static const String rulesPenyakitUrl ='https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/rules_penyakit';
|
||||
static const String rulesHamaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/rules_hama';
|
||||
static const String userUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/users';
|
||||
static const String diagnosaUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/diagnosa';
|
||||
static const String historiUrl = 'https://backend-sistem-pakar-diagnosa-penya.vercel.app/api/histori';
|
||||
static const String baseUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/auth';
|
||||
static const String gejalaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/gejala';
|
||||
static const String hamaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/hama';
|
||||
static const String penyakitUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/penyakit';
|
||||
static const String rulesPenyakitUrl ='https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/rules_penyakit';
|
||||
static const String rulesHamaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/rules_hama';
|
||||
static const String userUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/users';
|
||||
static const String diagnosaUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/diagnosa';
|
||||
static const String historiUrl = 'https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/histori';
|
||||
static const Duration timeout = Duration(seconds: 15);
|
||||
|
||||
/// 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 {
|
||||
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');
|
||||
final response = await http.get(url);
|
||||
|
||||
|
@ -721,7 +721,7 @@ Future<List<Map<String, dynamic>>> getAllHistori() async {
|
|||
|
||||
Future<Uint8List?> getPenyakitImageBytesByFilename(String filename) async {
|
||||
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');
|
||||
final response = await http.get(url);
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class _MyAppState extends State<MyApp> {
|
|||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Sistem Pakar Bayam',
|
||||
title: 'SIBAYAM',
|
||||
home: _initialScreen,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ class DetailRiwayatPage extends StatelessWidget {
|
|||
Text('Gejala: $gejalaList', style: TextStyle(fontSize: 16)),
|
||||
SizedBox(height: 8),
|
||||
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),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
|
|
|
@ -56,9 +56,14 @@ class _DiagnosaPageState extends State<DiagnosaPage> {
|
|||
}
|
||||
|
||||
void prosesHasilDiagnosa() async {
|
||||
if (gejalaTerpilihIds.isEmpty) {
|
||||
// Validasi minimal 3 gejala
|
||||
if (gejalaTerpilihIds.length < 3) {
|
||||
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;
|
||||
}
|
||||
|
@ -175,10 +180,30 @@ class _DiagnosaPageState extends State<DiagnosaPage> {
|
|||
),
|
||||
),
|
||||
Divider(color: Colors.grey),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Gejala Terpilih",
|
||||
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: 100,
|
||||
|
@ -202,15 +227,21 @@ class _DiagnosaPageState extends State<DiagnosaPage> {
|
|||
height: 30,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
backgroundColor: gejalaTerpilihIds.length >= 3 ? Colors.green : Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
onPressed: prosesHasilDiagnosa,
|
||||
onPressed: gejalaTerpilihIds.length >= 3 ? prosesHasilDiagnosa : null,
|
||||
child: Text(
|
||||
"Lihat Hasil Diagnosa",
|
||||
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
|
||||
gejalaTerpilihIds.length >= 3
|
||||
? "Lihat Hasil Diagnosa"
|
||||
: "Pilih minimal 3 gejala",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -257,10 +257,10 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (passwordController.text.length < 6) {
|
||||
if (passwordController.text.length < 8) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Password minimal 6 karakter.'),
|
||||
content: Text('Password minimal 8 karakter.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -614,6 +614,12 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
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
|
||||
List otherItems = [];
|
||||
|
||||
|
@ -626,7 +632,7 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
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) {
|
||||
String? itemId;
|
||||
if (type == 'penyakit') {
|
||||
|
@ -640,12 +646,19 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Skip if this item has 100% probability
|
||||
// Get item probability
|
||||
double itemProbabilitas = _getProbabilitas(item);
|
||||
|
||||
// Skip if this item has 100% probability
|
||||
if ((itemProbabilitas * 100).round() == 100) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if this item has higher probability than main diagnosis
|
||||
if (mainDiagnosisProbability != null && itemProbabilitas > mainDiagnosisProbability) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
} else {
|
||||
|
@ -975,7 +988,7 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${(value * 100).toStringAsFixed(0)}%',
|
||||
'${((value * 1000).floor()/10).toStringAsFixed(1)}%',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
|
@ -1055,7 +1068,7 @@ Widget _buildProbabilityIndicator(double value) {
|
|||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${(value * 100).toStringAsFixed(0)}%',
|
||||
'${((value * 1000).floor()/10).toStringAsFixed(1)}%',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -62,7 +62,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
}
|
||||
|
||||
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(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
|
|
|
@ -63,7 +63,7 @@ class _ProfilPageState extends State<ProfilPage> {
|
|||
}
|
||||
|
||||
// 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
|
||||
var response = await http.get(
|
||||
|
@ -135,7 +135,6 @@ class _ProfilPageState extends State<ProfilPage> {
|
|||
_nameController.text = userData?['name'] ?? '';
|
||||
_emailController.text = userData?['email'] ?? '';
|
||||
_alamatController.text = userData?['alamat'] ?? '';
|
||||
_nomorTeleponController.text = userData?['nomorTelepon'] ?? '';
|
||||
_passwordController.text = ''; // Empty for security
|
||||
|
||||
showDialog(
|
||||
|
@ -247,26 +246,26 @@ class _ProfilPageState extends State<ProfilPage> {
|
|||
: null,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _nomorTeleponController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nomor Telepon',
|
||||
prefixIcon: Icon(Icons.phone, color: Color(0xFF9DC08D)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Color(0xFF9DC08D)),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
validator:
|
||||
(value) =>
|
||||
value?.isEmpty ?? true
|
||||
? 'Nomor telepon tidak boleh kosong'
|
||||
: null,
|
||||
),
|
||||
// TextFormField(
|
||||
// controller: _nomorTeleponController,
|
||||
// decoration: InputDecoration(
|
||||
// labelText: 'Nomor Telepon',
|
||||
// prefixIcon: Icon(Icons.phone, color: Color(0xFF9DC08D)),
|
||||
// border: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.circular(8),
|
||||
// ),
|
||||
// focusedBorder: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.circular(8),
|
||||
// borderSide: BorderSide(color: Color(0xFF9DC08D)),
|
||||
// ),
|
||||
// ),
|
||||
// keyboardType: TextInputType.phone,
|
||||
// validator:
|
||||
// (value) =>
|
||||
// value?.isEmpty ?? true
|
||||
// ? 'Nomor telepon tidak boleh kosong'
|
||||
// : null,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -94,7 +94,9 @@ class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
|
|||
Future<void> _fetchUserData() async {
|
||||
try {
|
||||
// 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
|
||||
var response = await http.get(
|
||||
|
@ -489,7 +491,7 @@ class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
|
|||
),
|
||||
SizedBox(height: 4),
|
||||
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),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
|
|
Loading…
Reference in New Issue