Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,56 @@
|
|
| 1 |
"""
|
| 2 |
Beautiful Gradio interface for credit card recommendations
|
| 3 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
from datetime import date
|
| 5 |
from typing import Optional, Tuple, List, Dict, Any
|
| 6 |
import gradio as gr
|
|
@@ -100,6 +150,13 @@ def get_recommendation(
|
|
| 100 |
def get_recommendation_with_ai(user_id, merchant, category, amount):
|
| 101 |
"""Get card recommendation with LLM-powered explanation"""
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
try:
|
| 104 |
# Get base recommendation from orchestrator
|
| 105 |
result = client.get_recommendation(
|
|
@@ -110,9 +167,11 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
|
|
| 110 |
)
|
| 111 |
|
| 112 |
if not result.get('success'):
|
| 113 |
-
|
|
|
|
| 114 |
|
| 115 |
-
data
|
|
|
|
| 116 |
|
| 117 |
# Generate LLM explanation if enabled
|
| 118 |
ai_explanation = ""
|
|
@@ -125,9 +184,9 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
|
|
| 125 |
merchant=merchant,
|
| 126 |
category=category,
|
| 127 |
amount=float(amount),
|
| 128 |
-
warnings=data
|
| 129 |
-
annual_potential=data
|
| 130 |
-
alternatives=data
|
| 131 |
)
|
| 132 |
except Exception as e:
|
| 133 |
print(f"LLM explanation failed: {e}")
|
|
@@ -135,12 +194,19 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
|
|
| 135 |
|
| 136 |
# Format output with AI explanation
|
| 137 |
output = f"""
|
| 138 |
-
## π― Recommendation for ${amount} at {merchant}
|
| 139 |
|
| 140 |
### π³ Best Card: **{data['recommended_card']}**
|
| 141 |
|
| 142 |
**Rewards Earned:** ${data['rewards_earned']:.2f} ({data['rewards_rate']})
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
"""
|
| 145 |
|
| 146 |
# Add AI explanation if available
|
|
@@ -158,32 +224,89 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
|
|
| 158 |
output += f"""
|
| 159 |
### π Breakdown
|
| 160 |
|
| 161 |
-
- **Category:** {category}
|
| 162 |
-
- **
|
| 163 |
-
- **
|
|
|
|
| 164 |
"""
|
| 165 |
|
| 166 |
# Add warnings
|
| 167 |
-
if data
|
| 168 |
-
output += "\n\n### β οΈ Warnings\n\n"
|
| 169 |
for warning in data['warnings']:
|
| 170 |
output += f"- {warning}\n"
|
| 171 |
|
| 172 |
# Add alternatives
|
| 173 |
-
if data
|
| 174 |
output += "\n\n### π Alternative Options\n\n"
|
| 175 |
for alt in data['alternatives'][:3]:
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
-
# Create visualization
|
| 179 |
chart = create_rewards_comparison_chart(data)
|
| 180 |
|
| 181 |
return output, chart
|
| 182 |
|
| 183 |
except Exception as e:
|
| 184 |
-
|
| 185 |
-
|
|
|
|
|
|
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
def get_analytics_with_insights(user_id):
|
| 188 |
"""Get analytics with LLM-generated insights"""
|
| 189 |
|
|
@@ -747,26 +870,38 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
|
|
| 747 |
)
|
| 748 |
|
| 749 |
def respond(message, chat_history, user_id):
|
| 750 |
-
"""Handle chat responses"""
|
| 751 |
if not message.strip():
|
| 752 |
return "", chat_history
|
| 753 |
|
| 754 |
-
# Get user context
|
|
|
|
| 755 |
try:
|
| 756 |
analytics = client.get_user_analytics(user_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 757 |
user_context = {
|
| 758 |
-
'cards':
|
| 759 |
-
'monthly_spending':
|
| 760 |
-
'top_category':
|
| 761 |
}
|
| 762 |
-
except:
|
| 763 |
-
user_context = {}
|
| 764 |
|
| 765 |
-
# Generate AI response
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
|
| 771 |
chat_history.append((message, bot_response))
|
| 772 |
return "", chat_history
|
|
|
|
| 1 |
"""
|
| 2 |
Beautiful Gradio interface for credit card recommendations
|
| 3 |
"""
|
| 4 |
+
|
| 5 |
+
# Add after imports, before client initialization
|
| 6 |
+
from typing import Dict, Any
|
| 7 |
+
import traceback
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def safe_get(data: Dict, key: str, default: Any = None) -> Any:
|
| 11 |
+
"""Safely get value from dictionary with fallback"""
|
| 12 |
+
try:
|
| 13 |
+
return data.get(key, default)
|
| 14 |
+
except:
|
| 15 |
+
return default
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def normalize_recommendation_data(data: Dict) -> Dict:
|
| 19 |
+
"""Normalize API response to ensure all required fields exist"""
|
| 20 |
+
|
| 21 |
+
# Extract or compute rewards_earned
|
| 22 |
+
rewards_earned = safe_get(data, 'rewards_earned')
|
| 23 |
+
if rewards_earned is None:
|
| 24 |
+
amount = safe_get(data, 'amount', 0)
|
| 25 |
+
rewards_rate = safe_get(data, 'rewards_rate', '0x points')
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
if 'x' in str(rewards_rate):
|
| 29 |
+
multiplier = float(rewards_rate.split('x')[0])
|
| 30 |
+
rewards_earned = amount * (multiplier / 100)
|
| 31 |
+
elif '%' in str(rewards_rate):
|
| 32 |
+
multiplier = float(rewards_rate.replace('%', '').split()[0])
|
| 33 |
+
rewards_earned = amount * (multiplier / 100)
|
| 34 |
+
else:
|
| 35 |
+
rewards_earned = 0
|
| 36 |
+
except:
|
| 37 |
+
rewards_earned = 0
|
| 38 |
+
|
| 39 |
+
return {
|
| 40 |
+
'recommended_card': safe_get(data, 'recommended_card', 'Unknown Card'),
|
| 41 |
+
'rewards_earned': round(float(rewards_earned), 2),
|
| 42 |
+
'rewards_rate': safe_get(data, 'rewards_rate', 'N/A'),
|
| 43 |
+
'merchant': safe_get(data, 'merchant', 'Unknown Merchant'),
|
| 44 |
+
'category': safe_get(data, 'category', 'Unknown'),
|
| 45 |
+
'amount': float(safe_get(data, 'amount', 0)),
|
| 46 |
+
'annual_potential': float(safe_get(data, 'annual_potential', rewards_earned * 12)),
|
| 47 |
+
'optimization_score': int(safe_get(data, 'optimization_score', 75)),
|
| 48 |
+
'reasoning': safe_get(data, 'reasoning', 'Optimal choice for this category'),
|
| 49 |
+
'warnings': safe_get(data, 'warnings', []),
|
| 50 |
+
'alternatives': safe_get(data, 'alternatives', []),
|
| 51 |
+
'mock_data': safe_get(data, 'mock_data', False)
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
from datetime import date
|
| 55 |
from typing import Optional, Tuple, List, Dict, Any
|
| 56 |
import gradio as gr
|
|
|
|
| 150 |
def get_recommendation_with_ai(user_id, merchant, category, amount):
|
| 151 |
"""Get card recommendation with LLM-powered explanation"""
|
| 152 |
|
| 153 |
+
# Validate inputs
|
| 154 |
+
if not merchant or not merchant.strip():
|
| 155 |
+
return "β Please enter a merchant name.", None
|
| 156 |
+
|
| 157 |
+
if amount <= 0:
|
| 158 |
+
return "β Please enter a valid amount greater than $0.", None
|
| 159 |
+
|
| 160 |
try:
|
| 161 |
# Get base recommendation from orchestrator
|
| 162 |
result = client.get_recommendation(
|
|
|
|
| 167 |
)
|
| 168 |
|
| 169 |
if not result.get('success'):
|
| 170 |
+
error_msg = result.get('error', 'Unknown error')
|
| 171 |
+
return f"β Error: {error_msg}", None
|
| 172 |
|
| 173 |
+
# Normalize the data to ensure all fields exist
|
| 174 |
+
data = normalize_recommendation_data(result.get('data', {}))
|
| 175 |
|
| 176 |
# Generate LLM explanation if enabled
|
| 177 |
ai_explanation = ""
|
|
|
|
| 184 |
merchant=merchant,
|
| 185 |
category=category,
|
| 186 |
amount=float(amount),
|
| 187 |
+
warnings=data['warnings'] if data['warnings'] else None,
|
| 188 |
+
annual_potential=data['annual_potential'],
|
| 189 |
+
alternatives=data['alternatives']
|
| 190 |
)
|
| 191 |
except Exception as e:
|
| 192 |
print(f"LLM explanation failed: {e}")
|
|
|
|
| 194 |
|
| 195 |
# Format output with AI explanation
|
| 196 |
output = f"""
|
| 197 |
+
## π― Recommendation for ${amount:.2f} at {merchant}
|
| 198 |
|
| 199 |
### π³ Best Card: **{data['recommended_card']}**
|
| 200 |
|
| 201 |
**Rewards Earned:** ${data['rewards_earned']:.2f} ({data['rewards_rate']})
|
| 202 |
|
| 203 |
+
"""
|
| 204 |
+
|
| 205 |
+
# Add mock data indicator
|
| 206 |
+
if data.get('mock_data'):
|
| 207 |
+
output += """
|
| 208 |
+
> β οΈ **Demo Mode:** Using sample data. Connect to orchestrator for real recommendations.
|
| 209 |
+
|
| 210 |
"""
|
| 211 |
|
| 212 |
# Add AI explanation if available
|
|
|
|
| 224 |
output += f"""
|
| 225 |
### π Breakdown
|
| 226 |
|
| 227 |
+
- **Category:** {data['category']}
|
| 228 |
+
- **Merchant:** {data['merchant']}
|
| 229 |
+
- **Annual Potential:** ${data['annual_potential']:.2f}
|
| 230 |
+
- **Optimization Score:** {data['optimization_score']}/100
|
| 231 |
"""
|
| 232 |
|
| 233 |
# Add warnings
|
| 234 |
+
if data['warnings']:
|
| 235 |
+
output += "\n\n### β οΈ Important Warnings\n\n"
|
| 236 |
for warning in data['warnings']:
|
| 237 |
output += f"- {warning}\n"
|
| 238 |
|
| 239 |
# Add alternatives
|
| 240 |
+
if data['alternatives']:
|
| 241 |
output += "\n\n### π Alternative Options\n\n"
|
| 242 |
for alt in data['alternatives'][:3]:
|
| 243 |
+
alt_rewards = safe_get(alt, 'rewards', 0)
|
| 244 |
+
alt_rate = safe_get(alt, 'rate', 'N/A')
|
| 245 |
+
alt_card = safe_get(alt, 'card', 'Unknown')
|
| 246 |
+
output += f"- **{alt_card}:** ${alt_rewards:.2f} ({alt_rate})\n"
|
| 247 |
|
| 248 |
+
# Create visualization
|
| 249 |
chart = create_rewards_comparison_chart(data)
|
| 250 |
|
| 251 |
return output, chart
|
| 252 |
|
| 253 |
except Exception as e:
|
| 254 |
+
import traceback
|
| 255 |
+
error_details = traceback.format_exc()
|
| 256 |
+
print(f"Recommendation error: {error_details}")
|
| 257 |
+
return f"β Error: {str(e)}\n\nPlease check your API connection or try again.", None
|
| 258 |
|
| 259 |
+
def create_rewards_comparison_chart(data: Dict) -> go.Figure:
|
| 260 |
+
"""Create rewards comparison chart"""
|
| 261 |
+
|
| 262 |
+
try:
|
| 263 |
+
# Prepare data for chart
|
| 264 |
+
cards = [data['recommended_card']]
|
| 265 |
+
rewards = [data['rewards_earned']]
|
| 266 |
+
|
| 267 |
+
# Add alternatives
|
| 268 |
+
for alt in data.get('alternatives', [])[:3]:
|
| 269 |
+
cards.append(safe_get(alt, 'card', 'Unknown'))
|
| 270 |
+
rewards.append(float(safe_get(alt, 'rewards', 0)))
|
| 271 |
+
|
| 272 |
+
# Create bar chart
|
| 273 |
+
fig = go.Figure(data=[
|
| 274 |
+
go.Bar(
|
| 275 |
+
x=cards,
|
| 276 |
+
y=rewards,
|
| 277 |
+
marker=dict(
|
| 278 |
+
color=['#667eea'] + ['#a0aec0'] * (len(cards) - 1),
|
| 279 |
+
line=dict(color='white', width=2)
|
| 280 |
+
),
|
| 281 |
+
text=[f'${r:.2f}' for r in rewards],
|
| 282 |
+
textposition='outside',
|
| 283 |
+
)
|
| 284 |
+
])
|
| 285 |
+
|
| 286 |
+
fig.update_layout(
|
| 287 |
+
title='Rewards Comparison',
|
| 288 |
+
xaxis_title='Credit Card',
|
| 289 |
+
yaxis_title='Rewards Earned ($)',
|
| 290 |
+
template='plotly_white',
|
| 291 |
+
height=400,
|
| 292 |
+
showlegend=False,
|
| 293 |
+
margin=dict(t=50, b=50, l=50, r=50)
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
return fig
|
| 297 |
+
|
| 298 |
+
except Exception as e:
|
| 299 |
+
print(f"Chart creation error: {e}")
|
| 300 |
+
# Return empty chart with error message
|
| 301 |
+
fig = go.Figure()
|
| 302 |
+
fig.add_annotation(
|
| 303 |
+
text=f"Error creating chart: {str(e)}",
|
| 304 |
+
xref="paper", yref="paper",
|
| 305 |
+
x=0.5, y=0.5, showarrow=False,
|
| 306 |
+
font=dict(size=14, color="red")
|
| 307 |
+
)
|
| 308 |
+
return fig
|
| 309 |
+
|
| 310 |
def get_analytics_with_insights(user_id):
|
| 311 |
"""Get analytics with LLM-generated insights"""
|
| 312 |
|
|
|
|
| 870 |
)
|
| 871 |
|
| 872 |
def respond(message, chat_history, user_id):
|
| 873 |
+
"""Handle chat responses with error handling"""
|
| 874 |
if not message.strip():
|
| 875 |
return "", chat_history
|
| 876 |
|
| 877 |
+
# Get user context with error handling
|
| 878 |
+
user_context = {}
|
| 879 |
try:
|
| 880 |
analytics = client.get_user_analytics(user_id)
|
| 881 |
+
if analytics.get('success'):
|
| 882 |
+
data = analytics.get('data', {})
|
| 883 |
+
user_context = {
|
| 884 |
+
'cards': safe_get(data, 'cards', ['Amex Gold', 'Chase Sapphire Reserve']),
|
| 885 |
+
'monthly_spending': safe_get(data, 'total_spending', 0),
|
| 886 |
+
'top_category': safe_get(data, 'top_category', 'Groceries')
|
| 887 |
+
}
|
| 888 |
+
except Exception as e:
|
| 889 |
+
print(f"Error getting user context: {e}")
|
| 890 |
user_context = {
|
| 891 |
+
'cards': ['Amex Gold', 'Chase Sapphire Reserve'],
|
| 892 |
+
'monthly_spending': 3450.75,
|
| 893 |
+
'top_category': 'Groceries'
|
| 894 |
}
|
|
|
|
|
|
|
| 895 |
|
| 896 |
+
# Generate AI response with error handling
|
| 897 |
+
try:
|
| 898 |
+
if config.LLM_ENABLED:
|
| 899 |
+
bot_response = llm.chat_response(message, user_context, chat_history)
|
| 900 |
+
else:
|
| 901 |
+
bot_response = "I'm currently in fallback mode. Ask me about specific cards or categories!"
|
| 902 |
+
except Exception as e:
|
| 903 |
+
print(f"Chat error: {e}")
|
| 904 |
+
bot_response = f"I encountered an error. Please try asking your question differently."
|
| 905 |
|
| 906 |
chat_history.append((message, bot_response))
|
| 907 |
return "", chat_history
|