LogicGoInfotechSpaces commited on
Commit
f1019bd
·
1 Parent(s): 4a08c54

Fix budget _id check logic and remove excessive debug logs

Browse files
Files changed (1) hide show
  1. app/smart_recommendation.py +66 -96
app/smart_recommendation.py CHANGED
@@ -44,7 +44,7 @@ class SmartBudgetRecommender:
44
  # This ensures we only show recommendations for budgets the user actually created
45
  if not category_data:
46
  print(f"No budgets found for user_id: {user_id}, returning empty recommendations")
47
- return []
48
 
49
  recommendations: List[BudgetRecommendation] = []
50
 
@@ -84,7 +84,7 @@ class SmartBudgetRecommender:
84
  elif std_dev > avg_expense * 0.05:
85
  recommended_budget = avg_expense * 1.20
86
  action = "increase"
87
- else:
88
  recommended_budget = avg_expense * 1.05
89
  action = "increase"
90
 
@@ -125,8 +125,6 @@ class SmartBudgetRecommender:
125
  Returns:
126
  True if user has previous data for this category, False otherwise
127
  """
128
- print(f"🔍 check_user_has_category_data: Checking for user_id={user_id}, category_id={category_id}")
129
-
130
  # FIRST: Check if category_id is actually a budget _id
131
  # If so, find the budget and check if it belongs to the user and has categories
132
  try:
@@ -136,14 +134,15 @@ class SmartBudgetRecommender:
136
  budget_by_id = self.db.budgets.find_one({"_id": budget_id_objid})
137
  if budget_by_id:
138
  budget_created_by = budget_by_id.get("createdBy")
139
- # Check if this budget belongs to the user
140
- budget_user_match = (
141
- str(budget_created_by) == str(user_id) or
142
- budget_created_by == ObjectId(user_id) if isinstance(budget_created_by, ObjectId) else False
143
- )
 
 
144
 
145
  if budget_user_match:
146
- print(f"✅ category_id {category_id} is a budget _id for user {user_id}")
147
  # Extract all category IDs from this budget's headCategories
148
  head_categories = budget_by_id.get("headCategories", [])
149
  category_ids_in_budget = []
@@ -161,22 +160,13 @@ class SmartBudgetRecommender:
161
  if nc_id:
162
  category_ids_in_budget.append(str(nc_id))
163
 
164
- print(f"🔍 Found {len(category_ids_in_budget)} category IDs in budget {category_id}")
165
  # If budget has categories, consider it as having previous data
166
  if category_ids_in_budget:
167
- print(f"✅ Budget {category_id} contains categories: {category_ids_in_budget[:5]}...")
168
- print(f" Budget name: {budget_by_id.get('name', 'Unknown')}, maxAmount: {budget_by_id.get('maxAmount', 0)}")
169
  return True
170
- else:
171
- print(f"⚠️ Budget {category_id} exists but has no categories")
172
- else:
173
- print(f"⚠️ Budget {category_id} exists but belongs to different user")
174
  except (ValueError, TypeError):
175
  pass # category_id is not a valid ObjectId, continue with normal check
176
  except Exception as e:
177
- print(f"⚠️ Error checking if category_id is a budget _id: {e}")
178
- import traceback
179
- traceback.print_exc()
180
 
181
  # Build comprehensive user query
182
  user_conditions = []
@@ -222,79 +212,60 @@ class SmartBudgetRecommender:
222
  {"headCategories.categories.category": category_id},
223
  ]
224
 
225
- # FIRST: Check nested structure (headCategories[].categories[].category) - most common case
226
- # Budgets have structure: headCategories[].categories[].category (ObjectId)
227
- # User ID is stored in createdBy field
228
- print(f"🔍 Checking nested structure: headCategories[].categories[].category for category_id {category_id}")
229
  try:
230
  try:
231
  category_objid = ObjectId(category_id)
232
  except (ValueError, TypeError):
233
  category_objid = category_id
234
 
235
- # Try multiple nested query patterns to ensure we catch all cases
236
- nested_queries = []
237
-
238
- # Pattern 1: Using $elemMatch for nested arrays (most accurate)
239
- nested_queries.append({
240
- "$and": [
241
- {"$or": user_conditions},
242
- {
243
- "$or": [
244
- # Check if category_id is in headCategories[].categories[].category
245
- {"headCategories": {"$elemMatch": {"categories": {"$elemMatch": {"category": category_objid}}}}},
246
- {"headCategories": {"$elemMatch": {"categories": {"$elemMatch": {"category": category_id}}}}},
247
- # Also check if category_id is a headCategory itself
248
- {"headCategories": {"$elemMatch": {"headCategory": category_objid}}},
249
- {"headCategories": {"$elemMatch": {"headCategory": category_id}}},
250
- ]
251
- }
252
- ]
253
- })
254
-
255
- # Pattern 2: Using dot notation (simpler, might work for some MongoDB versions)
256
- nested_queries.append({
257
- "$and": [
258
- {"$or": user_conditions},
259
- {
260
- "$or": [
261
- {"headCategories.categories.category": category_objid},
262
- {"headCategories.categories.category": category_id},
263
- {"headCategories.headCategory": category_objid},
264
- {"headCategories.headCategory": category_id},
265
- ]
266
- }
267
- ]
268
- })
269
 
270
- # Try each nested query pattern
271
- for i, nested_query in enumerate(nested_queries, 1):
272
  try:
273
- nested_count = self.db.budgets.count_documents(nested_query)
274
- if nested_count > 0:
275
- print(f"✅ Found {nested_count} budget(s) with nested category structure for user {user_id} with category_id {category_id}")
276
- print(f" Nested query pattern {i} matched: headCategories[].categories[].category = {category_id}")
277
  return True
278
- except Exception as e:
279
- print(f"⚠️ Error with nested query pattern {i}: {e}")
280
  continue
281
-
282
- print(f"🔍 Nested query found 0 budgets for category_id {category_id}")
283
- except Exception as e:
284
- print(f"⚠️ Error checking nested categories: {e}")
285
- import traceback
286
- traceback.print_exc()
287
 
288
- # SECOND: Combine user and category conditions (direct fields)
289
- print(f"🔍 Checking direct category fields for category_id {category_id}")
290
  for user_cond in user_conditions:
291
  for cat_cond in category_conditions:
292
- budget_query = {**user_cond, **cat_cond}
293
- budget_count = self.db.budgets.count_documents(budget_query)
294
- if budget_count > 0:
295
- print(f"✅ Found {budget_count} budget(s) for user {user_id} with category_id {category_id}")
296
- print(f" Query that matched: {budget_query}")
297
- return True
298
 
299
  # THIRD: Also try a more comprehensive query using $and and $or
300
  try:
@@ -466,7 +437,6 @@ class SmartBudgetRecommender:
466
  import traceback
467
  traceback.print_exc()
468
 
469
- print(f"❌ No previous data found for user {user_id} with category_id {category_id}")
470
  return False
471
 
472
  def get_recommendation_for_category(self, user_id: str, category_id: str, month: int, year: int, budget_amount: Optional[float] = None) -> List[BudgetRecommendation]:
@@ -879,7 +849,7 @@ class SmartBudgetRecommender:
879
 
880
  if isinstance(date, str):
881
  try:
882
- date = datetime.fromisoformat(date.replace('Z', '+00:00'))
883
  except (ValueError, AttributeError):
884
  continue
885
  elif not isinstance(date, datetime):
@@ -1163,7 +1133,7 @@ class SmartBudgetRecommender:
1163
  print(f"Final fallback search failed: {e}")
1164
 
1165
  return str(category_id) if category_id else "Uncategorized"
1166
-
1167
  def _get_category_stats_from_budgets(
1168
  self, user_id: str, month: int, year: int
1169
  ) -> Dict:
@@ -1372,14 +1342,14 @@ class SmartBudgetRecommender:
1372
  result[result_key] = {
1373
  "category_name": category_name,
1374
  "category_id": str(category_id) if category_id else None,
1375
- "average_monthly": base_amount,
1376
- "total": base_amount,
1377
- "count": 1,
1378
- "months_analyzed": 1,
1379
- "std_dev": 0.0,
1380
- "monthly_values": [base_amount],
1381
- }
1382
- else:
1383
  result[result_key]["total"] += base_amount
1384
  result[result_key]["count"] += 1
1385
  result[result_key]["months_analyzed"] = result[result_key]["count"]
@@ -1404,14 +1374,14 @@ class SmartBudgetRecommender:
1404
  if not OPENAI_API_KEY:
1405
  print(f"⚠️ OpenAI API key not found in environment variables for category: {category}")
1406
  return None
1407
-
1408
  print(f"🔄 Calling OpenAI API for category: {category}...")
1409
 
1410
  # Handle empty monthly_values
1411
  if not data.get("monthly_values") or len(data["monthly_values"]) == 0:
1412
  history = f"{avg_expense:.0f}"
1413
  else:
1414
- history = ", ".join(f"{value:.0f}" for value in data["monthly_values"])
1415
 
1416
  # Build comprehensive data summary for OpenAI to analyze
1417
  monthly_values = data.get("monthly_values", [])
@@ -1450,8 +1420,8 @@ class SmartBudgetRecommender:
1450
 
1451
  if is_new_budget:
1452
  # This is a new budget - no historical data
1453
- summary = (
1454
- f"Category: {category}\n"
1455
  f"⚠️ IMPORTANT: This is a NEW BUDGET with NO historical spending data.\n"
1456
  f"The user has provided a budget amount of {avg_expense:,.2f} for this category.\n"
1457
  f"This is the ONLY data point available - there is NO spending history to analyze.\n\n"
@@ -1475,7 +1445,7 @@ class SmartBudgetRecommender:
1475
  f"Number of months analyzed: {data['months_analyzed']}\n"
1476
  f"Total spending: {data.get('total', avg_expense * data['months_analyzed']):,.2f}\n"
1477
  f"Trend Analysis: {trend_analysis}\n"
1478
- )
1479
 
1480
  prompt = (
1481
  "You are an expert global personal finance coach with deep knowledge of:\n"
 
44
  # This ensures we only show recommendations for budgets the user actually created
45
  if not category_data:
46
  print(f"No budgets found for user_id: {user_id}, returning empty recommendations")
47
+ return []
48
 
49
  recommendations: List[BudgetRecommendation] = []
50
 
 
84
  elif std_dev > avg_expense * 0.05:
85
  recommended_budget = avg_expense * 1.20
86
  action = "increase"
87
+ else:
88
  recommended_budget = avg_expense * 1.05
89
  action = "increase"
90
 
 
125
  Returns:
126
  True if user has previous data for this category, False otherwise
127
  """
 
 
