Commit
·
e95b2a4
1
Parent(s):
57331b8
Add budget_amount parameter to recommendations/check endpoint to generate recommendations based on provided budget
Browse files- Smart_Budget_Recommendation_API.postman_collection.json +1 -1
- app/main.py +23 -4
- app/models.py +1 -0
- app/smart_recommendation.py +54 -2
Smart_Budget_Recommendation_API.postman_collection.json
CHANGED
|
@@ -169,7 +169,7 @@
|
|
| 169 |
],
|
| 170 |
"body": {
|
| 171 |
"mode": "raw",
|
| 172 |
-
"raw": "{\n \"user_id\": \"
|
| 173 |
},
|
| 174 |
"url": {
|
| 175 |
"raw": "{{base_url}}/recommendations/check?month=1&year=2026",
|
|
|
|
| 169 |
],
|
| 170 |
"body": {
|
| 171 |
"mode": "raw",
|
| 172 |
+
"raw": "{\n \"user_id\": \"6741abd38d30ab5b7176397f\",\n \"category_id\": \"677f6c117ca4500f15dbf108\",\n \"budget_amount\": 10000.0\n}"
|
| 173 |
},
|
| 174 |
"url": {
|
| 175 |
"raw": "{{base_url}}/recommendations/check?month=1&year=2026",
|
app/main.py
CHANGED
|
@@ -207,13 +207,15 @@ async def check_and_get_recommendations(request: RecommendationRequest, month: O
|
|
| 207 |
# Check if user has previous data for this category
|
| 208 |
has_data = recommender.check_user_has_category_data(request.user_id, request.category_id)
|
| 209 |
|
| 210 |
-
if has_data:
|
| 211 |
# Get recommendations for this specific category
|
|
|
|
| 212 |
recommendations = recommender.get_recommendation_for_category(
|
| 213 |
request.user_id,
|
| 214 |
request.category_id,
|
| 215 |
month,
|
| 216 |
-
year
|
|
|
|
| 217 |
)
|
| 218 |
|
| 219 |
if recommendations:
|
|
@@ -230,11 +232,28 @@ async def check_and_get_recommendations(request: RecommendationRequest, month: O
|
|
| 230 |
message="User has previous data but no recommendations could be generated for this category."
|
| 231 |
)
|
| 232 |
else:
|
| 233 |
-
# User doesn't have previous data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
return RecommendationResponse(
|
| 235 |
has_previous_data=False,
|
| 236 |
recommendations=None,
|
| 237 |
-
message=f"User does not have previous data for category_id: {request.category_id}. Please
|
| 238 |
)
|
| 239 |
|
| 240 |
@app.get("/recommendations/{user_id}", response_model=List[BudgetRecommendation])
|
|
|
|
| 207 |
# Check if user has previous data for this category
|
| 208 |
has_data = recommender.check_user_has_category_data(request.user_id, request.category_id)
|
| 209 |
|
| 210 |
+
if has_data or request.budget_amount:
|
| 211 |
# Get recommendations for this specific category
|
| 212 |
+
# Pass budget_amount if provided
|
| 213 |
recommendations = recommender.get_recommendation_for_category(
|
| 214 |
request.user_id,
|
| 215 |
request.category_id,
|
| 216 |
month,
|
| 217 |
+
year,
|
| 218 |
+
request.budget_amount
|
| 219 |
)
|
| 220 |
|
| 221 |
if recommendations:
|
|
|
|
| 232 |
message="User has previous data but no recommendations could be generated for this category."
|
| 233 |
)
|
| 234 |
else:
|
| 235 |
+
# User doesn't have previous data, but if budget_amount is provided, generate recommendation anyway
|
| 236 |
+
if request.budget_amount and request.budget_amount > 0:
|
| 237 |
+
recommendations = recommender.get_recommendation_for_category(
|
| 238 |
+
request.user_id,
|
| 239 |
+
request.category_id,
|
| 240 |
+
month,
|
| 241 |
+
year,
|
| 242 |
+
request.budget_amount
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
if recommendations:
|
| 246 |
+
return RecommendationResponse(
|
| 247 |
+
has_previous_data=False,
|
| 248 |
+
recommendations=recommendations,
|
| 249 |
+
message=f"Recommendation generated based on provided budget amount. User does not have previous data for this category."
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
# User doesn't have previous data and no budget_amount provided
|
| 253 |
return RecommendationResponse(
|
| 254 |
has_previous_data=False,
|
| 255 |
recommendations=None,
|
| 256 |
+
message=f"User does not have previous data for category_id: {request.category_id}. Please provide a budget_amount or create a budget/expenses for this category first."
|
| 257 |
)
|
| 258 |
|
| 259 |
@app.get("/recommendations/{user_id}", response_model=List[BudgetRecommendation])
|
app/models.py
CHANGED
|
@@ -44,6 +44,7 @@ class CategoryExpense(BaseModel):
|
|
| 44 |
class RecommendationRequest(BaseModel):
|
| 45 |
user_id: str = Field(..., description="User identifier")
|
| 46 |
category_id: str = Field(..., description="Category ID to check for previous data")
|
|
|
|
| 47 |
|
| 48 |
class RecommendationResponse(BaseModel):
|
| 49 |
has_previous_data: bool
|
|
|
|
| 44 |
class RecommendationRequest(BaseModel):
|
| 45 |
user_id: str = Field(..., description="User identifier")
|
| 46 |
category_id: str = Field(..., description="Category ID to check for previous data")
|
| 47 |
+
budget_amount: Optional[float] = Field(None, description="Current budget amount for this category (optional)")
|
| 48 |
|
| 49 |
class RecommendationResponse(BaseModel):
|
| 50 |
has_previous_data: bool
|
app/smart_recommendation.py
CHANGED
|
@@ -3,7 +3,7 @@ import math
|
|
| 3 |
import os
|
| 4 |
from collections import defaultdict
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
-
from typing import Dict, List
|
| 7 |
|
| 8 |
import requests
|
| 9 |
from dotenv import load_dotenv
|
|
@@ -180,7 +180,7 @@ class SmartBudgetRecommender:
|
|
| 180 |
print(f"Error checking user category data: {e}")
|
| 181 |
return False
|
| 182 |
|
| 183 |
-
def get_recommendation_for_category(self, user_id: str, category_id: str, month: int, year: int) -> List[BudgetRecommendation]:
|
| 184 |
"""
|
| 185 |
Get budget recommendations for a specific category for a user.
|
| 186 |
|
|
@@ -189,6 +189,7 @@ class SmartBudgetRecommender:
|
|
| 189 |
category_id: Category ID to get recommendations for
|
| 190 |
month: Target month (1-12)
|
| 191 |
year: Target year
|
|
|
|
| 192 |
|
| 193 |
Returns:
|
| 194 |
List of budget recommendations for the specific category
|
|
@@ -202,6 +203,57 @@ class SmartBudgetRecommender:
|
|
| 202 |
if rec.category_id == category_id or str(rec.category_id) == str(category_id)
|
| 203 |
]
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
return filtered_recommendations
|
| 206 |
|
| 207 |
def _calculate_category_statistics(self, expenses: List[Dict], start_date: datetime, end_date: datetime) -> Dict:
|
|
|
|
| 3 |
import os
|
| 4 |
from collections import defaultdict
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
+
from typing import Dict, List, Optional
|
| 7 |
|
| 8 |
import requests
|
| 9 |
from dotenv import load_dotenv
|
|
|
|
| 180 |
print(f"Error checking user category data: {e}")
|
| 181 |
return False
|
| 182 |
|
| 183 |
+
def get_recommendation_for_category(self, user_id: str, category_id: str, month: int, year: int, budget_amount: Optional[float] = None) -> List[BudgetRecommendation]:
|
| 184 |
"""
|
| 185 |
Get budget recommendations for a specific category for a user.
|
| 186 |
|
|
|
|
| 189 |
category_id: Category ID to get recommendations for
|
| 190 |
month: Target month (1-12)
|
| 191 |
year: Target year
|
| 192 |
+
budget_amount: Optional current budget amount to use for recommendations
|
| 193 |
|
| 194 |
Returns:
|
| 195 |
List of budget recommendations for the specific category
|
|
|
|
| 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:
|