rdsarjito commited on
Commit
2bb0484
Β·
1 Parent(s): 8a26e5a
Files changed (2) hide show
  1. app.py +25 -3
  2. backup2.py +386 -0
app.py CHANGED
@@ -171,8 +171,19 @@ def load_model():
171
  def predict_allergen(model, vectorizer, input_text):
172
  X_input = vectorizer.transform([input_text])
173
  prediction = model.predict(X_input)
174
- probabilities = model.predict_proba(X_input)
175
- return prediction[0], probabilities[0]
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  # === Scraping bahan dari Cookpad ===
178
  def get_ingredients_from_cookpad(url):
@@ -217,7 +228,18 @@ def display_results(results, probabilities, labels):
217
  # Display each allergen result
218
  for i, (allergen, status) in enumerate(results.items()):
219
  emoji = allergen_emojis.get(allergen, 'πŸ“‹')
220
- confidence = probabilities[i][1] * 100 # Get probability for positive class
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  if status == 1: # Detected
223
  detected_allergens.append(allergen)
 
171
  def predict_allergen(model, vectorizer, input_text):
172
  X_input = vectorizer.transform([input_text])
173
  prediction = model.predict(X_input)
174
+ try:
175
+ probabilities = model.predict_proba(X_input)
176
+ return prediction[0], probabilities[0]
177
+ except:
178
+ # Fallback jika predict_proba tidak tersedia atau error
179
+ # Buat dummy probabilities berdasarkan prediction
180
+ dummy_probs = []
181
+ for pred in prediction[0]:
182
+ if pred == 1:
183
+ dummy_probs.append([0.1, 0.9]) # [prob_negative, prob_positive]
184
+ else:
185
+ dummy_probs.append([0.9, 0.1])
186
+ return prediction[0], dummy_probs
187
 
188
  # === Scraping bahan dari Cookpad ===
189
  def get_ingredients_from_cookpad(url):
 
228
  # Display each allergen result
229
  for i, (allergen, status) in enumerate(results.items()):
230
  emoji = allergen_emojis.get(allergen, 'πŸ“‹')
231
+
232
+ # Handle different probability formats
233
+ try:
234
+ if isinstance(probabilities[i], (list, tuple)) and len(probabilities[i]) >= 2:
235
+ confidence = probabilities[i][1] * 100 # Get probability for positive class
236
+ elif isinstance(probabilities[i], (int, float)):
237
+ confidence = probabilities[i] * 100
238
+ else:
239
+ confidence = 50.0 # Default fallback
240
+ except (IndexError, TypeError):
241
+ # Fallback confidence based on prediction
242
+ confidence = 85.0 if status == 1 else 15.0
243
 
244
  if status == 1: # Detected
245
  detected_allergens.append(allergen)
