Commit
·
b3d55f3
1
Parent(s):
9ab0023
Add validation to prevent always 'keep' recommendations - override lazy OpenAI responses with intelligent analysis
Browse files- app/smart_recommendation.py +103 -10
app/smart_recommendation.py
CHANGED
|
@@ -69,6 +69,25 @@ class SmartBudgetRecommender:
|
|
| 69 |
recommended_budget = ai_result.get("recommended_budget")
|
| 70 |
reason = ai_result.get("reason", f"AI recommendation for {category_name}")
|
| 71 |
action = ai_result.get("action")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
print(f"✅ OpenAI recommendation for {category_name}: {recommended_budget} (action: {action})")
|
| 73 |
else:
|
| 74 |
# Fallback to rule-based recommendation if OpenAI fails
|
|
@@ -234,8 +253,16 @@ class SmartBudgetRecommender:
|
|
| 234 |
ai_result = self._get_ai_recommendation(category_name, data, avg_expense)
|
| 235 |
if ai_result and ai_result.get("recommended_budget"):
|
| 236 |
recommended_budget = ai_result.get("recommended_budget")
|
| 237 |
-
reason = ai_result.get("reason", f"AI recommendation for {category_name} based on your budget of
|
| 238 |
action = ai_result.get("action")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
print(f"✅ OpenAI recommendation for {category_name} (budget: {budget_amount}): {recommended_budget} (action: {action})")
|
| 240 |
else:
|
| 241 |
# Fallback to rule-based recommendation if OpenAI fails
|
|
@@ -404,7 +431,49 @@ class SmartBudgetRecommender:
|
|
| 404 |
recommended_budget = ai_result.get("recommended_budget")
|
| 405 |
reason = ai_result.get("reason", f"AI recommendation for {category_name}")
|
| 406 |
action = ai_result.get("action")
|
| 407 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
else:
|
| 409 |
# Fallback to rule-based recommendation if OpenAI fails
|
| 410 |
recommended_budget = self._calculate_recommended_budget(avg_expense, data)
|
|
@@ -998,13 +1067,37 @@ class SmartBudgetRecommender:
|
|
| 998 |
" - Accounts for category-specific factors\n"
|
| 999 |
" - Includes appropriate buffer for variability and inflation\n"
|
| 1000 |
" - Is justified by your analysis and knowledge\n\n"
|
| 1001 |
-
"CRITICAL RULES:\n"
|
| 1002 |
-
"
|
| 1003 |
-
"
|
| 1004 |
-
"
|
| 1005 |
-
"- If
|
| 1006 |
-
"-
|
| 1007 |
-
"-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
"Respond strictly as JSON with the following keys:\n"
|
| 1009 |
'{ "recommended_budget": number, "action": "increase|decrease|keep", "reason": "string" }.\n\n'
|
| 1010 |
"The 'reason' field is CRITICAL - it must:\n"
|
|
@@ -1032,7 +1125,7 @@ class SmartBudgetRecommender:
|
|
| 1032 |
"messages": [
|
| 1033 |
{"role": "user", "content": prompt}
|
| 1034 |
],
|
| 1035 |
-
"temperature": 0.
|
| 1036 |
"response_format": {"type": "json_object"},
|
| 1037 |
},
|
| 1038 |
timeout=30,
|
|
|
|
| 69 |
recommended_budget = ai_result.get("recommended_budget")
|
| 70 |
reason = ai_result.get("reason", f"AI recommendation for {category_name}")
|
| 71 |
action = ai_result.get("action")
|
| 72 |
+
|
| 73 |
+
# Validate OpenAI recommendation (same logic as in get_recommendation_for_category)
|
| 74 |
+
if recommended_budget == avg_expense and action == "keep":
|
| 75 |
+
std_dev = data.get("std_dev", 0.0)
|
| 76 |
+
monthly_values = data.get("monthly_values", [])
|
| 77 |
+
has_trend = len(monthly_values) > 1 and (monthly_values[-1] != monthly_values[0])
|
| 78 |
+
|
| 79 |
+
if has_trend or std_dev > avg_expense * 0.05:
|
| 80 |
+
# Override with intelligent recommendation
|
| 81 |
+
if has_trend and monthly_values[-1] > monthly_values[0]:
|
| 82 |
+
recommended_budget = avg_expense * 1.15
|
| 83 |
+
action = "increase"
|
| 84 |
+
elif std_dev > avg_expense * 0.05:
|
| 85 |
+
recommended_budget = avg_expense * 1.20
|
| 86 |
+
action = "increase"
|
| 87 |
+
else:
|
| 88 |
+
recommended_budget = avg_expense * 1.05
|
| 89 |
+
action = "increase"
|
| 90 |
+
|
| 91 |
print(f"✅ OpenAI recommendation for {category_name}: {recommended_budget} (action: {action})")
|
| 92 |
else:
|
| 93 |
# Fallback to rule-based recommendation if OpenAI fails
|
|
|
|
| 253 |
ai_result = self._get_ai_recommendation(category_name, data, avg_expense)
|
| 254 |
if ai_result and ai_result.get("recommended_budget"):
|
| 255 |
recommended_budget = ai_result.get("recommended_budget")
|
| 256 |
+
reason = ai_result.get("reason", f"AI recommendation for {category_name} based on your budget of {budget_amount:,.2f}")
|
| 257 |
action = ai_result.get("action")
|
| 258 |
+
|
| 259 |
+
# Validate OpenAI recommendation
|
| 260 |
+
if recommended_budget == avg_expense and action == "keep":
|
| 261 |
+
# For budget_amount only, always add buffer
|
| 262 |
+
recommended_budget = avg_expense * 1.10
|
| 263 |
+
action = "increase"
|
| 264 |
+
reason = f"Based on your budget amount, I recommend increasing by 10% to {recommended_budget:,.0f} to account for variability and inflation."
|
| 265 |
+
|
| 266 |
print(f"✅ OpenAI recommendation for {category_name} (budget: {budget_amount}): {recommended_budget} (action: {action})")
|
| 267 |
else:
|
| 268 |
# Fallback to rule-based recommendation if OpenAI fails
|
|
|
|
| 431 |
recommended_budget = ai_result.get("recommended_budget")
|
| 432 |
reason = ai_result.get("reason", f"AI recommendation for {category_name}")
|
| 433 |
action = ai_result.get("action")
|
| 434 |
+
|
| 435 |
+
# VALIDATION: Check if OpenAI returned a lazy "keep" recommendation
|
| 436 |
+
# If recommended_budget equals avg_expense and action is "keep", validate if it's justified
|
| 437 |
+
monthly_values = data.get("monthly_values", [])
|
| 438 |
+
std_dev = data.get("std_dev", 0.0)
|
| 439 |
+
|
| 440 |
+
if recommended_budget == avg_expense and action == "keep":
|
| 441 |
+
# Check if this is justified
|
| 442 |
+
has_trend = False
|
| 443 |
+
if len(monthly_values) > 1:
|
| 444 |
+
# Check for upward trend
|
| 445 |
+
if monthly_values[-1] > monthly_values[0]:
|
| 446 |
+
has_trend = True
|
| 447 |
+
# Check for downward trend
|
| 448 |
+
elif monthly_values[-1] < monthly_values[0]:
|
| 449 |
+
has_trend = True
|
| 450 |
+
|
| 451 |
+
# If there's a trend or high variation, force a better recommendation
|
| 452 |
+
if has_trend or std_dev > avg_expense * 0.05:
|
| 453 |
+
print(f"⚠️ OpenAI returned 'keep' but data shows trend/variation - overriding with intelligent recommendation")
|
| 454 |
+
# Force increase with buffer
|
| 455 |
+
if has_trend and monthly_values[-1] > monthly_values[0]:
|
| 456 |
+
# Upward trend - increase by 15%
|
| 457 |
+
recommended_budget = avg_expense * 1.15
|
| 458 |
+
action = "increase"
|
| 459 |
+
reason = f"Your spending shows an upward trend. I recommend increasing your budget by 15% to {recommended_budget:,.0f} to accommodate this growth pattern and provide a buffer for continued increases."
|
| 460 |
+
elif has_trend and monthly_values[-1] < monthly_values[0]:
|
| 461 |
+
# Downward trend - decrease by 10%
|
| 462 |
+
recommended_budget = avg_expense * 0.90
|
| 463 |
+
action = "decrease"
|
| 464 |
+
reason = f"Your spending shows a downward trend. I recommend decreasing your budget by 10% to {recommended_budget:,.0f} to reflect this reduction pattern."
|
| 465 |
+
elif std_dev > avg_expense * 0.05:
|
| 466 |
+
# High variation - increase by 20%
|
| 467 |
+
recommended_budget = avg_expense * 1.20
|
| 468 |
+
action = "increase"
|
| 469 |
+
reason = f"Your spending shows high variability (std_dev: {std_dev:,.0f}). I recommend increasing your budget by 20% to {recommended_budget:,.0f} to create a safety buffer for unpredictable expenses."
|
| 470 |
+
else:
|
| 471 |
+
# Even for stable spending, add inflation buffer
|
| 472 |
+
recommended_budget = avg_expense * 1.05
|
| 473 |
+
action = "increase"
|
| 474 |
+
reason = f"While your spending is stable, I recommend adding a 5% buffer ({recommended_budget:,.0f}) to account for inflation and unexpected expenses."
|
| 475 |
+
|
| 476 |
+
print(f"✅ OpenAI recommendation for {category_name}: {recommended_budget:,.0f} (action: {action}, avg: {avg_expense:,.0f})")
|
| 477 |
else:
|
| 478 |
# Fallback to rule-based recommendation if OpenAI fails
|
| 479 |
recommended_budget = self._calculate_recommended_budget(avg_expense, data)
|
|
|
|
| 1067 |
" - Accounts for category-specific factors\n"
|
| 1068 |
" - Includes appropriate buffer for variability and inflation\n"
|
| 1069 |
" - Is justified by your analysis and knowledge\n\n"
|
| 1070 |
+
"CRITICAL RULES - READ CAREFULLY:\n"
|
| 1071 |
+
"⚠️ DO NOT ALWAYS RECOMMEND 'KEEP' - This is a common mistake. Analyze the data first!\n\n"
|
| 1072 |
+
"MANDATORY ANALYSIS STEPS:\n"
|
| 1073 |
+
"1. Look at the monthly_values array - is there a trend?\n"
|
| 1074 |
+
" - If values increase over time → MUST recommend INCREASE\n"
|
| 1075 |
+
" - If values decrease over time → MUST recommend DECREASE\n"
|
| 1076 |
+
" - Only if values are truly flat (all same) AND std_dev is very low → can recommend KEEP\n\n"
|
| 1077 |
+
"2. Check the std_dev (standard deviation):\n"
|
| 1078 |
+
" - If std_dev > 10% of average → MUST recommend INCREASE (high variability needs buffer)\n"
|
| 1079 |
+
" - If std_dev is moderate (5-10%) → Recommend INCREASE with 10-15% buffer\n"
|
| 1080 |
+
" - Only if std_dev < 3% AND no trend → can recommend KEEP with 5% buffer\n\n"
|
| 1081 |
+
"3. Consider inflation and best practices:\n"
|
| 1082 |
+
" - Even if spending is stable, inflation (2-5% annually) means you should INCREASE by at least 5-10%\n"
|
| 1083 |
+
" - Always add a buffer for unexpected expenses (5-15% depending on category)\n\n"
|
| 1084 |
+
"4. For single data point or new budgets:\n"
|
| 1085 |
+
" - MUST recommend INCREASE by 10-20% to account for variability\n"
|
| 1086 |
+
" - Never recommend KEEP for new/limited data\n\n"
|
| 1087 |
+
"DECISION TREE:\n"
|
| 1088 |
+
"- Upward trend? → INCREASE (10-25%)\n"
|
| 1089 |
+
"- Downward trend? → DECREASE (5-15%)\n"
|
| 1090 |
+
"- High variation (std_dev > 15%)? → INCREASE (20-30%)\n"
|
| 1091 |
+
"- Moderate variation (std_dev 5-15%)? → INCREASE (10-20%)\n"
|
| 1092 |
+
"- Stable with low variation (std_dev < 3%) AND no trend? → KEEP with 5-10% buffer\n"
|
| 1093 |
+
"- Single data point? → INCREASE (10-20%)\n\n"
|
| 1094 |
+
"⚠️ IMPORTANT: The recommended_budget MUST be different from average_expense in most cases.\n"
|
| 1095 |
+
"Only recommend the same amount if ALL of these are true:\n"
|
| 1096 |
+
"1. Spending is perfectly stable (all monthly values identical)\n"
|
| 1097 |
+
"2. Std_dev is very low (< 3% of average)\n"
|
| 1098 |
+
"3. No upward or downward trend\n"
|
| 1099 |
+
"4. Category is highly predictable\n"
|
| 1100 |
+
"Even then, add at least 5% buffer for inflation!\n\n"
|
| 1101 |
"Respond strictly as JSON with the following keys:\n"
|
| 1102 |
'{ "recommended_budget": number, "action": "increase|decrease|keep", "reason": "string" }.\n\n'
|
| 1103 |
"The 'reason' field is CRITICAL - it must:\n"
|
|
|
|
| 1125 |
"messages": [
|
| 1126 |
{"role": "user", "content": prompt}
|
| 1127 |
],
|
| 1128 |
+
"temperature": 0.9, # Higher temperature for more varied and creative recommendations
|
| 1129 |
"response_format": {"type": "json_object"},
|
| 1130 |
},
|
| 1131 |
timeout=30,
|