|
|
""" |
|
|
Beautiful Gradio interface for credit card recommendations |
|
|
""" |
|
|
from datetime import date |
|
|
from typing import Optional, Tuple, List, Dict, Any |
|
|
import gradio as gr |
|
|
from config import ( |
|
|
APP_TITLE, APP_DESCRIPTION, THEME, |
|
|
MCC_CATEGORIES, SAMPLE_USERS, |
|
|
MERCHANTS_BY_CATEGORY |
|
|
) |
|
|
from utils.api_client import RewardPilotClient |
|
|
from utils.formatters import ( |
|
|
format_full_recommendation, |
|
|
format_comparison_table, |
|
|
) |
|
|
|
|
|
|
|
|
client = RewardPilotClient() |
|
|
|
|
|
|
|
|
def get_recommendation( |
|
|
user_id: str, |
|
|
merchant: str, |
|
|
category: str, |
|
|
amount: float, |
|
|
use_custom_mcc: bool, |
|
|
custom_mcc: str, |
|
|
transaction_date: Optional[str] |
|
|
) -> tuple: |
|
|
"""Get card recommendation and format response""" |
|
|
|
|
|
if not user_id or not merchant or amount <= 0: |
|
|
return ( |
|
|
"❌ **Error:** Please fill in all required fields.", |
|
|
None, |
|
|
None, |
|
|
) |
|
|
|
|
|
|
|
|
if use_custom_mcc and custom_mcc: |
|
|
mcc = custom_mcc |
|
|
else: |
|
|
mcc = MCC_CATEGORIES.get(category, "5999") |
|
|
|
|
|
|
|
|
if not transaction_date: |
|
|
transaction_date = str(date.today()) |
|
|
|
|
|
|
|
|
response: Dict[str, Any] = client.get_recommendation_sync( |
|
|
user_id=user_id, |
|
|
merchant=merchant, |
|
|
mcc=mcc, |
|
|
amount_usd=amount, |
|
|
transaction_date=transaction_date, |
|
|
) |
|
|
|
|
|
|
|
|
formatted_text = format_full_recommendation(response) |
|
|
|
|
|
|
|
|
comparison_table: Optional[str] |
|
|
stats: Optional[str] |
|
|
if not response.get("error"): |
|
|
recommended = response.get("recommended_card", {}) or {} |
|
|
alternatives: List[Dict[str, Any]] = response.get("alternative_cards", []) or [] |
|
|
all_cards = [c for c in ([recommended] + alternatives) if c] |
|
|
comparison_table = format_comparison_table(all_cards) if all_cards else None |
|
|
|
|
|
|
|
|
total_analyzed = response.get("total_cards_analyzed", len(all_cards)) |
|
|
best_reward = (recommended.get("reward_amount") or 0.0) |
|
|
services_used = response.get("services_used", []) |
|
|
stats = f""" |
|
|
**Cards Analyzed:** {total_analyzed} |
|
|
**Best Reward:** ${best_reward:.2f} |
|
|
**Services Used:** {', '.join(services_used)} |
|
|
""".strip() |
|
|
else: |
|
|
comparison_table = None |
|
|
stats = None |
|
|
|
|
|
return formatted_text, comparison_table, stats |
|
|
|
|
|
|
|
|
EXAMPLES = [ |
|
|
["u_alice", "Groceries", "Whole Foods", 125.50, False, "", "2025-01-15"], |
|
|
["u_bob", "Restaurants", "Olive Garden", 65.75, False, "", "2025-01-15"], |
|
|
["u_charlie", "Airlines", "United Airlines", 450.00, False, "", "2025-01-15"], |
|
|
["u_alice", "Fast Food", "Starbucks", 15.75, False, "", ""], |
|
|
["u_bob", "Gas Stations", "Shell", 45.00, False, "", ""], |
|
|
] |
|
|
|
|
|
|
|
|
def _toggle_custom_mcc(use_custom: bool): |
|
|
return gr.update(visible=use_custom, value="") |
|
|
|
|
|
with gr.Blocks( |
|
|
theme=THEME if isinstance(THEME, gr.themes.ThemeClass) else gr.themes.Soft(), |
|
|
title=APP_TITLE, |
|
|
css=""" |
|
|
.gradio-container { |
|
|
max-width: 1200px !important; |
|
|
} |
|
|
.recommendation-output { |
|
|
font-size: 16px; |
|
|
line-height: 1.6; |
|
|
} |
|
|
/* Metric Cards Styling */ |
|
|
.metric-card { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white; |
|
|
padding: 30px 20px; |
|
|
border-radius: 16px; |
|
|
text-align: center; |
|
|
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3); |
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
|
|
margin: 10px; |
|
|
} |
|
|
.metric-card:hover { |
|
|
transform: translateY(-5px); |
|
|
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.4); |
|
|
} |
|
|
.metric-card h2 { |
|
|
font-size: 48px; |
|
|
font-weight: 700; |
|
|
margin: 0 0 10px 0; |
|
|
color: white; |
|
|
} |
|
|
.metric-card p { |
|
|
font-size: 16px; |
|
|
margin: 0; |
|
|
opacity: 0.9; |
|
|
color: white; |
|
|
} |
|
|
.metric-card-green { |
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); |
|
|
} |
|
|
.metric-card-orange { |
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
|
|
} |
|
|
.metric-card-blue { |
|
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
|
|
} |
|
|
/* Table styling */ |
|
|
table { |
|
|
width: 100%; |
|
|
border-collapse: collapse; |
|
|
margin: 20px 0; |
|
|
background: white; |
|
|
border-radius: 8px; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
|
|
} |
|
|
table th { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white; |
|
|
padding: 12px; |
|
|
text-align: left; |
|
|
font-weight: 600; |
|
|
} |
|
|
table td { |
|
|
padding: 12px; |
|
|
border-bottom: 1px solid #f0f0f0; |
|
|
} |
|
|
table tr:last-child td { |
|
|
border-bottom: none; |
|
|
} |
|
|
table tr:hover { |
|
|
background: #f8f9fa; |
|
|
} |
|
|
""", |
|
|
) as app: |
|
|
|
|
|
gr.Markdown( |
|
|
f""" |
|
|
# {APP_TITLE} |
|
|
{APP_DESCRIPTION} |
|
|
|
|
|
Get AI-powered credit card recommendations that maximize your rewards based on: |
|
|
- 💰 **Reward Rates** - Optimal card selection for each purchase |
|
|
- 📚 **Card Benefits** - Detailed information from our knowledge base |
|
|
- ⚠️ **Spending Caps** - Risk warnings to avoid missing out on bonuses |
|
|
|
|
|
--- |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
with gr.Tab("🎯 Get Recommendation"): |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### Transaction Details") |
|
|
user_dropdown = gr.Dropdown( |
|
|
choices=SAMPLE_USERS, |
|
|
value=SAMPLE_USERS[0], |
|
|
label="User ID", |
|
|
info="Select a user" |
|
|
) |
|
|
|
|
|
|
|
|
category_dropdown = gr.Dropdown( |
|
|
choices=list(MCC_CATEGORIES.keys()), |
|
|
value="Groceries", |
|
|
label="🏷️ Type of Purchase", |
|
|
info="Select the category first" |
|
|
) |
|
|
|
|
|
|
|
|
merchant_dropdown = gr.Dropdown( |
|
|
choices=MERCHANTS_BY_CATEGORY["Groceries"], |
|
|
value="Whole Foods", |
|
|
label="🏪 Merchant Name", |
|
|
info="Select merchant (changes based on category)", |
|
|
allow_custom_value=True |
|
|
) |
|
|
|
|
|
amount_input = gr.Number( |
|
|
label="💵 Amount (USD)", |
|
|
value=125.50, |
|
|
minimum=0.01, |
|
|
step=0.01 |
|
|
) |
|
|
|
|
|
date_input = gr.Textbox( |
|
|
label="📅 Transaction Date (Optional)", |
|
|
placeholder="YYYY-MM-DD or leave blank for today", |
|
|
value="" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Accordion("⚙️ Advanced Options", open=False): |
|
|
use_custom_mcc = gr.Checkbox( |
|
|
label="Use Custom MCC Code", |
|
|
value=False |
|
|
) |
|
|
custom_mcc_input = gr.Textbox( |
|
|
label="Custom MCC Code", |
|
|
placeholder="e.g., 5411", |
|
|
visible=False |
|
|
) |
|
|
|
|
|
def toggle_custom_mcc(use_custom): |
|
|
return gr.update(visible=use_custom) |
|
|
|
|
|
use_custom_mcc.change( |
|
|
fn=toggle_custom_mcc, |
|
|
inputs=[use_custom_mcc], |
|
|
outputs=[custom_mcc_input] |
|
|
) |
|
|
|
|
|
recommend_btn = gr.Button( |
|
|
"🚀 Get Recommendation", |
|
|
variant="primary", |
|
|
size="lg" |
|
|
) |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
gr.Markdown("### 💡 Recommendation") |
|
|
recommendation_output = gr.Markdown( |
|
|
value="✨ Select a category and merchant, then click 'Get Recommendation'", |
|
|
elem_classes=["recommendation-output"] |
|
|
) |
|
|
|
|
|
def update_merchant_choices(category): |
|
|
"""Update merchant dropdown based on selected category""" |
|
|
merchants = MERCHANTS_BY_CATEGORY.get(category, ["Custom Merchant"]) |
|
|
return gr.update( |
|
|
choices=merchants, |
|
|
value=merchants[0] if merchants else "" |
|
|
) |
|
|
|
|
|
|
|
|
category_dropdown.change( |
|
|
fn=update_merchant_choices, |
|
|
inputs=[category_dropdown], |
|
|
outputs=[merchant_dropdown] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.Markdown("### 📊 Quick Stats") |
|
|
stats_output = gr.Markdown() |
|
|
|
|
|
with gr.Column(): |
|
|
gr.Markdown("### 🔄 Card Comparison") |
|
|
comparison_output = gr.Markdown() |
|
|
|
|
|
|
|
|
recommend_btn.click( |
|
|
fn=get_recommendation, |
|
|
inputs=[ |
|
|
user_dropdown, |
|
|
merchant_dropdown, |
|
|
category_dropdown, |
|
|
amount_input, |
|
|
use_custom_mcc, |
|
|
custom_mcc_input, |
|
|
date_input |
|
|
], |
|
|
outputs=[ |
|
|
recommendation_output, |
|
|
comparison_output, |
|
|
stats_output |
|
|
] |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### 📝 Example Transactions") |
|
|
gr.Examples( |
|
|
examples=EXAMPLES, |
|
|
inputs=[ |
|
|
user_dropdown, |
|
|
category_dropdown, |
|
|
merchant_dropdown, |
|
|
amount_input, |
|
|
use_custom_mcc, |
|
|
custom_mcc_input, |
|
|
date_input |
|
|
], |
|
|
outputs=[ |
|
|
recommendation_output, |
|
|
comparison_output, |
|
|
stats_output |
|
|
], |
|
|
fn=get_recommendation, |
|
|
cache_examples=False |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("📊 Analytics"): |
|
|
gr.Markdown("## 🎯 Your Rewards Optimization Dashboard") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
analytics_user = gr.Dropdown( |
|
|
choices=SAMPLE_USERS, |
|
|
value=SAMPLE_USERS[0], |
|
|
label="👤 View Analytics For User", |
|
|
scale=3 |
|
|
) |
|
|
refresh_analytics_btn = gr.Button( |
|
|
"🔄 Refresh Analytics", |
|
|
variant="secondary", |
|
|
scale=1 |
|
|
) |
|
|
|
|
|
|
|
|
metrics_display = gr.HTML( |
|
|
value=""" |
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;"> |
|
|
<div class="metric-card" style="flex: 1; min-width: 200px;"> |
|
|
<h2>$342</h2> |
|
|
<p>💰 Potential Annual Savings</p> |
|
|
</div> |
|
|
<div class="metric-card metric-card-green" style="flex: 1; min-width: 200px;"> |
|
|
<h2>23%</h2> |
|
|
<p>📈 Rewards Rate Increase</p> |
|
|
</div> |
|
|
<div class="metric-card metric-card-orange" style="flex: 1; min-width: 200px;"> |
|
|
<h2>156</h2> |
|
|
<p>✅ Optimized Transactions</p> |
|
|
</div> |
|
|
<div class="metric-card metric-card-blue" style="flex: 1; min-width: 200px;"> |
|
|
<h2>87/100</h2> |
|
|
<p>⭐ Optimization Score</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### 💰 Category Spending Breakdown") |
|
|
spending_table = gr.Markdown( |
|
|
value=""" |
|
|
| Category | Monthly Spend | Best Card | Rewards | Rate | |
|
|
|----------|---------------|-----------|---------|------| |
|
|
| 🛒 Groceries | $450.00 | Amex Gold | $27.00 | 6% | |
|
|
| 🍽️ Restaurants | $320.00 | Amex Gold | $12.80 | 4% | |
|
|
| ⛽ Gas | $180.00 | Costco Visa | $7.20 | 4% | |
|
|
| ✈️ Travel | $850.00 | Sapphire Reserve | $42.50 | 5% | |
|
|
| 🎬 Entertainment | $125.00 | Freedom Unlimited | $1.88 | 1.5% | |
|
|
| 🏪 Online Shopping | $280.00 | Amazon Prime | $16.80 | 6% | |
|
|
| **Total** | **$2,205.00** | - | **$108.18** | **4.9%** | |
|
|
""" |
|
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### 📈 Monthly Trends & Insights") |
|
|
insights_display = gr.Markdown( |
|
|
value=""" |
|
|
**🔥 Top Spending Categories:** |
|
|
1. ✈️ Travel: $850 (↑ 45% from last month) |
|
|
2. 🛒 Groceries: $450 (↑ 12%) |
|
|
3. 🍽️ Restaurants: $320 (↓ 5%) |
|
|
|
|
|
**💡 Optimization Opportunities:** |
|
|
- ✅ You're using optimal cards 87% of the time |
|
|
- 🎯 Switch to Chase Freedom for Q4 5% grocery bonus |
|
|
- ⚠️ Amex Gold dining cap approaching ($2,000 limit) |
|
|
|
|
|
**🏆 Best Performing Card:** |
|
|
Chase Sapphire Reserve - $42.50 rewards earned |
|
|
|
|
|
**📊 Year-to-Date:** |
|
|
- Total Rewards: $1,298.16 |
|
|
- Potential if optimized: $1,640.00 |
|
|
- **Money left on table: $341.84** |
|
|
""" |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
|
|
|
forecast_display = gr.Markdown( |
|
|
value=""" |
|
|
### 🔮 Next Month Forecast |
|
|
|
|
|
Based on your spending patterns: |
|
|
- **Predicted Spend:** $2,350 |
|
|
- **Predicted Rewards:** $115.25 |
|
|
- **Cards to Watch:** Amex Gold (dining cap), Freedom (quarterly bonus) |
|
|
|
|
|
**Recommendations:** |
|
|
1. 💳 Use Chase Freedom for groceries in Q4 (5% back) |
|
|
2. ⚠️ Monitor Amex Gold dining spend (cap at $2,000) |
|
|
3. 🎯 Book holiday travel with Sapphire Reserve for 5x points |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
analytics_status = gr.Markdown( |
|
|
value="*Analytics loaded for u_alice*", |
|
|
elem_classes=["status-text"] |
|
|
) |
|
|
|
|
|
|
|
|
def update_analytics(user_id: str) -> tuple: |
|
|
"""Fetch and format analytics for selected user""" |
|
|
try: |
|
|
|
|
|
analytics_data = client.get_user_analytics(user_id) |
|
|
|
|
|
|
|
|
from utils.formatters import format_analytics_metrics |
|
|
metrics_html, table_md, insights_md, forecast_md = format_analytics_metrics(analytics_data) |
|
|
|
|
|
|
|
|
from datetime import datetime |
|
|
status = f"*Analytics updated for {user_id} at {datetime.now().strftime('%I:%M %p')}*" |
|
|
|
|
|
return metrics_html, table_md, insights_md, forecast_md, status |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"❌ Error loading analytics: {str(e)}" |
|
|
return ( |
|
|
"<p>Error loading metrics</p>", |
|
|
"Error loading table", |
|
|
"Error loading insights", |
|
|
"Error loading forecast", |
|
|
error_msg |
|
|
) |
|
|
|
|
|
|
|
|
refresh_analytics_btn.click( |
|
|
fn=update_analytics, |
|
|
inputs=[analytics_user], |
|
|
outputs=[ |
|
|
metrics_display, |
|
|
spending_table, |
|
|
insights_display, |
|
|
forecast_display, |
|
|
analytics_status |
|
|
] |
|
|
) |
|
|
|
|
|
analytics_user.change( |
|
|
fn=update_analytics, |
|
|
inputs=[analytics_user], |
|
|
outputs=[ |
|
|
metrics_display, |
|
|
spending_table, |
|
|
insights_display, |
|
|
forecast_display, |
|
|
analytics_status |
|
|
] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("ℹ️ About"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
## About RewardPilot |
|
|
|
|
|
RewardPilot is an AI-powered credit card recommendation system built using the **Model Context Protocol (MCP)** architecture. |
|
|
|
|
|
### 🏗️ Architecture |
|
|
|
|
|
The system consists of multiple microservices: |
|
|
|
|
|
1. **Smart Wallet** - Analyzes transaction context and selects optimal cards |
|
|
2. **Rewards-RAG** - Retrieves detailed card benefit information using RAG |
|
|
3. **Spend-Forecast** - Predicts spending patterns and warns about cap risks |
|
|
4. **Orchestrator** - Coordinates all services for comprehensive recommendations |
|
|
|
|
|
### 🎯 How It Works |
|
|
|
|
|
1. **Enter Transaction Details** - Merchant, amount, category |
|
|
2. **AI Analysis** - System analyzes your wallet and transaction context |
|
|
3. **Get Recommendation** - Receive the best card with detailed reasoning |
|
|
4. **Maximize Rewards** - Earn more points/cashback on every purchase |
|
|
|
|
|
### 🔧 Technology Stack |
|
|
|
|
|
- **Backend:** FastAPI, Python |
|
|
- **Frontend:** Gradio |
|
|
- **AI/ML:** RAG (Retrieval-Augmented Generation) |
|
|
- **Architecture:** MCP (Model Context Protocol) |
|
|
- **Deployment:** Hugging Face Spaces |
|
|
|
|
|
### 📚 MCC Categories Supported |
|
|
|
|
|
- Groceries (5411) |
|
|
- Restaurants (5812) |
|
|
- Gas Stations (5541) |
|
|
- Airlines (3000-3999) |
|
|
- Hotels (7011) |
|
|
- Entertainment (7832, 7841) |
|
|
- And many more... |
|
|
|
|
|
### 🎓 Built For |
|
|
|
|
|
**MCP 1st Birthday Hackathon** - Celebrating one year of the Model Context Protocol |
|
|
|
|
|
### 👨💻 Developer |
|
|
|
|
|
Built with ❤️ for the MCP community |
|
|
|
|
|
--- |
|
|
|
|
|
**Version:** 1.0.0 |
|
|
**Last Updated:** November 2025 |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("📖 API Docs"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
## API Endpoints |
|
|
|
|
|
### Orchestrator API |
|
|
|
|
|
**Base URL:** `https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space` |
|
|
|
|
|
#### POST `/recommend` |
|
|
|
|
|
Get comprehensive card recommendation. |
|
|
|
|
|
**Request:** |
|
|
```json |
|
|
{ |
|
|
"user_id": "u_alice", |
|
|
"merchant": "Whole Foods", |
|
|
"mcc": "5411", |
|
|
"amount_usd": 125.50, |
|
|
"transaction_date": "2025-01-15" |
|
|
} |
|
|
``` |
|
|
|
|
|
**Response:** |
|
|
```json |
|
|
{ |
|
|
"user_id": "u_alice", |
|
|
"merchant": "Whole Foods", |
|
|
"amount_usd": 125.5, |
|
|
"recommended_card": { |
|
|
"card_id": "c_amex_gold", |
|
|
"card_name": "American Express Gold Card", |
|
|
"reward_rate": 4.0, |
|
|
"reward_amount": 502.0, |
|
|
"category": "Groceries", |
|
|
"reasoning": "Earns 4x points on Groceries" |
|
|
}, |
|
|
"alternative_cards": ["..."], |
|
|
"rag_insights": { "...": "..." }, |
|
|
"forecast_warning": { "...": "..." }, |
|
|
"services_used": ["smart_wallet", "rewards_rag", "spend_forecast"], |
|
|
"final_recommendation": "..." |
|
|
} |
|
|
``` |
|
|
|
|
|
### Other Services |
|
|
|
|
|
- Smart Wallet: https://mcp-1st-birthday-rewardpilot-smart-wallet.hf.space |
|
|
- Rewards-RAG: https://mcp-1st-birthday-rewardpilot-rewards-rag.hf.space |
|
|
- Spend-Forecast: https://mcp-1st-birthday-rewardpilot-spend-forecast.hf.space |
|
|
|
|
|
### Interactive Docs |
|
|
|
|
|
Visit `/docs` on any service for interactive Swagger UI documentation. |
|
|
|
|
|
### cURL Examples |
|
|
|
|
|
```bash |
|
|
# Get recommendation |
|
|
curl -X POST https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space/recommend \\ |
|
|
-H "Content-Type: application/json" \\ |
|
|
-d '{ |
|
|
"user_id": "u_alice", |
|
|
"merchant": "Whole Foods", |
|
|
"mcc": "5411", |
|
|
"amount_usd": 125.50 |
|
|
}' |
|
|
``` |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
app.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
) |
|
|
|