LogicGoInfotechSpaces commited on
Commit
dccf408
·
1 Parent(s): e95b2a4

Fix recommendation generation when user has data but no recommendations found - Fixed MongoDB query structure for budget lookup - Added fallback logic to generate recommendations from budget data - Added debug logging

Browse files
Files changed (2) hide show
  1. app/smart_recommendation.py +166 -51
  2. find_user_with_data.py +134 -0
app/smart_recommendation.py CHANGED
@@ -196,65 +196,180 @@ class SmartBudgetRecommender:
196
  """
197
  # Get all recommendations for the user
198
  all_recommendations = self.get_recommendations(user_id, month, year)
 
199
 
200
  # Filter to only include recommendations for the specified category_id
201
  filtered_recommendations = [
202
  rec for rec in all_recommendations
203
  if rec.category_id == category_id or str(rec.category_id) == str(category_id)
204
  ]
205
-
206
- # If budget_amount is provided and we have a recommendation, use it to generate a new recommendation
207
- if budget_amount and budget_amount > 0 and filtered_recommendations:
208
- # Get the first recommendation (should be the one for this category)
209
- original_rec = filtered_recommendations[0]
210
-
211
- # Get category name
212
- category_name = original_rec.category
213
-
214
- # Use the provided budget_amount as average_expense for comparison
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  avg_expense = budget_amount
216
-
217
- # Create data structure for recommendation calculation
218
- data = {
219
- "average_monthly": avg_expense,
220
- "total": avg_expense,
221
- "count": 1,
222
- "months_analyzed": 1,
223
- "std_dev": 0.0,
224
- "monthly_values": [avg_expense],
225
- }
226
-
227
- confidence = self._calculate_confidence(data)
228
-
229
- # Always try OpenAI first (primary source of recommendation)
230
- ai_result = self._get_ai_recommendation(category_name, data, avg_expense)
231
- if ai_result and ai_result.get("recommended_budget"):
232
- recommended_budget = ai_result.get("recommended_budget")
233
- reason = ai_result.get("reason", f"AI recommendation for {category_name} based on your budget of Rs.{budget_amount:,.2f}")
234
- action = ai_result.get("action")
235
- print(f"✅ OpenAI recommendation for {category_name} (budget: {budget_amount}): {recommended_budget} (action: {action})")
236
- else:
237
- # Fallback to rule-based recommendation if OpenAI fails
238
- recommended_budget = self._calculate_recommended_budget(avg_expense, data)
239
- reason = self._generate_reason(category_name, avg_expense, recommended_budget)
240
- action = None
241
- if not ai_result:
242
- print(f"❌ OpenAI unavailable (no API key or error), using rule-based for {category_name} (budget: {budget_amount}): {recommended_budget}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  else:
244
- print(f"⚠️ OpenAI returned invalid data, using rule-based for {category_name} (budget: {budget_amount}): {recommended_budget}")
245
-
246
- # Create new recommendation based on provided budget_amount
247
- filtered_recommendations = [BudgetRecommendation(
248
- category=category_name,
249
- category_id=category_id,
250
- average_expense=round(avg_expense, 2),
251
- recommended_budget=round(recommended_budget or 0, 2),
252
- reason=reason,
253
- confidence=confidence,
254
- action=action
255
- )]
256
-
257
- return filtered_recommendations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
  def _calculate_category_statistics(self, expenses: List[Dict], start_date: datetime, end_date: datetime) -> Dict:
260
  """Calculate statistics for each category"""
 
196
  """
197
  # Get all recommendations for the user
198
  all_recommendations = self.get_recommendations(user_id, month, year)
199
+ print(f"🔍 get_recommendation_for_category: Found {len(all_recommendations)} total recommendations for user {user_id}")
200
 
201
  # Filter to only include recommendations for the specified category_id
202
  filtered_recommendations = [
203
  rec for rec in all_recommendations
204
  if rec.category_id == category_id or str(rec.category_id) == str(category_id)
205
  ]
