LogicGoInfotechSpaces commited on
Commit
fa62699
·
verified ·
1 Parent(s): fb2a74d

Update app/smart_recommendation.py

Browse files
Files changed (1) hide show
  1. app/smart_recommendation.py +130 -36
app/smart_recommendation.py CHANGED
@@ -262,6 +262,28 @@ class SmartBudgetRecommender:
262
  result.sort(key=lambda x: x.average_monthly_expense, reverse=True)
263
  return result
264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  def _get_category_stats_from_budgets(
266
  self, user_id: str, month: int, year: int
267
  ) -> Dict:
@@ -270,40 +292,41 @@ class SmartBudgetRecommender:
270
 
271
  We treat each budget document (e.g. \"Office Maintenance\", \"LOGICGO\")
272
  as a spending category and derive an \"average\" from its amounts.
 
273
  """
274
  budgets = []
275
 
276
- # Try multiple query patterns to find budgets
277
- # Pattern 1: Try with ObjectId (most common in WalletSync)
278
  try:
279
- query_objid = {"status": "OPEN", "createdBy": ObjectId(user_id)}
280
  budgets_objid = list(self.db.budgets.find(query_objid))
281
  if budgets_objid:
282
  budgets.extend(budgets_objid)
283
  except Exception:
284
  pass
285
 
286
- # Pattern 2: Try with string user_id
287
  try:
288
- query_str = {"status": "OPEN", "createdBy": user_id}
289
  budgets_str = list(self.db.budgets.find(query_str))
290
  if budgets_str:
291
  budgets.extend(budgets_str)
292
  except Exception:
293
  pass
294
 
295
- # Pattern 3: Try with user_id field (alternative field name)
296
  try:
297
- query_userid = {"status": "OPEN", "user_id": user_id}
298
  budgets_userid = list(self.db.budgets.find(query_userid))
299
  if budgets_userid:
300
  budgets.extend(budgets_userid)
301
  except Exception:
302
  pass
303
 
304
- # Pattern 4: Try ObjectId with user_id field
305
  try:
306
- query_objid_userid = {"status": "OPEN", "user_id": ObjectId(user_id)}
307
  budgets_objid_userid = list(self.db.budgets.find(query_objid_userid))
308
  if budgets_objid_userid:
309
  budgets.extend(budgets_objid_userid)
@@ -329,14 +352,85 @@ class SmartBudgetRecommender:
329
 
330
  result: Dict[str, Dict] = {}
331
  for b in budgets:
332
- # Use budget "name" as category label
333
- category = b.get("name", "Uncategorized")
334
- if not category or category == "Uncategorized":
335
- # Try alternative field names
336
- category = b.get("category") or b.get("title") or "Uncategorized"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
  # Derive a base amount from WalletSync fields
339
- # Try multiple field name variations
340
  max_amount = float(b.get("maxAmount", 0) or b.get("max_amount", 0) or b.get("amount", 0) or 0)
341
  spend_amount = float(b.get("spendAmount", 0) or b.get("spend_amount", 0) or b.get("spent", 0) or 0)
342
  budget_amount = float(b.get("budget", 0) or b.get("budgetAmount", 0) or 0)
@@ -349,26 +443,27 @@ class SmartBudgetRecommender:
349
  elif budget_amount > 0:
350
  base_amount = budget_amount
351
  else:
352
- continue # Skip if no valid amount found
353
-
354
- if category not in result:
355
- result[category] = {
356
- "average_monthly": base_amount,
357
- "total": base_amount,
358
- "count": 1,
359
- "months_analyzed": 1,
360
- "std_dev": 0.0,
361
- "monthly_values": [base_amount],
362
- }
363
- else:
364
- # If multiple budgets per category, average them
365
- result[category]["total"] += base_amount
366
- result[category]["count"] += 1
367
- result[category]["months_analyzed"] = result[category]["count"]
368
- result[category]["average_monthly"] = (
369
- result[category]["total"] / result[category]["count"]
370
- )
371
- result[category]["monthly_values"].append(base_amount)
 
372
 
373
  print(f"Processed {len(result)} budget categories for recommendations")
374
  return result
@@ -423,7 +518,6 @@ class SmartBudgetRecommender:
423
  return None
424
 
425
 
426
-
427
  # import json
428
  # import math
429
  # import os
 
262
  result.sort(key=lambda x: x.average_monthly_expense, reverse=True)
263
  return result
264
 
265
+ def _get_category_name(self, category_id) -> str:
266
+ """Look up category name from categories collection"""
267
+ if not category_id:
268
+ return "Uncategorized"
269
+
270
+ try:
271
+ # Try to find category in categories collection
272
+ if isinstance(category_id, ObjectId):
273
+ category_doc = self.db.categories.find_one({"_id": category_id})
274
+ else:
275
+ try:
276
+ category_doc = self.db.categories.find_one({"_id": ObjectId(category_id)})
277
+ except:
278
+ category_doc = self.db.categories.find_one({"_id": category_id})
279
+
280
+ if category_doc:
281
+ return category_doc.get("name") or category_doc.get("title") or str(category_id)
282
+ except Exception:
283
+ pass
284
+
285
+ return str(category_id) if category_id else "Uncategorized"
286
+
287
  def _get_category_stats_from_budgets(
288
  self, user_id: str, month: int, year: int
289
  ) -> Dict:
 
292
 
293
  We treat each budget document (e.g. \"Office Maintenance\", \"LOGICGO\")
294
  as a spending category and derive an \"average\" from its amounts.
295
+ Also extracts categories from headCategories array.
296
  """
297
  budgets = []
298
 
299
+ # Try multiple query patterns to find budgets (include both OPEN and CLOSE status)
300
+ # Pattern 1: Try with ObjectId (most common in WalletSync) - no status filter
301
  try:
302
+ query_objid = {"createdBy": ObjectId(user_id)}
303
  budgets_objid = list(self.db.budgets.find(query_objid))
304
  if budgets_objid:
305
  budgets.extend(budgets_objid)
306
  except Exception:
307
  pass
308
 
309
+ # Pattern 2: Try with string user_id - no status filter
310
  try:
311
+ query_str = {"createdBy": user_id}
312
  budgets_str = list(self.db.budgets.find(query_str))
313
  if budgets_str:
314
  budgets.extend(budgets_str)
315
  except Exception:
316
  pass
317
 
318
+ # Pattern 3: Try with user_id field (alternative field name) - no status filter
319
  try:
320
+ query_userid = {"user_id": user_id}
321
  budgets_userid = list(self.db.budgets.find(query_userid))
322
  if budgets_userid:
323
  budgets.extend(budgets_userid)
324
  except Exception:
325
  pass
326
 
327
+ # Pattern 4: Try ObjectId with user_id field - no status filter
328
  try:
329
+ query_objid_userid = {"user_id": ObjectId(user_id)}
330
  budgets_objid_userid = list(self.db.budgets.find(query_objid_userid))
331
  if budgets_objid_userid:
332
  budgets.extend(budgets_objid_userid)
 
352
 
353
  result: Dict[str, Dict] = {}
354
  for b in budgets:
355
+ # First, try to extract categories from headCategories array
356
+ head_categories = b.get("headCategories", [])
357
+
358
+ if head_categories and isinstance(head_categories, list):
359
+ # Process nested categories from headCategories
360
+ for head_cat in head_categories:
361
+ if not isinstance(head_cat, dict):
362
+ continue
363
+
364
+ # Get headCategory ID and amounts
365
+ head_cat_id = head_cat.get("headCategory")
366
+ head_cat_max = float(head_cat.get("maxAmount", 0) or 0)
367
+ head_cat_spend = float(head_cat.get("spendAmount", 0) or 0)
368
+
369
+ # Process nested categories within headCategory
370
+ nested_categories = head_cat.get("categories", [])
371
+ if nested_categories and isinstance(nested_categories, list):
372
+ for nested_cat in nested_categories:
373
+ if not isinstance(nested_cat, dict):
374
+ continue
375
+
376
+ nested_cat_id = nested_cat.get("category")
377
+ nested_cat_max = float(nested_cat.get("maxAmount", 0) or 0)
378
+ nested_cat_spend = float(nested_cat.get("spendAmount", 0) or 0)
379
+ spend_limit_type = nested_cat.get("spendLimitType", "NO_LIMIT")
380
+
381
+ # Only include categories with limits (maxAmount > 0 or spendLimitType != "NO_LIMIT")
382
+ if nested_cat_max > 0 or (spend_limit_type != "NO_LIMIT" and nested_cat_spend > 0):
383
+ # Look up actual category name
384
+ nested_category_name = self._get_category_name(nested_cat_id)
385
+ nested_base_amount = nested_cat_spend if nested_cat_spend > 0 else nested_cat_max
386
+
387
+ if nested_category_name not in result:
388
+ result[nested_category_name] = {
389
+ "average_monthly": nested_base_amount,
390
+ "total": nested_base_amount,
391
+ "count": 1,
392
+ "months_analyzed": 1,
393
+ "std_dev": 0.0,
394
+ "monthly_values": [nested_base_amount],
395
+ }
396
+ else:
397
+ result[nested_category_name]["total"] += nested_base_amount
398
+ result[nested_category_name]["count"] += 1
399
+ result[nested_category_name]["months_analyzed"] = result[nested_category_name]["count"]
400
+ result[nested_category_name]["average_monthly"] = (
401
+ result[nested_category_name]["total"] / result[nested_category_name]["count"]
402
+ )
403
+ result[nested_category_name]["monthly_values"].append(nested_base_amount)
404
+
405
+ # Also include headCategory if it has amounts
406
+ if head_cat_max > 0 or head_cat_spend > 0:
407
+ head_category_name = self._get_category_name(head_cat_id)
408
+ head_base_amount = head_cat_spend if head_cat_spend > 0 else head_cat_max
409
+
410
+ if head_category_name not in result:
411
+ result[head_category_name] = {
412
+ "average_monthly": head_base_amount,
413
+ "total": head_base_amount,
414
+ "count": 1,
415
+ "months_analyzed": 1,
416
+ "std_dev": 0.0,
417
+ "monthly_values": [head_base_amount],
418
+ }
419
+ else:
420
+ result[head_category_name]["total"] += head_base_amount
421
+ result[head_category_name]["count"] += 1
422
+ result[head_category_name]["months_analyzed"] = result[head_category_name]["count"]
423
+ result[head_category_name]["average_monthly"] = (
424
+ result[head_category_name]["total"] / result[head_category_name]["count"]
425
+ )
426
+ result[head_category_name]["monthly_values"].append(head_base_amount)
427
+
428
+ # Also include the main budget as a category (if it has amounts)
429
+ budget_name = b.get("name", "Uncategorized")
430
+ if not budget_name or budget_name == "Uncategorized":
431
+ budget_name = b.get("category") or b.get("title") or "Uncategorized"
432
 
433
  # Derive a base amount from WalletSync fields
 
434
  max_amount = float(b.get("maxAmount", 0) or b.get("max_amount", 0) or b.get("amount", 0) or 0)
435
  spend_amount = float(b.get("spendAmount", 0) or b.get("spend_amount", 0) or b.get("spent", 0) or 0)
436
  budget_amount = float(b.get("budget", 0) or b.get("budgetAmount", 0) or 0)
 
443
  elif budget_amount > 0:
444
  base_amount = budget_amount
445
  else:
446
+ base_amount = 0
447
+
448
+ # Only add main budget if it has an amount and we haven't processed categories
449
+ if base_amount > 0:
450
+ if budget_name not in result:
451
+ result[budget_name] = {
452
+ "average_monthly": base_amount,
453
+ "total": base_amount,
454
+ "count": 1,
455
+ "months_analyzed": 1,
456
+ "std_dev": 0.0,
457
+ "monthly_values": [base_amount],
458
+ }
459
+ else:
460
+ result[budget_name]["total"] += base_amount
461
+ result[budget_name]["count"] += 1
462
+ result[budget_name]["months_analyzed"] = result[budget_name]["count"]
463
+ result[budget_name]["average_monthly"] = (
464
+ result[budget_name]["total"] / result[budget_name]["count"]
465
+ )
466
+ result[budget_name]["monthly_values"].append(base_amount)
467
 
468
  print(f"Processed {len(result)} budget categories for recommendations")
469
  return result
 
518
  return None
519
 
520
 
 
521
  # import json
522
  # import math
523
  # import os