backup2.py ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pickle
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+
6
+ # === Custom CSS for better styling ===
7
+ def load_css():
8
+ st.markdown("""
9
+ <style>
10
+ /* Main app styling */
11
+ .main-header {
12
+ text-align: center;
13
+ padding: 2rem 0;
14
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15
+ color: white;
16
+ border-radius: 10px;
17
+ margin-bottom: 2rem;
18
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
19
+ }
20
+
21
+ .main-header h1 {
22
+ font-size: 2.5rem;
23
+ margin-bottom: 0.5rem;
24
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
25
+ }
26
+
27
+ .main-header p {
28
+ font-size: 1.1rem;
29
+ opacity: 0.9;
30
+ margin: 0;
31
+ }
32
+
33
+ /* Card styling */
34
+ .info-card {
35
+ background: white;
36
+ padding: 1.5rem;
37
+ border-radius: 10px;
38
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
39
+ border-left: 4px solid #667eea;
40
+ margin: 1rem 0;
41
+ }
42
+
43
+ /* Results styling */
44
+ .result-positive {
45
+ background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
46
+ color: white;
47
+ padding: 1rem;
48
+ border-radius: 8px;
49
+ margin: 0.5rem 0;
50
+ box-shadow: 0 2px 4px rgba(255, 107, 107, 0.3);
51
+ }
52
+
53
+ .result-negative {
54
+ background: linear-gradient(135deg, #51cf66, #69db7c);
55
+ color: white;
56
+ padding: 1rem;
57
+ border-radius: 8px;
58
+ margin: 0.5rem 0;
59
+ box-shadow: 0 2px 4px rgba(81, 207, 102, 0.3);
60
+ }
61
+
62
+ /* Button styling */
63
+ .stButton > button {
64
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
65
+ color: white;
66
+ border: none;
67
+ border-radius: 25px;
68
+ padding: 0.75rem 2rem;
69
+ font-weight: bold;
70
+ transition: all 0.3s ease;
71
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
72
+ }
73
+
74
+ .stButton > button:hover {
75
+ transform: translateY(-2px);
76
+ box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
77
+ }
78
+
79
+ /* Radio button styling */
80
+ .stRadio > div {
81
+ background: white;
82
+ padding: 1rem;
83
+ border-radius: 10px;
84
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
85
+ }
86
+
87
+ /* Text area styling */
88
+ .stTextArea > div > div > textarea {
89
+ border-radius: 10px;
90
+ border: 2px solid #e0e0e0;
91
+ transition: border-color 0.3s ease;
92
+ }
93
+
94
+ .stTextArea > div > div > textarea:focus {
95
+ border-color: #667eea;
96
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.2);
97
+ }
98
+
99
+ /* Expander styling */
100
+ .streamlit-expanderHeader {
101
+ background: linear-gradient(135deg, #f8f9fa, #e9ecef);
102
+ border-radius: 10px;
103
+ border: 1px solid #dee2e6;
104
+ }
105
+
106
+ /* Progress indicator */
107
+ .progress-text {
108
+ text-align: center;
109
+ font-weight: bold;
110
+ color: #667eea;
111
+ margin: 1rem 0;
112
+ }
113
+
114
+ /* Improved ingredient list styling - single div */
115
+ .ingredients-container {
116
+ background: #f8f9fa;
117
+ padding: 1rem 1.5rem;
118
+ margin: 1rem 0;
119
+ border-radius: 10px;
120
+ border-left: 4px solid #667eea;
121
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
122
+ line-height: 1.6;
123
+ font-size: 1rem;
124
+ }
125
+
126
+ .ingredients-container strong {
127
+ color: #495057;
128
+ font-weight: 600;
129
+ }
130
+
131
+ /* Footer */
132
+ .footer {
133
+ text-align: center;
134
+ padding: 2rem 0;
135
+ color: #6c757d;
136
+ border-top: 1px solid #e9ecef;
137
+ margin-top: 3rem;
138
+ }
139
+ </style>
140
+ """, unsafe_allow_html=True)
141
+
142
+ # === Load TF-IDF Vectorizer ===
143
+ @st.cache_resource
144
+ def load_vectorizer():
145
+ with open("saved_models/tfidf_vectorizer.pkl", "rb") as f:
146
+ return pickle.load(f)
147
+
148
+ # === Load XGBoost Model ===
149
+ @st.cache_resource
150
+ def load_model():
151
+ with open("saved_models/XGBoost_model.pkl", "rb") as f:
152
+ return pickle.load(f)
153
+
154
+ # === Prediksi ===
155
+ def predict_allergen(model, vectorizer, input_text):
156
+ X_input = vectorizer.transform([input_text])
157
+ prediction = model.predict(X_input)
158
+ return prediction[0]
159
+
160
+ # === Scraping bahan dari Cookpad ===
161
+ def get_ingredients_from_cookpad(url):
162
+ headers = {"User-Agent": "Mozilla/5.0"}
163
+ try:
164
+ response = requests.get(url, headers=headers)
165
+ if response.status_code != 200:
166
+ return None, "Gagal mengambil halaman."
167
+ soup = BeautifulSoup(response.text, "html.parser")
168
+ ingredient_div = soup.find("div", class_="ingredient-list")
169
+ if not ingredient_div:
170
+ return None, "Tidak menemukan elemen bahan."
171
+
172
+ ingredients = []
173
+ for item in ingredient_div.find_all("li"):
174
+ amount = item.find("bdi")
175
+ name = item.find("span")
176
+ if amount and name:
177
+ ingredients.append(f"{amount.get_text(strip=True)} {name.get_text(strip=True)}")
178
+ else:
179
+ ingredients.append(item.get_text(strip=True))
180
+
181
+ return ingredients, None
182
+ except Exception as e:
183
+ return None, f"Terjadi kesalahan: {str(e)}"
184
+
185
+ # === Display results with custom styling ===
186
+ def display_results(results):
187
+ st.markdown("### 🎯 Hasil Analisis Alergen")
188
+
189
+ positive_results = []
190
+ negative_results = []
191
+
192
+ for allergen, status in results.items():
193
+ if status == 1:
194
+ positive_results.append(allergen)
195
+ else:
196
+ negative_results.append(allergen)
197
+
198
+ # Display positive results (allergens detected)
199
+ if positive_results:
200
+ st.markdown("#### ⚠️ **Alergen Terdeteksi:**")
201
+ for allergen in positive_results:
202
+ st.markdown(f'<div class="result-positive">🚨 <strong>{allergen}</strong></div>', unsafe_allow_html=True)
203
+
204
+ # Display negative results (safe allergens)
205
+ if negative_results:
206
+ st.markdown("#### βœ… **Aman dari Alergen:**")
207
+ for allergen in negative_results:
208
+ st.markdown(f'<div class="result-negative">βœ“ {allergen}</div>', unsafe_allow_html=True)
209
+
210
+ # Show summary
211
+ if not positive_results:
212
+ st.markdown('<div class="result-negative">πŸŽ‰ <strong>Tidak ada alergen berbahaya terdeteksi!</strong></div>', unsafe_allow_html=True)
213
+
214
+ # === Main UI ===
215
+ def main():
216
+ st.set_page_config(
217
+ page_title="Deteksi Alergen Makanan",
218
+ page_icon="πŸ₯˜",
219
+ layout="wide",
220
+ initial_sidebar_state="expanded"
221
+ )
222
+
223
+ # Load custom CSS
224
+ load_css()
225
+
226
+ # Header
227
+ st.markdown("""
228
+ <div class="main-header">
229
+ <h1>πŸ₯˜ Deteksi Alergen Makanan</h1>
230
+ <p>Analisis kandungan alergen dalam resep makanan dengan teknologi AI</p>
231
+ </div>
232
+ """, unsafe_allow_html=True)
233
+
234
+ # Sidebar info
235
+ with st.sidebar:
236
+ st.markdown("### πŸ“‹ Informasi Alergen")
237
+ st.markdown("""
238
+ **Alergen yang dapat dideteksi:**
239
+ - πŸ₯› Susu
240
+ - πŸ₯œ Kacang
241
+ - πŸ₯š Telur
242
+ - 🦐 Makanan Laut
243
+ - 🌾 Gandum
244
+ """)
245
+
246
+ st.markdown("### πŸ’‘ Tips Penggunaan")
247
+ st.markdown("""
248
+ - Masukkan bahan dengan detail
249
+ - Gunakan nama bahan dalam bahasa Indonesia
250
+ - Untuk URL Cookpad, pastikan link valid
251
+ - Maksimal 20 URL per analisis
252
+ """)
253
+
254
+ # Main content
255
+ col1, col2, col3 = st.columns([1, 6, 1])
256
+
257
+ with col2:
258
+ # Input method selection
259
+ st.markdown("### πŸ”§ Pilih Metode Input")
260
+ input_mode = st.radio(
261
+ "",
262
+ ["πŸ“ Input Manual", "πŸ”— URL Cookpad"],
263
+ horizontal=True
264
+ )
265
+
266
+ # Load model components
267
+ try:
268
+ vectorizer = load_vectorizer()
269
+ model = load_model()
270
+ labels = ['Susu', 'Kacang', 'Telur', 'Makanan Laut', 'Gandum']
271
+ except Exception as e:
272
+ st.error(f"❌ Gagal memuat model: {str(e)}")
273
+ st.stop()
274
+
275
+ st.markdown("---")
276
+
277
+ if input_mode == "πŸ“ Input Manual":
278
+ st.markdown("### πŸ“ Masukkan Bahan Makanan")
279
+
280
+ # Info card
281
+ st.markdown("""
282
+ <div class="info-card">
283
+ <strong>πŸ’‘ Petunjuk:</strong> Masukkan daftar bahan makanan yang ingin dianalisis.
284
+ Pisahkan setiap bahan dengan koma atau baris baru.
285
+ </div>
286
+ """, unsafe_allow_html=True)
287
+
288
+ input_text = st.text_area(
289
+ "",
290
+ height=150,
291
+ placeholder="Contoh: telur, susu, tepung terigu, garam, mentega..."
292
+ )
293
+
294
+ col_btn1, col_btn2, col_btn3 = st.columns([2, 2, 2])
295
+ with col_btn2:
296
+ if st.button("πŸ” Analisis Alergen", use_container_width=True):
297
+ if not input_text.strip():
298
+ st.warning("⚠️ Mohon masukkan bahan makanan terlebih dahulu.")
299
+ else:
300
+ with st.spinner("πŸ”„ Sedang menganalisis..."):
301
+ pred = predict_allergen(model, vectorizer, input_text)
302
+ results = dict(zip(labels, pred))
303
+
304
+ st.success("βœ… Analisis selesai!")
305
+ display_results(results)
306
+
307
+ elif input_mode == "πŸ”— URL Cookpad":
308
+ st.markdown("### πŸ”— Analisis dari URL Cookpad")
309
+
310
+ # Info card
311
+ st.markdown("""
312
+ <div class="info-card">
313
+ <strong>πŸ’‘ Petunjuk:</strong> Masukkan hingga 20 URL resep dari Cookpad.
314
+ Setiap URL harus dalam baris terpisah.
315
+ </div>
316
+ """, unsafe_allow_html=True)
317
+
318
+ urls_input = st.text_area(
319
+ "",
320
+ placeholder="https://cookpad.com/id/resep/...\nhttps://cookpad.com/id/resep/...",
321
+ height=200
322
+ )
323
+
324
+ urls = [url.strip() for url in urls_input.splitlines() if url.strip()]
325
+ if len(urls) > 20:
326
+ st.warning("⚠️ Maksimal hanya bisa memproses 20 URL. Menggunakan 20 URL pertama.")
327
+ urls = urls[:20]
328
+
329
+ if urls:
330
+ st.info(f"πŸ“Š Siap memproses {len(urls)} URL")
331
+
332
+ if st.button("πŸ” Analisis dari URL", use_container_width=True):
333
+ if not urls:
334
+ st.warning("⚠️ Mohon masukkan minimal satu URL.")
335
+ else:
336
+ # Progress bar
337
+ progress_bar = st.progress(0)
338
+ status_text = st.empty()
339
+
340
+ for i, url in enumerate(urls):
341
+ # Update progress
342
+ progress = (i + 1) / len(urls)
343
+ progress_bar.progress(progress)
344
+ status_text.markdown(f'<div class="progress-text">Memproses resep {i+1} dari {len(urls)}</div>', unsafe_allow_html=True)
345
+
346
+ ingredients, error = get_ingredients_from_cookpad(url)
347
+
348
+ with st.expander(f"πŸ“– Resep #{i+1}", expanded=False):
349
+ st.markdown(f"**URL:** {url}")
350
+
351
+ if error:
352
+ st.error(f"❌ {error}")
353
+ else:
354
+ st.success("βœ… Bahan berhasil diambil!")
355
+
356
+ # Display ingredients in a single nice container
357
+ ingredients_text = ", ".join(ingredients)
358
+ st.markdown(f'''
359
+ <div class="ingredients-container">
360
+ <strong>🧾 Daftar Bahan:</strong><br>
361
+ {ingredients_text}
362
+ </div>
363
+ ''', unsafe_allow_html=True)
364
+
365
+ # Predict allergens
366
+ joined_ingredients = " ".join(ingredients)
367
+ pred = predict_allergen(model, vectorizer, joined_ingredients)
368
+ results = dict(zip(labels, pred))
369
+
370
+ st.markdown("---")
371
+ display_results(results)
372
+
373
+ # Clear progress indicators
374
+ progress_bar.empty()
375
+ status_text.empty()
376
+ st.success("πŸŽ‰ Semua resep telah dianalisis!")
377
+
378
+ # Footer
379
+ st.markdown("""
380
+ <div class="footer">
381
+ <p>πŸ”¬ Powered by XGBoost & TF-IDF | Made with ❀️ using Streamlit</p>
382
+ </div>
383
+ """, unsafe_allow_html=True)
384
+
385
+ if __name__ == "__main__":
386
+ main()