128
  # FIRST: Check if category_id is actually a budget _id
129
  # If so, find the budget and check if it belongs to the user and has categories
130
  try:
 
134
  budget_by_id = self.db.budgets.find_one({"_id": budget_id_objid})
135
  if budget_by_id:
136
  budget_created_by = budget_by_id.get("createdBy")
137
+ # Check if this budget belongs to the user (handle both ObjectId and string comparisons)
138
+ budget_user_match = False
139
+ if budget_created_by:
140
+ if isinstance(budget_created_by, ObjectId):
141
+ budget_user_match = (str(budget_created_by) == str(user_id) or budget_created_by == ObjectId(user_id))
142
+ else:
143
+ budget_user_match = (str(budget_created_by) == str(user_id))
144
 
145
  if budget_user_match:
 
146
  # Extract all category IDs from this budget's headCategories
147
  head_categories = budget_by_id.get("headCategories", [])
148
  category_ids_in_budget = []
 
160
  if nc_id:
161
  category_ids_in_budget.append(str(nc_id))
162
 
 
163
  # If budget has categories, consider it as having previous data
164
  if category_ids_in_budget:
 
 
165
  return True
 
 
 
 
166
  except (ValueError, TypeError):
167
  pass # category_id is not a valid ObjectId, continue with normal check