206
+ print(f"🔍 get_recommendation_for_category: Filtered to {len(filtered_recommendations)} recommendations for category_id {category_id}")
207
+
208
+ # If we found a recommendation, use it (or regenerate with budget_amount if provided)
209
+ if filtered_recommendations:
210
+ # If budget_amount is provided, regenerate recommendation with it
211
+ if budget_amount and budget_amount > 0:
212
+ # Get the first recommendation (should be the one for this category)
213
+ original_rec = filtered_recommendations[0]
214
+
215
+ # Get category name
216
+ category_name = original_rec.category
217
+
218
+ # Use the provided budget_amount as average_expense for comparison
219
+ avg_expense = budget_amount
220
+
221
+ # Create data structure for recommendation calculation
222
+ data = {
223
+ "average_monthly": avg_expense,
224
+ "total": avg_expense,
225
+ "count": 1,
226
+ "months_analyzed": 1,
227
+ "std_dev": 0.0,
228
+ "monthly_values": [avg_expense],
229
+ }
230
+
231
+ confidence = self._calculate_confidence(data)
232
+
233
+ # Always try OpenAI first (primary source of recommendation)
234
+ ai_result = self._get_ai_recommendation(category_name, data, avg_expense)
235
+ if ai_result and ai_result.get("recommended_budget"):
236
+ recommended_budget = ai_result.get("recommended_budget")
237
+ reason = ai_result.get("reason", f"AI recommendation for {category_name} based on your budget of Rs.{budget_amount:,.2f}")
238
+ action = ai_result.get("action")
239
+ print(f"✅ OpenAI recommendation for {category_name} (budget: {budget_amount}): {recommended_budget} (action: {action})")
240
+ else:
241
+ # Fallback to rule-based recommendation if OpenAI fails
242
+ recommended_budget = self._calculate_recommended_budget(avg_expense, data)
243
+ reason = self._generate_reason(category_name, avg_expense, recommended_budget)
244
+ action = None
245
+ if not ai_result:
246
+ print(f"❌ OpenAI unavailable (no API key or error), using rule-based for {category_name} (budget: {budget_amount}): {recommended_budget}")
247
+ else:
248
+ print(f"⚠️ OpenAI returned invalid data, using rule-based for {category_name} (budget: {budget_amount}): {recommended_budget}")
249
+
250
+ # Create new recommendation based on provided budget_amount
251
+ filtered_recommendations = [BudgetRecommendation(
252
+ category=category_name,
253
+ category_id=category_id,
254
+ average_expense=round(avg_expense, 2),
255
+ recommended_budget=round(recommended_budget or 0, 2),
256
+ reason=reason,
257
+ confidence=confidence,
258
+ action=action
259
+ )]
260
+ return filtered_recommendations
261
+
262
+ # If no recommendations found, try to generate one
263
+ # First, get category name
264
+ category_name = self._get_category_name(category_id)
265
+
266
+ # If budget_amount is provided, use it
267
+ if budget_amount and budget_amount > 0:
268
  avg_expense = budget_amount
269
+ else:
270
+ # Try to get budget data for this category
271
+ try:
272
+ # Try to find budgets with this category_id
273
+ try:
274
+ category_objid = ObjectId(category_id)
275
+ except (ValueError, TypeError):
276
+ category_objid = category_id
277
+
278
+ # Build user query
279
+ user_query = {
280
+ "$or": [
281
+ {"createdBy": ObjectId(user_id) if len(user_id) == 24 and all(c in '0123456789abcdefABCDEF' for c in user_id) else user_id},
282
+ {"createdBy": user_id},
283
+ {"user_id": ObjectId(user_id) if len(user_id) == 24 and all(c in '0123456789abcdefABCDEF' for c in user_id) else user_id},
284
+ {"user_id": user_id}
285
+ ]
286
+ }
287
+
288
+ # Build category query
289
+ category_query = {
290
+ "$or": [
291
+ {"category": category_objid},
292
+ {"categoryId": category_objid},
293
+ {"headCategory": category_objid},
294
+ {"headCategories.headCategory": category_objid},
295
+ {"headCategories.categories.category": category_objid}
296
+ ]
297
+ }
298
+
299
+ # Combine queries
300
+ budget_query = {"$and": [user_query, category_query]}
301
+
302
+ budgets = list(self.db.budgets.find(budget_query).limit(10))
303
+ print(f"🔍 get_recommendation_for_category: Found {len(budgets)} budgets for user {user_id} and category {category_id}")
304
+
305
+ if budgets:
306
+ # Calculate average from budgets
307
+ total_amount = 0
308
+ count = 0
309
+ for budget in budgets:
310
+ try:
311
+ max_amount = float(budget.get("maxAmount", 0) or budget.get("max_amount", 0) or budget.get("amount", 0) or 0)
312
+ spend_amount = float(budget.get("spendAmount", 0) or budget.get("spend_amount", 0) or budget.get("spent", 0) or 0)
313
+ budget_amount_val = float(budget.get("budget", 0) or budget.get("budgetAmount", 0) or 0)
314
+
315
+ base_amount = spend_amount if spend_amount > 0 else (max_amount if max_amount > 0 else budget_amount_val)
316
+ if base_amount > 0:
317
+ total_amount += base_amount
318
+ count += 1
319
+ except (ValueError, TypeError):
320
+ continue
321
+
322
+ if count > 0:
323
+ avg_expense = total_amount / count
324
+ else:
325
+ # No valid amounts found, can't generate recommendation
326
+ return []
327
  else:
328
+ # No budgets found for this category
329
+ return []
330
+ except Exception as e:
331
+ print(f"Error getting budget data for category: {e}")
332
+ return []
333
+
334
+ # Generate recommendation
335
+ data = {
336
+ "average_monthly": avg_expense,
337
+ "total": avg_expense,
338
+ "count": 1,
339
+ "months_analyzed": 1,
340
+ "std_dev": 0.0,
341
+ "monthly_values": [avg_expense],
342
+ }
343
+
344
+ confidence = self._calculate_confidence(data)
345
+
346
+ # Always try OpenAI first
347
+ ai_result = self._get_ai_recommendation(category_name, data, avg_expense)
348
+ if ai_result and ai_result.get("recommended_budget"):
349
+ recommended_budget = ai_result.get("recommended_budget")
350
+ reason = ai_result.get("reason", f"AI recommendation for {category_name}")
351
+ action = ai_result.get("action")
352
+ print(f"✅ OpenAI recommendation for {category_name}: {recommended_budget} (action: {action})")
353
+ else:
354
+ # Fallback to rule-based recommendation if OpenAI fails
355
+ recommended_budget = self._calculate_recommended_budget(avg_expense, data)
356
+ reason = self._generate_reason(category_name, avg_expense, recommended_budget)
357
+ action = None
358
+ if not ai_result:
359
+ print(f"❌ OpenAI unavailable (no API key or error), using rule-based for {category_name}: {recommended_budget}")
360
+ else:
361
+ print(f"⚠️ OpenAI returned invalid data, using rule-based for {category_name}: {recommended_budget}")
362
+
363
+ # Create recommendation
364
+ return [BudgetRecommendation(
365
+ category=category_name,
366
+ category_id=category_id,
367
+ average_expense=round(avg_expense, 2),
368
+ recommended_budget=round(recommended_budget or 0, 2),
369
+ reason=reason,
370
+ confidence=confidence,
371
+ action=action
372
+ )]
373
 
374
  def _calculate_category_statistics(self, expenses: List[Dict], start_date: datetime, end_date: datetime) -> Dict:
375
  """Calculate statistics for each category"""
