Commit
·
21a5b7f
1
Parent(s):
2210c72
Improve check_user_has_category_data to comprehensively search budgets collection and fix logic to prioritize historical data over budget_amount
Browse files- app/main.py +32 -35
- app/smart_recommendation.py +83 -56
app/main.py
CHANGED
|
@@ -207,32 +207,23 @@ 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 |
-
#
|
| 212 |
-
|
| 213 |
recommendations = recommender.get_recommendation_for_category(
|
| 214 |
request.user_id,
|
| 215 |
request.category_id,
|
| 216 |
month,
|
| 217 |
year,
|
| 218 |
-
|
| 219 |
)
|
| 220 |
|
| 221 |
if recommendations:
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
recommendations=recommendations,
|
| 228 |
-
message="Recommendation generated based on provided budget amount. User does not have previous spending data for this category."
|
| 229 |
-
)
|
| 230 |
-
else:
|
| 231 |
-
return RecommendationResponse(
|
| 232 |
-
has_previous_data=True,
|
| 233 |
-
recommendations=recommendations,
|
| 234 |
-
message=None
|
| 235 |
-
)
|
| 236 |
else:
|
| 237 |
# User has data but no recommendations generated (edge case)
|
| 238 |
return RecommendationResponse(
|
|
@@ -240,24 +231,30 @@ async def check_and_get_recommendations(request: RecommendationRequest, month: O
|
|
| 240 |
recommendations=[],
|
| 241 |
message="User has previous data but no recommendations could be generated for this category."
|
| 242 |
)
|
| 243 |
-
|
| 244 |
-
# User doesn't have previous data, but
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
if recommendations:
|
| 255 |
-
return RecommendationResponse(
|
| 256 |
-
has_previous_data=False,
|
| 257 |
-
recommendations=recommendations,
|
| 258 |
-
message=f"Recommendation generated based on provided budget amount. User does not have previous data for this category."
|
| 259 |
-
)
|
| 260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
# User doesn't have previous data and no budget_amount provided
|
| 262 |
return RecommendationResponse(
|
| 263 |
has_previous_data=False,
|
|
|
|
| 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 |
+
# User has previous data - use it for recommendations (ignore budget_amount if provided)
|
| 212 |
+
print(f"✅ User {request.user_id} has previous data for category {request.category_id} - using historical data")
|
| 213 |
recommendations = recommender.get_recommendation_for_category(
|
| 214 |
request.user_id,
|
| 215 |
request.category_id,
|
| 216 |
month,
|
| 217 |
year,
|
| 218 |
+
budget_amount=None # Don't use budget_amount if user has historical data
|
| 219 |
)
|
| 220 |
|
| 221 |
if recommendations:
|
| 222 |
+
return RecommendationResponse(
|
| 223 |
+
has_previous_data=True,
|
| 224 |
+
recommendations=recommendations,
|
| 225 |
+
message=None
|
| 226 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
else:
|
| 228 |
# User has data but no recommendations generated (edge case)
|
| 229 |
return RecommendationResponse(
|
|
|
|
| 231 |
recommendations=[],
|
| 232 |
message="User has previous data but no recommendations could be generated for this category."
|
| 233 |
)
|
| 234 |
+
elif request.budget_amount and request.budget_amount > 0:
|
| 235 |
+
# User doesn't have previous data, but provided budget_amount - generate recommendation based on it
|
| 236 |
+
print(f"ℹ️ User {request.user_id} does not have previous data for category {request.category_id} - using provided budget_amount")
|
| 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="Recommendation generated based on provided budget amount. User does not have previous spending data for this category."
|
| 250 |
+
)
|
| 251 |
+
else:
|
| 252 |
+
return RecommendationResponse(
|
| 253 |
+
has_previous_data=False,
|
| 254 |
+
recommendations=None,
|
| 255 |
+
message="Could not generate recommendations even with provided budget amount."
|
| 256 |
+
)
|
| 257 |
+
else:
|
| 258 |
# User doesn't have previous data and no budget_amount provided
|
| 259 |
return RecommendationResponse(
|
| 260 |
has_previous_data=False,
|
app/smart_recommendation.py
CHANGED
|
@@ -125,79 +125,106 @@ class SmartBudgetRecommender:
|
|
| 125 |
Returns:
|
| 126 |
True if user has previous data for this category, False otherwise
|
| 127 |
"""
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
try:
|
| 130 |
-
# Try ObjectId format
|
| 131 |
try:
|
| 132 |
category_objid = ObjectId(category_id)
|
| 133 |
-
|
| 134 |
"$or": [
|
| 135 |
-
{"createdBy": ObjectId(user_id), "category": category_objid},
|
| 136 |
-
{"createdBy": ObjectId(user_id), "categoryId": category_objid},
|
| 137 |
-
{"createdBy": ObjectId(user_id), "headCategory": category_objid},
|
| 138 |
-
{"createdBy": user_id, "category": category_objid},
|
| 139 |
-
{"createdBy": user_id, "categoryId": category_objid},
|
| 140 |
-
{"createdBy": user_id, "headCategory": category_objid},
|
| 141 |
{"user_id": ObjectId(user_id), "category": category_objid},
|
| 142 |
-
{"user_id": user_id, "category": category_objid},
|
| 143 |
-
]
|
| 144 |
-
}
|
| 145 |
-
except (ValueError, TypeError):
|
| 146 |
-
# category_id is not a valid ObjectId, try as string
|
| 147 |
-
budget_query = {
|
| 148 |
-
"$or": [
|
| 149 |
-
{"createdBy": ObjectId(user_id), "category": category_id},
|
| 150 |
-
{"createdBy": ObjectId(user_id), "categoryId": category_id},
|
| 151 |
-
{"createdBy": ObjectId(user_id), "headCategory": category_id},
|
| 152 |
-
{"createdBy": user_id, "category": category_id},
|
| 153 |
-
{"createdBy": user_id, "categoryId": category_id},
|
| 154 |
-
{"createdBy": user_id, "headCategory": category_id},
|
| 155 |
{"user_id": ObjectId(user_id), "category": category_id},
|
|
|
|
| 156 |
{"user_id": user_id, "category": category_id},
|
| 157 |
]
|
| 158 |
}
|
| 159 |
-
|
| 160 |
-
# Check budgets collection
|
| 161 |
-
budget_count = self.db.budgets.count_documents(budget_query)
|
| 162 |
-
if budget_count > 0:
|
| 163 |
-
print(f"✅ Found {budget_count} budget(s) for user {user_id} with category_id {category_id}")
|
| 164 |
-
return True
|
| 165 |
-
|
| 166 |
-
# Also check if category_id is in headCategories array
|
| 167 |
-
head_cat_query = {
|
| 168 |
-
"$or": [
|
| 169 |
-
{"createdBy": ObjectId(user_id), "headCategories.category": category_id},
|
| 170 |
-
{"createdBy": user_id, "headCategories.category": category_id},
|
| 171 |
-
{"user_id": ObjectId(user_id), "headCategories.category": category_id},
|
| 172 |
-
{"user_id": user_id, "headCategories.category": category_id},
|
| 173 |
-
]
|
| 174 |
-
}
|
| 175 |
-
head_cat_count = self.db.budgets.count_documents(head_cat_query)
|
| 176 |
-
if head_cat_count > 0:
|
| 177 |
-
print(f"✅ Found {head_cat_count} budget(s) with category_id {category_id} in headCategories for user {user_id}")
|
| 178 |
-
return True
|
| 179 |
-
|
| 180 |
-
# Check expenses collection
|
| 181 |
-
try:
|
| 182 |
expense_query = {
|
| 183 |
"$or": [
|
| 184 |
{"user_id": ObjectId(user_id), "category": category_id},
|
| 185 |
{"user_id": user_id, "category": category_id},
|
| 186 |
]
|
| 187 |
}
|
| 188 |
-
expense_count = self.db.expenses.count_documents(expense_query)
|
| 189 |
-
if expense_count > 0:
|
| 190 |
-
print(f"✅ Found {expense_count} expense(s) for user {user_id} with category_id {category_id}")
|
| 191 |
-
return True
|
| 192 |
-
except Exception as e:
|
| 193 |
-
print(f"Error checking expenses: {e}")
|
| 194 |
-
|
| 195 |
-
print(f"❌ No previous data found for user {user_id} with category_id {category_id}")
|
| 196 |
-
return False
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
except Exception as e:
|
| 199 |
-
print(f"Error checking
|
| 200 |
-
|
|
|
|
|
|
|
| 201 |
|
| 202 |
def get_recommendation_for_category(self, user_id: str, category_id: str, month: int, year: int, budget_amount: Optional[float] = None) -> List[BudgetRecommendation]:
|
| 203 |
"""
|
|
|
|
| 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 |
+
# Build comprehensive user query
|
| 131 |
+
user_conditions = []
|
| 132 |
+
try:
|
| 133 |
+
user_objid = ObjectId(user_id)
|
| 134 |
+
user_conditions = [
|
| 135 |
+
{"createdBy": user_objid},
|
| 136 |
+
{"createdBy": user_id},
|
| 137 |
+
{"user_id": user_objid},
|
| 138 |
+
{"user_id": user_id}
|
| 139 |
+
]
|
| 140 |
+
except (ValueError, TypeError):
|
| 141 |
+
user_conditions = [
|
| 142 |
+
{"createdBy": user_id},
|
| 143 |
+
{"user_id": user_id}
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
# Build comprehensive category query - check all possible fields
|
| 147 |
+
category_conditions = []
|
| 148 |
+
try:
|
| 149 |
+
category_objid = ObjectId(category_id)
|
| 150 |
+
# Try as ObjectId
|
| 151 |
+
category_conditions = [
|
| 152 |
+
{"category": category_objid},
|
| 153 |
+
{"categoryId": category_objid},
|
| 154 |
+
{"headCategory": category_objid},
|
| 155 |
+
{"headCategories.headCategory": category_objid},
|
| 156 |
+
{"headCategories.categories.category": category_objid},
|
| 157 |
+
# Also try as string
|
| 158 |
+
{"category": category_id},
|
| 159 |
+
{"categoryId": category_id},
|
| 160 |
+
{"headCategory": category_id},
|
| 161 |
+
{"headCategories.headCategory": category_id},
|
| 162 |
+
{"headCategories.categories.category": category_id},
|
| 163 |
+
]
|
| 164 |
+
except (ValueError, TypeError):
|
| 165 |
+
# category_id is not a valid ObjectId, try as string only
|
| 166 |
+
category_conditions = [
|
| 167 |
+
{"category": category_id},
|
| 168 |
+
{"categoryId": category_id},
|
| 169 |
+
{"headCategory": category_id},
|
| 170 |
+
{"headCategories.headCategory": category_id},
|
| 171 |
+
{"headCategories.categories.category": category_id},
|
| 172 |
+
]
|
| 173 |
+
|
| 174 |
+
# Combine user and category conditions
|
| 175 |
+
for user_cond in user_conditions:
|
| 176 |
+
for cat_cond in category_conditions:
|
| 177 |
+
budget_query = {**user_cond, **cat_cond}
|
| 178 |
+
budget_count = self.db.budgets.count_documents(budget_query)
|
| 179 |
+
if budget_count > 0:
|
| 180 |
+
print(f"✅ Found {budget_count} budget(s) for user {user_id} with category_id {category_id}")
|
| 181 |
+
print(f" Query that matched: {budget_query}")
|
| 182 |
+
return True
|
| 183 |
+
|
| 184 |
+
# Also try a more comprehensive query using $and and $or
|
| 185 |
+
try:
|
| 186 |
+
comprehensive_query = {
|
| 187 |
+
"$and": [
|
| 188 |
+
{"$or": user_conditions},
|
| 189 |
+
{"$or": category_conditions}
|
| 190 |
+
]
|
| 191 |
+
}
|
| 192 |
+
budget_count = self.db.budgets.count_documents(comprehensive_query)
|
| 193 |
+
if budget_count > 0:
|
| 194 |
+
print(f"✅ Found {budget_count} budget(s) using comprehensive query for user {user_id} with category_id {category_id}")
|
| 195 |
+
return True
|
| 196 |
+
except Exception as e:
|
| 197 |
+
print(f"⚠️ Error with comprehensive query: {e}")
|
| 198 |
+
|
| 199 |
+
# Check expenses collection as fallback
|
| 200 |
try:
|
|
|
|
| 201 |
try:
|
| 202 |
category_objid = ObjectId(category_id)
|
| 203 |
+
expense_query = {
|
| 204 |
"$or": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
{"user_id": ObjectId(user_id), "category": category_objid},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
{"user_id": ObjectId(user_id), "category": category_id},
|
| 207 |
+
{"user_id": user_id, "category": category_objid},
|
| 208 |
{"user_id": user_id, "category": category_id},
|
| 209 |
]
|
| 210 |
}
|
| 211 |
+
except (ValueError, TypeError):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
expense_query = {
|
| 213 |
"$or": [
|
| 214 |
{"user_id": ObjectId(user_id), "category": category_id},
|
| 215 |
{"user_id": user_id, "category": category_id},
|
| 216 |
]
|
| 217 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
+
expense_count = self.db.expenses.count_documents(expense_query)
|
| 220 |
+
if expense_count > 0:
|
| 221 |
+
print(f"✅ Found {expense_count} expense(s) for user {user_id} with category_id {category_id}")
|
| 222 |
+
return True
|
| 223 |
except Exception as e:
|
| 224 |
+
print(f"Error checking expenses: {e}")
|
| 225 |
+
|
| 226 |
+
print(f"❌ No previous data found for user {user_id} with category_id {category_id}")
|
| 227 |
+
return False
|
| 228 |
|
| 229 |
def get_recommendation_for_category(self, user_id: str, category_id: str, month: int, year: int, budget_amount: Optional[float] = None) -> List[BudgetRecommendation]:
|
| 230 |
"""
|