168
  except Exception as e:
169
+ pass # Silently continue if budget check fails
 
 
170
 
171
  # Build comprehensive user query
172
  user_conditions = []
 
212
  {"headCategories.categories.category": category_id},
213
  ]
214
 
215
+ # SECOND: Check nested structure (headCategories[].categories[].category) - most common case
 
 
 
216
  try:
217
  try:
218
  category_objid = ObjectId(category_id)
219
  except (ValueError, TypeError):
220
  category_objid = category_id
221
 
222
+ # Try multiple nested query patterns
223
+ nested_queries = [
224
+ {
225
+ "$and": [
226
+ {"$or": user_conditions},
227
+ {
228
+ "$or": [
229
+ {"headCategories": {"$elemMatch": {"categories": {"$elemMatch": {"category": category_objid}}}}},
230
+ {"headCategories": {"$elemMatch": {"categories": {"$elemMatch": {"category": category_id}}}}},
231
+ {"headCategories": {"$elemMatch": {"headCategory": category_objid}}},
232
+ {"headCategories": {"$elemMatch": {"headCategory": category_id}}},
233
+ ]
234
+ }
235
+ ]
236
+ },
237
+ {
238
+ "$and": [
239
+ {"$or": user_conditions},
240
+ {
241
+ "$or": [
242
+ {"headCategories.categories.category": category_objid},
243
+ {"headCategories.categories.category": category_id},
244
+ {"headCategories.headCategory": category_objid},
245
+ {"headCategories.headCategory": category_id},
246
+ ]
247
+ }
248
+ ]
249
+ }
250
+ ]
 
 
 
 
 
