Commit
·
caeed61
1
Parent(s):
32daeaa
Use budgets (createdBy) as primary source for recommendations
Browse files- app/smart_recommendation.py +43 -33
app/smart_recommendation.py
CHANGED
|
@@ -36,33 +36,34 @@ class SmartBudgetRecommender:
|
|
| 36 |
Returns:
|
| 37 |
List of budget recommendations for each category
|
| 38 |
"""
|
| 39 |
-
#
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
)
|
| 51 |
-
)
|
| 52 |
|
| 53 |
-
|
|
|
|
| 54 |
|
| 55 |
-
if expenses:
|
| 56 |
# Group expenses by category and calculate monthly averages
|
| 57 |
category_data = self._calculate_category_statistics(
|
| 58 |
expenses, start_date, end_date
|
| 59 |
)
|
| 60 |
-
else:
|
| 61 |
-
# No expenses found: fallback to existing budgets for this period
|
| 62 |
-
category_data = self._get_category_stats_from_budgets(user_id, month, year)
|
| 63 |
-
if not category_data:
|
| 64 |
-
return []
|
| 65 |
|
|
|
|
|
|
|
| 66 |
for category, data in category_data.items():
|
| 67 |
avg_expense = data["average_monthly"]
|
| 68 |
confidence = self._calculate_confidence(data)
|
|
@@ -249,17 +250,16 @@ class SmartBudgetRecommender:
|
|
| 249 |
self, user_id: str, month: int, year: int
|
| 250 |
) -> Dict:
|
| 251 |
"""
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
| 254 |
"""
|
| 255 |
budgets = list(
|
| 256 |
self.db.budgets.find(
|
| 257 |
{
|
| 258 |
-
"
|
| 259 |
-
"
|
| 260 |
-
"start_date": {
|
| 261 |
-
"$lte": datetime(year, month, 28)
|
| 262 |
-
}, # loosely within month
|
| 263 |
}
|
| 264 |
)
|
| 265 |
)
|
|
@@ -269,26 +269,36 @@ class SmartBudgetRecommender:
|
|
| 269 |
|
| 270 |
result: Dict[str, Dict] = {}
|
| 271 |
for b in budgets:
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
if category not in result:
|
| 275 |
result[category] = {
|
| 276 |
-
"average_monthly":
|
| 277 |
-
"total":
|
| 278 |
"count": 1,
|
| 279 |
"months_analyzed": 1,
|
| 280 |
"std_dev": 0.0,
|
| 281 |
-
"monthly_values": [
|
| 282 |
}
|
| 283 |
else:
|
| 284 |
# If multiple budgets per category, average them
|
| 285 |
-
result[category]["total"] +=
|
| 286 |
result[category]["count"] += 1
|
| 287 |
result[category]["months_analyzed"] = result[category]["count"]
|
| 288 |
result[category]["average_monthly"] = (
|
| 289 |
result[category]["total"] / result[category]["count"]
|
| 290 |
)
|
| 291 |
-
result[category]["monthly_values"].append(
|
| 292 |
|
| 293 |
return result
|
| 294 |
|
|
|
|
| 36 |
Returns:
|
| 37 |
List of budget recommendations for each category
|
| 38 |
"""
|
| 39 |
+
# 1) Try to build stats from existing budgets for this user (createdBy)
|
| 40 |
+
category_data = self._get_category_stats_from_budgets(user_id, month, year)
|
| 41 |
+
|
| 42 |
+
# 2) If there are no budgets, fall back to expenses history
|
| 43 |
+
if not category_data:
|
| 44 |
+
end_date = datetime(year, month, 1) - timedelta(days=1)
|
| 45 |
+
start_date = end_date - timedelta(days=180) # ~6 months
|
| 46 |
+
|
| 47 |
+
expenses = list(
|
| 48 |
+
self.db.expenses.find(
|
| 49 |
+
{
|
| 50 |
+
"user_id": user_id,
|
| 51 |
+
"date": {"$gte": start_date, "$lte": end_date},
|
| 52 |
+
"type": "expense",
|
| 53 |
+
}
|
| 54 |
+
)
|
| 55 |
)
|
|
|
|
| 56 |
|
| 57 |
+
if not expenses:
|
| 58 |
+
return []
|
| 59 |
|
|
|
|
| 60 |
# Group expenses by category and calculate monthly averages
|
| 61 |
category_data = self._calculate_category_statistics(
|
| 62 |
expenses, start_date, end_date
|
| 63 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
recommendations: List[BudgetRecommendation] = []
|
| 66 |
+
|
| 67 |
for category, data in category_data.items():
|
| 68 |
avg_expense = data["average_monthly"]
|
| 69 |
confidence = self._calculate_confidence(data)
|
|
|
|
| 250 |
self, user_id: str, month: int, year: int
|
| 251 |
) -> Dict:
|
| 252 |
"""
|
| 253 |
+
Build category stats from existing budgets for this user.
|
| 254 |
+
|
| 255 |
+
We treat each budget document (e.g. \"Office Maintenance\", \"LOGICGO\")
|
| 256 |
+
as a spending category and derive an \"average\" from its amounts.
|
| 257 |
"""
|
| 258 |
budgets = list(
|
| 259 |
self.db.budgets.find(
|
| 260 |
{
|
| 261 |
+
"createdBy": user_id,
|
| 262 |
+
"status": "OPEN",
|
|
|
|
|
|
|
|
|
|
| 263 |
}
|
| 264 |
)
|
| 265 |
)
|
|
|
|
| 269 |
|
| 270 |
result: Dict[str, Dict] = {}
|
| 271 |
for b in budgets:
|
| 272 |
+
# Use budget \"name\" as category label
|
| 273 |
+
category = b.get("name", "Uncategorized")
|
| 274 |
+
|
| 275 |
+
# Derive a base amount from WalletSync fields
|
| 276 |
+
max_amount = float(b.get("maxAmount", 0) or 0)
|
| 277 |
+
spend_amount = float(b.get("spendAmount", 0) or 0)
|
| 278 |
+
|
| 279 |
+
# If there is recorded spend, use that as \"average\"; otherwise maxAmount
|
| 280 |
+
base_amount = spend_amount if spend_amount > 0 else max_amount
|
| 281 |
+
if base_amount <= 0:
|
| 282 |
+
continue
|
| 283 |
+
|
| 284 |
if category not in result:
|
| 285 |
result[category] = {
|
| 286 |
+
"average_monthly": base_amount,
|
| 287 |
+
"total": base_amount,
|
| 288 |
"count": 1,
|
| 289 |
"months_analyzed": 1,
|
| 290 |
"std_dev": 0.0,
|
| 291 |
+
"monthly_values": [base_amount],
|
| 292 |
}
|
| 293 |
else:
|
| 294 |
# If multiple budgets per category, average them
|
| 295 |
+
result[category]["total"] += base_amount
|
| 296 |
result[category]["count"] += 1
|
| 297 |
result[category]["months_analyzed"] = result[category]["count"]
|
| 298 |
result[category]["average_monthly"] = (
|
| 299 |
result[category]["total"] / result[category]["count"]
|
| 300 |
)
|
| 301 |
+
result[category]["monthly_values"].append(base_amount)
|
| 302 |
|
| 303 |
return result
|
| 304 |
|