find_user_with_data.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ from pymongo import MongoClient
3
+ from bson import ObjectId
4
+ import sys
5
+ import io
6
+ import json
7
+
8
+ # Fix encoding for Windows console
9
+ if sys.platform == 'win32':
10
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
11
+
12
+ # MongoDB connection string
13
+ MONGODB_URI = "mongodb://expenseuser:Kem_6o%3F%3F@165.227.69.221:27017/expense?authSource=admin"
14
+
15
+ try:
16
+ # Connect to MongoDB
17
+ client = MongoClient(MONGODB_URI)
18
+ db = client.expense
19
+
20
+ # Test connection
21
+ client.admin.command('ping')
22
+ print("[OK] Successfully connected to MongoDB")
23
+ print(f"Database: expense\n")
24
+
25
+ # Find users with budgets that have category_id
26
+ print("Searching for users with budgets that have category_id...")
27
+
28
+ # Get user with most budgets
29
+ user_with_most_budgets = "6741abd38d30ab5b7176397f"
30
+ print(f"\nChecking user: {user_with_most_budgets} (has 243 budgets)")
31
+
32
+ # Get budgets for this user
33
+ user_budgets = list(db.budgets.find({
34
+ "$or": [
35
+ {"createdBy": ObjectId(user_with_most_budgets)},
36
+ {"createdBy": user_with_most_budgets},
37
+ {"user_id": ObjectId(user_with_most_budgets)},
38
+ {"user_id": user_with_most_budgets}
39
+ ]
40
+ }).limit(20))
41
+
42
+ print(f"Found {len(user_budgets)} budgets for this user\n")
43
+
44
+ valid_combinations = []
45
+
46
+ for budget in user_budgets:
47
+ created_by = str(budget.get("createdBy") or budget.get("user_id", ""))
48
+ category_id = None
49
+
50
+ # Try direct category fields
51
+ category_id = budget.get("category") or budget.get("categoryId") or budget.get("headCategory")
52
+
53
+ # Check headCategories array
54
+ if not category_id:
55
+ head_categories = budget.get("headCategories", [])
56
+ if head_categories and isinstance(head_categories, list):
57
+ for head_cat in head_categories:
58
+ if isinstance(head_cat, dict):
59
+ # Get headCategory ID
60
+ head_cat_id = head_cat.get("headCategory")
61
+ if head_cat_id:
62
+ category_id = head_cat_id
63
+ break
64
+
65
+ # Get nested category IDs
66
+ nested_cats = head_cat.get("categories", [])
67
+ if nested_cats and isinstance(nested_cats, list):
68
+ for nested_cat in nested_cats:
69
+ if isinstance(nested_cat, dict):
70
+ nested_cat_id = nested_cat.get("category")
71
+ if nested_cat_id:
72
+ category_id = nested_cat_id
73
+ break
74
+ if category_id:
75
+ break
76
+
77
+ if category_id:
78
+ valid_combinations.append({
79
+ "user_id": created_by,
80
+ "category_id": str(category_id),
81
+ "budget_name": budget.get("name", "N/A"),
82
+ "budget_id": str(budget.get("_id"))
83
+ })
84
+
85
+ print(f"{'='*60}")
86
+ print(f"Found {len(valid_combinations)} budgets with category_id")
87
+ print(f"{'='*60}\n")
88
+
89
+ if valid_combinations:
90
+ print("[OK] User ID and Category ID combinations for testing:")
91
+ print("-" * 60)
92
+ seen = set()
93
+ for i, combo in enumerate(valid_combinations[:10], 1):
94
+ key = (combo["user_id"], combo["category_id"])
95
+ if key not in seen:
96
+ seen.add(key)
97
+ print(f"{i}. user_id: {combo['user_id']}")
98
+ print(f" category_id: {combo['category_id']}")
99
+ print(f" budget_name: {combo['budget_name']}")
100
+ print()
101
+
102
+ print(f"\n{'='*60}")
103
+ print("[RECOMMENDED] Use this for testing:")
104
+ print(f"{'='*60}")
105
+ first = valid_combinations[0]
106
+ print(f"user_id: {first['user_id']}")
107
+ print(f"category_id: {first['category_id']}")
108
+ print(f"{'='*60}\n")
109
+ else:
110
+ print("[WARNING] No budgets found with category_id")
111
+ print("Checking sample budget structure...\n")
112
+
113
+ # Show structure of first budget
114
+ if user_budgets:
115
+ sample = user_budgets[0]
116
+ print("Sample budget structure:")
117
+ print(f" _id: {sample.get('_id')}")
118
+ print(f" createdBy: {sample.get('createdBy')}")
119
+ print(f" user_id: {sample.get('user_id')}")
120
+ print(f" name: {sample.get('name')}")
121
+ print(f" category: {sample.get('category')}")
122
+ print(f" categoryId: {sample.get('categoryId')}")
123
+ print(f" headCategory: {sample.get('headCategory')}")
124
+ print(f" has headCategories array: {bool(sample.get('headCategories'))}")
125
+ if sample.get('headCategories'):
126
+ print(f" headCategories length: {len(sample.get('headCategories', []))}")
127
+ if len(sample.get('headCategories', [])) > 0:
128
+ print(f" First headCategory structure:")
129
+ print(json.dumps(sample.get('headCategories')[0], indent=2, default=str))
130
+
131
+ except Exception as e:
132
+ print(f"[ERROR] Error: {e}")
133
+ import traceback
134
+ traceback.print_exc()