251
 
252
+ for nested_query in nested_queries:
 
253
  try:
254
+ if self.db.budgets.count_documents(nested_query) > 0:
 
 
 
255
  return True
256
+ except Exception:
 
257
  continue
258
+ except Exception:
259
+ pass
 
 
 
 
260
 
261
+ # THIRD: Check direct category fields
 
262
  for user_cond in user_conditions:
263
  for cat_cond in category_conditions:
264
+ try:
265
+ if self.db.budgets.count_documents({**user_cond, **cat_cond}) > 0:
266
+ return True
267
+ except Exception:
268
+ continue
 
269
 
270
  # THIRD: Also try a more comprehensive query using $and and $or
271
  try:
 
437
  import traceback
438
  traceback.print_exc()
439
 
 
440
  return False
441
 
442
  def get_recommendation_for_category(self, user_id: str, category_id: str, month: int, year: int, budget_amount: Optional[float] = None) -> List[BudgetRecommendation]:
 
849
 
850
  if isinstance(date, str):
851
  try:
852
+ date = datetime.fromisoformat(date.replace('Z', '+00:00'))
853
  except (ValueError, AttributeError):
854
  continue
855
  elif not isinstance(date, datetime):
 
1133
  print(f"Final fallback search failed: {e}")
1134
 
1135
  return str(category_id) if category_id else "Uncategorized"
1136
+
1137
  def _get_category_stats_from_budgets(
1138
  self, user_id: str, month: int, year: int
1139
  ) -> Dict:
 
1342
  result[result_key] = {
1343
  "category_name": category_name,
1344
  "category_id": str(category_id) if category_id else None,
1345
+ "average_monthly": base_amount,
1346
+ "total": base_amount,
1347
+ "count": 1,
1348
+ "months_analyzed": 1,
1349
+ "std_dev": 0.0,
1350
+ "monthly_values": [base_amount],
1351
+ }
1352
+ else:
1353
  result[result_key]["total"] += base_amount
1354
  result[result_key]["count"] += 1
1355
  result[result_key]["months_analyzed"] = result[result_key]["count"]
 
1374
  if not OPENAI_API_KEY:
1375
  print(f"⚠️ OpenAI API key not found in environment variables for category: {category}")
1376
  return None
1377
+
1378
  print(f"🔄 Calling OpenAI API for category: {category}...")
1379
 
1380
  # Handle empty monthly_values
1381
  if not data.get("monthly_values") or len(data["monthly_values"]) == 0:
1382
  history = f"{avg_expense:.0f}"
1383
  else:
1384
+ history = ", ".join(f"{value:.0f}" for value in data["monthly_values"])
1385
 
1386
  # Build comprehensive data summary for OpenAI to analyze
1387
  monthly_values = data.get("monthly_values", [])
 
1420
 
1421
  if is_new_budget:
1422
  # This is a new budget - no historical data
1423
+ summary = (
1424
+ f"Category: {category}\n"
1425
  f"⚠️ IMPORTANT: This is a NEW BUDGET with NO historical spending data.\n"
1426
  f"The user has provided a budget amount of {avg_expense:,.2f} for this category.\n"
1427
  f"This is the ONLY data point available - there is NO spending history to analyze.\n\n"
 
1445
  f"Number of months analyzed: {data['months_analyzed']}\n"
1446
  f"Total spending: {data.get('total', avg_expense * data['months_analyzed']):,.2f}\n"
1447
  f"Trend Analysis: {trend_analysis}\n"
1448
+ )
1449
 
1450
  prompt = (
1451
  "You are an expert global personal finance coach with deep knowledge of:\n"