Commit
·
32daeaa
1
Parent(s):
26bdcce
Fallback to budgets when no expenses
Browse files- app/smart_recommendation.py +69 -12
app/smart_recommendation.py
CHANGED
|
@@ -40,19 +40,29 @@ class SmartBudgetRecommender:
|
|
| 40 |
end_date = datetime(year, month, 1) - timedelta(days=1)
|
| 41 |
start_date = end_date - timedelta(days=180) # ~6 months
|
| 42 |
|
| 43 |
-
expenses = list(
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
recommendations = []
|
| 56 |
for category, data in category_data.items():
|
| 57 |
avg_expense = data["average_monthly"]
|
| 58 |
confidence = self._calculate_confidence(data)
|
|
@@ -235,6 +245,53 @@ class SmartBudgetRecommender:
|
|
| 235 |
result.sort(key=lambda x: x.average_monthly_expense, reverse=True)
|
| 236 |
return result
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
def _get_ai_recommendation(self, category: str, data: Dict, avg_expense: float):
|
| 239 |
"""Use OpenAI to refine the budget recommendation."""
|
| 240 |
if not OPENAI_API_KEY:
|
|
|
|
| 40 |
end_date = datetime(year, month, 1) - timedelta(days=1)
|
| 41 |
start_date = end_date - timedelta(days=180) # ~6 months
|
| 42 |
|
| 43 |
+
expenses = list(
|
| 44 |
+
self.db.expenses.find(
|
| 45 |
+
{
|
| 46 |
+
"user_id": user_id,
|
| 47 |
+
"date": {"$gte": start_date, "$lte": end_date},
|
| 48 |
+
"type": "expense",
|
| 49 |
+
}
|
| 50 |
+
)
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
recommendations: List[BudgetRecommendation] = []
|
| 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)
|
|
|
|
| 245 |
result.sort(key=lambda x: x.average_monthly_expense, reverse=True)
|
| 246 |
return result
|
| 247 |
|
| 248 |
+
def _get_category_stats_from_budgets(
|
| 249 |
+
self, user_id: str, month: int, year: int
|
| 250 |
+
) -> Dict:
|
| 251 |
+
"""
|
| 252 |
+
Fallback source when there are no expenses:
|
| 253 |
+
use existing budgets for the requested month/year as the 'average' spend.
|
| 254 |
+
"""
|
| 255 |
+
budgets = list(
|
| 256 |
+
self.db.budgets.find(
|
| 257 |
+
{
|
| 258 |
+
"user_id": user_id,
|
| 259 |
+
"period": {"$in": ["monthly", "MONTHLY"]},
|
| 260 |
+
"start_date": {
|
| 261 |
+
"$lte": datetime(year, month, 28)
|
| 262 |
+
}, # loosely within month
|
| 263 |
+
}
|
| 264 |
+
)
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
if not budgets:
|
| 268 |
+
return {}
|
| 269 |
+
|
| 270 |
+
result: Dict[str, Dict] = {}
|
| 271 |
+
for b in budgets:
|
| 272 |
+
category = b.get("category", "Uncategorized")
|
| 273 |
+
amount = float(b.get("amount", 0))
|
| 274 |
+
if category not in result:
|
| 275 |
+
result[category] = {
|
| 276 |
+
"average_monthly": amount,
|
| 277 |
+
"total": amount,
|
| 278 |
+
"count": 1,
|
| 279 |
+
"months_analyzed": 1,
|
| 280 |
+
"std_dev": 0.0,
|
| 281 |
+
"monthly_values": [amount],
|
| 282 |
+
}
|
| 283 |
+
else:
|
| 284 |
+
# If multiple budgets per category, average them
|
| 285 |
+
result[category]["total"] += amount
|
| 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(amount)
|
| 292 |
+
|
| 293 |
+
return result
|
| 294 |
+
|
| 295 |
def _get_ai_recommendation(self, category: str, data: Dict, avg_expense: float):
|
| 296 |
"""Use OpenAI to refine the budget recommendation."""
|
| 297 |
if not OPENAI_API_KEY:
|