Rida-Zehra commited on
Commit
d2f4a4e
Β·
verified Β·
1 Parent(s): 0f765d4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1189 -0
app.py ADDED
@@ -0,0 +1,1189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # budget_tracker_with_voice_ocr.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ import seaborn as sns
7
+ import plotly.express as px
8
+ import plotly.graph_objects as go
9
+ from datetime import datetime, timedelta
10
+ import pytesseract
11
+ from PIL import Image
12
+ import speech_recognition as sr
13
+ import io
14
+ import base64
15
+ import warnings
16
+ import re
17
+ import json
18
+ import os
19
+ import tempfile
20
+ from transformers import pipeline
21
+ import torch
22
+ import pytesseract
23
+ import os
24
+
25
+
26
+ warnings.filterwarnings('ignore')
27
+
28
+ # Set Tesseract path (update this path according to your system)
29
+ # For Windows: r"C:\Program Files\Tesseract-OCR\tesseract.exe"
30
+ # For Mac: "/usr/local/bin/tesseract"
31
+ # For Linux: "/usr/bin/tesseract"
32
+ try:
33
+ # You can set your Tesseract path here
34
+ TESSERACT_PATH = os.getenv("TESSERACT_PATH", r'/usr/bin/tesseract') # Default for Linux
35
+ pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH
36
+ except:
37
+ pass # Use default path
38
+
39
+
40
+
41
+ # Initialize session state for data persistence
42
+ def initialize_session_state():
43
+ """Initialize all session state variables"""
44
+ try:
45
+ if 'expenses' not in st.session_state:
46
+ st.session_state.expenses = pd.DataFrame(columns=['date', 'amount', 'category', 'description', 'receipt_image'])
47
+ if 'budgets' not in st.session_state:
48
+ st.session_state.budgets = pd.DataFrame(columns=['category', 'budget_amount', 'period'])
49
+ if 'savings_goals' not in st.session_state:
50
+ st.session_state.savings_goals = pd.DataFrame(columns=['goal_name', 'target_amount', 'current_amount', 'target_date'])
51
+ if 'notifications' not in st.session_state:
52
+ st.session_state.notifications = []
53
+ if 'whisper_model' not in st.session_state:
54
+ st.session_state.whisper_model = None
55
+ return True
56
+ except Exception as e:
57
+ st.error(f"Error initializing session state: {str(e)}")
58
+ return False
59
+
60
+ # Voice Recognition with Whisper (Corrected Implementation)
61
+ def load_whisper_model():
62
+ """Load Whisper model for speech recognition"""
63
+ try:
64
+ if st.session_state.whisper_model is None:
65
+ with st.spinner("Loading Whisper model... This may take a moment."):
66
+ st.session_state.whisper_model = pipeline(
67
+ "automatic-speech-recognition",
68
+ model="openai/whisper-tiny", # Using tiny model for faster loading
69
+ chunk_length_s=30,
70
+ )
71
+ return st.session_state.whisper_model
72
+ except Exception as e:
73
+ st.error(f"Error loading Whisper model: {str(e)}")
74
+ return None
75
+
76
+ def transcribe_audio_with_whisper(audio_file_path):
77
+ """Transcribe audio using Whisper model"""
78
+ try:
79
+ model = load_whisper_model()
80
+ if model is None:
81
+ return None
82
+
83
+ with st.spinner("Transcribing audio..."):
84
+ output = model(
85
+ audio_file_path,
86
+ generate_kwargs={"task": "transcribe"},
87
+ batch_size=8,
88
+ return_timestamps=False,
89
+ )
90
+ return output["text"]
91
+ except Exception as e:
92
+ st.error(f"Error in transcription: {str(e)}")
93
+ return None
94
+
95
+ def transcribe_audio_with_google(audio_file_path):
96
+ """Fallback: Transcribe audio using Google Speech Recognition"""
97
+ try:
98
+ recognizer = sr.Recognizer()
99
+ with sr.AudioFile(audio_file_path) as source:
100
+ audio = recognizer.record(source)
101
+ text = recognizer.recognize_google(audio)
102
+ return text
103
+ except Exception as e:
104
+ st.error(f"Google Speech Recognition error: {str(e)}")
105
+ return None
106
+
107
+ def voice_expense_recording():
108
+ """
109
+ Function to record expense using voice input
110
+ LLM Needed: NO - Uses Whisper/GSR for speech recognition
111
+ Could use LLM for better natural language understanding
112
+ """
113
+ try:
114
+ st.subheader("🎀 Voice Expense Recording")
115
+
116
+ # Audio input options
117
+ audio_option = st.radio("Choose audio input method:",
118
+ ["Microphone (Real-time)", "Upload Audio File"])
119
+
120
+ if audio_option == "Microphone (Real-time)":
121
+ # Check if microphone is available
122
+ try:
123
+ recognizer = sr.Recognizer()
124
+ mic_available = True
125
+ except:
126
+ mic_available = False
127
+ st.warning("Microphone not available. Please check your device settings.")
128
+
129
+ if mic_available and st.button("πŸŽ™οΈ Start Voice Recording"):
130
+ try:
131
+ with sr.Microphone() as source:
132
+ st.info("🎀 Listening... Please speak your expense (e.g., 'I spent 500 rupees on groceries')")
133
+ # Adjust for ambient noise
134
+ recognizer.adjust_for_ambient_noise(source, duration=1)
135
+ audio = recognizer.listen(source, timeout=10)
136
+
137
+ # Save audio to temporary file for processing
138
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
139
+ with open(tmp_file.name, "wb") as f:
140
+ f.write(audio.get_wav_data())
141
+ temp_filename = tmp_file.name
142
+
143
+ # Try Whisper first, fallback to Google
144
+ text = transcribe_audio_with_whisper(temp_filename)
145
+ if text is None:
146
+ text = transcribe_audio_with_google(temp_filename)
147
+
148
+ # Clean up temporary file
149
+ os.unlink(temp_filename)
150
+
151
+ if text:
152
+ st.success(f"βœ… Recognized: {text}")
153
+ process_voice_text(text)
154
+ else:
155
+ st.error("❌ Failed to transcribe audio")
156
+
157
+ except sr.WaitTimeoutError:
158
+ st.error("⏰ Timeout: No speech detected within 10 seconds")
159
+ except sr.UnknownValueError:
160
+ st.error("πŸ€” Could not understand audio. Please try again.")
161
+ except sr.RequestError as e:
162
+ st.error(f"🌐 Could not request results: {e}")
163
+ except Exception as e:
164
+ st.error(f"❌ Error processing voice input: {str(e)}")
165
+
166
+ else: # Upload Audio File
167
+ uploaded_audio = st.file_uploader("Upload Audio File", type=['wav', 'mp3', 'm4a'])
168
+
169
+ if uploaded_audio is not None:
170
+ if st.button("πŸ”Š Process Audio File"):
171
+ try:
172
+ # Save uploaded file temporarily
173
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(uploaded_audio.name)[1]) as tmp_file:
174
+ tmp_file.write(uploaded_audio.getvalue())
175
+ temp_filename = tmp_file.name
176
+
177
+ # Process audio file
178
+ with st.spinner("Processing audio file..."):
179
+ # Try Whisper first, fallback to Google
180
+ text = transcribe_audio_with_whisper(temp_filename)
181
+ if text is None:
182
+ text = transcribe_audio_with_google(temp_filename)
183
+
184
+ # Clean up temporary file
185
+ os.unlink(temp_filename)
186
+
187
+ if text:
188
+ st.success(f"βœ… Transcribed: {text}")
189
+ process_voice_text(text)
190
+ else:
191
+ st.error("❌ Failed to transcribe audio file")
192
+
193
+ except Exception as e:
194
+ st.error(f"❌ Error processing audio file: {str(e)}")
195
+
196
+ # Instructions
197
+ st.info("πŸ’‘ Tip: Say something like 'I spent 500 rupees on groceries at Big Bazaar'")
198
+
199
+ except Exception as e:
200
+ st.error(f"❌ Critical error in voice recording: {str(e)}")
201
+
202
+ def process_voice_text(text):
203
+ """Process transcribed voice text to extract expense details"""
204
+ try:
205
+ # Enhanced parsing logic
206
+ st.info("πŸ”„ Processing voice input...")
207
+ amount = 0
208
+ category = "Other"
209
+ description = text
210
+
211
+ # Enhanced category detection
212
+ categories = {
213
+ 'Food': ['food', 'groceries', 'restaurant', 'cafe', 'meal', 'lunch', 'dinner', 'breakfast', 'dhaba', 'hotel'],
214
+ 'Transport': ['transport', 'travel', 'taxi', 'uber', 'ola', 'bus', 'train', 'flight', 'fuel', 'petrol', 'diesel', 'auto'],
215
+ 'Shopping': ['shopping', 'clothes', 'electronics', 'purchase', 'buy', 'mall', 'store', 'market'],
216
+ 'Entertainment': ['entertainment', 'movie', 'cinema', 'game', 'fun', 'party', 'netflix', 'spotify'],
217
+ 'Bills': ['bill', 'electricity', 'water', 'internet', 'phone', 'rent', 'insurance', 'subscription'],
218
+ 'Health': ['medicine', 'doctor', 'hospital', 'pharmacy', 'health', 'medical'],
219
+ 'Education': ['education', 'school', 'college', 'books', 'course', 'tuition', 'study']
220
+ }
221
+
222
+ text_lower = text.lower()
223
+ for cat, keywords in categories.items():
224
+ if any(keyword in text_lower for keyword in keywords):
225
+ category = cat
226
+ break
227
+
228
+ # Extract numbers for amount using regex
229
+ amount_pattern = r'(?:$|\$|rs|rupees?|dollars?)\s*(\d+(?:\.\d+)?)|(\d+(?:\.\d+)?)\s*(?:$|\$|rs|rupees?|dollars?)'
230
+
231
+ # amount_pattern = r'(?:$|rs|rupees?)\s*(\d+(?:\.\d+)?)|(\d+(?:\.\d+)?)\s*(?:$|rs|rupees?)'
232
+ matches = re.findall(amount_pattern, text_lower)
233
+ if matches:
234
+ for match in matches:
235
+ for group in match:
236
+ if group and (group.replace('.', '').isdigit()):
237
+ amount = float(group)
238
+ break
239
+ if amount > 0:
240
+ break
241
+
242
+ # Fallback: look for any number
243
+ if amount == 0:
244
+ numbers = re.findall(r'\d+(?:\.\d+)?', text)
245
+ if numbers:
246
+ amount = float(numbers[0])
247
+
248
+ # Save to expenses
249
+ new_expense = pd.DataFrame({
250
+ 'date': [datetime.now().strftime('%Y-%m-%d')],
251
+ 'amount': [amount],
252
+ 'category': [category],
253
+ 'description': [description],
254
+ 'receipt_image': ['']
255
+ })
256
+ st.session_state.expenses = pd.concat([st.session_state.expenses, new_expense], ignore_index=True)
257
+ st.success(f"βœ… Expense logged: ${amount:.2f} for {category}")
258
+
259
+
260
+ # Check budget alerts
261
+ check_budget_alerts(amount, category)
262
+
263
+ except Exception as e:
264
+ st.error(f"❌ Error processing voice text: {str(e)}")
265
+
266
+ # OCR Processing (Corrected Implementation)
267
+ class OCRExtractor:
268
+ def __init__(self):
269
+ pass
270
+
271
+ def extract_text_from_image(self, image_input):
272
+ """Extract text from image using Tesseract OCR"""
273
+ try:
274
+ # Handle different input types
275
+ if hasattr(image_input, 'read'):
276
+ # Uploaded file
277
+ image = Image.open(image_input)
278
+ elif isinstance(image_input, str):
279
+ # File path
280
+ image = Image.open(image_input)
281
+ else:
282
+ # PIL Image
283
+ image = image_input
284
+
285
+ # Convert to RGB if necessary
286
+ if image.mode != 'RGB':
287
+ image = image.convert('RGB')
288
+
289
+ # Extract text with multiple languages support
290
+ custom_config = r'--oem 3 --psm 6 -l eng'
291
+ text = pytesseract.image_to_string(image, config=custom_config)
292
+
293
+ return text.strip()
294
+ except Exception as e:
295
+ st.error(f"OCR Error: {e}")
296
+ return None
297
+
298
+ def extract_structured_data(self, image_input):
299
+ """Extract structured data from receipt image"""
300
+ try:
301
+ text = self.extract_text_from_image(image_input)
302
+ if not text:
303
+ return None
304
+
305
+ # Basic structure extraction
306
+ lines = [line.strip() for line in text.split('\n') if line.strip()]
307
+
308
+ return {
309
+ 'raw_text': text,
310
+ 'lines': lines,
311
+ 'extracted_at': str(pd.Timestamp.now())
312
+ }
313
+ except Exception as e:
314
+ st.error(f"Error extracting structured data: {e}")
315
+ return None
316
+
317
+ def preprocess_image(self, image):
318
+ """Preprocess image for better OCR results"""
319
+ try:
320
+ # Convert to grayscale
321
+ if image.mode != 'L':
322
+ image = image.convert('L')
323
+
324
+ # You can add more preprocessing steps here
325
+ # like noise reduction, contrast enhancement, etc.
326
+
327
+ return image
328
+ except Exception as e:
329
+ st.error(f"Error preprocessing image: {e}")
330
+ return image
331
+
332
+ def ocr_receipt_processing():
333
+ """
334
+ Function to process receipt images using OCR
335
+ LLM Needed: NO - Uses Tesseract OCR for text extraction
336
+ Could use LLM for better data parsing and categorization
337
+ """
338
+ try:
339
+ st.subheader("πŸ“Έ Receipt OCR Processing")
340
+
341
+ uploaded_file = st.file_uploader("Upload Receipt Image", type=['jpg', 'jpeg', 'png'])
342
+
343
+ if uploaded_file is not None:
344
+ try:
345
+ image = Image.open(uploaded_file)
346
+ st.image(image, caption="πŸ“Έ Uploaded Receipt", use_container_width=True)
347
+
348
+ if st.button("πŸ” Process Receipt"):
349
+ # Initialize OCR extractor
350
+ ocr_extractor = OCRExtractor()
351
+
352
+ # Use Tesseract OCR
353
+ try:
354
+ # Preprocess image for better results
355
+ processed_image = ocr_extractor.preprocess_image(image)
356
+
357
+ # Extract text
358
+ extracted_text = ocr_extractor.extract_text_from_image(processed_image)
359
+
360
+ if extracted_text:
361
+ st.text_area("πŸ“„ Extracted Text", extracted_text, height=200)
362
+
363
+ # Parse receipt data
364
+ amount = 0
365
+ category = "Other"
366
+ description = "Receipt expense"
367
+
368
+ # Extract amount with multiple patterns
369
+ amount_patterns = [
370
+ r'[$$€£]\s*(\d+(?:\.\d+)?)',
371
+ r'(\d+(?:\.\d+)?)\s*[$$€£]',
372
+ r'(?:total|amount|paid|grand total).*?(\d+(?:\.\d+)?)',
373
+ r'(?:bill|invoice).*?(\d+(?:\.\d+)?)'
374
+ ]
375
+
376
+ for pattern in amount_patterns:
377
+ matches = re.findall(pattern, extracted_text.lower(), re.IGNORECASE)
378
+ if matches:
379
+ for match in matches:
380
+ if isinstance(match, tuple):
381
+ for group in match:
382
+ if group and (group.replace('.', '').isdigit()):
383
+ amount = float(group)
384
+ break
385
+ elif match.replace('.', '').isdigit():
386
+ amount = float(match)
387
+ break
388
+ if amount > 0:
389
+ break
390
+
391
+ # Enhanced category detection
392
+ categories_keywords = {
393
+ 'Food': ['restaurant', 'cafe', 'grocery', 'food', 'meal', 'supermarket', 'big bazaar', 'dmart', 'walmart'],
394
+ 'Transport': ['taxi', 'uber', 'ola', 'fuel', 'petrol', 'bus', 'train', 'airport', 'parking'],
395
+ 'Shopping': ['mall', 'store', 'shop', 'purchase', 'clothes', 'electronics', 'amazon', 'flipkart'],
396
+ 'Entertainment': ['movie', 'cinema', 'game', 'entertainment', 'theatre', 'netflix'],
397
+ 'Bills': ['electricity', 'water', 'internet', 'phone', 'rent', 'subscription', 'bill'],
398
+ 'Health': ['pharmacy', 'medicine', 'doctor', 'hospital', 'medical', 'apollo', 'apollo'],
399
+ 'Education': ['school', 'college', 'books', 'stationery', 'tution', 'course']
400
+ }
401
+
402
+ text_lower = extracted_text.lower()
403
+ for cat, keywords in categories_keywords.items():
404
+ if any(keyword in text_lower for keyword in keywords):
405
+ category = cat
406
+ break
407
+
408
+ # Save to expenses with image data
409
+ image_data = f"data:image/png;base64,{base64.b64encode(uploaded_file.getvalue()).decode()}"
410
+ new_expense = pd.DataFrame({
411
+ 'date': [datetime.now().strftime('%Y-%m-%d')],
412
+ 'amount': [amount],
413
+ 'category': [category],
414
+ 'description': [description],
415
+ 'receipt_image': [image_data]
416
+ })
417
+ st.session_state.expenses = pd.concat([st.session_state.expenses, new_expense], ignore_index=True)
418
+ st.success(f"βœ… Receipt processed successfully: ${amount:.2f} for {category}")
419
+
420
+ # Check budget alerts
421
+ check_budget_alerts(amount, category)
422
+ else:
423
+ st.error("❌ Could not extract text from image. Please try a clearer image.")
424
+
425
+ except Exception as e:
426
+ st.error(f"❌ OCR processing failed: {str(e)}")
427
+ st.info("πŸ’‘ Make sure Tesseract OCR is properly installed on your system")
428
+
429
+ except Exception as e:
430
+ st.error(f"❌ Error processing image: {str(e)}")
431
+ else:
432
+ st.info("πŸ“€ Please upload a receipt image (JPG, JPEG, PNG)")
433
+
434
+ except Exception as e:
435
+ st.error(f"❌ Critical error in OCR processing: {str(e)}")
436
+
437
+ def create_budget():
438
+ """
439
+ Function to create and manage budgets
440
+ LLM Needed: NO - Simple form-based input
441
+ Could use LLM for budget recommendations based on spending patterns
442
+ """
443
+ try:
444
+ st.subheader("πŸ’° Create Budget")
445
+
446
+ col1, col2 = st.columns(2)
447
+ with col1:
448
+ predefined_categories = ["Food", "Transport", "Shopping", "Entertainment", "Bills", "Health", "Education", "Other"]
449
+ category_type = st.radio("Category Type", ["Predefined", "Custom"])
450
+ if category_type == "Predefined":
451
+ category = st.selectbox("Category", predefined_categories)
452
+ else:
453
+ category = st.text_input("Enter custom category")
454
+
455
+ with col2:
456
+ budget_amount = st.number_input("Budget Amount ($)", min_value=0.0, step=100.0, value=1000.0)
457
+ period = st.selectbox("Period", ["Monthly", "Weekly", "Custom"])
458
+
459
+ if st.button("πŸ“Š Set Budget"):
460
+ if category and budget_amount > 0:
461
+ try:
462
+ # Check if budget already exists for this category
463
+ existing_budget = st.session_state.budgets[
464
+ st.session_state.budgets['category'] == category
465
+ ]
466
+
467
+ if not existing_budget.empty:
468
+ # Update existing budget
469
+ st.session_state.budgets.loc[
470
+ st.session_state.budgets['category'] == category, 'budget_amount'
471
+ ] = budget_amount
472
+ st.session_state.budgets.loc[
473
+ st.session_state.budgets['category'] == category, 'period'
474
+ ] = period
475
+ st.success(f"πŸ”„ Budget updated: ${budget_amount:.2f} for {category}")
476
+ else:
477
+ # Add new budget
478
+ new_budget = pd.DataFrame({
479
+ 'category': [category],
480
+ 'budget_amount': [budget_amount],
481
+ 'period': [period]
482
+ })
483
+ st.session_state.budgets = pd.concat([st.session_state.budgets, new_budget], ignore_index=True)
484
+ st.success(f"βœ… Budget set: ${budget_amount:.2f} for {category}")
485
+ except Exception as e:
486
+ st.error(f"❌ Error setting budget: {str(e)}")
487
+ else:
488
+ st.error("⚠️ Please enter valid category and amount")
489
+
490
+ # Display existing budgets
491
+ if not st.session_state.budgets.empty:
492
+ st.subheader("πŸ“Š Current Budgets")
493
+ st.dataframe(st.session_state.budgets)
494
+
495
+ # Option to delete budgets
496
+ if st.checkbox("πŸ—‘οΈ Show delete options"):
497
+ budget_to_delete = st.selectbox("Select budget to delete",
498
+ st.session_state.budgets['category'].tolist())
499
+ if st.button("πŸ—‘οΈ Delete Budget"):
500
+ st.session_state.budgets = st.session_state.budgets[
501
+ st.session_state.budgets['category'] != budget_to_delete
502
+ ]
503
+ st.success(f"βœ… Budget for {budget_to_delete} deleted")
504
+ else:
505
+ st.info("πŸ“ No budgets set yet. Create your first budget!")
506
+
507
+ except Exception as e:
508
+ st.error(f"❌ Critical error in budget creation: {str(e)}")
509
+
510
+ def set_savings_goals():
511
+ """
512
+ Function to set and track savings goals
513
+ LLM Needed: NO - Simple goal tracking
514
+ Could use LLM for personalized savings recommendations
515
+ """
516
+ try:
517
+ st.subheader("🎯 Savings Goals")
518
+
519
+ col1, col2, col3 = st.columns(3)
520
+ with col1:
521
+ goal_name = st.text_input("Goal Name", placeholder="e.g., Vacation, Emergency Fund")
522
+ with col2:
523
+ target_amount = st.number_input("Target Amount ($)", min_value=0.0, step=1000.0, value=10000.0)
524
+ with col3:
525
+ target_date = st.date_input("Target Date",
526
+ value=datetime.now() + timedelta(days=30))
527
+
528
+ if st.button("🎯 Set Goal"):
529
+ if goal_name and target_amount > 0:
530
+ try:
531
+ new_goal = pd.DataFrame({
532
+ 'goal_name': [goal_name],
533
+ 'target_amount': [target_amount],
534
+ 'current_amount': [0.0],
535
+ 'target_date': [target_date]
536
+ })
537
+ st.session_state.savings_goals = pd.concat([st.session_state.savings_goals, new_goal], ignore_index=True)
538
+ st.success(f"βœ… Savings goal '{goal_name}' set!")
539
+ st.balloons()
540
+ except Exception as e:
541
+ st.error(f"❌ Error setting goal: {str(e)}")
542
+ else:
543
+ st.error("⚠️ Please enter valid goal name and target amount")
544
+
545
+ # Display existing goals
546
+ if not st.session_state.savings_goals.empty:
547
+ st.subheader("πŸ† Current Goals")
548
+
549
+ for idx, goal in st.session_state.savings_goals.iterrows():
550
+ try:
551
+ progress = (goal['current_amount'] / goal['target_amount']) * 100 if goal['target_amount'] > 0 else 0
552
+ days_left = (goal['target_date'] - datetime.now().date()).days
553
+
554
+ st.write(f"**{goal['goal_name']}**")
555
+ st.progress(min(progress/100, 1.0))
556
+ st.write(f"πŸ’° ${goal['current_amount']:.2f} / ${goal['target_amount']:.2f} ({progress:.1f}%)")
557
+ st.write(f"πŸ“… Target Date: {goal['target_date']} ({days_left} days left)")
558
+
559
+ # Add to current savings
560
+ add_amount = st.number_input(f"Add to {goal['goal_name']}",
561
+ min_value=0.0, step=100.0, key=f"add_{idx}")
562
+ if st.button(f"βž• Add to {goal['goal_name']}", key=f"btn_{idx}"):
563
+ if add_amount > 0:
564
+ st.session_state.savings_goals.at[idx, 'current_amount'] += add_amount
565
+ st.success(f"βœ… Added ${add_amount:.2f} to {goal['goal_name']}")
566
+ st.rerun()
567
+
568
+ st.write("---")
569
+ except Exception as e:
570
+ st.error(f"❌ Error displaying goal: {str(e)}")
571
+ else:
572
+ st.info("πŸ“ No savings goals set yet. Create your first goal!")
573
+
574
+ except Exception as e:
575
+ st.error(f"❌ Critical error in savings goals: {str(e)}")
576
+
577
+ def spending_categorization():
578
+ """
579
+ Function to categorize and review spending
580
+ LLM Needed: NO - Rule-based categorization
581
+ Could use LLM for smarter automatic categorization
582
+ """
583
+ try:
584
+ st.subheader("🏷️ Spending Categorization")
585
+
586
+ if not st.session_state.expenses.empty:
587
+ # Display expenses that need categorization
588
+ uncategorized = st.session_state.expenses[st.session_state.expenses['category'] == 'Other']
589
+ if not uncategorized.empty:
590
+ st.write("πŸ“ Uncategorized Expenses:")
591
+ for idx, expense in uncategorized.iterrows():
592
+ try:
593
+ st.write(f"πŸ“… {expense['date']} | ${expense['amount']:.2f} | {expense['description']}")
594
+ predefined_categories = ["Food", "Transport", "Shopping", "Entertainment", "Bills", "Health", "Education", "Other"]
595
+ new_category = st.selectbox(f"Re-categorize", predefined_categories,
596
+ key=f"cat_{idx}")
597
+ if st.button(f"πŸ”„ Update Category {idx}"):
598
+ if new_category and new_category != 'Other':
599
+ st.session_state.expenses.at[idx, 'category'] = new_category
600
+ st.success(f"βœ… Category updated to {new_category}!")
601
+ st.rerun()
602
+ except Exception as e:
603
+ st.error(f"❌ Error updating category: {str(e)}")
604
+ st.write("---")
605
+
606
+ # Show all expenses with filter options
607
+ st.subheader("πŸ“‹ All Expenses")
608
+
609
+ # Filters
610
+ col1, col2, col3 = st.columns(3)
611
+ with col1:
612
+ category_filter = st.selectbox("Filter by Category",
613
+ ["All"] + list(st.session_state.expenses['category'].unique()))
614
+ with col2:
615
+ date_filter = st.selectbox("Date Range", ["All", "Last 7 days", "Last 30 days", "This month"])
616
+ with col3:
617
+ min_amount = st.number_input("Min Amount", min_value=0.0, step=10.0)
618
+ max_amount = st.number_input("Max Amount", min_value=0.0,
619
+ value=float(st.session_state.expenses['amount'].max()) if not st.session_state.expenses.empty else 10000.0)
620
+
621
+ # Apply filters
622
+ filtered_expenses = st.session_state.expenses.copy()
623
+
624
+ if category_filter != "All":
625
+ filtered_expenses = filtered_expenses[filtered_expenses['category'] == category_filter]
626
+
627
+ if date_filter == "Last 7 days":
628
+ cutoff_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
629
+ filtered_expenses = filtered_expenses[filtered_expenses['date'] >= cutoff_date]
630
+ elif date_filter == "Last 30 days":
631
+ cutoff_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
632
+ filtered_expenses = filtered_expenses[filtered_expenses['date'] >= cutoff_date]
633
+ elif date_filter == "This month":
634
+ current_month = datetime.now().strftime('%Y-%m')
635
+ filtered_expenses = filtered_expenses[filtered_expenses['date'].str.startswith(current_month)]
636
+
637
+ filtered_expenses = filtered_expenses[
638
+ (filtered_expenses['amount'] >= min_amount) &
639
+ (filtered_expenses['amount'] <= max_amount)
640
+ ]
641
+
642
+ if not filtered_expenses.empty:
643
+ st.dataframe(filtered_expenses[['date', 'amount', 'category', 'description']])
644
+
645
+ # Summary statistics
646
+ st.subheader("πŸ“Š Summary")
647
+ total_spent = filtered_expenses['amount'].sum()
648
+ avg_spent = filtered_expenses['amount'].mean()
649
+ st.metric("Total Spent", f"${total_spent:.2f}")
650
+
651
+ st.metric("Average Expense", f"${avg_spent:.2f}")
652
+
653
+ else:
654
+ st.info("πŸ” No expenses match the current filters")
655
+ else:
656
+ st.info("πŸ“ No expenses recorded yet. Start by adding expenses through voice or receipt scanning!")
657
+
658
+ except Exception as e:
659
+ st.error(f"❌ Critical error in spending categorization: {str(e)}")
660
+
661
+ def check_budget_alerts(amount, category):
662
+ """
663
+ Helper function to check budget alerts
664
+ """
665
+ try:
666
+ if not st.session_state.budgets.empty:
667
+ category_budget = st.session_state.budgets[st.session_state.budgets['category'] == category]
668
+ if not category_budget.empty:
669
+ budget_amount = category_budget.iloc[0]['budget_amount']
670
+ current_spending = st.session_state.expenses[
671
+ st.session_state.expenses['category'] == category
672
+ ]['amount'].sum()
673
+
674
+ if current_spending > budget_amount:
675
+ alert_msg = f"🚨 OVERSPENT: {category} - ${current_spending:.2f}/${budget_amount:.2f}"
676
+
677
+
678
+ if alert_msg not in st.session_state.notifications:
679
+ st.session_state.notifications.append(alert_msg)
680
+ elif current_spending > budget_amount * 0.8: # 80% threshold
681
+ alert_msg = f"⚠️ WARNING: {category} - ${current_spending:.2f}/${budget_amount:.2f} ({((current_spending/budget_amount)*100):.1f}%)"
682
+
683
+
684
+ if alert_msg not in st.session_state.notifications:
685
+ st.session_state.notifications.append(alert_msg)
686
+ except Exception as e:
687
+ st.error(f"❌ Error checking budget alerts: {str(e)}")
688
+
689
+ def alerts_and_notifications():
690
+ """
691
+ Function to check and display budget alerts
692
+ LLM Needed: NO - Simple threshold checking
693
+ Could use LLM for personalized alert messages
694
+ """
695
+ try:
696
+ st.subheader("πŸ”” Budget Alerts & Notifications")
697
+
698
+ # Clear notifications button
699
+ if st.session_state.notifications:
700
+ if st.button("🧹 Clear All Notifications"):
701
+ st.session_state.notifications = []
702
+ st.rerun()
703
+
704
+ # Check for new alerts
705
+ if not st.session_state.expenses.empty and not st.session_state.budgets.empty:
706
+ try:
707
+ # Calculate spending by category
708
+ spending_by_category = st.session_state.expenses.groupby('category')['amount'].sum().reset_index()
709
+
710
+ alerts = []
711
+ for _, budget in st.session_state.budgets.iterrows():
712
+ category_spending = spending_by_category[spending_by_category['category'] == budget['category']]
713
+ if not category_spending.empty:
714
+ spent = category_spending.iloc[0]['amount']
715
+ budget_amount = budget['budget_amount']
716
+
717
+ if spent > budget_amount:
718
+ alerts.append(f"🚨 OVERSPENT: {budget['category']} - ${spent:.2f}/${budget_amount:.2f} ({((spent/budget_amount)*100):.1f}%)")
719
+
720
+
721
+ elif spent > budget_amount * 0.8: # 80% threshold
722
+ alerts.append(f"⚠️ WARNING: {budget['category']} - ${spent:.2f}/${budget_amount:.2f} ({((spent/budget_amount)*100):.1f}%)")
723
+
724
+
725
+
726
+ # Display alerts
727
+ if alerts:
728
+ for alert in alerts:
729
+ st.warning(alert)
730
+ if alert not in st.session_state.notifications:
731
+ st.session_state.notifications.append(alert)
732
+ else:
733
+ st.success("βœ… All budgets are within limits!")
734
+
735
+ except Exception as e:
736
+ st.error(f"❌ Error calculating alerts: {str(e)}")
737
+ else:
738
+ st.info("πŸ“ Set up budgets and record expenses to see alerts")
739
+
740
+ # Display all notifications
741
+ if st.session_state.notifications:
742
+ st.subheader("πŸ“œ Recent Notifications")
743
+ for notification in reversed(st.session_state.notifications[-10:]): # Show last 10
744
+ st.info(notification)
745
+ else:
746
+ st.info("πŸ“­ No notifications yet")
747
+
748
+ except Exception as e:
749
+ st.error(f"❌ Critical error in alerts system: {str(e)}")
750
+
751
+ def visualizations_and_summaries():
752
+ """
753
+ Function to create charts and summaries
754
+ LLM Needed: NO - Standard data visualization
755
+ Could use LLM for generating insights and summaries
756
+ """
757
+ try:
758
+ st.subheader("πŸ“Š Financial Visualizations")
759
+
760
+ if not st.session_state.expenses.empty:
761
+ try:
762
+ # Spending by category pie chart
763
+ spending_by_category = st.session_state.expenses.groupby('category')['amount'].sum()
764
+
765
+ col1, col2 = st.columns(2)
766
+
767
+ with col1:
768
+ st.write("πŸ’° Spending by Category")
769
+ if len(spending_by_category) > 0:
770
+ fig1 = px.pie(values=spending_by_category.values, names=spending_by_category.index,
771
+ title="Expense Distribution by Category")
772
+ st.plotly_chart(fig1, use_container_width=True)
773
+ else:
774
+ st.info("No spending data to visualize")
775
+
776
+ with col2:
777
+ st.write("πŸ“ˆ Spending Trend")
778
+ daily_spending = st.session_state.expenses.groupby('date')['amount'].sum().reset_index()
779
+ if len(daily_spending) > 1:
780
+ fig2 = px.line(daily_spending, x='date', y='amount',
781
+ title='Daily Spending Trend')
782
+ fig2.update_xaxes(type='category')
783
+ st.plotly_chart(fig2, use_container_width=True)
784
+ else:
785
+ st.info("Need more data points for trend analysis")
786
+
787
+ # Monthly summary
788
+ st.subheader("πŸ“‹ Monthly Summary")
789
+ current_month = datetime.now().strftime('%Y-%m')
790
+ monthly_expenses = st.session_state.expenses[
791
+ st.session_state.expenses['date'].str.startswith(current_month)
792
+ ]
793
+
794
+ if not monthly_expenses.empty:
795
+ total_spent = monthly_expenses['amount'].sum()
796
+ st.metric("Total Monthly Spending", f"${total_spent:.2f}")
797
+
798
+
799
+ category_summary = monthly_expenses.groupby('category')['amount'].sum().reset_index()
800
+ fig3 = px.bar(category_summary, x='category', y='amount',
801
+ title='Monthly Spending by Category')
802
+ st.plotly_chart(fig3, use_container_width=True)
803
+
804
+ st.dataframe(category_summary)
805
+ else:
806
+ st.info("No expenses recorded this month.")
807
+
808
+ # Budget vs Actual comparison
809
+ if not st.session_state.budgets.empty:
810
+ st.subheader("βš–οΈ Budget vs Actual Comparison")
811
+ budget_comparison = []
812
+ for _, budget in st.session_state.budgets.iterrows():
813
+ actual_spent = st.session_state.expenses[
814
+ st.session_state.expenses['category'] == budget['category']
815
+ ]['amount'].sum()
816
+ budget_comparison.append({
817
+ 'Category': budget['category'],
818
+ 'Budget': budget['budget_amount'],
819
+ 'Actual': actual_spent,
820
+ 'Difference': budget['budget_amount'] - actual_spent
821
+ })
822
+
823
+ if budget_comparison:
824
+ comparison_df = pd.DataFrame(budget_comparison)
825
+ st.dataframe(comparison_df)
826
+
827
+ # Visualization
828
+ fig4 = go.Figure()
829
+ fig4.add_trace(go.Bar(name='Budget', x=comparison_df['Category'], y=comparison_df['Budget']))
830
+ fig4.add_trace(go.Bar(name='Actual', x=comparison_df['Category'], y=comparison_df['Actual']))
831
+ fig4.update_layout(title="Budget vs Actual Spending", barmode='group')
832
+ st.plotly_chart(fig4, use_container_width=True)
833
+
834
+ except Exception as e:
835
+ st.error(f"❌ Error creating visualizations: {str(e)}")
836
+ else:
837
+ st.info("πŸ“ No data to visualize yet. Start by recording expenses!")
838
+
839
+ except Exception as e:
840
+ st.error(f"❌ Critical error in visualizations: {str(e)}")
841
+
842
+ def receipt_management():
843
+ """
844
+ Function to manage and view stored receipts
845
+ LLM Needed: NO - Simple storage and retrieval
846
+ Could use LLM for receipt categorization and insights
847
+ """
848
+ try:
849
+ st.subheader("🧾 Receipt Management")
850
+
851
+ if not st.session_state.expenses.empty:
852
+ receipts = st.session_state.expenses[st.session_state.expenses['receipt_image'] != '']
853
+ if not receipts.empty:
854
+ st.write(f"πŸ“ Found {len(receipts)} receipts")
855
+
856
+ # Search functionality
857
+ search_term = st.text_input("οΏ½οΏ½ Search receipts by description or category")
858
+ if search_term:
859
+ receipts = receipts[
860
+ receipts['description'].str.contains(search_term, case=False) |
861
+ receipts['category'].str.contains(search_term, case=False)
862
+ ]
863
+ st.write(f"πŸ” Found {len(receipts)} matching receipts")
864
+
865
+ # Sort options
866
+ sort_option = st.selectbox("Sort by", ["Date (Newest)", "Date (Oldest)", "Amount (High to Low)", "Amount (Low to High)"])
867
+
868
+ if sort_option == "Date (Newest)":
869
+ receipts = receipts.sort_values('date', ascending=False)
870
+ elif sort_option == "Date (Oldest)":
871
+ receipts = receipts.sort_values('date', ascending=True)
872
+ elif sort_option == "Amount (High to Low)":
873
+ receipts = receipts.sort_values('amount', ascending=False)
874
+ elif sort_option == "Amount (Low to High)":
875
+ receipts = receipts.sort_values('amount', ascending=True)
876
+
877
+ # Display receipts in a grid
878
+ if not receipts.empty:
879
+ cols = st.columns(3)
880
+ for idx, (i, receipt) in enumerate(receipts.iterrows()):
881
+ try:
882
+ with cols[idx % 3]:
883
+ st.write(f"**πŸ“… {receipt['date']}**")
884
+ st.write(f"πŸ’° ${receipt['amount']:.2f}")
885
+
886
+ st.write(f"🏷️ {receipt['category']}")
887
+ if receipt['receipt_image'].startswith('data:image'):
888
+ # Display base64 image
889
+ st.image(receipt['receipt_image'], width=200)
890
+ st.write(f"πŸ“ {receipt['description'][:50]}...")
891
+ st.write("---")
892
+ except Exception as e:
893
+ st.error(f"❌ Error displaying receipt: {str(e)}")
894
+ else:
895
+ st.info("πŸ” No receipts match your search criteria")
896
+ else:
897
+ st.info("πŸ“ No receipts uploaded yet. Upload receipts through the OCR feature!")
898
+ else:
899
+ st.info("πŸ“ No expenses recorded yet. Start by recording expenses!")
900
+
901
+ except Exception as e:
902
+ st.error(f"❌ Critical error in receipt management: {str(e)}")
903
+
904
+ def data_security_and_privacy():
905
+ """
906
+ Function to handle data security (simulated)
907
+ LLM Needed: NO - Just UI for privacy settings
908
+ """
909
+ try:
910
+ st.subheader("πŸ”’ Data Security & Privacy")
911
+
912
+ st.write("πŸ›‘οΈ Your financial data is stored locally and never shared with third parties.")
913
+ st.write("πŸ” All data is encrypted and protected according to privacy regulations.")
914
+
915
+ # Security settings
916
+ st.subheader("βš™οΈ Security Settings")
917
+
918
+ if st.checkbox("Enable Data Encryption", value=True):
919
+ st.success("βœ… Data encryption is enabled!")
920
+
921
+ if st.checkbox("Enable Automatic Backups"):
922
+ backup_frequency = st.selectbox("Backup Frequency", ["Daily", "Weekly", "Monthly"])
923
+ st.info(f"πŸ“… Automatic backups will run {backup_frequency.lower()}")
924
+
925
+ # Data export
926
+ st.subheader("πŸ“€ Data Export")
927
+ if st.button("πŸ’Ύ Export All Data"):
928
+ try:
929
+ export_data = {
930
+ 'expenses': st.session_state.expenses.to_dict('records') if not st.session_state.expenses.empty else [],
931
+ 'budgets': st.session_state.budgets.to_dict('records') if not st.session_state.budgets.empty else [],
932
+ 'savings_goals': st.session_state.savings_goals.to_dict('records') if not st.session_state.savings_goals.empty else []
933
+ }
934
+
935
+ json_str = json.dumps(export_data, indent=2, default=str)
936
+ st.download_button(
937
+ label="πŸ“₯ Download Data as JSON",
938
+ data=json_str,
939
+ file_name=f"budget_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
940
+ mime="application/json"
941
+ )
942
+
943
+ # Also provide CSV export
944
+ if not st.session_state.expenses.empty:
945
+ st.download_button(
946
+ label="πŸ“Š Download Expenses CSV",
947
+ data=st.session_state.expenses.to_csv(index=False).encode('utf-8'),
948
+ file_name=f"expenses_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
949
+ mime="text/csv"
950
+ )
951
+ except Exception as e:
952
+ st.error(f"❌ Error exporting data: {str(e)}")
953
+
954
+ # Privacy policy
955
+ st.subheader("πŸ“œ Privacy Policy")
956
+ with st.expander("Read Privacy Policy"):
957
+ st.write("""
958
+ **Data Collection:**
959
+ - We collect only the financial data you enter
960
+ - No personal identification information is collected
961
+ - All data is stored locally on your device
962
+
963
+ **Data Usage:**
964
+ - Your data is used only for the functionality of this application
965
+ - We do not share your data with any third parties
966
+ - Data is not transmitted over the internet
967
+
968
+ **Data Security:**
969
+ - All data is encrypted at rest
970
+ - You have full control over your data
971
+ - You can export or delete your data at any time
972
+ """)
973
+
974
+ except Exception as e:
975
+ st.error(f"❌ Critical error in security section: {str(e)}")
976
+
977
+ def bank_integration_placeholder():
978
+ """
979
+ Placeholder for bank integration feature
980
+ LLM Needed: NO - Just UI placeholder
981
+ Would need LLM for natural language banking queries
982
+ """
983
+ try:
984
+ st.subheader("🏦 Bank Integration (Coming Soon)")
985
+
986
+ st.info("πŸš€ This feature will allow automatic syncing with your bank accounts!")
987
+
988
+ st.write("πŸ“‹ Planned Features:")
989
+ st.write("β€’ Automatic transaction import")
990
+ st.write("β€’ Real-time balance updates")
991
+ st.write("β€’ Bank statement analysis")
992
+ st.write("β€’ Automatic expense categorization")
993
+
994
+ bank_name = st.selectbox("Select Bank", ["HDFC", "ICICI", "SBI", "Axis", "Kotak", "Other"])
995
+ if bank_name:
996
+ st.info(f"Bank integration for {bank_name} will be available soon!")
997
+
998
+ except Exception as e:
999
+ st.error(f"❌ Error in bank integration section: {str(e)}")
1000
+
1001
+ def main_dashboard():
1002
+ """
1003
+ Main dashboard overview
1004
+ """
1005
+ try:
1006
+ st.subheader("🏠 Dashboard Overview")
1007
+
1008
+ # Key metrics
1009
+ col1, col2, col3, col4 = st.columns(4)
1010
+
1011
+ total_expenses = st.session_state.expenses['amount'].sum() if not st.session_state.expenses.empty else 0
1012
+ total_budget = st.session_state.budgets['budget_amount'].sum() if not st.session_state.budgets.empty else 0
1013
+ total_savings = st.session_state.savings_goals['current_amount'].sum() if not st.session_state.savings_goals.empty else 0
1014
+ expense_count = len(st.session_state.expenses)
1015
+
1016
+ with col1:
1017
+ st.metric("πŸ’° Total Expenses", f"${total_expenses:.2f}")
1018
+
1019
+ with col2:
1020
+ st.metric("πŸ“Š Total Budget", f"${total_budget:.2f}")
1021
+
1022
+ with col3:
1023
+ st.metric("πŸ† Total Savings", f"${total_savings:.2f}")
1024
+
1025
+ with col4:
1026
+ st.metric("🧾 Expense Count", f"{expense_count}")
1027
+
1028
+ # Quick actions
1029
+ st.subheader("⚑ Quick Actions")
1030
+ col1, col2, col3 = st.columns(3)
1031
+ with col1:
1032
+ if st.button("🎀 Record Voice Expense"):
1033
+ st.session_state.current_page = "🎀 Voice Expense"
1034
+ st.rerun()
1035
+ with col2:
1036
+ if st.button("πŸ“Έ Scan Receipt"):
1037
+ st.session_state.current_page = "πŸ“Έ OCR Receipts"
1038
+ st.rerun()
1039
+ with col3:
1040
+ if st.button("πŸ“Š View Analytics"):
1041
+ st.session_state.current_page = "πŸ“Š Visualizations"
1042
+ st.rerun()
1043
+
1044
+ # Recent activity
1045
+ if not st.session_state.expenses.empty:
1046
+ st.subheader("πŸ“… Recent Expenses")
1047
+ recent_expenses = st.session_state.expenses.tail(5)
1048
+ st.dataframe(recent_expenses[['date', 'amount', 'category', 'description']])
1049
+ else:
1050
+ st.info("πŸ“ No recent expenses. Start by adding your first expense!")
1051
+
1052
+ # Budget status
1053
+ if not st.session_state.budgets.empty:
1054
+ st.subheader("πŸ“Š Budget Status")
1055
+ for _, budget in st.session_state.budgets.iterrows():
1056
+ category_spending = st.session_state.expenses[
1057
+ st.session_state.expenses['category'] == budget['category']
1058
+ ]['amount'].sum()
1059
+
1060
+ progress = (category_spending / budget['budget_amount']) * 100 if budget['budget_amount'] > 0 else 0
1061
+ st.write(f"**{budget['category']}**")
1062
+ st.progress(min(progress/100, 1.0))
1063
+ st.write(f"${category_spending:.2f} / ${budget['budget_amount']:.2f} ({progress:.1f}%)")
1064
+
1065
+
1066
+
1067
+ # Savings goals progress
1068
+ if not st.session_state.savings_goals.empty:
1069
+ st.subheader("🎯 Savings Goals Progress")
1070
+ for _, goal in st.session_state.savings_goals.iterrows():
1071
+ progress = (goal['current_amount'] / goal['target_amount']) * 100 if goal['target_amount'] > 0 else 0
1072
+ st.write(f"**{goal['goal_name']}**")
1073
+ st.progress(min(progress/100, 1.0))
1074
+ st.write(f"${goal['current_amount']:.2f} / ${goal['target_amount']:.2f} ({progress:.1f}%)")
1075
+
1076
+
1077
+
1078
+ except Exception as e:
1079
+ st.error(f"❌ Error in dashboard: {str(e)}")
1080
+
1081
+ def main():
1082
+ """
1083
+ Main application function with error handling
1084
+ """
1085
+ try:
1086
+ # Initialize session state
1087
+ if not initialize_session_state():
1088
+ st.error("❌ Failed to initialize application. Please refresh the page.")
1089
+ return
1090
+
1091
+ # Set page config
1092
+ st.set_page_config(
1093
+ page_title="πŸ’° Budget Tracker Pro",
1094
+ page_icon="πŸ’°",
1095
+ layout="wide",
1096
+ initial_sidebar_state="expanded"
1097
+ )
1098
+
1099
+ # Custom CSS for better UI
1100
+ st.markdown("""
1101
+ <style>
1102
+ .stApp {
1103
+ background-color: #f0f2f6;
1104
+ }
1105
+ .stMetric {
1106
+ background-color: white;
1107
+ padding: 10px;
1108
+ border-radius: 10px;
1109
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1110
+ }
1111
+ .css-1d391kg {
1112
+ background-color: #262730;
1113
+ }
1114
+ </style>
1115
+ """, unsafe_allow_html=True)
1116
+
1117
+ # App title and description
1118
+ st.title("πŸ’° Budget Tracker Pro")
1119
+ st.markdown("*Your intelligent personal finance assistant*")
1120
+
1121
+ # Sidebar navigation
1122
+ st.sidebar.title("🧭 Navigation")
1123
+
1124
+ # Initialize current page in session state
1125
+ if 'current_page' not in st.session_state:
1126
+ st.session_state.current_page = "🏠 Dashboard"
1127
+
1128
+ menu = [
1129
+ "🏠 Dashboard",
1130
+ "🎀 Voice Expense",
1131
+ "πŸ“Έ OCR Receipts",
1132
+ "πŸ’° Budget",
1133
+ "🎯 Savings Goals",
1134
+ "🏷️ Categorization",
1135
+ "πŸ”” Alerts",
1136
+ "πŸ“Š Visualizations",
1137
+ "🧾 Receipts",
1138
+ "πŸ”’ Security",
1139
+ "🏦 Bank Integration"
1140
+ ]
1141
+
1142
+ # Page selection
1143
+ choice = st.sidebar.selectbox("Choose a section", menu,
1144
+ index=menu.index(st.session_state.current_page))
1145
+ st.session_state.current_page = choice
1146
+
1147
+ # Display notifications in sidebar
1148
+ if st.session_state.notifications:
1149
+ st.sidebar.subheader("πŸ”” Notifications")
1150
+ for notification in st.session_state.notifications[-3:]: # Show last 3
1151
+ if "🚨" in notification:
1152
+ st.sidebar.error(notification)
1153
+ elif "⚠️" in notification:
1154
+ st.sidebar.warning(notification)
1155
+
1156
+ # Route to appropriate function
1157
+ if choice == "🏠 Dashboard":
1158
+ main_dashboard()
1159
+ elif choice == "🎀 Voice Expense":
1160
+ voice_expense_recording()
1161
+ elif choice == "πŸ“Έ OCR Receipts":
1162
+ ocr_receipt_processing()
1163
+ elif choice == "πŸ’° Budget":
1164
+ create_budget()
1165
+ elif choice == "🎯 Savings Goals":
1166
+ set_savings_goals()
1167
+ elif choice == "🏷️ Categorization":
1168
+ spending_categorization()
1169
+ elif choice == "πŸ”” Alerts":
1170
+ alerts_and_notifications()
1171
+ elif choice == "πŸ“Š Visualizations":
1172
+ visualizations_and_summaries()
1173
+ elif choice == "🧾 Receipts":
1174
+ receipt_management()
1175
+ elif choice == "πŸ”’ Security":
1176
+ data_security_and_privacy()
1177
+ elif choice == "🏦 Bank Integration":
1178
+ bank_integration_placeholder()
1179
+
1180
+ # Footer
1181
+ st.sidebar.markdown("---")
1182
+ st.sidebar.info("πŸ’‘ Tip: Use voice commands for quick expense logging!")
1183
+
1184
+ except Exception as e:
1185
+ st.error(f"❌ Critical application error: {str(e)}")
1186
+ st.info("πŸ”„ Please refresh the page or contact support if the issue persists.")
1187
+
1188
+ if __name__ == "__main__":
1189
+ main()