import 'package:flutter/material.dart'; import 'package:SIBAYAM/user/home_page.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:SIBAYAM/api_services/api_services.dart'; import 'dart:typed_data'; class HasilDiagnosaPage extends StatefulWidget { final Map hasilDiagnosa; final List gejalaTerpilih; HasilDiagnosaPage({ required this.hasilDiagnosa, required this.gejalaTerpilih, }); @override _HasilDiagnosaPageState createState() => _HasilDiagnosaPageState(); } class _HasilDiagnosaPageState extends State { // Maps to store additional data fetched for each item Map> penyakitDetails = {}; Map> hamaDetails = {}; bool isLoading = true; // Create an instance of your ApiService final ApiService _apiService = ApiService(); List> semuaPenyakit = []; List> semuaHama = []; @override void initState() { super.initState(); _fetchAdditionalData(); } @override Widget build(BuildContext context) { // Extract data from the nested structure final data = widget.hasilDiagnosa['data'] ?? {}; final List penyakitList = data['penyakit'] ?? []; final List hamaList = data['hama'] ?? []; final Map? hasilTertinggi = data['hasil_tertinggi']; // Ambiguity information from backend final bool isAmbiguous = data['is_ambiguous'] ?? false; final Map? ambiguityResolution = data['ambiguity_resolution']; // Filter information from backend final Map? filterInfo = data['filter_info']; // Get the first penyakit and hama (if any) Map? firstPenyakit = penyakitList.isNotEmpty ? penyakitList.first : null; Map? firstHama = hamaList.isNotEmpty ? hamaList.first : null; return Scaffold( appBar: AppBar( title: Text('Hasil Diagnosa'), backgroundColor: Color(0xFFEDF1D6), foregroundColor: Color(0xFF40513B), ), bottomNavigationBar: Padding( padding: EdgeInsets.all(16), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF40513B), padding: EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => HomePage()), ); }, child: Text( 'Selesai Diagnosa', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), body: Container( color: Color(0xFFEDF1D6), child: isLoading ? Center(child: CircularProgressIndicator()) : SingleChildScrollView( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Filter notification (if applicable) if (filterInfo != null) _buildFilterNotification(filterInfo), // Ambiguity notification (if applicable) if (isAmbiguous && ambiguityResolution != null) _buildAmbiguityNotification(ambiguityResolution), // Main result display - use hasil_tertinggi from backend _buildDetailedResultFromBackend(context, hasilTertinggi), 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', _buildOtherPossibilities( penyakitList, hasilTertinggi, 'penyakit', ), ), SizedBox(height: 24), // Other possible pests section _buildSection( context, 'Kemungkinan Hama Lainnya', _buildOtherPossibilities( hamaList, hasilTertinggi, 'hama', ), ), ], ), ), ), ); } Widget _buildFilterNotification(Map filterInfo) { final int totalSebelum = filterInfo['total_sebelum_filter'] ?? 0; final int totalSetelah = filterInfo['total_setelah_filter'] ?? 0; final int hasilTerfilter = filterInfo['hasil_terfilter'] ?? 0; final bool fallbackToSymptomCount = filterInfo['fallback_to_symptom_count'] ?? false; final String? fallbackReason = filterInfo['fallback_reason']; // Only show notification if there's filtering activity if (hasilTerfilter == 0 && !fallbackToSymptomCount) { return SizedBox.shrink(); } Color cardColor; Color iconColor; IconData iconData; String title; String description; if (fallbackToSymptomCount) { // Fallback scenario cardColor = Colors.orange.shade50; iconColor = Colors.orange.shade700; iconData = Icons.warning_amber_outlined; title = 'Penyesuaian Hasil Diagnosa'; description = 'Sistem menggunakan kecocokan gejala terbanyak karena hasil dengan akurasi 100% hanya cocok dengan 1 gejala.'; } else { // Normal filtering cardColor = Colors.green.shade50; iconColor = Colors.green.shade700; iconData = Icons.filter_alt_outlined; title = 'Filter Hasil Diagnosa'; description = 'Sistem memfilter $hasilTerfilter hasil dengan akurasi 100% yang hanya cocok dengan 1 gejala untuk memberikan diagnosis yang lebih akurat.'; } return Card( color: cardColor, elevation: 2, margin: EdgeInsets.only(bottom: 16), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(iconData, color: iconColor, size: 24), SizedBox(width: 8), Expanded( child: Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: iconColor, ), ), ), ], ), SizedBox(height: 12), Text(description, style: TextStyle(fontSize: 14)), if (fallbackReason != null) ...[ SizedBox(height: 8), Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.orange.shade100, borderRadius: BorderRadius.circular(8), ), child: Text( 'Alasan: $fallbackReason', style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: Colors.orange.shade800, ), ), ), ] else if (!fallbackToSymptomCount) ...[ SizedBox(height: 8), Row( children: [ Text( 'Total hasil: $totalSebelum → $totalSetelah', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.green.shade700, ), ), ], ), ], ], ), ), ); } Widget _buildAmbiguityNotification(Map ambiguityResolution) { final totalKandidat = ambiguityResolution['total_kandidat'] ?? 0; final terpilih = ambiguityResolution['terpilih'] ?? {}; final alasan = terpilih['alasan'] ?? ''; 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, ), ), ], ), ), ); } Widget _buildDetailedResultFromBackend( BuildContext context, Map? hasilTertinggi, ) { // If no result from backend, show empty message if (hasilTertinggi == null) { return _buildEmptyResult('Tidak ada hasil diagnosa yang tersedia'); } // Determine type based on the presence of id fields String type = ''; bool isPenyakit = false; if (hasilTertinggi.containsKey('id_penyakit') && hasilTertinggi['id_penyakit'] != null) { type = 'penyakit'; 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'; } // Get the complete data for the result final completeData = _getCompleteItemData(hasilTertinggi, type); // Extract the data we need with safe access 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(hasilTertinggi); // Get additional ambiguity info if available final jumlahGejalacocok = hasilTertinggi['jumlah_gejala_cocok']; final totalGejalaEntity = hasilTertinggi['total_gejala_entity']; final persentaseKesesuaian = hasilTertinggi['persentase_kesesuaian']; // Check if this is a perfect match result that would normally be filtered final isPerfectSingleMatch = (probabilitas * 100).round() == 100 && jumlahGejalacocok == 1; // Debug log print('DEBUG - Building detailed result for: $nama'); print('DEBUG - Type: $type, isPenyakit: $isPenyakit'); print('DEBUG - Probabilitas: $probabilitas'); print('DEBUG - Is perfect single match: $isPerfectSingleMatch'); return Card( elevation: 6, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), side: BorderSide( color: isPenyakit ? Colors.red.shade300 : Colors.orange.shade300, width: 2, ), ), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( isPenyakit ? Icons.coronavirus_outlined : Icons.bug_report, color: isPenyakit ? Colors.red.shade700 : Colors.orange.shade700, size: 28, ), SizedBox(width: 8), Expanded( child: Text( 'Kemungkinan Terbesar: $nama', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: isPenyakit ? Colors.red.shade700 : Colors.orange.shade700, ), ), ), _buildProbabilityIndicator(probabilitas), ], ), // Show warning if this is a perfect single match result if (isPerfectSingleMatch) Container( margin: EdgeInsets.only(top: 8), padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.yellow.shade100, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.yellow.shade300), ), child: Row( children: [ Icon( Icons.info_outline, size: 16, color: Colors.orange.shade700, ), SizedBox(width: 6), Expanded( child: Text( 'Hasil ini dipilih berdasarkan kecocokan gejala terbanyak', style: TextStyle( fontSize: 12, color: Colors.orange.shade700, fontWeight: FontWeight.w500, ), ), ), ], ), ), // 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( future: ApiService().getPenyakitImageBytesByFilename( foto.toString(), ), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Container( height: 180, color: Colors.grey.shade200, child: Center(child: CircularProgressIndicator()), ); } else if (snapshot.hasData && snapshot.data != null) { return Container( height: 180, color: Colors.grey.shade200, child: Center( child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.memory( snapshot.data!, fit: BoxFit.contain, width: double.infinity, height: 180, ), ), ), ); } else { return Container( height: 180, color: Colors.grey.shade200, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.image_not_supported, size: 40, color: Colors.grey, ), SizedBox(height: 8), Text( 'Gambar tidak tersedia', style: TextStyle(color: Colors.grey), ), ], ), ), ); } }, ), SizedBox(height: 16), // Description Text( 'Deskripsi', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), SizedBox(height: 8), Text(deskripsi, style: TextStyle(fontSize: 14)), SizedBox(height: 16), // Treatment Text( 'Penanganan', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), SizedBox(height: 8), Text(penanganan, style: TextStyle(fontSize: 14)), ], ), ), ); } Widget _buildOtherPossibilities( List itemList, Map? hasilTertinggi, String type, ) { // Check if there's a 100% match in hasilTertinggi if (hasilTertinggi != null) { double probabilitas = _getProbabilitas(hasilTertinggi); if ((probabilitas * 100).round() == 100) { return _buildEmptyResult( 'Ditemukan kecocokan 100% pada diagnosa utama', ); } } if (itemList.isEmpty) { 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 = []; 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 AND items with 100% probability AND items with higher probability than main diagnosis otherItems = itemList.where((item) { String? itemId; if (type == 'penyakit') { itemId = item['id_penyakit']?.toString(); } else { itemId = item['id_hama']?.toString(); } // Skip if this is the top result if (topResultId != null && itemId == topResultId) { return false; } // 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 { // If no hasilTertinggi, filter out 100% probability items from all except first otherItems = itemList.skip(1).where((item) { double itemProbabilitas = _getProbabilitas(item); return (itemProbabilitas * 100).round() != 100; }).toList(); } if (otherItems.isEmpty) { return _buildEmptyResult('Tidak ada kemungkinan ${type} lainnya'); } return Column( children: otherItems.map((item) => _buildItemCard(item, type)).toList(), ); } Future _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 penyakitList = data['penyakit'] ?? []; final List 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: () => {}, ); 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: () => {}, ); 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 _getCompleteItemData( Map item, String type, ) { Map 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? 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: () => {}, ); 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: () => {}, ); 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 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']; final persentaseKesesuaian = item['persentase_kesesuaian']; return Card( margin: EdgeInsets.only(bottom: 8), elevation: 2, child: ListTile( leading: Icon( type == 'penyakit' ? Icons.coronavirus_outlined : Icons.bug_report, color: type == 'penyakit' ? Color(0xFF9DC08D) : Color(0xFF7A9A6D), size: 24, ), title: Text( nama, style: TextStyle( fontWeight: FontWeight.w500, color: Color(0xFF40513B), ), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (jumlahGejalacocok != null && totalGejalaEntity != null) ...[ SizedBox(height: 4), Text( 'Kesesuaian: $jumlahGejalacocok/$totalGejalaEntity gejala', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), if (persentaseKesesuaian != null) Text( '(${persentaseKesesuaian.toStringAsFixed(1)}%)', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ], ), trailing: Container( width: 60, height: 30, decoration: BoxDecoration( color: type == 'penyakit' ? Color(0xFF9DC08D) : Color(0xFF7A9A6D), borderRadius: BorderRadius.circular(15), ), child: Center( child: Text( '${(probabilitas * 100).toStringAsFixed(0)}%', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12, ), ), ), ), ), ); } Widget _buildSection(BuildContext context, String title, Widget content) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), ), child: Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF40513B), ), ), ), SizedBox(height: 10), content, ], ); } Widget _buildEmptyResult(String message) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Center( child: Text( message, style: TextStyle( fontSize: 16, fontStyle: FontStyle.italic, color: Colors.grey.shade700, ), ), ), ); } Widget _buildProbabilityIndicator(double value) { final Color indicatorColor = value > 0.7 ? Colors.red : value > 0.4 ? Colors.orange : Colors.green; return Container( width: 60, height: 30, decoration: BoxDecoration( color: indicatorColor, borderRadius: BorderRadius.circular(15), ), child: Center( child: Text( '${((value * 1000).floor()/10).toStringAsFixed(1)}%', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), ), ); } double _getProbabilitas(Map? item) { if (item == null) { return 0.0; } if (item.containsKey('probabilitas_persen')) { var value = item['probabilitas_persen']; if (value is num) { return value.toDouble() / 100; } else if (value is String) { return (double.tryParse(value) ?? 0.0) / 100; } } if (item.containsKey('nilai_bayes')) { var value = item['nilai_bayes']; if (value is num) { return value.toDouble(); } else if (value is String) { return double.tryParse(value) ?? 0.0; } } if (item.containsKey('probabilitas')) { var value = item['probabilitas']; if (value is num) { return value.toDouble(); } else if (value is String) { return double.tryParse(value) ?? 0.0; } } return 0.0; } } Widget _buildEmptyResult(String message) { return Card( elevation: 2, child: Padding( padding: EdgeInsets.all(16), child: Center( child: Text( message, style: TextStyle( fontSize: 16, fontStyle: FontStyle.italic, color: Colors.grey[600], ), ), ), ), ); } Widget _buildProbabilityIndicator(double value) { Color color; if (value >= 0.7) { color = Colors.red; } else if (value >= 0.4) { color = Colors.orange; } else { color = Colors.green; } return Container( width: 60, height: 30, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(15), ), child: Center( child: Text( '${((value * 1000).floor()/10).toStringAsFixed(1)}%', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), ), ); }